Skip to content

fix(candywalk): admit charly's BARE repo-root ref as a corpus source, keep the pre-cutover subpath form refused - #470

Merged
github-actions[bot] merged 1 commit into
mainfrom
feat/charly-repo-root-corpus-source
Oct 24, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
feat/charly-repo-root-corpus-source

Conversation

@aitrawog

@aitrawog aitrawog commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

candywalk.acceptedRef refused github.com/opencharly/charly outright, so the corpus could never
read charly as a source: three skills (go, go-quality, plugin) authored in charly beside the
code they document were unreachable, and a fourth (charly-version) had no authority but pod-charly-core's
retired 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, spec and sdk are already pinned as, and the shape charly began authoring
its skills in on 2026-10-23 (092f82e4, charly#1047) — after the refusal existed. Keyed on
RepoPath alone, the predicate could not tell the two apart; spec.ParsedRef.SubPath already carries
the distinction.

The change is that one predicate, in the single home both readers share:

if pr.RepoPath == "" {
    return ""
}
// The pre-cutover IN-REPO charly form is still refused: … The BARE repo-root form is a CORPUS SOURCE …
if pr.RepoPath == "github.com/opencharly/charly" && pr.SubPath != "" {
    return ""
}

Per file:

  • candywalk/candywalk.go — the skip narrowed to the SUBPATH form (+32/−13 in the file: 4 added
    code 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 (which
    said refs INTO charly are "skipped" full stop — it now states the subpath reason AND the repo-root
    admission), acceptedRef's own comment, and parseRemoteRef's, which claimed collectRefs "skips
    RepoPath == 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: nodes
    unioned in with Dir: "." and the fetched tree as SourceRoot.

What this PR does NOT do, and it is deliberate: it does not run the real charly marketplace generate
— that is the CONSUMER leg (marketplace refs PR, producer-first after this merges and tags), and its
arm is the with/without-pin corpus diff. It also does not touch the local-root asymmetry measured while
deciding this (collectEntitiesWithRemote reads a repo's ROOT charly.yml only 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, no
candy. The repo's gate is .github/workflows/ci.yml; the changed path is additionally executed LIVE
against the REAL 225-ref marketplace refs list and the REAL charly@v2026.296.2303 tree, with an
offline resolver stub (nothing fetched, no network).

The control that fails — RED, both levels, on the unmodified source

Source at origin/main d515baf, git diff --stat candywalk/candywalk.go empty, only the new
test file present:

# source UNMODIFIED at d515baf; the test file is the only change on disk
$ cd /home/atrawog/Sync/Atrapub/mo/dsh/opencharly/.worktrees/sdk468/sdk
$ git diff --stat candywalk/candywalk.go     # (no output — the code is the pre-fix code)
$ GOTMPDIR=~/.cache/go-tmp TMPDIR=/tmp go test ./candywalk \
    -run 'TestCollectRefs_CharlyRepoRootIsACorpusSource|TestCollectEntitiesRemote_CharlyRepoRootManifestEmitsSkills' -v
--- FAIL: TestCollectRefs_CharlyRepoRootIsACorpusSource (0.00s)
    candywalk_refs_charly_repo_root_test.go:62: collectRefs DROPPED "@github.com/opencharly/charly:v2026.296.2303"
      — the bare repo-root form is a corpus SOURCE (opencharly/sdk#468) and a sibling declared ref must
      never vanish silently (opencharly/sdk#322); got [@github.com/opencharly/layer-nodejs:v2026.240.0001]
--- FAIL: TestCollectEntitiesRemote_CharlyRepoRootManifestEmitsSkills (0.00s)
    candywalk_refs_charly_repo_root_test.go:169: the bare charly repo-root ref reached the resolver 0 times,
      want exactly 1 — the corpus walk must FETCH it (opencharly/sdk#468); resolver calls: []
FAIL
rc=1

And at the generator's own entrance — the REAL refs list (marketplace origin/main 5bcd6ea,
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:

# pre-fix source (d515baf), resolver stubbed offline; the charly arm's tree is the real tag tree
== ARM without-charly ==
resolver calls: total=225 unique=225
charly refs queued: 0
entities: total=1 skills=0

== ARM with-charly ==
resolver calls: total=225 unique=225          <-- NOT ONE MORE: the pin is refused
charly refs queued: 0 -> []
entities: total=1 skills=0

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

# head f0ade11 (git rev-parse HEAD); tree b110a67
$ cd /home/atrawog/Sync/Atrapub/mo/dsh/opencharly/.worktrees/sdk468/sdk
$ GOTMPDIR=~/.cache/go-tmp TMPDIR=/tmp \
    MEAS468_ROOT_NO=/tmp/sdk468/rootNo MEAS468_ROOT_YES=/tmp/sdk468/rootYes \
    MEAS468_ROOT_SUB=/tmp/sdk468/rootSub MEAS468_CHARLY_TREE=/tmp/sdk468/trees/charly-2303 \
    go test -count=1 ./candywalk -run TestMeas468Entrance -v
== ARM without-charly ==
resolver calls: total=225 unique=225
charly refs queued: 0
entities: total=1 skills=0

== ARM with-charly ==
resolver calls: total=282 unique=282
charly refs queued: 1 -> [github.com/opencharly/charly@v2026.296.2303]
entities: total=132 skills=4
  SKILL github.com/opencharly/charly:v2026.296.2303|charly-version-skill|.
  SKILL github.com/opencharly/charly:v2026.296.2303|go-quality-skill|.
  SKILL github.com/opencharly/charly:v2026.296.2303|go-skill|.
  SKILL github.com/opencharly/charly:v2026.296.2303|plugin-skill|.

== CLOSURE GROWTH (unique resolver keys present ONLY with the charly pin) ==
count=57
  of which the charly repo itself: 1 ; declared-ref twins: 56

== ARM subpath-only (the pre-cutover form, must stay REFUSED) ==
resolver calls: unique=225 ; charly refs queued: 0 ; entities: 1

The four skills, all owner=charly-skill-shelf, dir=. — the issue's four, counted from the real
tag tree.
The unit arms on the same head:

$ go test ./candywalk -run 'CharlyRepoRoot' -v
--- PASS: TestCollectRefs_CharlyRepoRootIsACorpusSource (0.00s)
--- PASS: TestCollectEntitiesRemote_CharlyRepoRootManifestEmitsSkills (0.00s)
PASS

The regression guard is the subpath arm: 225 keys, 0 charly refs, entities: 1 — unchanged from the
without-charly arm.
A box submodule still carrying @github.com/opencharly/charly/candy/<name> keeps
resolving 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 a
twin of a repo this list already pins at a newer tag). Each is a distinct (repo, version) key, so a
cold run pays 56 extra shallow clones and 56 twins enter the dedupe. The mitigation that already exists
is resolve.go's direct-pin authority (preferEntity step 2: a DIRECT ref beats a closure twin), and
this 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, tree b110a67)

$ git rev-parse HEAD; git rev-parse HEAD^{tree}; git diff --stat origin/main...HEAD
f0ade11f13cf14471453fa9b7ef7f15d615975a5
b110a674b84520c2d2cd273ec6ed1ea679ef4bdb
 candywalk/candywalk.go                            |  45 +++--
 candywalk/candywalk_refs_charly_repo_root_test.go | 199 ++++++++++++++++++++++
 2 files changed, 231 insertions(+), 13 deletions(-)
$ test -z "$(gofmt -l .)" && echo 'gofmt: clean'
gofmt: clean
$ go vet ./... && echo 'vet: clean'
vet: clean
$ go test ./...            # 19 packages ok, 1 no-test-files, 0 FAIL
ok  	github.com/opencharly/sdk	0.532s
ok  	github.com/opencharly/sdk/agentkit	0.588s
ok  	github.com/opencharly/sdk/candywalk	0.054s
ok  	github.com/opencharly/sdk/deploykit	26.357s
ok  	github.com/opencharly/sdk/kit	12.351s
ok  	github.com/opencharly/sdk/loaderkit	3.897s
… (all ok, rc=0)
$ for a in amd64 arm64 arm 386; do GOOS=linux GOARCH=$a CGO_ENABLED=0 go build ./... || echo "FAIL $a"; done
linux/amd64 rc=0
linux/arm64 rc=0
linux/arm rc=0
linux/386 rc=0
$ GOTOOLCHAIN=go1.26.8 go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2 \
    run --allow-parallel-runners
0 issues.

golangci-lint was built and run the CI way — GOTOOLCHAIN=go1.26.8, the version ci.yml pins via
go-version-file: go.mod, and the linter at the ci.yml pin v2.13.2
— and it reports 0 issues for
the whole module. opencharly/sdk#466 (a host-built linter cannot read this module's export data) did
NOT 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.8 is the difference between a real gate and a
toolchain-skew false red.

Rulebook compliance

  • R0 — loaded /charly-internals/plugin, /charly-internals/marketplace,
    /charly-internals/go, /charly-internals/git-workflow and this repo's AGENTS.md before the first
    edit; every command above was run after that.
  • R1 — no finding was parked. The change's own defect is the issue (#468); the asymmetry found
    while measuring it is #469, filed with the measurement and its blast radius rather than absorbed.
    Zero warnings on the gate.
  • R2 — one ref policy, one home: acceptedRef, shared by the text fallback and the declared-scalar
    reader, was already the single authority and stays it. No second predicate, no per-caller exception.
  • R3 — no sleep, retry, suppression, or fallback branch; the measurement harness is a test-local
    resolver stub and touches no production code.
  • R4/R5 — the doc comments that stated the old rule are corrected in this commit, including one in
    parseRemoteRef that would otherwise have described a skip the code no longer performs. The
    retracted claim was swept: grep -rn 'pr.RepoPath == "" || pr.RepoPath' candywalk/ → no match.
  • R6 — git status --short read before staging; one commit, one push; no force-push, no
    --no-verify, no main push, no foreign file.
  • R7 — the coverage fails without the change (both new tests red on d515baf, pasted above), and
    the changed path ran live against the real refs list and the real tag tree.
  • R8 — no live service boundary: the walk is filesystem discovery, and the resolver seam is stubbed
    offline ON PURPOSE, so the measurement is deterministic and fetches nothing. The real fetch is the
    consumer leg's arm.
  • R9 — no binary/runtime surface touched by this diff (no charly build, no candy, no packaging).
  • R10 — no disposable: true target is affected; the change class's gate is ci.yml, run on the
    committed tree above.
  • Cutover — this is one unit of a tracked multi-unit cutover (opencharly/opencharly#600): the
    producer leg. Its consumer leg (marketplace refs 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 by
deploykit's label+validate collector). No schema change, no wire type, no runtime target, no image.
Producer-first: sdk → tag v0.<YYYYDDD>.<HHMM> → marketplace refs PR → corpus proof.

Filed issues

  • opencharly/sdk#469 — candywalk: a repo's ROOT charly.yml is read only on the FETCHED path; the
    same tag tree emits 1 entity / 0 skill nodes as --root and 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. — harness DeepSeek Harness, model deepseek-v4.1-flash; cited with
opencharly/opencharly#538.

Agent: charly-sdk468

Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated)

… 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)
@github-actions

Copy link
Copy Markdown

Review — PASS

What the diff actually does

candywalk/candywalk.go narrows the charly refusal in acceptedRef from a RepoPath-only test to a RepoPath && SubPath != "" test, with an early RepoPath == "" return. That is exactly the predicate quoted in the body, and it is the single shared home both readers (text fallback + declared-scalar) already used, so no second policy path is introduced. The three comments that stated the old blanket rule (collectRefs doc block, acceptedRef, parseRemoteRef) are corrected in the same diff; the grep the body cites (pr.RepoPath == "" || pr.RepoPath) is genuinely gone from the changed lines. No dead code, no leftover legacy branch, no duplicated predicate.

Line accounting: the diff header/stat is +32/−13 for candywalk.go, which I re-derived hunk-by-hunk (13+15+4 added, 6+4+3 removed) — it matches the body. The body's "9 added code lines, 1 removed, the rest comments" is only exact if the 5 inline comment lines inside the function are counted with the 4 code lines (4 code lines strictly); that is a loose label, not a contradiction of the substance, and the aggregate numbers are right.

Tests — the control is real

candywalk_refs_charly_repo_root_test.go is a genuine regression guard, not a tautology:

  • The collector arm declares the bare repo-root ref, both spellings of the pre-cutover subpath form (versioned and versionless), and an unrelated sibling. It rejects any unexpected collected ref, requires both expected refs, and length-checks. Pre-fix, repoRoot is dropped, so it fails — exactly the RED output pasted in the body (the Fatalf text in the diff matches the pasted failure verbatim).
  • The walk arm asserts the resolver is hit exactly once for github.com/opencharly/charly:v2026.296.2303, and that 4 skill: nodes come back with Dir: "." and SourceRoot equal to the fetched tree. Pre-fix the ref never reaches the resolver (charlyRefs == 0) — again matching the pasted RED. The docs-sources candy fixture correctly must not perturb the count.

Crucially, both arms are behavioural: if spec.ParsedRef.SubPath were not populated as the change assumes, the subpath assertions would fail. So the crux assumption is pinned by a test that can fail, not merely asserted in prose. The live 225-ref / real-tag-tree measurement (TestMeas468Entrance) is a local harness not committed here; it is supplementary evidence and the body does not claim it ships, so its absence from the diff is not a defect.

Honesty / consistency of the body

Re-derived against the diff and it holds: 2 changed files, 199 added test lines, head f0ade11, +32/−13 in the source file, the RED messages matching the test source, and the GREEN arm's four skills (charly-version, go, go-quality, plugin) matching the test fixture. The body explicitly scopes out what it does not do — the charly marketplace generate consumer leg and the local-root asymmetry filed as #469 — and discloses the 56-ref closure growth with the existing preferEntity direct-pin mitigation rather than hiding it. That is a disclosure, not a parked defect: the defect this PR closes is #468, and #469 is filed with its measurement.

Security / scope

No 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-off

There 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

  • The body's "9 added code lines" is only true if inline comments are counted as code (strictly 4 code lines added). Worth tightening for precision in a future write-up, but not a material inaccuracy.
  • Admitting the bare root means a project that both has a local charly checkout as a root and pins the bare charly ref could surface the same tree under two namespaces; the body discloses the related local-root asymmetry as #469 and the dedupe authority lives in resolve.go, so this is a corpus-leg concern for the follow-up PR, not a defect in this one.

Verdict: PASS


ollama-cloud/deepseek-v4.1-flash — action-review.

View action run

@aitrawog

Copy link
Copy Markdown
Contributor Author

STATUS — verdict read in full and both non-blocking observations dispositioned. PASS stands; the head does NOT move (f0ade11), so no re-run is needed: neither observation is a code finding, and a re-run on a green head would only mint another same-name check run on the same SHA.

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 grep -vc '^+//', which does not match the INDENTED inline comments inside the function. Re-derived exactly:

$ git diff -U0 origin/main...HEAD -- candywalk/candywalk.go | grep '^+' | grep -v '^+++' | grep -vcE '^\+\s*//'
4
$ … | grep '^-' | grep -v '^---' | grep -vcE '^-\s*//'
1

so +32/−13 = 4 added code lines + 28 added comment lines, 1 removed code line + 12 removed comment lines. The body now says exactly that. The aggregate was right; the label was not, and it is the kind of number I should have derived rather than eyeballed.

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:

  • It is not reachable in the corpus's own configuration: marketplace generate --root <marketplace> walks THIS repo as the local root, and the marketplace repo has no charly checkout in it. The condition needs a project that is simultaneously a charly checkout and pins charly — i.e. a consumer's project, where resolve.go's preferEntity step 1 (local beats fetched) already decides in favour of the local tree, deterministically.
  • The related asymmetry you name is opencharly/sdk#469 (the LOCAL root's charly.yml is never read — measured: 1 entity / 0 skill nodes local vs 132 / 4 fetched on the same tag tree), and it is the reason the duplicate CANNOT silently double-publish today: the local side contributes no skill: nodes at all. Fixing #469 is what would make this collision real, and that issue's own fix must answer it — which is why it is filed with the question rather than patched here.

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: You are a coding agent powered by the deepseek-v4.1-flash model. — harness DeepSeek Harness, model deepseek-v4.1-flash; cited with opencharly/opencharly#538.

Agent: charly-sdk468

Assisted-by: DeepSeek Harness deepseek-v4.1-flash (analysed on a live system)

@github-actions
github-actions Bot enabled auto-merge (squash) October 24, 2026 00:34
@github-actions
github-actions Bot merged commit 16dfb13 into main Oct 24, 2026
8 checks passed
charly-auto-merge Bot added a commit that referenced this pull request Oct 24, 2026
aitrawog pushed a commit to opencharly/marketplace that referenced this pull request Oct 24, 2026
…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)
github-actions Bot pushed a commit to opencharly/marketplace that referenced this pull request Oct 24, 2026
…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>
github-actions Bot pushed a commit to opencharly/plugin-marketplace that referenced this pull request Oct 24, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants