Repository navigation
fix(candywalk): admit charly's BARE repo-root ref as a corpus source, keep the pre-cutover subpath form refused - #470
Conversation
… keep the pre-cutover subpath form refused `acceptedRef` refused `github.com/opencharly/charly` outright. That refusal was written (87a2fcd, #167, 2026-08-25) for ONE form: `@github.com/opencharly/charly/candy/<name>`, the pre-cutover IN-REPO ref that resolves to the local charly checkout's candy dir (which the local FS walk already surfaces) and that the box submodule repos' own cutover sweeps. Keyed on `RepoPath` alone it could not tell that form from the BARE repo-root form — the shape `opencharly/opencharly`, `spec` and `sdk` are already pinned as, and the shape `charly` began authoring its own skills in (`.agents/skills/`, 2026-10-23) AFTER the refusal existed. So the skip is narrowed to the form it was written for: if pr.RepoPath == "github.com/opencharly/charly" && pr.SubPath != "" { return "" } `spec.ParsedRef.SubPath` already carries the distinction. Consequences, measured on the real refs list (225 first-hop refs) with an offline resolver stub and the real `charly@v2026.296.2303` tree: - pre-fix the charly pin is refused SILENTLY: 225 resolver keys, 0 charly refs, `rc=0`, no message — three skills unreachable from the corpus; - post-fix the same list queues the charly ref, the walk reads its root manifest and emits exactly 4 `skill:` nodes (`go`, `go-quality`, `plugin`, `charly-version`, all `owner=charly-skill-shelf`, `dir=.`); - the pre-cutover SUBPATH form is still refused, versioned and versionless alike (the regression guard: a box submodule carrying it must not start fetching); - the closure grows by charly's 56 declared refs, all at old `v2026.27x/28x` tags, every one a twin of a repo already pinned at a newer tag — mitigated by `resolve.go`'s direct-pin authority (`preferEntity` step 2) and proven by the marketplace refs leg's with/without-pin corpus diff, not absorbed here. The `collectRefs` doc comment that states the reason is corrected in the same change (R5), as are `acceptedRef`'s and `parseRemoteRef`'s, so no comment still claims a blanket refusal of the repo. Refs #468, opencharly/opencharly#600. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated)
Review — PASSWhat the diff actually does
Line accounting: the diff header/stat is Tests — the control is real
Crucially, both arms are behavioural: if Honesty / consistency of the bodyRe-derived against the diff and it holds: 2 changed files, 199 added test lines, head Security / scopeNo secrets, no input-handling, no privilege or dependency change; pure library predicate + tests. Scope is coherent: one predicate, its comments, and its coverage. No foreign files, no runtime surface. Comments / sign-offThere are 0 comments, so nothing to re-derive or disposition, and no maintainer sign-off is present or claimed. With no designated maintainers configured, the change stands on its own evidence — which it does (shipped red-without-change coverage + matching gate output). Non-blocking observations
Verdict: PASS ollama-cloud/deepseek-v4.1-flash — action-review. |
|
STATUS — verdict read in full and both non-blocking observations dispositioned. PASS stands; the head does NOT move ( Observation 1 — the line accounting was loose. FIXED in the body (live now). You were right: my count of "9 added code lines" came from a so Observation 2 — the duplicate-namespace case (a local charly checkout as a root AND a bare charly pin). REBUTTED as out of this diff's scope, on the record, with what would catch it. Two reasons, both measurable:
The corpus leg that would show any residue is the marketplace refs PR's with/without-pin diff: zero bodies changed is the gate, and it inspects every emitted path. A3. This session's delivered system message reads, verbatim: Agent: charly-sdk468 Assisted-by: DeepSeek Harness deepseek-v4.1-flash (analysed on a live system) |
…ee (validator advisory 1) The comment beside the charly pin claimed the go/go-quality/plugin DISPATCHER rows carry this pin's corrected spec/schema/*.cue trigger. Measured on the artifact, only go and plugin move: go-quality's trigger is byte-identical at both sources, so its row does not change. The comment now says exactly that, names the twins' real provenance (the distro-fedora / distro-cachyos candy closures, opencharly/distro-fedora#107), and records that the admission LANDED as opencharly/sdk#470 for issue opencharly/sdk#468. Comment-only: no generated byte changes, and the corpus is untouched. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated)
…s, the bytes do not (#568) * feat(refs): pin charly as a DIRECT corpus source — the authority moves, the bytes do not `charly` now publishes the four skills that describe its own code from the repo that authors them: `go`, `go-quality`, `plugin` (family internals) and `charly-version` (family core). One pin, plus the generator that can read it. - `candy/charly-marketplace/charly.yml`: `@github.com/opencharly/charly:v2026.297.0032` as a DIRECT ref, and the header note that said the repo's candies are "NOT referenced here … resolved to unknown candy" corrected in the same change — that note described the pre-cutover SUBPATH form's refusal (opencharly/sdk#468 narrowed it) and an older candy-resolution failure. - `tools/marketplace-cli/go.mod` + `go.sum`: `github.com/opencharly/sdk` → `v0.2026297.35`. This ONE line is what carries the fix into the corpus: the generator is host-built from this shim, which requires BOTH `plugin-marketplace` and `sdk` directly, so MVS takes the maximum and no `plugin-marketplace` PR is needed. - `DISPATCHER.md`: the two trigger rows the authority move corrects (`go`, `plugin`: the retired carrier's stale `sdk/schema/*.cue` becomes charly's `spec/schema/*.cue`). Measured with two full generations of the real refs list, same generator, one line apart (with and without the pin): 496 artifacts each, **zero bodies changed**, nothing added or removed, one file changed — the two DISPATCHER rows. The four authored bodies are byte-identical at `charly@v2026.297.0032` and in this corpus (banner-stripped sha256, `candywalk.ReadSkillFile`). The pin also queues charly's declared refs at OLD tags — 29 NEW `(repo, tag)` keys in this walk (the other 27 were already being fetched at the same tags by other closures) — and NOT ONE published body moved: `resolve.go` preferEntity step 2 (a DIRECT ref beats a closure twin) is what holds, proven by the diff rather than assumed. `layer-charly-internals`'s pin stays at `v2026.296.1907`: it still serves `egress`, `marketplace` and `vm-deploy-target`, whose homes are not corpus sources yet. Refs opencharly/sdk#468, opencharly/opencharly#600. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated) * docs(refs): the shipped comment names the TWO rows that move, not three (validator advisory 1) The comment beside the charly pin claimed the go/go-quality/plugin DISPATCHER rows carry this pin's corrected spec/schema/*.cue trigger. Measured on the artifact, only go and plugin move: go-quality's trigger is byte-identical at both sources, so its row does not change. The comment now says exactly that, names the twins' real provenance (the distro-fedora / distro-cachyos candy closures, opencharly/distro-fedora#107), and records that the admission LANDED as opencharly/sdk#470 for issue opencharly/sdk#468. Comment-only: no generated byte changes, and the corpus is untouched. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated) --------- Co-authored-by: Andreas Trawoeger <atrawog@gmail.com>
…t reproduces (#42) (#52) `charly task skills:drift` was red in four different ways at once, and no CI ran it, so nothing could see it. This change makes the gate runnable, gives it a job that owns the GENERATOR side of the corpus contract, and re-points the corpus pin at the commit this generator actually reproduces. THE RECIPE (root `charly.yml`, `skills:drift`) - The parameter was `out` — lower-case. The task runner's expansion grammar matches UPPER-CASE names only (`sdk/kit/checkvars_expand.go:16-17`), so the value never reached the command and the shell dropped the empty word ("--out: expected string value but got EOL"). It is `CORPUS` now. - `--root` named the repo root, but the corpus's ONE generation input — the refs list, `candy/charly-marketplace/charly.yml` — lives INSIDE the corpus checkout. Both flags now name the corpus checkout. - The "the tree is committed" half ran `git status` in THIS repo, so it checked the plugin's tree, not the corpus's. It names the corpus now. - The drift step carried no `timeout:`, and the per-step never-hang bound is 1m30s. The drift resolves EVERY corpus source (the refs list names ~1200 repos at pinned versions) before it compares a single byte — measured 11m48s in the marketplace repo's own deploy workflow — so the step was killed at the ceiling and the gate reported a TIMEOUT where the truth was "slow, and green". The bound is a hang guard, not a budget. THE SDK PIN (candy/plugin-marketplace/go.mod) sdk v0.2026296.2152's `acceptedRef` refuses EVERY `github.com/opencharly/charly` ref. The corpus's refs list RELIES on the BARE repo-root form (`@github.com/opencharly/charly:v2026.297.0032`) being a corpus source. A standalone build of the generator therefore dropped the charly corpus source, the closure twin `layer-charly-internals` won, and a rule-renumbering difference (R2/R3 -> R3/R4) left 9 stale artifacts. sdk v0.2026297.129 admits the bare repo-root form and refuses only the SUBPATH form (opencharly/sdk#468 -> opencharly/sdk#470), so the generator reproduces the corpus again. The declared-refs fix PR #47 adopted (opencharly/sdk#467) is present in both versions — the bump is a strict superset, not a revert. THE CORPUS PIN (`var: MARKETPLACE_REF`, and the literal that duplicates it) The current generator no longer reproduces the Oct-15 corpus commit: at e802da68 the gate is red with 4 stale artifacts (`plugins/.well-known/skills/index.json`, `plugins/DISPATCHER.md`, `plugins/check/skills/openclaw/SKILL.md`, `plugins/tools/skills/crabbox/SKILL.md`). At cc10955e — the corpus's current `main` head, which is the invariant this repo declares — it is green with 0. THE CI JOB (.github/workflows/drift-gate.yml) The marketplace repo's deploy workflow owns the CORPUS side. Nothing owned the GENERATOR side. The job runs the recipe on every merge to `main`, against the corpus commit this repo DECLARES, read out of `candy/plugin-marketplace/charly.yml` rather than repeated as a second literal (one pin, not two that can drift). It runs on `main` and not on `pull_request`, and that is a design constraint. A generator change that alters the output makes the declared corpus commit stale, and the corpus can only be regenerated with a RELEASED generator — so a pull-request gate would be red by construction for exactly the change it should inform, and it would deadlock the chain that repairs it (generator merged and tagged -> the corpus regenerates with that tag -> the pin is bumped here). It would also be a de-facto required check: the org pr-validator's `arm` job arms native auto-merge only when EVERY check on the head is green. THE PARAM IS QUOTED AT ITS USE SITES A task param reaches a command step as shell TEXT, not as a data channel (opencharly/plugin-task#34 owns the class-level fix), so `${CORPUS}` is quoted where it is used. A corpus checkout under a path with a space would otherwise split into two words and the CLI would reject the arguments. TWO DEFECTS FOUND AND FIXED IN PASSING - `task marketplace:pin`'s second step compared the corpus's `main` head against the file's LAST 40-hex string — which is the sha256 prefix of the byte-identical SKILL.md the candy's file records, not a pin. The step could never be true (measured: it compared cc10955e against 7d4d2676). Each occurrence is now found by its ANCHOR (`MARKETPLACE_REF:` and `contains:`), which a third string cannot satisfy. The same anchoring is what the CI job reads the baseline with. - The `setup` launcher and the `.claude/hooks` surface were removed at source in 2026.241.1105, but the README, the box description, the candy's description, the plugin's package comments and the prune boundary still described them as shipped. They are gone from the prose (R5), with the generator's own test (`assertNotWritten(t, out, "setup")`) as the truth they now match. Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated) Co-authored-by: Andreas Trawoeger <atrawog@gmail.com>
Summary
candywalk.acceptedRefrefusedgithub.com/opencharly/charlyoutright, so the corpus could neverread
charlyas a source: three skills (go,go-quality,plugin) authored incharlybeside thecode they document were unreachable, and a fourth (
charly-version) had no authority butpod-charly-core'sretired copy (
opencharly/sdk#468;opencharly/opencharly#600§8 decides it).The refusal is correct for the one form it was written for —
@github.com/opencharly/charly/candy/<name>,the pre-cutover IN-REPO ref that resolves to the LOCAL charly checkout's candy dir (the local FS walk
already surfaces that) and that the box submodule repos' own cutover sweeps (
87a2fcd,#167,2026-08-25) — and obsolete for the BARE repo-root form now blocked, the shape
opencharly/opencharly,specandsdkare already pinned as, and the shapecharlybegan authoringits skills in on 2026-10-23 (
092f82e4, charly#1047) — after the refusal existed. Keyed onRepoPathalone, the predicate could not tell the two apart;spec.ParsedRef.SubPathalready carriesthe distinction.
The change is that one predicate, in the single home both readers share:
Per file:
candywalk/candywalk.go— the skip narrowed to the SUBPATH form (+32/−13in the file: 4 addedcode lines, 1 removed, the other 28 added and 12 removed lines being comments); and the three
comments that stated the old rule corrected in the SAME change (R5):
collectRefs' doc block (whichsaid refs INTO charly are "skipped" full stop — it now states the subpath reason AND the repo-root
admission),
acceptedRef's own comment, andparseRemoteRef's, which claimedcollectRefs"skipsRepoPath == github.com/opencharly/charly". No comment still claims a blanket refusal.candywalk/candywalk_refs_charly_repo_root_test.go(new) — two arms with the control that fails:the collector admits the bare repo-root ref while BOTH spellings of the pre-cutover subpath form
(versioned and versionless) stay refused beside an untouched sibling ref; and the walk-level arm —
a refs list declaring the bare ref has the fetched ROOT manifest walked and exactly 4
skill:nodesunioned in with
Dir: "."and the fetched tree asSourceRoot.What this PR does NOT do, and it is deliberate: it does not run the real
charly marketplace generate— that is the CONSUMER leg (
marketplacerefs PR, producer-first after this merges and tags), and itsarm is the with/without-pin corpus diff. It also does not touch the local-root asymmetry measured while
deciding this (
collectEntitiesWithRemotereads a repo's ROOTcharly.ymlonly on the FETCHED path);that is
opencharly/sdk#469, filed with the measurement, not absorbed here.How tested
Change class: Go library source in a shared kit (
sdk/candywalk) — no runtime target, no image, nocandy. The repo's gate is
.github/workflows/ci.yml; the changed path is additionally executed LIVEagainst the REAL 225-ref marketplace refs list and the REAL
charly@v2026.296.2303tree, with anoffline resolver stub (nothing fetched, no network).
The control that fails — RED, both levels, on the unmodified source
Source at
origin/maind515baf,git diff --stat candywalk/candywalk.goempty, only the newtest file present:
And at the generator's own entrance — the REAL refs list (
marketplaceorigin/main5bcd6ea,candy/charly-marketplace/charly.yml, 225 refs) in two arms that differ by ONE added line,'@github.com/opencharly/charly:v2026.296.2303', plus a third arm carrying the pre-cutover subpath form:0 charly entries, the same 225 keys,
rc=0, no message — the silent drop the issue measured.GREEN — the same two arms on this head
The four skills, all
owner=charly-skill-shelf,dir=.— the issue's four, counted from the realtag tree. The unit arms on the same head:
The regression guard is the subpath arm: 225 keys, 0 charly refs,
entities: 1— unchanged from thewithout-charly arm. A box submodule still carrying
@github.com/opencharly/charly/candy/<name>keepsresolving it the way it does today (the local FS walk), and never through the network.
The 56-ref closure growth this admits — measured, and NOT absorbed
count=57= the charly repo itself + 56 declared refs in its root manifest, all at OLD tags(
distro-arch:v2026.274.1256,layer-kind:v2026.274.0645,plugin-kube:v2026.276.1350… every one atwin of a repo this list already pins at a newer tag). Each is a distinct
(repo, version)key, so acold run pays 56 extra shallow clones and 56 twins enter the dedupe. The mitigation that already exists
is
resolve.go's direct-pin authority (preferEntitystep 2: a DIRECT ref beats a closure twin), andthis PR does not take it on faith — the marketplace refs leg proves it with the with/without-pin
corpus diff, which is the arm that can fail. Named here so it is not discovered later.
The gate on the committed tree (
f0ade11, treeb110a67)golangci-lintwas built and run the CI way —GOTOOLCHAIN=go1.26.8, the versionci.ymlpins viago-version-file: go.mod, and the linter at theci.ymlpin v2.13.2 — and it reports0 issuesforthe whole module.
opencharly/sdk#466(a host-built linter cannot read this module's export data) didNOT bite here: building the linter with the module's own toolchain is exactly what avoids it. On a host
whose default toolchain is newer,
GOTOOLCHAIN=go1.26.8is the difference between a real gate and atoolchain-skew false red.
Rulebook compliance
/charly-internals/plugin,/charly-internals/marketplace,/charly-internals/go,/charly-internals/git-workflowand this repo'sAGENTS.mdbefore the firstedit; every command above was run after that.
#468); the asymmetry foundwhile measuring it is
#469, filed with the measurement and its blast radius rather than absorbed.Zero warnings on the gate.
acceptedRef, shared by the text fallback and the declared-scalarreader, was already the single authority and stays it. No second predicate, no per-caller exception.
resolver stub and touches no production code.
parseRemoteRefthat would otherwise have described a skip the code no longer performs. Theretracted claim was swept:
grep -rn 'pr.RepoPath == "" || pr.RepoPath' candywalk/→ no match.git status --shortread before staging; one commit, one push; no force-push, no--no-verify, nomainpush, no foreign file.d515baf, pasted above), andthe changed path ran live against the real refs list and the real tag tree.
offline ON PURPOSE, so the measurement is deterministic and fetches nothing. The real fetch is the
consumer leg's arm.
charlybuild, no candy, no packaging).disposable: truetarget is affected; the change class's gate isci.yml, run on thecommitted tree above.
opencharly/opencharly#600): theproducer leg. Its consumer leg (
marketplacerefs PR: pin + regenerated corpus, expected delta =authority only) follows producer-first, and the carrier pin does not move in this PR.
Change classification
Go library source (
sdk/candywalk, a mechanism kit consumed by every corpus generator and bydeploykit's label+validate collector). No schema change, no wire type, no runtime target, no image.Producer-first:
sdk→ tagv0.<YYYYDDD>.<HHMM>→marketplacerefs PR → corpus proof.Filed issues
opencharly/sdk#469—candywalk: a repo's ROOTcharly.ymlis read only on the FETCHED path; thesame tag tree emits 1 entity / 0 skill nodes as
--rootand 132 entities / 4 skill nodes fetched(measured here, with its blast radius and the fix's open question). Not absorbed into this PR.
A3. This session's delivered system message reads, verbatim:
You are a coding agent powered by the deepseek-v4.1-flash model.— harnessDeepSeek Harness, modeldeepseek-v4.1-flash; cited withopencharly/opencharly#538.
Agent: charly-sdk468
Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated)