diff --git a/CHANGELOG/2026.302.0039.md b/CHANGELOG/2026.302.0039.md index 2cad072..8af93e9 100644 --- a/CHANGELOG/2026.302.0039.md +++ b/CHANGELOG/2026.302.0039.md @@ -107,7 +107,7 @@ exit: 0 **5. The control fails without the change (R7 — a coverage that cannot fail is invalid).** -The same tree, with ONLY the sdk floor put back to `v0.2026293.1143` — the pre-change value — and the flipped assertions left in place. Exactly the three flipped assertions go RED, and nothing else does: +The same tree, with the sdk floor put back to `v0.2026293.1143` — the pre-change value — and the flipped assertions left in place. The floor is restored in `go.mod` **and** `go.sum` together, because the version and its checksums are one change; the floor alone does not build. Exactly the three flipped assertions go RED, and nothing else does: ``` --- FAIL: TestValidateSkillBodies_BesideTheCandyIsReported (0.00s) @@ -121,6 +121,8 @@ FAIL github.com/opencharly/plugin-box/candy/plugin-box 0.026s The old sdk's own diagnostic — *"no SKILL.md exists under .agents/skills **beside** the owning candy"* — is the literal opposite of the new one's (*"no SKILL.md exists **at the repo root of** the owning candy"*), and the two arms above read the two of them. That is the whole defect in two sentences: this repo's tests were proving the OLD base, and the shipped binary enforces the NEW one. +*Corrected after the merge.* The sentence that introduced this section said *"with ONLY the sdk floor put back"*, which read as a claim that `go.mod` alone changed; the reproduction restores `go.sum` with it, because the floor and its checksums are one change. The `charly/pr-validator` raised this on opencharly/plugin-box#74, and the wording was corrected here — in the record, not only in the pull request. + **6. R5's claim-keyed sweep — the stale base.** The claim: **no statement in this repo names the old base, and no statement points at a file that no longer holds it.** ``` diff --git a/candy/plugin-box/validate_skills_test.go b/candy/plugin-box/validate_skills_test.go index 0de4468..2de38a3 100644 --- a/candy/plugin-box/validate_skills_test.go +++ b/candy/plugin-box/validate_skills_test.go @@ -54,12 +54,16 @@ func skillBodiesCfg(t *testing.T, atRoot bool) (*spec.Config, map[string]deployk // BESIDE the candy — the placement the lookup does NOT read — so it must be reported, and the // diagnostic must name the repo root it looked in. The wording is pinned because the diagnostic IS // the remediation: an author who reads it learns which tree to put the body in. +// +// EXACTLY ONE finding, not "at least one": a count that is not asserted cannot fail, and a +// loader that reported the same body twice would slip past a "did it report at all?" check while +// the author sees a duplicated finding. The count is part of the contract this arm pins. func TestValidateSkillBodies_BesideTheCandyIsReported(t *testing.T) { cfg, dk := skillBodiesCfg(t, false) e := &vErr{} validateSkillBodies(cfg, dk, e) - if len(e.msgs) == 0 { - t.Fatal("a body beside the candy must be reported — the loader reads the owning candy's REPO ROOT, not the candy's own directory") + if len(e.msgs) != 1 { + t.Fatalf("a body beside the candy must be reported as EXACTLY ONE finding — the loader reads the owning candy's REPO ROOT, not the candy's own directory; got %d finding(s): %v", len(e.msgs), e.msgs) } if !strings.Contains(e.msgs[0], "exists at the repo root of the owning candy") { t.Fatalf("the diagnostic must name the repo root it looked in, got %q", e.msgs[0])