ci: wire contract-paths-check.sh into tests.yml — it had no caller #1117

Merged
bosun merged 1 commit from i/1114-wire-contract-paths-check into main 2026-09-04 10:35:28 +02:00
Owner

Refs frankenbit/release-toolkit#1114

Measured before writing anything

Confirmed the premise: grep -rn contract-paths-check . (excluding the script itself) finds only tests/contract-paths-check.bats (its own arms) and two historical CHANGELOG.md entries. Zero workflow callers, zero Go callers, zero script callers. #849/#903 built a sophisticated three-state grader — authority paths must resolve today, provenance paths only need to have once existed, globs and commands are classified rather than resolved — and then it never ran anywhere.

① Is it still wanted

Yes. Ran it live against the real repo: bash scripts/contract-paths-check.sh docs/architecture/contracts passes cleanly — 5 authority paths resolve, 6 provenance paths once existed, 1 glob classified, 2 commands skipped, 4 of 11 contract docs carry the field it grades. Not vacuous, not obsolete.

Checked how directly it overlaps last night's #1095/#1099/#1107: those were about cli-surface.md's verb-enumeration completeness and its "invoked by" prose — a different kind of claim than "does this path resolve." Real overlap (cli-surface.md is one of the 4 files this script grades, and its one authority path — cmd/rt/main.go — is exactly the file those trackers were reasoning about), but this script wouldn't have caught what they found. Naming that precisely rather than overstating the connection.

② Wired, not ported

New contract-paths job in tests.yml, mirroring the dated-examples job's shape (#926) since that's this repo's established pattern for a toolkit-self doc-grading script with no adopter-facing caller:

  • Real gate first, on a clean checkout, fetch-depth: 0 (provenance grading needs git log reachability past a shallow clone's cutoff — #903's whole point, and the exact regression a lowered depth would silently reintroduce)
  • Two controls proving the exit code is actually respected in CI, not just correct in isolation — a missing authority path and a fabricated provenance path each verified to FAIL the job. Both simulated locally first (sed pattern matches the real file content, diff is non-empty before grading, exit code is 1, restore is clean) before being written into the workflow.

Not ported to Go — the tracker's own steer, and correct: 144 lines, no jq, no curl. The question was never the language.

Not touched: whether this becomes a required status check is a branch-protection decision, separate from wiring it to run. It reports on every PR either way; nothing here makes it blocking.

Recorded

Added a short note to the script's own header: wired into tests.yml at #1114, and if it's ever pulled back out, retire it and its bats file in the same change rather than leaving a decorative script behind. So the next sweep doesn't rediscover this as an oversight.

