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 fleet 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 fleet 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-fleet’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 fleet add vm:<name> <ref>applies the recipe inside a running VM over SSH. - Kubernetes deploy (
kubernetes:substrate) —charly fleet add <name>emits 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-fleet’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 an EmitTarget 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")); the pod-overlay deploy path reaches deploykit.Generator.EmitTasks DIRECTLY — charly/tasks.go holds no shim and cannot hold one (P16 import purity forbids charly/ importing the sdk); that path 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 |
|---|---|
spec/spec/provider_carriers.go + charly/provider_registry.go |
The provider carrier interfaces are spec.StepContractCarrier / StructuralKindCarrier / ValidatingKindCarrier / DeployTraitsCarrier / PhaseCarrier (spec-side, so host and plugin read ONE definition); core keeps only the phaseOfProvider reader in charly/provider_registry.go. charly/install_plan.go is GONE — every IR type it once declared natively lives in spec: InstallPlan + EmitTarget → spec/spec/install_plan.go; InstallStep → spec/spec/install_step.go; EmitOpts → spec/spec/deploy_executor.go; GateEnabled → spec/spec/install_plan_view.go; ReverseOp → CUE-sourced at spec/schema/deploy.cue (#ReverseOp, listed below); HomeToken → spec.HomeToken, re-exported by sdk/deploykit/plan.go. Package main and the SDK share ONE type (R3) — a charly-side re-declaration is the 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 (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 — the charly generate-packages plugin build + 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 the compiler-emitted kinds are plugin-served (ExternalPlugin 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 — DELETED (K-wave 2 cone CONTESTED). |
FINAL/K5 unit 6a, M4b + K-wave 2 cone CONTESTED: the vm lifecyclePrepareHook DATA-seam is GONE (hard cutover) — candy/plugin-deploy-vm resolves its OWN spec.LifecyclePrepareInput end-to-end by self-loading the project directly (K-wave W3a A3-phase-2: sdk/loaderkit.ResolveVmEntityViaExecutor, vmPrepareVenue — the reference every future Lifecycle:true substrate’s own prepare body follows; no HostBuild seam remains for this leg). The last survivor, the F12 vmAttachResolver, was then DELETED too (K-wave 2 cone CONTESTED): its body was strings.Join(cmd, " "), derivable from the raw cmd candy/plugin-fleet threads for OpAttach — plugin-deploy-vm/candy/plugin-deploy-vm/lifecycle.go’s vmAttach now derives the script from lifecycleParams.Cmd, and charly/unified_targets.go’s Attach threads the raw cmd for a hookless lifecycle substrate (the lifecycleAttachPlanHooks map keeps only “pod”). The vm lifecyclePostTeardownHook/vmLifecyclePostTeardown had already moved plugin-side (plugin-deploy-vm/candy/plugin-deploy-vm/lifecycle.go’s vmPostTeardown, F6 vm-lifecycle move; the residual ephemeral-lifecycle host cleanup — systemd transient timers + libvirt snapshot refcounts — runs there, reached via OpEphemeralTeardown). 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 plugin-deploy-vm/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/kubernetes/android) resolve to it via ResolveTarget; every method (Add/Update/Del/Start/Stop/Status/Logs/Shell/Attach/Rebuild — Test DELETED, #55 W3 B3 remainder: zero real callers anywhere in the tree) marshals a spec.DeployTargetDispatchRequest{Op: "add"|"update"|…} and calls its own dispatch(), which threads a live executor and calls dispatchDeployTarget (below). One thing stays core-resident BY DESIGN, wrapping the dispatch rather than living inside it: 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). The arbiter acquire/release ENV bracket is FULLY PLUGIN-SIDE (candy/plugin-fleet’s handleLifecycleSimple Invokes verb:arbiter directly; the “arbiter-bracket-*” HostBuild seam is DELETED, K-wave 2 cone R2 bank E). The ONE remaining core arbiter primitive is the op=“remove” release chain folded into host_build_pod_lifecycle_dispatch.go (K-wave 2 cone CONTESTED — envPreemptLeaseHeld/arbiterProxy/arbiterInvoke/ReleaseClaimant/releaseResourceClaim, relocated from the deleted preempt.go): CHARLY_PREEMPT_LEASE is host-process env state that only behaves correctly in the SAME OS process as the host, and candy/plugin-pod ships out-of-process — so the pod-remove release bracket stays host-side |
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 uses (the former dispatchEphemeralOp is DELETED — only a comment survives; no broker needed — command:fleet is COMPILED-IN), then Invokes command:fleet’s sdk.OpDeployDispatch with the marshalled spec.DeployTargetDispatchRequest. Core never touches the substrate’s *grpcProvider directly once this call returns |
charly/host_build_arbiter_bracket.go — DELETED (K-wave 2 cone R2 bank E). |
The Q1 resource-arbiter bracket is FULLY PLUGIN-SIDE: plugin-fleet/candy/plugin-fleet/deploy_target.go’s handleLifecycleSimple wires the acquire/release as InvokeProvider("verb","arbiter",OpRun) calls (arbiterBracketAcquire/arbiterBracketRelease) into the pure, directly-testable runLifecycleBracket (brackets only when HasPlan + non-nil Node + Start or Stop). The CHARLY_PREEMPT_LEASE outer-orchestrator GUARD lives in candy/plugin-preempt’s invokeArbiter (the arbiter is the ONE dispatch every acquire/release passes through; plugin-fleet + plugin-preempt are both compiled-in, sharing charly’s process — a nested charly subprocess still inherits the lease and skips re-acquiring). The former “placement-agnostic plugin cannot own [the lease env]” framing was stale. |
plugin-fleet/candy/plugin-fleet/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) and the pod/vm plan-hook tables — genuinely core-resident, process-env-gated or zero-dependency respectively. Secret injection/artifact retrieval (injectCandySecrets/retrieveArtifactsAndDispatchRegisters, secrets_artifacts.go) and --verify (verifyLocalDeployScope/localDeployScopePlan, verify_local.go, #55 W3 B3) run HERE too, inside handleDeployApply itself, not core siblings wrapping the dispatch call — pluginDeployTarget.Add (charly/unified_targets.go) only dispatches and returns now |
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 — 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 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 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/detect-devices/list-sidecars — the ensure-image and ssh-key legs DIED, K-wave W3a B6: candy/plugin-deploy-pod drives podman/build:ensure and reads the host SSH-key FS itself now), resolve-target-add/deploy-node-del-dispatch/deploy-plugins-connect (deploy-entity-resolve DIED too, K-wave W3a A3-phase-2 — every kind:deploy-members-up/-down DIED at K-wave W3a A4 — candy/plugin-fleet calls sdk/deploykit.BringUpMembers/TearDownMembers directly; deploy-from-box DIED at K-wave 2 cone R2 — candy/plugin-fleet’s from_box_pod.go reaches deploy:pod’s OpConfigSetup by direct InvokeProvider; deploy-del-resolve DIED at K-wave 2 cone R2 bank C — the del resolution moved to candy/plugin-fleet’s del_resolve.go; config-resolve DIED at K-wave 2 cone R2 bank D — the three consumers self-serve via loaderkit: candy/plugin-vm’s hostConfigResolve, plugin-deploy-vm’s resolvePriorVmState, plugin-kube’s hostConfigResolveVmState; config-persist is DELETED — persist moved plugin-side to candy/plugin-vm/vm_host_persist.go), check-run/check-load-plugins, check-bed-gpu-prereq (the ONE narrow seam surviving check-bed’s full dissolution, K-wave W3 B2-full — GPU host-DETECTION is the project’s operator-dropped exception; every other former check-bed responsibility — flock/lease/repo-override/deploy-config env — moved into candy/plugin-check/bed_session.go, no HostBuild round-trip; arbiter-bracket-acquire/-release DIED at K-wave 2 cone R2 bank E — the bracket is plugin-side InvokeProvider(verb:arbiter); retention-defaults DIED at K-wave 2 cone R6 — the defaults resolve plugin-side via loaderkit.ResolveRetentionDefaultsViaExecutor; feature DIED at K-wave 2 cone R6 — the enumeration loads the project plugin-side via loaderkit), remote-image-resolve, box-fetch-resolve, validate-project-checks, render-service, construct-step, 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_executor_reverse.go + candy/plugin-installstep/oci_dispatch.go |
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); the former charly/plugin_step_external.go externalPluginStepProvider is DELETED (K-wave 2), its in-proc EmitOCI now candy/plugin-installstep/oci_dispatch.go’s dispatchExternalPluginVerb (Invoke(OpEmit) — 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. |
spec/schema/deploy.cue + spec/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/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 |
candy/plugin-kube/preresolve.go |
F6/FINAL-K5-unit-6a: the PLUGIN-side deploy:kubernetes preresolver (relocated from the now-DELETED charly/k8s_deploy_preresolve.go) — serves Invoke(OpPreresolve), resolves the image Capabilities + the kind:kubernetes cluster template by self-loading the project PLUGIN-SIDE (K-wave W3a A3-phase-2: sdk/loaderkit.ResolveKubernetesEntityViaExecutor — no HostBuild round trip remains for this lookup), generates the egress-validated Kustomize tree ENTIRELY plugin-side too (materialize.go’s materializeKustomize, InvokeProviding the compiled-in verb:k8sgen/OpEmit + verb:egress/OpValidate peer-to-peer — the former host_build_k8s_generate.go “k8s-generate-kustomize” HostBuild seam + charly/k8s_generate.go’s GenerateK8sKustomize it used to wrap are BOTH DELETED, K5-A item 6), and returns a spec.KubernetesDeployVenue carrying the overlay path |
spec/exec/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 |
spec/exec/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+spec/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 (plugin-fleet/candy/plugin-fleet/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) |
sdk/deploykit/plan_compile_test.go |
Compiler integration tests (ripgrep, dev-tools, pixi layer — the former charly/install_build_test.go is DELETED, K-wave 2) |
Reference Index
Section titled “Reference Index”| Topic | File |
|---|---|
Core types (InstallPlan, InstallStep, the Scope/Venue/Phase/Gate/StepKind enums), the full step-kind catalog (the builtin kinds + the open external:<word> family), the check-vs-production toolchain distinction, and the BuildDeployPlan compiler |
references/step-kinds.md |
The EmitTarget interface and every target implementation: deploykit.OCITarget (pod-overlay walker), pod/local/vm/kubernetes (each an external plugin), and pluginDeployTarget + candy/plugin-fleet (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,sdk/deploykit/compile_service_steps.go(service_render, relocated),candy/plugin-fleet/deploy_ref.go(deploy_ref, relocated))/charly-internals:vm-deploy-target— the externalvmdeploy (candy/plugin-deploy-vm) + the plugin-implemented venue lifecycle reached throughpluginDeployTarget/candy/plugin-fleet’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 fleet add/delsurface (host / container / vm: / kubernetes)/charly-local:local-deploy— host-target user-facing behavior (ledger, gates, ReverseOps)/charly-kubernetes:kubernetes— Kubernetes-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 fleet 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 spec/spec/install_plan.go (the IR + EmitTarget interface — the former charly/install_plan.go is DELETED, K-wave 2), sdk/deploykit/install_build.go, sdk/deploykit/oci_target.go (the build/deploy targets — the former charly/build_target_*.go/charly/deploy_target_*.go are DELETED, K-wave 2); 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.