install-plan
Recipe card from the charly-internals plugin (Development — contributor internals).
This card has additional detail pages:
InstallPlan IR — shared IR for build + deploy
Section titled “InstallPlan IR — shared IR for build + deploy”Overview
Section titled “Overview”charly has several DEPLOY paths that all need to know “what does applying this layer mean?”:
- Pod deploy (
pod:substrate, default) —charly bundle add <name> <ref>runs the image via quadlet. Served OUT-OF-PROCESS bycandy/plugin-deploy-pod(deploy:pod), but unlike the others its plugin WALKS NOTHING — pod bakes its steps INTO the image, so itscandy/plugin-deploy-podlifecycle (M4 + P11c) builds the overlay Containerfile (viadeploykit.OCITarget+deploykit.NewRenderGeneratorFromProject, in the candy’s own code) HOST-SIDE inPrepareVenueand owns the container config/start/remove lifecycle. - Local deploy (
local:substrate) —charly bundle add <name> <ref>applies the recipe to the destination machine’s filesystem (host: local→ direct shell;host: <user@machine>→ SSH). Served OUT-OF-PROCESS bycandy/plugin-deploy-local(deploy:local):pluginDeployTarget(charly/unified_targets.go) hands the plugin this IR viacandy/plugin-bundle’sInvoke(OpDeployDispatch)(S3b), the plugin walks it over the executor reverse channel (sdk/kit.WalkPlans), and the host executor isShellExecutorforhost:local/absent orSSHExecutorforhost:<user@machine>(picked byrootExecutorForDeployNode). - VM deploy (
target: vm) —charly bundle add vm:<name> <ref>applies the recipe inside a running VM over SSH. - Kubernetes deploy (
target: k8s) —charly bundle add <name> --target k8semits a Kustomize base/overlays tree. - External out-of-process deploy — an external deploy-substrate plugin applies on the host venue over the executor reverse channel (
OpExecute), dispatched through the thin core proxypluginDeployTarget→candy/plugin-bundle’sInvoke(OpDeployDispatch)→ the substrate’s ownsdk.Executor.InvokeProvider; see “External (out-of-process) deploy target” below.
All these DEPLOY paths are unified behind one IR. A pure compiler (BuildDeployPlan) turns Layer + ResolvedBox + HostContext into an InstallPlan; each deploy path is a DeployTarget consuming the plan.
Boundary-law note: the InstallPlan IR and the deploy wire replies (VenueDescriptor/DeployVenue/EmitReply/BuilderResolveReply) are this subsystem’s E-envelopes under the kernel/plugin boundary law — see /charly-internals:plugin (“The kernel/plugin boundary law”) for the full doctrine.
Build mode is a SEPARATE path, NOT the IR. charly box build / charly box generate emit Containerfiles via the WriteCandySteps → EmitTasks generator in sdk/deploykit (deploykit.Generator, relocated from charly/generate.go in #67, driven by candy/plugin-build over the envelope + HostBuild("render-seam")); emitTasks (charly/tasks.go) is a thin shim to deploykit.Generator.EmitTasks that STAYS for the pod-overlay. The render walks each layer’s ops directly. It shares the compiler helpers (resolveCascadePackages, compileShellSnippetSteps, deploykit.RenderLocalPkgImageInstall — R3, relocated to sdk/deploykit in W3) with the IR, but does NOT walk an InstallPlan or use deploykit.OCITarget. deploykit.OCITarget is itself deploy-mode: the candy plugin-deploy-pod constructs it only for add_candy: overlay-Containerfile synthesis — in its own code (deploykit.NewRenderGeneratorFromProject), reached HOST-SIDE via HostBuild("overlay") prep + HostBuild("step-emit","oci-emit-step") per-step dispatch (P11c; BuildDeployPlan is called only by the deploy command path).
This skill is the single source of truth for the IR shape. Add a new step kind by editing install_plan.go, the compiler in install_build.go, and each target’s emit* method — this skill lists every place that needs to stay in sync.
File layout
Section titled “File layout”| File | Role |
|---|---|
charly/install_plan.go |
Host-side-only carrier interfaces (stepContractCarrier/structuralKindCarrier/validatingKindCarrier/deployTraitsCarrier/phaseCarrier + phaseOfProvider) + the HomeToken doc comment. The IR types this file used to define natively — InstallPlan struct, DeployTarget interface, InstallStep interface, EmitOpts, ReverseOp, the enums — relocated to sdk/spec (the SDK envelope relocation): InstallPlan + DeployTarget → sdk/spec/install_plan.go; InstallStep → sdk/spec/install_step.go; EmitOpts → sdk/spec/deploy_executor.go; ReverseOp → CUE-sourced at sdk/schema/deploy.cue (#ReverseOp, already listed below); GateEnabled → sdk/deploykit/plan.go. Package main and the SDK share ONE type (R3) — a charly-side re-declaration is the K3 ZERO-ALIASES violation this move dissolved |
sdk/deploykit/install_build.go |
BuildDeployPlan pure compiler: Candy → InstallPlan (moved from charly/install_build.go) |
charly/build_target_oci.go |
Residual helper only (P11c): the pod-overlay Containerfile WALKER (OCITarget) MOVED to sdk/deploykit/oci_target.go as the kind-blind deploykit.OCITarget walker, and the per-step DISPATCH ENTRY POINT is charly/oci_step_emit.go (ociEmitStep/dispatchOCIStep — a thin forwarder; the dispatch DECISION itself relocated to candy/plugin-installstep’s "oci-dispatch" word, K5-A item 2). This file keeps only formatDefCacheMountDefs (a FormatDef.CacheMount bridge deploy_host_helpers.go reads). The DEPLOY-mode overlay render — add_candy: Containerfile synthesis — now lives in candy/plugin-deploy-pod/overlay.go; dispatchOCIStep forwards the plugin-served build-emit kinds (deploykit.PluginEmitStepWords — the PURE C1.1 kinds + the no-op-emit reboot (C1.6) + the HOST-COUPLED system-packages (C1.2) + builder (C1.3) + local-pkg-install (C1.4) + op (C1.5)) plus the authored external:<word> step and the ExternalPlugin verb step to the compiled-in class:step plugin candy/plugin-installstep’s "oci-dispatch" word, which itself resolves the TARGET provider via the class-generic DescribeProvider (cached StepContract metadata) + InvokeProvider (dispatch) reverse-channel legs — no direct in-core providerRegistry consult anymore. The host-coupled kinds (system-packages/builder/local-pkg-install/op) no longer call back a host step-emit renderer at all (K5-Unit-6b): the plugin’s "oci-dispatch" word fetches the "resolved-project" envelope once per project dir and renders them DIRECTLY against its own deploykit.Generator (built via deploykit.NewRenderGeneratorFromProject), so the HostBuild("step-emit","oci-emit-step") seam is a thin dispatch forwarder only, not a render callback; a no-op-emit word like apk-install/reboot declares Emits=false and the plugin’s dispatch skips its OpEmit entirely |
candy/plugin-installstep |
Compiled-in DUAL-PLACEMENT class:step plugin serving the BUILD-context OpEmit for the compiler-emitted builtin InstallStep kinds. Three sub-categories: the PURE kinds — file/shell-hook/shell-snippet/service-packaged/service-custom/repo-change/apk-install (C1.1) + the no-op-emit reboot (C1.6) — whose fragment is pure string formatting from the compiler-produced spec.InstallStepView (the SAME view the deploy walk consumes), returned directly from OpEmit; local-pkg-install (C1.4), whose render (deploykit.RenderLocalPkgImageInstall — deploykit.BuildLocalPkgOnHost + host-dir staging for a dev bed, the release-download RUN for production, W3) is a PURE sdk/deploykit function operating only on its step argument (no *Config, no live *Candy graph) — it renders the SAME pure way as its siblings (no host callback); and the genuinely HOST-COUPLED system-packages (C1.2) + builder (C1.3) + op (C1.5) kinds. NONE of these call back a host step-emit renderer (K5-Unit-6b): the plugin’s "oci-dispatch" word fetches the "resolved-project" envelope once per project dir and renders all four DIRECTLY against its own deploykit.Generator (built via deploykit.NewRenderGeneratorFromProject) — system-packages against the DistroDef format templates, builder against the multi-stage buildStageContext + RenderTemplate engine, op (the RICHEST) via the SAME Generator.emitTasks the box build uses (the full per-verb render pipeline with COPY staging + op coalescing), just invoked from the plugin’s own Generator instance rather than a host round-trip. apk-install and reboot declare Emits=false (no-op build-emit — an image build installs no apk / reboots nothing). The DEPLOY leg for ALL these kinds is UNCHANGED (sdk/kit.WalkPlans; system-packages + builder + local-pkg-install are host-engine via RunHostStep → renderHostPackageCommand / runVenueBuilderStep / deploykit.ExecLocalPkgInstall; op is the act-OpStep resolveProvisionScript path (charly/checkrun_act.go / charly/plugin_executor_reverse.go — the former renderOpCommand wrapper around it was dead-code-radical-removal-batch deleted, test-only, zero production callers); reboot is the host-side guest reboot via RunHostStep → rebootVenueAndWait) — this plugin serves ONLY OpEmit; with C1.6 all 12 compiler-emitted kinds are plugin-served (ExternalPlugin is the 13th, dispatched by its own class:step plugin) |
charly/deploy_target_pod.go |
DELETED (P11c). The former in-core pod overlay target — the overlay Containerfile synthesis + tagDeployAlias + the render helpers — MOVED to candy/plugin-deploy-pod/overlay.go (the candy renders the overlay in its own code via deploykit.OCITarget + deploykit.NewRenderGeneratorFromProject). The two pure helpers core still needs (collectOverlayCandies, readImageRegistry) moved into charly/build_overlay.go. The pod config-WRITE path (quadlet) is a SEPARATE batch (#86, excluded from P11c) |
charly/build_overlay.go |
The host-side pod-overlay build PREP+RESOLVE M-seam (P11c — the overlay-BUILD dissolution). hostBuildOverlay is the F10 overlay host-builder: it reconstructs the core *Generator (with the deploy’s add_candy refs as ExtraCandyRefs), resolves the base image ref + distro + init + base-image metadata (ExtractMetadata), stages remote candy copies (host-fs createRemoteCandyCopies), projects an overlay-scoped *spec.ResolvedProject, serializes the live plans, caches the buildEngineContext for the "oci-emit-step" step-emitter, and returns the OverlayBuildReply envelope. The candy (candy/plugin-deploy-pod podPrepareVenue) consumes the envelope, constructs a deploykit.Generator via the shared deploykit.NewRenderGeneratorFromProject, renders the overlay Containerfile in its own code, and runs podman build + the alias tag via the served executor. The per-step fragments render via the generic "step-emit" host-builder (HostBuild("step-emit","oci-emit-step") → dispatchOCIStep, a thin forwarder to candy/plugin-installstep’s "oci-dispatch" word — K5-A item 2). The LIVE plans + nested-venue ParentExec/ParentNode ride the ctx (overlayBuildInputs), never serialized |
sdk/deploykit/deploy_chain.go |
rootExecutorForDeployNode — picks the root DeployExecutor for an external local: deploy node (ShellExecutor for host:local/absent, SSHExecutor for host:<user@machine>); the deploy:local substrate itself is served out-of-process by candy/plugin-deploy-local, which walks the plan via sdk/kit.WalkPlans (moved from charly/deploy_chain.go) |
charly/vm_lifecycle_preresolve.go |
FINAL/K5 unit 6a, M4b: the vm lifecyclePrepareHook DATA-seam is GONE (hard cutover) — candy/plugin-deploy-vm now resolves its OWN spec.LifecyclePrepareInput end-to-end via the generic “deploy-entity-resolve” HostBuild seam (vmPrepareVenue, the reference every future Lifecycle:true substrate’s own prepare body follows). This file keeps only the F12 vmAttachResolver (a live-session script the plugin cannot derive itself, for charly shell/charly cmd) + the vm lifecyclePostTeardownHook/vmLifecyclePostTeardown (the residual ephemeral-lifecycle host cleanup the plugin can’t do — systemd transient timers + libvirt snapshot refcounts). The vm venue lifecycle ITSELF (boot the domain, build the guest SSHExecutor, nested pod-in-guest, teardown, charly vm Start/Stop/…/Rebuild) is IMPLEMENTED IN THE PLUGIN candy/plugin-deploy-vm/lifecycle.go over kit + HostBuild("cli") + the served guest executor; the plan WALK likewise runs there via kit.WalkPlans over the guest executor (so the same walk runs inside the guest) |
charly/unified_targets.go |
S3b (Unit-6 core-minimization): pluginDeployTarget — the THIN, DATA-ONLY UnifiedDeployTarget/LifecycleTarget proxy that replaced externalDeployTarget/grpcSubstrateLifecycle. It holds ONLY plain data (name/word/hasLifecycle/hasPreresolve/node) + a live venue executor — never a core-private *grpcProvider (constructed only at plugin-CONNECT time, a clause-M mechanism that cannot move into a plugin). ALL FIVE substrates (local/vm/pod/k8s/android) resolve to it via ResolveTarget; every method (Add/Update/Del/Test/Start/Stop/Status/Logs/Shell/Attach/Rebuild) marshals a spec.DeployTargetDispatchRequest{Op: "add"|"update"|…} and calls its own dispatch(), which threads a live executor and calls dispatchDeployTarget (below). Two things stay core-resident BY DESIGN, wrapping the dispatch rather than living inside it: the arbiter acquire/release bracket (charly/arbiter_bracket.go, Start/Stop only — CHARLY_PREEMPT_LEASE is process-ENV state that only behaves correctly in the SAME OS process as the host) and the pod Start/Stop/Attach/Logs plan-hook table read (pod_lifecycle_dispatch.go, unmoved — a pure ctx-opts marshal with no core-only dependency) |
charly/deploy_target_dispatch.go |
dispatchDeployTarget — the core-side half of the S3b move: threads a live executor onto the ctx via the SAME “compiled-in in-proc reverse channel” pattern arbiterInvoke/dispatchEphemeralOp use (no broker needed — command:bundle is COMPILED-IN), then Invokes command:bundle’s sdk.OpDeployDispatch with the marshalled spec.DeployTargetDispatchRequest. Core never touches the substrate’s *grpcProvider directly once this call returns |
charly/arbiter_bracket.go |
S3b Unit-6 design Q1: arbiterBracketedStart/arbiterBracketedStop — the core-resident resource-arbiter acquire/release bracket around pluginDeployTarget.Start/.Stop’s dispatch call (acquire BEFORE dispatch, release on the failure path for Start, release AFTER dispatch for Stop). hasPlan is read from the SAME lifecycleStartPlanHooks/lifecycleStopPlanHooks table the caller already consults (R3) |
candy/plugin-bundle/deploy_target.go |
S3b: the ORCHESTRATION bulk of the former externalDeployTarget (deploy_target_external.go, DELETED) + grpcSubstrateLifecycle (substrate_lifecycle_grpc.go, DELETED), ported here behind the ONE generic sdk.OpDeployDispatch selector (runDeployDispatch, discriminated by the request’s Op field: add/update/del/start/stop/status/logs/shell/attach/rebuild). What moved: PrepareVenue, the plan/venue marshal + InvokeProvider dispatch to the ACTUAL substrate provider (candy/plugin-deploy-pod/-vm/-local, candy/plugin-kube, candy/plugin-adb — S1), recordDeploy, recordVenueLedger, prepareReverseState, Del’s ledger-read+teardown+PostTeardown, ArtifactKey, PostApply, and the ready-to-dispatch Start/Stop/Status/Logs/Shell/Attach/Rebuild bodies. What did NOT move (per Unit-6 design Q1–Q4): the arbiter bracket (above), the pod/vm plan-hook tables, and the core-only siblings prepareCandySecrets/retrieveArtifactsAndK3s/checkLocalDeployScope, which wrap the dispatch call from pluginDeployTarget.Add instead |
charly/plugin_dispatch_reverse.go |
The F10 reverse legs on ExecutorService (host-served on the SAME broker InvokeWithExecutor stands up). InvokeProvider — PLUGIN↔PLUGIN: the host resolves another provider by (class, word) + Invokes it on the calling plugin’s behalf, threading the SAME venue executor into an OUT-OF-PROCESS target via InvokeWithExecutor (a fresh nested broker — the generalization of the RunHostStep ExternalPlugin arm from OpExecute-only to ANY class/op) or a direct Invoke for an in-proc target. HostBuild — the calling plugin requests a HOST-side build (the build engine is in core TODAY — K3 build-engine migration inventory, not permanent core; the box-build podman DRIVE moved into candy/plugin-build in P8b, and the Containerfile RENDER DRIVE moved to sdk/deploykit (#67, driven by plugin-build over the envelope + the render-seam reverse legs; the host render-leg is DELETED)): the host runs the registered hostBuilder for kind (registerHostBuilder/hostBuilderFor — ~54 registered kinds today; the count drifts per cone, so the authoritative list is git grep 'registerHostBuilder(' charly/*.go resolving the *BuilderKind constants). The build/render-relevant: the 8 buildengine-* kinds → hostBuildPrep/hostBuildScanLocal/hostBuildScanRemote/hostBuildCollectRemoteRefs/hostBuildEnsureRepo/hostBuildConnectPlugins/hostBuildNamespaced/hostBuildContextIgnoreBaseline (host_build_buildengine.go — REPLACES the former fat build-prep seam hostBuildBuildResolve/build_resolve_host.go, DELETED — the box-build loader + prep + resolved-project envelope seam the candy drive calls, now split into the 8 buildengine verbs) + render-seam → hostBuildRenderSeam (host_build_render_seam.go — the #67 render’s host-coupled seams: RenderService, builder resolves, ValidateEgress, EmitPluginOp, localpkg) + overlay → hostBuildOverlay (build_overlay.go — the prep+resolve M-seam, P11c) + step-emit → hostBuildStepEmit (step_emit_hostbuild.go) + plugin-binary → buildPluginBinary (F10). bake_plugin: binary baking is INLINE via deploykit.EmitBakedPlugins (the former bake-plugins/hostBuildBakePlugins host-builder + host_build_bake_plugins.go are DELETED — no HostBuild round-trip). The remaining kinds span the loader-* (bootstrap/walk/threaded/materialize), pod-* (start/stop/shell/cmd/logs/update/service/remove lifecycle verbs), pod-config-* (setup/remove/ensure-image/detect-devices/ssh-key/list-sidecars), deploy-del-resolve/deploy-entity-resolve/deploy-members-up/-down/resolve-target-add/deploy-node-del-dispatch/deploy-plugins-connect/deploy-from-box, config-resolve (config-persist is DELETED — persist moved plugin-side to candy/plugin-vm/vm_host_persist.go), check-bed/check-run/check-load-plugins, arbiter-bracket-acquire/-release, retention-defaults, raw-project, remote-image-resolve, box-fetch-resolve, validate-project-checks, render-service, construct-step, feature, hostprobe, and cli families (host_build_*.go). SDK clients: sdk.Executor.InvokeProvider / .HostBuild. (M13 k8s-gen + M16 egress did NOT use HostBuild — they are compiled-in candies fronted by a host shim, the lighter pattern when the subsystem has no venue-coupled build.) The dispatch + host-build MACHINERY M14/M17 consume; example candy/plugin-example-dispatch |
charly/plugin_step_external.go |
externalPluginStepProvider — the StepProvider for StepKindExternalPlugin (a plugin-verb run: step — authored as <word>: <input> sugar, desugared to the internal plugin/plugin_input op — served by an OUT-OF-PROCESS plugin): EmitOCI→Invoke(OpEmit) is the only in-proc Emit (the image-build / pod-overlay Containerfile fragment). At DEPLOY time the step is executed via the shared invokeExternalStep dispatch (charly/plugin_executor_reverse.go, S4) — Invoke(OpExecute) WITH the host’s executor over the reverse channel, recording the reply’s dynamic ReverseOps to the CandyRecord; the external local/vm deploy walks reach it as a host-engine step over RunHostStep. The executorInvoker capability is the discriminator |
charly/step_emit_hostbuild.go |
F-STEP-EMIT: the generic step-emit HostBuild builder — registerHostBuilder("step-emit", hostBuildStepEmit) dispatches by step WORD to the stepEmitters per-word registry (registerStepEmitter), returning a spec.EmitReply. After K5-Unit-6b the FOUR HOST-COUPLED per-word emitters (stepEmitSystemPackages / stepEmitBuilder / stepEmitLocalPkgInstall / stepEmitOp, the former C1.2–C1.5 host renderers) are GONE — the host-coupled kinds no longer call back a host step-emit renderer at all; candy/plugin-installstep’s "oci-dispatch" word fetches the "resolved-project" envelope once per project dir and renders them DIRECTLY against its own deploykit.Generator (built via deploykit.NewRenderGeneratorFromProject). What REMAINS on this seam is the oci-emit-step dispatch forwarder (stepEmitOCIEmitStep, the pod-overlay step-emitter): the candy’s deploykit.OCITarget.EmitStepOp reaches it over HostBuild("step-emit","oci-emit-step"), and it forwards the step/plan WIRE views + the caller’s Distros/buildEngineContext to candy/plugin-installstep’s "oci-dispatch" word — a thin forwarder, NOT a render (the render lives plugin-side). The spec.StepEmitRequest envelope ({Word,Payload,Distros}) carries the dispatch payload. |
sdk/schema/deploy.cue + sdk/schema/buildwire.cue (appended/new; SDD conversion — the former hand-written sdk/spec/deploy_wire.go is deleted) |
The deploy IR wire types shared with the plugin SDK: #ReverseOp (+ the hand Scope/ReverseOpKind named-enum types + ReverseOpPluginScript const in spec/deploy_consts.go), #InstallPlanView, #DeployVenue, #DeployReply (deploy.cue), plus the build-time #BuildEnv / #EmitReply (verb/step OpEmit) / #StepEmitRequest (F-STEP-EMIT: the HostBuild("step-emit", …) envelope for a host-coupled external step — {Word,Payload,Distros}) / #BuilderResolveReply (builder OpResolve: {Stage, CopyArtifacts}) / #BuildRequest + #BuildReply (the BUILD-ENGINE DISPATCH envelope for the build:box / build:generate plugin’s OpBuild HostBuild call — charly box build/generate; see /charly-internals:plugin) / #OverlayBuildRequest + #OverlayBuildReply (the pod-overlay dispatch envelope for the F10 overlay host-builder — candy/plugin-deploy-pod’s PrepareVenue (M4); scalars only, the live plans + venue ride the ctx) (buildwire.cue) |
charly/plugin_prescan.go |
The byte-gated, additive parse pre-scan that recognizes an EXTERNAL deploy SUBSTRATE word at config-parse time, before the provider connects |
charly/k8s_generate.go |
GenerateK8sKustomize — now a thin in-core SHIM (M13): lifts the 3 caps scalars + spec.Deploy/spec.K8s into spec.K8sGenInput, Invokes the compiled-in candy/plugin-k8sgen (verb:k8sgen/OpEmit) for the manifest docs, validates each HOST-SIDE via the M16 egress shim, writes the base/+overlays/ tree. Called by the k8s deploy preresolver + from-box --target k8s (unchanged signature), NOT a DeployTarget. The generator itself lives in candy/plugin-k8sgen/k8sgen.go |
candy/plugin-kube/preresolve.go |
F6/FINAL-K5-unit-6a: the PLUGIN-side deploy:k8s preresolver (relocated from the now-DELETED charly/k8s_deploy_preresolve.go) — serves Invoke(OpPreresolve), resolves the image Capabilities + the kind:k8s cluster template (the LoadUnified-coupled lookup reaches the host via the “deploy-entity-resolve” seam), generates the egress-validated Kustomize tree via the host’s host_build_k8s_generate.go “k8s-generate-kustomize” HostBuild seam (wrapping charly/k8s_generate.go’s GenerateK8sKustomize, which STAYS core-only glue), and returns a spec.K8sDeployVenue carrying the overlay path |
sdk/kit/deploy_executor*.go |
DeployExecutor interface + ShellExecutor + SSHExecutor — shell + file-copy abstraction (the core SSHExecutor.WaitFor* are thin delegates to sdk/kit’s WaitForSSH / WaitForCloudInit / WaitForPackageLock). The external vm deploy’s OpPrepareVenue builds the guest SSHExecutor the reverse channel serves; a local: {host: user@machine} remote also uses SSHExecutor |
sdk/kit/venue_descriptor.go |
FIX ROUND (S3b follow-up, R10 bed-found): VenueFromDescriptor(spec.VenueDescriptor) (spec.DeployExecutor, error) re-materializes a wire descriptor into a live ShellExecutor/*SSHExecutor (moved here from the deleted charly/substrate_lifecycle_grpc.go’s venueFromDescriptor — zero core-only dependency, pure sdk/kit+sdk/spec); its pure INVERSE DescriptorFromExecutor(spec.DeployExecutor) spec.VenueDescriptor flattens a live executor back into the wire shape (""/"shell"/"ssh" only — any other concrete type, e.g. a composed *NestedExecutor, returns the zero descriptor). Promoted here because it now has TWO callers needing the byte-identical conversion: the host (charly/unified_targets.go’s pluginDeployTarget.applyParentExecOverride, converting a nested child’s live ParentExec into venue_json before it crosses the wire) and the plugin (candy/plugin-bundle/deploy_target.go’s venueDescriptorFromExecutor, now a thin forward to this SAME function — R3, one function for both directions) |
charly/install_plan_test.go |
IR unit tests (scope/venue/gate/reverse derivations) |
charly/install_build_test.go |
Compiler integration tests (ripgrep, dev-tools, pixi layer) |
Reference Index
Section titled “Reference Index”| Topic | File |
|---|---|
Core types (InstallPlan, InstallStep, the Scope/Venue/Phase/Gate/StepKind enums), the full step-kind catalog (thirteen builtin kinds + the open external:<word> family), the check-vs-production toolchain distinction, and the BuildDeployPlan compiler |
references/step-kinds.md |
The DeployTarget interface and every target implementation: deploykit.OCITarget (pod-overlay walker), pod/local/vm/k8s (each an external plugin), and pluginDeployTarget + candy/plugin-bundle (the S3b unified dispatch, including nested-child venue threading and what stays core-resident) |
references/deploy-targets.md |
The four Op selectors (OpEmit/OpResolve/OpExecute/OpRun), StepBatch batching, deferred home resolution, the ReverseOp catalogue, EmitOpts flags, and the testing layout |
references/emit-opts-and-testing.md |
Cross-References
Section titled “Cross-References”/charly-internals:local-infra— supporting files (hostdistro, ledger, builder_run, shell_profile, reverse_ops, service_render, deploy_ref)/charly-internals:vm-deploy-target— the externalvmdeploy (candy/plugin-deploy-vm) + the plugin-implemented venue lifecycle reached throughpluginDeployTarget/candy/plugin-bundle’sOpDeployDispatch+ DeployExecutor + SSHExecutor + VmDeployState/charly-internals:vm-spec— VmSpec shape the vm deploy reads/charly-internals:go— overall Go code map; Kong CLI framework; mode-purity invariant/charly-internals:generate-source— Containerfile generation call graph; howdeploykit.OCITargetplugs into the render/charly-core:deploy— user-facingcharly bundle add/delsurface (host / container / vm: / kubernetes)/charly-local:local-deploy— host-target user-facing behavior (ledger, gates, ReverseOps)/charly-kubernetes:kubernetes— K8s-target user-facing behavior (cluster profiles, Kustomize layout)/charly-vm:vm— VM command family; the vm deploy’s venue (charly vm create, or auto-booted by the vm deploy plugin’sOpPrepareVenue, beforecharly bundle add vm:...)/charly-build:build— build-mode user-facing surface; three-phase template story/charly-image:layer— charly.yml schema including unifiedservice:that map toServicePackagedStep/ServiceCustomStep
When to Use This Skill
Section titled “When to Use This Skill”MUST be invoked when reading or modifying any of charly/install_plan.go, sdk/deploykit/install_build.go, charly/build_target_*.go, charly/deploy_target_*.go; when adding a new step kind, target, or reverse-op kind; or when debugging why a particular layer produces a plan that doesn’t match expectations. Invoke BEFORE reading the source files or running Explore agents against this subsystem.