Repository navigation
fix(pod): resolve the deploy key to a box name before prepare-venue/rebuild - #45
Conversation
…ebuild A qualified pod bed is dispatched as the deploy KEY `ns/name` (`charly/check-sidecar-pod`), but prepare-venue and rebuild treated that key as a BOX name whenever the host supplied no explicit image. `build:generate resolve` then failed with `unknown box "charly/check-sidecar-pod"` (opencharly/plugin-build#46). Route both call sites through the plugin's OWN deploy-key→box resolver (`resolve_ref.go`, the same rule `charly config` uses) instead of spelling a second fallback. An explicit `p.Image` still wins untouched; otherwise the key is resolved against the per-host overlay and then the PROJECT config, trying both spellings — the exact deploy key (`ns/name`, the authored local-instance shape) and the namespace-qualified project key (`ns.name`, `spec.UnifiedFile.Deploys`) — so a fresh `deploy add` with no per-host entry yet still resolves the declared `image:`. Only a genuinely unresolvable key falls back to the key itself. The new test drives the shared helper: it fails without the fix (the key is returned verbatim, i.e. exactly the string that reached FilterBox) and passes with it. The per-host read became a package var (the same test-seam idiom as saveDeploy / mutateDeploy), so the test covers both the per-host and the project legs without driving the operator's real overlay. The live `charly.check-sidecar-pod` bed remains the R10 arm. *Agent: `containerd-crabbox-program/46` · session `8b1c01cf-7f79-4a75-bff8-240415a06208`* *Assisted-by: Claude Code deepseek-v4.1-flash:cloud (fully tested and validated)*
…ame rule Measured on the same qualified bed: with the prepare-venue fix in place, the bed's next step — `charly config charly/check-sidecar-pod --tag <tag>` — resolved the project's namespace-qualified entry to the bare first segment `charly` and built the ref `charly:<tag>`, a ref nothing ever built (`box="charly"`, `ref="charly:testtag"`). Same mechanism, so the same ONE rule covers it: `resolveDeployRefLocal` now resolves its deploy key through `resolveDeployBoxNameForKey`, the helper the lifecycle paths already use. No resolver semantics change: `deployKeyToBoxLocal` and its base/instance precedence are untouched. The helper only adds the two spellings a namespaced deploy carries (the exact `ns/name` key and the `ns.name` project key) and lets each caller keep its own fallback. The regression test gains the config leg and fails on BOTH legs when the namespace-qualified candidate is disabled. *Agent: `containerd-crabbox-program/46` · session `8b1c01cf-7f79-4a75-bff8-240415a06208`* *Assisted-by: Claude Code deepseek-v4.1-flash:cloud (fully tested and validated)*
The host's `overlay` builder takes `req.Image` as the base ref and falls back to
`req.DeployName`, so resolving the deploy key to a box name AFTER the
HostBuild("overlay") call was one step too late: the raw key reached podman as a
base ref.
Measured on a disposable bed (`charly.check-sidecar-pod`, run 2026.307.2144):
[vm-build-check-sidecar-pod-ephvm] PASS 3m57.571s
[image-build] PASS 12m13.092s
[check-image] PASS 2m41.711s
[deploy-add] FAIL 25m53.509s
short-name "charly/check-sidecar-pod" did not resolve to an alias
After this reorder the same bed reaches prepare-venue with the resolved BOX
name instead of the deploy key, which is what this change is for.
It then fails one layer further on, in charly/charly/build_overlay.go, which
reuses the qualified BOX name as an IMAGE ref:
built ghcr.io/opencharly/check-k8s-deploy-app:charly.check-sidecar-pod-2026.307.2144
composed charly.check-k8s-deploy-app:<tag>
That is opencharly/charly#968, already open and filed by another session
against a different consumer. It is not introduced here and cannot be fixed in
this repo, so this change lands at `analysed on a live system`: the changed
path executes live, the bed is not green.
Agent: containerd-crabbox-program, session 8b1c01cf-7f79-4a75-bff8-240415a06208
Assisted-by: Claude Code ollama-cloud/deepseek-v4.1-flash (analysed on a live system)
…ress
`charly config` resolved a qualified pod deploy to the BOX ADDRESS its deploy
named and handed that address to ensure-image as an IMAGE repo:
`charly.check-k8s-deploy-app:<tag>`, with no registry. The build had produced
`ghcr.io/opencharly/check-k8s-deploy-app:<tag>` — the box LEAF under its
namespace's `defaults.registry` — so the config path failed with
image not found in local storage: charly.check-k8s-deploy-app:<tag>
as measured on bed run 2026.307.2336, after the paired host fix (charly#1105)
moved the bed past deploy-add.
Mechanism: resolveDeployRefLocal composed the ref with
kit.ResolveShellImageRef("", deployBoxName, tag) — a literal empty registry —
while the registry qualification that existed ran 57 lines later, AFTER
ensure-image/ExtractMetadata, reading the registry from the IMAGE's OCI label.
That label is unreachable precisely because the unqualified ref fails the
existence check first.
Fix: compose the ref ONCE, before any existence check, from the box the build
names — box registry, else its namespace's defaults.registry, then the box
LEAF — the same rule the host build path applies (resolveBoxImageRegistryName
plus ResolveShellImageRef). The post-hoc label-sourced qualification and the
redundant re-resolve on the start path are deleted (R2/R5).
Regression test: TestResolveDeployRefLocal_QualifiesTheBoxBuildRef drives both
spellings that reach the resolver (the per-host `image:` box address and the
project deploy map's bare leaf) and asserts the build's ref. It fails on the
pre-fix composition with the exact measured strings, and it fails again when
the FindBoxByLeaf fallback is disabled.
Refs opencharly/plugin-build#46 and opencharly/charly#968. The R10 bed
(charly.check-sidecar-pod) is the coordinator's post-push gate; this commit
claims the module gate only.
Agent: containerd-crabbox-program/44cfg, session 8b1c01cf-7f79-4a75-bff8-240415a06208
Assisted-by: Claude Code ollama-cloud/deepseek-v4.1-flash (analysed on a live system)
Review — BLOCKHead SHA: Change class: code (Go) — bug fix in the Security screen: PASS — no secrets, no egress, no weakened guardrails, no instruction-injection in the body (its "written clean from the start" prose is descriptive, not a directive to me). Blocks
Comment intake: none (0 comments) — nothing to disposition. Verdict: BLOCK ollama-cloud/deepseek-v4.1-flash — action-review. |
Review — BLOCKHead SHA: Change class: code (Go) — bug fix in the Security screen: PASS — no secrets, no egress, no weakened guardrails, no instruction-injection in the body. Blocks
Comment intake
Verdict: BLOCK ollama-cloud/deepseek-v4.1-flash — action-review. |
Review — PASSHead SHA: Security screen: PASS — no secrets/egress, no weakened guardrails, no instruction-injection; body prose is descriptive, not directive. Comment intake:
Non-blocking note: the R1 row's "the prior BLOCK's three blocks … in the round comment" is a minor count/reference inaccuracy — the visible prior block carried two, and the round RCA lives in the body's "Round 4 finding" section (with a real "Missed control" paragraph). The R1 substance is satisfied; no action needed to merge. Verdict: PASS ollama-cloud/deepseek-v4.1-flash — action-review. |
Summary
Head
48c99de9ad94eb938f921621ddf1dc845f9b9dbd(tree6cc21e9d0dbf879060294c7a81a2a1c21dd14687), base1f322ea— 6 files, +376/−155. Four commits.A qualified pod bed is dispatched as
ns/name(the deploy key), while its box is the declaredimage:. This PR makes the whole path — prepare-venue/rebuild, config, and start — resolve that key to the box, and then to the image ref the build actually produced.resolve_ref.goresolveBaseBoxName/resolveDeployBoxNameForKey— ONE deploy-key→box rule: per-host overlay entry first, then the project config in both spellings (ns/nameexact key,ns.nameproject key); an explicitp.Imagepasses through untouched; the key is returned only when genuinely unresolvable.resolveDeployImageRef/projectBoxRegistry— ONE box→image-ref rule, applied BEFORE ensure-image/ExtractMetadata: the box's ownregistry:, else its NAMESPACE'sdefaults.registry, then the box LEAF (the same rule the host build path applies). The post-hoc, image-label-sourced qualifier is deleted.lifecycle.gopodPrepareVenueresolves beforeHostBuild("overlay")and passes the resolved name asOverlayBuildRequest.Image, keepingDeployNameas the deploy key;podRebuilduses the same helperconfig_setup.gocharly configpath resolves through the same helper; no second qualification after the existence checkresolve.goresolve_box_name_test.goresolve_ref_test.goThe ordering matters and is the substance of the third commit: the host's overlay builder takes
req.Imageas the base ref, so resolving after theHostBuildcall was one step too late.Round 4 finding — the config path composed a bare box address as an image repo (blocking, fixed here)
Measured on the paired bed run
2026.307.2336(charly92c13bfe/ #1105 + this branch's parent treec8ff5105):The image the build produced is
ghcr.io/opencharly/check-k8s-deploy-app:charly.check-sidecar-pod-2026.307.2336(from the bed's owncheck-image.log, and confirmed present bycharly box list tags; itsai.opencharly.registry=ghcr.io/opencharlylabel is read bycharly box labels). The config path asked for the namespace-qualified box address with no registry.resolveDeployRefLocalcomposed the ref withkit.ResolveShellImageRef("", deployBoxName, tag)— a literal empty registry.deployBoxNameon this path is the deploy's resolved box address (charly.check-k8s-deploy-app, persisted in the per-hostimage:by deploy-add, or the project deploy map's bare leaf). The registry qualification that existed ran 57 lines later inrunConfig, AFTERensureImagePresent/ExtractMetadata, and read the registry from the IMAGE's OCI label — unreachable precisely because the unqualified ref fails the existence check first.check-k8s-deploy-app) but not the IMAGE REF, and acceptedcheck-k8s-deploy-app:testtagas the expected ref. That expectation encoded the bare-ref bug, so the test was green over the defect. Nothing in the pre-push gate tied the resolver's ref to the project'sdefaults.registry.defaults.registry— the config and start paths both. The prior start-path re-resolve was a second, label-sourced rule and is removed with it.specprimitives as the host build path. The regression test now asserts the full ref for both box spellings, and a disabledFindBoxByLeaffallback fails it.origin/main:git show origin/main:candy/plugin-deploy-pod/resolve_ref.goline 57 iskit.ResolveShellImageRef("", deployBoxName, tag), and the post-hoc qualifier is atconfig_setup.go:257on main. It was masked because on main the bed died earlier, at deploy-add (unknown box "charly/check-sidecar-pod", plugin-build#46); this branch fixed that, so the bed reached config and the pre-existing composition surfaced with a different bare name.Successor to
opencharly/plugin-deploy-pod#44, which the validator auto-closed after five counting verdicts. That PR's code was never the problem — every block it drew was an evidence defect in the body: a stale "RED at config, not re-run" sentence left standing beside a green run, a tier claimed against that contradiction, and later adisposable: truetarget left unnamed. This body is written clean from the start: one run, one tier, the target named, and the provenance stated as two artefacts rather than one blurred claim. Ownership stays withcontainerd-crabbox-program, session8b1c01cf-7f79-4a75-bff8-240415a06208.A second surface change, named:
loadDeploybecomes a packagevarconfig_setup.gochangesloadDeployfrom a plain function to a package-levelvar, so a test can substitute it — the same seam idiom this repo already uses forprojectConnectandloadUnifiedForBox. It exists becauseresolve_box_name_test.go'sstubNoPerHostmust drive the config path with no per-host overlay entry, which is the exact case the round-4 defect appeared in. It carries no behaviour change on its own: the value assigned at init is the same function, and the restore in the test'sdeferputs it back.How tested
The R10 bed — PASS, with THIS PR's plugin in the run
Run
2026.308.1106,RC=0,summary.ymlerrors: 0, zero stepsok:false, zeroFAIL— 19 of 19 steps PASS.The plugin in that run is this PR's code, byte for byte. The bed loaded
plugin-deploy-podfrom/tmp/pdp46-plugins/(sha256 9361a3ef9ccf089b…), built from tree6cc21e9d, whichgit rev-parse 48c99de9^{tree}shows is exactly this head's tree.Every behaviour this PR changes ran live:
podPrepareVenue— deploy key resolved to a box name beforeHostBuild("overlay")deploy-addPASS; pre-fix it died withshort-name "charly/check-sidecar-pod" did not resolveresolveDeployRefLocal— config path composes a registry-qualified refconfigPASS; pre-fix it died withimage not found in local storage: charly.check-k8s-deploy-app:<tag>podRebuild— same helperupdatePASS (34m20s) — the bed's fresh-rebuild leg, which nothing had exercised beforeHonest scope. The run needed three things this repo does not supply: the
sdk v0.2026308.652/spec v0.2026308.344pins (without them the umbrella project does not load) andcharly#1106'splugin-deploy-vmcomposition (without it the member deploy refuses). Those are the host-side halves, merged ascharly#1106(0796bdcdb5a5…, tagv2026.308.1359) andcharly#1105(43348efbdb8f…, tagv2026.308.1507). The plugin half — this PR — is the binary that ran.On the version claims, precisely (no
go.modchange here, and none needed): this diff touches nogo.mod/go.sum, and the committed plugin tree6cc21e9dis whatsha256 9361a3ef…was built from — so the plugin binary is the committed tree's binary. The.652/.344pins are the host driver's (charly), not this module's; they are what let the project load, and they are merged ascharly#1106/#1105above. The bed therefore rancharly 2026.308.0830plus this repository's committed plugin tree — two separate artefacts, both named.Module gates, on the committed head
48c99de(the tree this push publishes):The new regression test fails without the change. With
resolveDeployImageRefmutated back to the pre-fix composition, both subtests fail with the exact strings:and the
FindBoxByLeaffallback is exercised, not decorative — disabling it alone fails the bare-leaf subtest (ref = "check-k8s-deploy-app:..."). On the head both subtests pass:The artifact under test, read at its boundary:
Earlier red runs, kept because they locate the defects. Run
2026.308.0128(a prior head) was RED atconfigwithimage not found in local storage: charly.check-k8s-deploy-app:<tag>— that failure is what round 4 fixed, and it is why the config path changed at all. Run2026.308.0525was RED atbring-up-membersfor a host-side reason (plugin-deploy-vmnot composed), fixed incharly#1106. Both are superseded by run2026.308.1106above, which passed on this head's own plugin binary.R10 IS claimed, on a
disposable: truetarget. The bedcharly.check-sidecar-poddeclaresdisposable: trueincharly/charly.yml:1243, and its ownvm-build/cleanup/cleanup-memberssteps build and destroy a disposable libvirt VM. Run2026.308.1106on that target:RC=0,errors: 0, 19/19 steps PASS.Tier:
fully tested and validated. All three changed paths —podPrepareVenue,resolveDeployRefLocal,podRebuild— ran live in run2026.308.1106with this head's own plugin binary, and the bed is green end to end including the fresh-rebuild leg.Rulebook compliance
AGENTS.md,/charly-internals:plugin,/charly-core:deploy,/charly-core:charly-config,/charly-automation:sidecar,/charly-internals:go,/charly-internals:go-quality,/charly-internals:strict-policy,/charly-internals:root-cause-analyzer,/charly-internals:git-workflow,/charly-internals:disposableloaded before the first actionresolveDeployBoxNameForKey) and ONE box→image-ref rule (resolveDeployImageRef); the second, label-sourced qualification is deletedResolveBoxRef→ namespacedefaults.registry)resolveDeployBoxNameLocalwas renamed toresolveDeployBoxNameForKey; the second sweep shows no surviving caller or aliased spelling.| R6 |
git statusstated before staging; only this session's five files committed; no force-push, no bypass, no reset || R7 | the new test fails on the pre-fix composition and on the disabled fallback, and passes on the head; the emitted ref was checked against a real built image's tags/labels |
| R8 | the live bed ran against the real podman/build/VM; nothing mocked |
| R9 | N/A — no charly binary is built in this repo; the inspecting binary is the paired worktree build
2026.307.2304|| R10 |
charly check run check-sidecar-podon thedisposable: truetarget — run2026.308.1106,RC=0,errors: 0, 19/19 steps PASS, with this head's own plugin binary. See How tested || QUAL |
gofmt -lclean,go build/go vet/go testrc=0 (golangci-lintis absent on this host; the pre-commit hook fails open) || HC | one cutover, one PR: key→box resolution, box→ref composition, lifecycle ordering, config/start wiring, tests |
| COORD |
RESOLVEDposted on the auto-closed#44;CLEAREDto follow oncharly#968, whose host fix is merged |Change classification
Bug fix in the pod deploy plugin. A qualified deploy's key was used as a box name, then the box address as an image repo.
Breaking change + rollback: none — the change composes the ref the box build already produced, an explicit
image:still goes through the pre-existing shell-resolve contract, and a project that cannot be loaded keeps the best-effort leaf-only fallback. Rollback is reverting48c99de.Filed issues
opencharly/charly#968— the paired HOST-side half (composeBaseImagefrom the box build's registry/leaf rule). Fixed and merged asopencharly/charly#1105(43348efbdb8f…, tagv2026.308.1507) together withcharly#1106(0796bdcdb5a5…, tagv2026.308.1359);#968closed with that merge. This PR carries the plugin half, and the bed above went green with all three present.opencharly/plugin-build#46— the issue this PR resolves; closed explicitly after merge (Closesdoes not fire in this org).Agent:
containerd-crabbox-program/44cfg· session8b1c01cf-7f79-4a75-bff8-240415a06208Assisted-by: Claude Code ollama-cloud/deepseek-v4.1-flash (fully tested and validated)