Repository navigation
chore(deps): adopt plugin-task v0.2026297.1542 and cut the containerd provider path over to the renamed plugin repo - #1059
Conversation
…s a step that ran nothing)
`plugin-task` is compiled into the charly binary, so the pinned module version IS
the behaviour of `charly task` — and of the `git-submodules` verb that `charly task
sync` runs.
At v0.2026296.2153 the task runner counts STEPS and FAILURES only. A step that ran
nothing is neither: it is not a failure, so it is counted among the N steps, and it
is not disclosed. A green task line can read "26 step(s), 0 failed" over a run in
which six comparisons never happened. The org already owns the vocabulary for
exactly this state — `sdk.CheckSkippedExitCode = 3` ("the phase RAN NOTHING —
neither a pass nor a failure") — and the check runner honours it. The task runner
had no equivalent.
Two merged releases carry it: opencharly/plugin-task#45 (squash 7e26679, root tag
v2026.297.1509, module tag candy/plugin-task/v0.2026297.1509) adds the third
bucket, and opencharly/plugin-task#46 (squash c24c743d, root tag v2026.297.1542,
module tag candy/plugin-task/v0.2026297.1542) carries the stderr-capture fix that
#45's PASS asked for. This adopts the second.
`charly/go.mod:141` moves `github.com/opencharly/plugin-task/candy/plugin-task`
from `v0.2026296.2153` to `v0.2026297.1542`. `charly/go.sum` moves with it,
derived by `go mod tidy`: the two superseded pairs (`v0.2026296.1911`,
`v0.2026296.2153`) go and the one live pair (`v0.2026297.1542`) arrives. The
`v0.2026296.1911` pair was already stale on this tree — #1050 added the `2153` pair
without tidying — so the tidy also restores the invariant this repo's own precedent
(#1045) holds: exactly ONE plugin-task version in `charly/go.sum`.
The two versions' own `candy/plugin-task/go.mod` are byte-identical in their
requirements (`sdk v0.2026276.1822`, `spec v0.2026276.1636`), so no `sdk`/`spec`
move is behind this bump. The pin is stated once: `charly/charly.yml` names the
compiled-in plugin WORD (`plugin-task`), never a version.
Closes #1057
Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)
…gin repo The out-of-tree engine plugin was RENAMED `plugin-nerdctl` -> `plugin-containerd` (the engine word changed first; the repo name followed), and its module path moved with it: `candy/plugin-nerdctl` -> `candy/plugin-containerd`, module `github.com/opencharly/plugin-containerd/candy/plugin-containerd`. Three surfaces in this repo still named the pre-rename path: - `charly/plugin_corpus.txt:79` — the corpus entry for the plugin repo; - `charly/plugins_refs_generated.go` — the generated word->provider index, whose three containerd words still pointed at the old module path; - `charly/engine_word_cutover_test.go:106` — the guardrail's canonical ref. `charly/plugins_refs_generated.go` is a PROJECTION of the corpus's word->provider map: pluginsgen fetches each corpus repo at its default branch — deliberately, because the word->ref FACT lives in the plugin repos — and records where each word is served (internal/pluginsgen/main.go, collectWordRefs). A projection does not follow its source by itself, so the index was re-projected with the SAME pluginsgen invocation the build uses (scripts/bootstrap-charly.sh). The corpus entry and the guardrail's constant follow the rename; regenerating the index is the replacement for the three generated keys. This is what the repo's own gate demands: `TestPluginsRefsIndexSound` compares the committed index against what the corpus serves RIGHT NOW, and its own failure message says "the index is FALSE; regenerate and commit it". The gate had gone red for every checkout of this repo — including a fresh clone of main — because the corpus moved after the index was last committed. The build's `scripts/check-generated-clean.sh` deliberately classifies this file as `upstream` and RESTORES the tracked copy, so a build never leaves the tree carrying a fresh projection: the remedy is a commit here. The retired words (`engine:nerdctl`, `verb:nerdctl`, `command:nerdctl`) stay retired: the guardrail's negative arms still reject them. Refs: opencharly/opencharly#589 (the containerd programme's consumer legs). Agent: charly *Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)*
Review — PASSWhat the diff actually doesThe change is exactly what the title claims, and it is small and coherent:
Counts: the body claims 5 files, 11 insertions, 10 deletions. Summing the diffs: insertions 4+1+2+1+3 = 11, deletions 1+1+4+1+3 = 10. ✔ Reproduced. Correctness / consistency checks
TestsThe bump's behavioural proof is the pasted live differential (same fixture, before vs. new binary, check fails on before). That is self-reported and cannot be re-executed here, but it is the class-appropriate proof for a compiled-in module bump, and the guardrail test in the diff is updated consistently. The cutover itself is additionally covered by the existing Body honestyFile count, ±counts, version strings and hashes are all reproduced by the diff. Two cosmetic notes, neither affecting the code:
CommentsThere are no comments ( DispositionThe diff is complete, coherent, and faithful to the body's substantive claims. The only issues are cosmetic line-number drift in pasted evidence and the absence of an in-repo test for the module's skipped-bucket behaviour — neither is a correctness, security, or scope defect in the change itself. Verdict: PASS ollama-cloud/deepseek-v4.1-flash — action-review. |
|
STATUS — the review's two notes are answered with measurements; the diff stands as it is, and no follow-up commit is needed. Agent: charly Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated) Note 1 — the
|
Closes #1057
Summary
Head
c0abdf6eaf868f336531597fcdea96ce4259c469— 5 files changed, 11 insertions(+), 10 deletions(-).Two changes, one gate, one PR — and they are one scope, not two cutovers: the second is a prerequisite of the first's gate, because the gate the bump's class demands was already red on
origin/main(the evidence is pasted below).1. The task runner's third bucket reaches the product.
plugin-taskis compiled into the charly binary, so the pinned module version IS the behaviour ofcharly task— and of thegit-submodulesverb thatcharly task syncruns.At the pinned version the task runner counts steps and failures only. A step that ran nothing is neither: it is not a failure, so it is counted among the N steps, and it is not disclosed. A green task line can therefore read "26 step(s), 0 failed" over a run in which six comparisons never happened. The org already owns the vocabulary for exactly this state —
sdk.CheckSkippedExitCode = 3("the phase RAN NOTHING — neither a pass nor a failure") — and the check runner honours it. The task runner had no equivalent.This adopts the release that adds it. Two merged releases carry it: opencharly/plugin-task#45 (squash
7e26679, root tagv2026.297.1509, module tagcandy/plugin-task/v0.2026297.1509) adds the third bucket — the headline becomestask %s: %d step(s), %d failed, %d skipped, the hop's aggregateran %d task(s), %d step(s) failed, %d skippedis kept as the prefix whenever a step ran nothing, the JSON report carries"skipped", and the exit status is untouched (a skip is not a failure) — and opencharly/plugin-task#46 (squashc24c743d, root tagv2026.297.1542, module tagcandy/plugin-task/v0.2026297.1542) carries the stderr-capture fix that #45's PASS asked for.charly/go.mod:141movesgithub.com/opencharly/plugin-task/candy/plugin-taskfromv0.2026296.2153tov0.2026297.1542.charly/go.summoves with it, derived bygo mod tidy: the two superseded pairs (v0.2026296.1911,v0.2026296.2153) go and the one live pair (v0.2026297.1542) arrives. Thev0.2026296.1911pair was already stale on this tree — #1050 added the2153pair without tidying — so the tidy also restores the invariant this repo's own precedent (#1045) holds: exactly ONE plugin-task version incharly/go.sum.No cascade. The two versions' own
candy/plugin-task/go.modare byte-identical in their requirements (sdk v0.2026276.1822,spec v0.2026276.1636), so this is a single-module bump with nosdk/specmove behind it. The pin is stated once:charly/charly.ymlnames the compiled-in plugin word (plugin-task), never a version.2. The containerd provider path is cut over to the renamed plugin repo. The out-of-tree engine plugin was renamed when the engine word changed (the engine word is
containerd; the retirednerdctlspelling is only the CLI binary), and its module path moved with it. Three surfaces in this repo still named the pre-rename path:charly/plugin_corpus.txtcharly/plugins_refs_generated.gocharly/engine_word_cutover_test.goThe index is a projection of the corpus's live word→provider map: pluginsgen fetches each corpus repo at its default branch — deliberately, because the word→ref FACT lives in the plugin repos — and records where each word is served (
internal/pluginsgen/main.go,collectWordRefs). A projection does not follow its source by itself, so the index was re-projected with the same pluginsgen invocation the build uses (scripts/bootstrap-charly.sh). The corpus entry and the guardrail's constant follow the rename; regenerating the index is the replacement for the three generated keys.This is also what the repo's own gate demands.
TestPluginsRefsIndexSoundcompares the committed index against what the corpus serves right now, and its own failure message says "the index is FALSE; regenerate and commit it". The gate had gone red for every checkout of this repo — including a fresh clone ofmain— because the corpus moved after the index was last committed. The build'sscripts/check-generated-clean.shdeliberately classifies this file asupstreamand restores the tracked copy, so a build never leaves the tree carrying a fresh projection: the remedy is a commit here.What this change does not do. It changes no line of product logic of its own — the task runner's behaviour is the module's, and the module is upstream. It does not touch the umbrella's guidance; the umbrella's leg follows this one, because the umbrella's binary is built from this repo's pin. The containerd programme's remaining consumer legs stay with their own repos, tracked at opencharly/opencharly#589.
How tested
The change class is a dependency bump plus a generated-artifact cutover: the pinned module IS the behaviour, so the class's gate is this repo's own CI shape — the worktree-local build, the generated-wiring check,
go vet,go test, thetools/*sweep,gofmt, andgolangci-lintovercharly/and every tools module — plus the live differential, because a bump's proof is the behaviour the two binaries differ in: the same fixture task, run against the binary built fromorigin/mainand against the binary this change builds. The check must FAIL against the before binary, or it proves nothing.The toolchain the gate runs on. The gate pins
GOTOOLCHAIN=go1.26.8— the version this repo's owncharly/go.moddeclares, and the version CI uses (go-version-file: charly/go.mod). The host's Go is 1.27.3, whose export data (version 5) the pinnedgolangci-lint v2.13.2cannot decode ("export data version 5 is greater than maximum supported version 4"), so a lint run on the host toolchain fails on every stdlib import for a reason that has nothing to do with the tree. Pinning the repo's own toolchain makes the local gate the same gate CI runs.The gate, run on the committed tree this PR pushes:
The live differential. The SAME fixture task — one step that runs, one step that ran nothing (
skip: true) — run against two binaries built from two trees: this branch's tree, andorigin/main(the tree this branch's parent is). The check must FAIL against the before binary, or it proves nothing (R7).The BEFORE build's
check-generated-cleanNOTICE is the drift this PR cuts over: onorigin/mainthe committed index differs from what the corpus serves, so the build restores the tracked file and says so. The gate on THIS tree prints no such notice — step 2 above,GENERATED_RC=0.The re-projection is derived, not hand-written. Regenerating the index on the cut-over corpus, with the build's own invocation, produces no diff — the committed file is byte-identical to the one the build derives:
The R5 sweeps. Two claims, two keys, each swept over the product tree (
charly/) — and over the WHOLE tree for the second claim,CHANGELOG/excepted.Claim 1 — the superseded plugin-task versions are gone from the tree.
Claim 2 — the pre-rename plugin repo path is gone from the tree.
The old version strings and the old repo path survive only in
CHANGELOG/, where history belongs.The anchor — every pasted line above belongs to this commit:
Rulebook compliance
/charly-internals:git-workflow(the landing mechanics),/charly-internals:go(this repo's Go gate recipe),/charly-internals:go-quality(the formatter gate and the lint sweep),/charly-internals:plugin(the generated plugin wiring and the provider index),/charly-internals:strict-policyand/charly-internals:root-cause-analyzer(the discipline and the anomalies), plus this repo's ownAGENTS.md.1.
charly/go.sumcarried a superseded version's hash pair — FIXED here, control gap filed.Mechanism: a pin bump performed with
go getadds the new version's hashes and never prunes the superseded pair; nothing in the bump path re-derivescharly/go.sum. #1045's bump replaced thev0.2026296.641pair with thev0.2026296.1911pair, and #1050's later bump added thev0.2026296.2153pair without re-deriving the file — so the lock carried two plugin-task pairs.Missed control: this repo has no gate that asserts the core module's lock is the tidy artifact of its
go.mod: CI runs the tests, thetools/*sweep,gofmtandgolangci-lintbut no tidy, and themods-tidytask tidies thetools/*modules only. The file that would have shown the drift is exactly the file nothing checks.Blast radius: one stale pair in
charly/go.sum; no build, test or runtime effect (Go ignores entries the module graph does not need), but the lock stops being answerable — a reader cannot tell fromcharly/go.sumwhich version is live.Root fix: derive
charly/go.sumwithgo mod tidy— which is what this change's diff does, and it restores the one-version invariant the repo's own precedent (#1045) holds. The control gap behind it is filed as #1058, with the RCA and the shape of the gate that would close it (tidy the core module, thengit diff --exit-codeoncharly/go.mod+charly/go.sum).2. The generated provider-ref index named a candy path the renamed repo no longer carries — FIXED here.
Mechanism:
charly/plugins_refs_generated.gois a projection of the corpus's live word→provider map: pluginsgen fetches each corpus repo at its default branch — deliberately, because the word→ref fact lives in the plugin repos — and records where each word is served. The out-of-tree engine plugin was renamed when the engine word changed, and its module path moved with it. A projection does not follow its source by itself, so the committed index kept the pre-rename module path for the three containerd words while the corpus already served them from the renamed one.Missed control:
scripts/check-generated-clean.shdeliberately classifies this file asupstreamand restores the tracked copy, so no build ever leaves the tree carrying a fresh projection — the remedy is a commit here. The only gate isTestPluginsRefsIndexSound, which needs the network to compare the committed index against what the corpus serves now; it went red for every checkout of this repo, including a fresh clone ofmain, and nothing in the build or CI surfaced it earlier.Blast radius: the three containerd words resolved to a ref whose candy path no longer exists in the repo the old path resolves to — that repo's
candy/holdsplugin-containerdalone — and whose module path publishes nothing newer thanv0.2026295.1321, while the renamed path is atv0.2026297.553; and the repo's own gate was red on every checkout, blocking every PR in the repo. The old repo path still RESOLVES — GitHub's rename redirect answers it — which is exactly why the drift stayed invisible. Measured, not inferred:Root fix: cut the corpus entry and the guardrail's constant over to the renamed repo, and re-project the index with the build's own pluginsgen invocation (
scripts/bootstrap-charly.sh) — the derivation, not a hand edit that satisfies the gate. The regeneration is a no-op on the committed tree (pasted above). The tracker's thread is opencharly/opencharly#589.3. The corpus entry and the guardrail's constant still named the pre-rename repo — FIXED here.
Mechanism:
charly/plugin_corpus.txtis a hand-maintained list of plugin repo paths, and the plugin repo's rename landed without a consumer leg in this repo. Nothing in the build or CI asserts that a corpus entry is a repo that exists under that name.Missed control: no sweep of the corpus against the org's live repo names; the drift stays invisible because GitHub's rename redirect answers the old path, so the fetch succeeds and returns the renamed module path — the mismatch only shows up as the index disagreement above.
Blast radius: one corpus entry and one test constant; the index followed from them.
Root fix: both are cut over in this change, and the claim-keyed sweep is pasted above — the pre-rename path no longer occurs anywhere in the tree outside
CHANGELOG/.One anomaly that is a gate-hygiene fact, not a product defect. The repo's pinned
golangci-lint v2.13.2cannot decode the host Go 1.27.3's export data (version 5 > the maximum version 4 it supports), so a lint run on the host toolchain failed on every stdlib import — on a tree CI lints green. Missed control: the local gate recipe did not pin the repo's declared toolchain. Root fix: the gate runs withGOTOOLCHAIN=go1.26.8— the version this repo's owncharly/go.moddeclares and the version CI uses (go-version-file: charly/go.mod). No tree change: CI was already right; the local gate now runs the same toolchain.charly/go.mod;charly/go.sumcarries the two hashes that Go derives from it — and, after the tidy, exactly one version's pair, as the repo's own precedent (chore(deps): adopt plugin-task v0.2026296.1911 (the bump advances an unmaterialized pin in the index) #1045) holds — andcharly/charly.ymlnames the compiled-in plugin word, not a version. The provider path is stated once per surface it must appear on, and the index that repeats it is generated from the corpus, not hand-maintained beside it.engine:nerdctl,verb:nerdctl,command:nerdctl) stay retired — the guardrail's negative arms still reject them.git statuswas read before the first edit; the change is two commits on a branch cut fromorigin/main; no history was rewritten, no force-push, no amend after a push, no hook bypass, no direct push tomain, and no submodule was edited. The before-arm's detached worktree is a sibling worktree of the same repo, created for the differential.origin/mainit fails — the headline does not disclose the step that ran nothing — and against the binary this PR builds it passes. The same fixture, the same assertion, two binaries, two different headlines. The repo's ownTestPluginsRefsIndexSoundis the second discriminator: it was red on the tree before this change and green on the tree this PR pushes.charly versionreports the CalVer of this commit — andgo version -mon that binary shows the new module in its dependency set. The shared host install was never used.charly/pr-validatoris the merge gate.Change classification
A dependency bump — the pinned module IS the behaviour, so the class's gate is this repo's own CI shape (build, generated-wiring check,
go vet,go test, thetools/*sweep,gofmt,golangci-lint) plus a live differential between the binary built fromorigin/mainand the binary this PR builds — together with a generated-artifact cutover, whose gate is the repo's ownTestPluginsRefsIndexSoundplus a regeneration that is a no-op on the committed tree. All of it is pasted above, run on the committed tree this PR pushes, with zero warnings. The two parts are one scope, not two cutovers in one PR: the containerd cut-over is the prerequisite of the bump's gate —TestPluginsRefsIndexSoundwas already red onorigin/main(the BEFORE build'scheck-generated-cleanNOTICE, pasted above, is that drift), so the bump alone could not pass the gate its own class demands, and R1 classifies a red gate as blocking on this change. The tier earned isfully tested and validated: the changed path executed live, and the check that proves it fails without the change.Filed issues
charly/go.sumcan carry a superseded module version's hash pair: nothing checks that the core module's lock is the tidy artifact of itsgo.mod. The finding this change FIXED (the stalev0.2026296.1911pair, removed here by the tidy) is the symptom; the control gap behind it — no tidy gate over the core module, becausecharly task mods-tidycovers onlytools/*— is filed there, with the RCA.Agent: charly
Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)