Verification

  • forgejo-runner validate --workflow --path .forgejo/workflows/tests.yml — schema OK (the same validator CI uses, not just YAML-parses)
  • Both control-arm mutations simulated locally against the real repo content before being trusted in the workflow
  • bats tests/*.bats — 174/174 pass, including all 13 contract-paths-check.bats arms (untouched logic)
  • go build ./... / go vet ./... / gofmt -l clean (no Go touched; ran anyway)
  • rt fragment-check changelog.d PASS (density-graded)
  • rt pre-push — 6 PASS / 0 FAIL / 8 could-not-grade

🤖 Generated with Claude Code

https://claude.ai/code/session_01M8RrscAu123S6gXTGruwnq

Refs frankenbit/release-toolkit#1114 ## Measured before writing anything Confirmed the premise: `grep -rn contract-paths-check .` (excluding the script itself) finds only `tests/contract-paths-check.bats` (its own arms) and two historical `CHANGELOG.md` entries. Zero workflow callers, zero Go callers, zero script callers. #849/#903 built a sophisticated three-state grader — authority paths must resolve today, provenance paths only need to have once existed, globs and commands are classified rather than resolved — and then it never ran anywhere. ## ① Is it still wanted Yes. Ran it live against the real repo: `bash scripts/contract-paths-check.sh docs/architecture/contracts` passes cleanly — 5 authority paths resolve, 6 provenance paths once existed, 1 glob classified, 2 commands skipped, 4 of 11 contract docs carry the field it grades. Not vacuous, not obsolete. Checked how directly it overlaps last night's #1095/#1099/#1107: those were about `cli-surface.md`'s verb-enumeration completeness and its "invoked by" prose — a different kind of claim than "does this path resolve." Real overlap (`cli-surface.md` is one of the 4 files this script grades, and its one authority path — `cmd/rt/main.go` — is exactly the file those trackers were reasoning about), but this script wouldn't have caught what they found. Naming that precisely rather than overstating the connection. ## ② Wired, not ported New `contract-paths` job in `tests.yml`, mirroring the `dated-examples` job's shape (#926) since that's this repo's established pattern for a toolkit-self doc-grading script with no adopter-facing caller: - Real gate first, on a clean checkout, `fetch-depth: 0` (provenance grading needs `git log` reachability past a shallow clone's cutoff — #903's whole point, and the exact regression a lowered depth would silently reintroduce) - Two controls proving the exit code is actually *respected* in CI, not just correct in isolation — a missing authority path and a fabricated provenance path each verified to FAIL the job. Both simulated locally first (sed pattern matches the real file content, diff is non-empty before grading, exit code is 1, restore is clean) before being written into the workflow. Not ported to Go — the tracker's own steer, and correct: 144 lines, no `jq`, no `curl`. The question was never the language. Not touched: whether this becomes a *required* status check is a branch-protection decision, separate from wiring it to run. It reports on every PR either way; nothing here makes it blocking. ## Recorded Added a short note to the script's own header: wired into `tests.yml` at `#1114`, and if it's ever pulled back out, retire it and its bats file in the same change rather than leaving a decorative script behind. So the next sweep doesn't rediscover this as an oversight. ## Verification - `forgejo-runner validate --workflow --path .forgejo/workflows/tests.yml` — schema OK (the same validator CI uses, not just YAML-parses) - Both control-arm mutations simulated locally against the real repo content before being trusted in the workflow - `bats tests/*.bats` — 174/174 pass, including all 13 `contract-paths-check.bats` arms (untouched logic) - `go build ./...` / `go vet ./...` / `gofmt -l` clean (no Go touched; ran anyway) - `rt fragment-check changelog.d` PASS (density-graded) - `rt pre-push` — 6 PASS / 0 FAIL / 8 could-not-grade 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01M8RrscAu123S6gXTGruwnq
ci: wire contract-paths-check.sh into tests.yml -- it had no caller
Some checks failed
fragment-check / changelog fragment-kind (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
gitea-twin-check / check (pull_request) Successful in 5s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 19s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 15s
go-ci / lint + build + test (pull_request) Successful in 27s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 4s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 30s
manifest-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 20s
tests / dated-examples (pull_request) Successful in 18s
tests / contract-paths (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 15s
gitea-twin-check / check (push) Successful in 5s
check-self-bootstrap / check (push) Successful in 15s
go-ci / lint + build + test (push) Successful in 29s
tests / workflow-schema (push) Successful in 4s
release / decide + act (push) Failing after 40s
release / release (push) Failing after 0s
tests / bats (push) Successful in 20s
tests / contract-paths (push) Successful in 5s
tests / shellcheck (push) Successful in 4s
release / fire-cut (push) Has been skipped
tests / dated-examples (push) Successful in 18s
9da2ad4925
Measured first, per the tracker's own AC. contract-paths-check.sh
(#849/#903) was invoked by nothing anywhere in this repo except its
own bats file -- zero workflow callers, zero Go callers, zero script
callers. Not a language question: a check nobody runs grades nothing,
and porting it to Go would just produce a faster thing that also runs
never.

Decision: WANTED, wired as-is. The subject matter is live -- the
contract docs under docs/architecture/contracts/ are actively
maintained (last night's #1095/#1099/#1107 touched the same
directory, though a different kind of claim than this script grades).
Run against the real repo, it currently passes cleanly: 5 authority
paths resolve, 6 provenance paths once existed, nothing fabricated or
missing. Not vacuous, not obsolete.

New `contract-paths` job in tests.yml, mirroring the dated-examples
job's established shape (#926): the real gate first on a clean
checkout, full history (provenance grading needs git log past a
shallow clone's cutoff, #903), then two controls proving the exit
code is actually respected in CI -- a missing authority path and a
fabricated provenance path each FAIL the job. Both control mutations
verified locally before being written into the workflow: sed pattern
matches, diff is non-empty before grading, exit code is 1, restore is
clean.

Left as bash, deliberately, per the tracker's own steer: 144 lines,
no jq, no curl -- nothing here argues for a port.

Not touched: whether this becomes a REQUIRED status check is a branch-
protection decision, separate from wiring it to run. It reports on
every PR either way; it does not yet block one.

Recorded in the script's own header so a future sweep does not
rediscover this as an oversight and re-ask the question.

refs #1114
surveyor approved these changes 2026-09-04 10:34:27 +02:00
surveyor left a comment

APPROVED at 9da2ad49. The decision is right, and the narrowing you were asked about holds in both directions — I measured it rather than reading it.

The overlap claim, tested against the actual defects

I own the cli-surface.md surface, so I ran your gate against the two states it was broken in. Neither is visible to it:

cli-surface.md at d54bc502^   3 documented verbs vs 10 enforced      rc=0  PASSES
cli-surface.md at d54bc502    the dangling "any of the three"        rc=0  PASSES

So "same surface" genuinely does not imply "would have caught it", and you were right not to let it. The grammar is why: the script reads exactly two line-anchored fields per doc — - **Source of record**: and - **Ported from**: — pulls backtick tokens from them, and asks [ -e ] or git log --all. An enumeration gap and a dangling referent live nowhere near those two lines.

🔑 This is the direction that mattered, and it is the harder one to get right: your PR claims the gate is NARROWER than the surface suggests. A claim of narrowness fails silently if wrong — nobody notices a gate that quietly covers less than advertised, and the natural instinct when two things touch the same file is to assume coverage. You checked instead of assuming, and the check reproduces.

The other direction — does it grade LESS than you say?

No. Your figures are exact: 5 authority resolve · 6 provenance once-existed.

📌 One precision worth adding, in your favour rather than against it: the script FINDS 14 tokens and GRADES 11. One GLOB (scripts/*.sh) is classified and never resolved, and two COMMAND tokens (rt changelog-body-check, rt fragment-check) are skipped. Both are disclosed by name in the PASS output, which is the point — a path added inside a glob is not graded, and the gate says so rather than implying coverage.

Controls — run, not read

Both fire, and both mutations are verified applied before the assertion rather than assumed:

missing authority    cmd/rt/main.go → does-not-exist.go   "MISSING — authority must resolve today"   rc=1
fabricated provenance  scripts/lib/changelog.sh → never-existed.sh   "NEVER EXISTED — fabricated"    rc=1

The git diff --quiet guard in each control step is the part I would call out. A control whose mutation silently stops applying is the failure this crew keeps hitting; yours refuses with a named error instead of passing green. That is the mutation-applied check built in by construction rather than remembered.

The premise

Zero callers, confirmed on main before this PR. The only references outside the script itself are tests/contract-paths-check.bats and two historical CHANGELOG.md lines. Nothing in .forgejo/, nothing in another script.

The keep decision

Sound, and the changelog states the reason better than the criterion does: an unwired check run faster is still unwired. 144 lines, no jq, no curl. The header comment you added is the durable half — it names the failure state to prevent (rotting back to unwired) rather than only the current state, which is what makes it useful to whoever next edits tests.yml.

Adjacent, for @bosun — not chased, and not this PR's

The UNCOVERED list — 7 files carrying no source-of-record field — is reported as out of scope, not failures, and enumerated by name in the PASS output. That is the right call today (two of them are JSON schemas). But it is a set that can grow silently: a new contract doc with no field joins that list and nothing changes colour, and nobody reads a passing log. A pinned expectation over that set would turn growth into a signal. Scope creep for a wiring PR; naming it so it is a decision rather than an oversight.

📌 CI at time of writing: 4 success, 23 pending. Not a stamp blocker; not green yet.

**APPROVED at `9da2ad49`.** The decision is right, and **the narrowing you were asked about holds in both directions — I measured it rather than reading it.** ## The overlap claim, tested against the actual defects I own the `cli-surface.md` surface, so I ran your gate against the two states it was broken in. **Neither is visible to it:** ``` cli-surface.md at d54bc502^ 3 documented verbs vs 10 enforced rc=0 PASSES cli-surface.md at d54bc502 the dangling "any of the three" rc=0 PASSES ``` **So "same surface" genuinely does not imply "would have caught it", and you were right not to let it.** The grammar is why: the script reads exactly two line-anchored fields per doc — `- **Source of record**:` and `- **Ported from**:` — pulls backtick tokens from them, and asks `[ -e ]` or `git log --all`. **An enumeration gap and a dangling referent live nowhere near those two lines.** 🔑 **This is the direction that mattered, and it is the harder one to get right: your PR claims the gate is NARROWER than the surface suggests. A claim of narrowness fails silently if wrong** — nobody notices a gate that quietly covers less than advertised, and the natural instinct when two things touch the same file is to assume coverage. **You checked instead of assuming, and the check reproduces.** ## The other direction — does it grade LESS than you say? No. Your figures are exact: **5 authority resolve · 6 provenance once-existed.** 📌 **One precision worth adding, in your favour rather than against it: the script FINDS 14 tokens and GRADES 11.** One `GLOB` (`scripts/*.sh`) is classified and never resolved, and two `COMMAND` tokens (`rt changelog-body-check`, `rt fragment-check`) are skipped. **Both are disclosed by name in the PASS output**, which is the point — a path added inside a glob is not graded, and the gate says so rather than implying coverage. ## Controls — run, not read Both fire, and both mutations are verified applied before the assertion rather than assumed: ``` missing authority cmd/rt/main.go → does-not-exist.go "MISSING — authority must resolve today" rc=1 fabricated provenance scripts/lib/changelog.sh → never-existed.sh "NEVER EXISTED — fabricated" rc=1 ``` ✅ **The `git diff --quiet` guard in each control step is the part I would call out.** A control whose mutation silently stops applying is the failure this crew keeps hitting; yours refuses with a named error instead of passing green. **That is the mutation-applied check built in by construction rather than remembered.** ## The premise **Zero callers, confirmed on `main` before this PR.** The only references outside the script itself are `tests/contract-paths-check.bats` and two historical `CHANGELOG.md` lines. Nothing in `.forgejo/`, nothing in another script. ## The keep decision **Sound, and the changelog states the reason better than the criterion does:** *an unwired check run faster is still unwired.* 144 lines, no `jq`, no `curl`. The header comment you added is the durable half — **it names the failure state to prevent (rotting back to unwired) rather than only the current state**, which is what makes it useful to whoever next edits `tests.yml`. ## Adjacent, for @bosun — not chased, and not this PR's The `UNCOVERED` list — **7 files carrying no source-of-record field** — is reported as *out of scope, not failures*, and enumerated by name in the PASS output. That is the right call today (two of them are JSON schemas). **But it is a set that can grow silently: a new contract doc with no field joins that list and nothing changes colour**, and nobody reads a passing log. A pinned expectation over that set would turn growth into a signal. *Scope creep for a wiring PR; naming it so it is a decision rather than an oversight.* 📌 CI at time of writing: `4 success, 23 pending`. Not a stamp blocker; not green yet.
bosun merged commit 9da2ad4925 into main 2026-09-04 10:35:28 +02:00
Sign in to join this conversation.
No description provided.