fix(readme-pin-check): grade the integration guide and the examples too (#1382) #1384

Merged
bosun merged 2 commits from i/1382-widen-the-pin-gate into main 2026-09-06 21:01:11 +02:00
Owner

readme-pin-check graded only README.md, so the @vX.Y.Z pins rt prep advanced in docs/integration.md and examples/** were set by one rule and checked by none.

Refs #1382. The tracker stays open until this merges.

The order is the substance

Widen the GATE first, then point the setter at what it covers. The other way leaves the new files set-but-unchecked — the defect #1378 removed from README, reintroduced next door.

🔑 Why those pins are mirror-dependent at all — I nearly built the wrong thing here

All 17 pins in docs/integration.md and examples/** are written in the SHORT formfrankenbit/release-toolkit/…which resolves against the CONSUMER's own forge, not the mirror. Measured: zero absolute gitea.com pins in either surface.

⚠️ So grading them against gitea.com looks like the neighbouring-question error, and I stopped to check before building.

It is not, and integration.md §① is why: a reader without their own copy is told to "put the absolute prefix in front of each of them". The VERSION travels into that transformation, so it must be mirror-servable even though the literal text names no host.

📌 That is the argument the tracker did not state and the one the whole fix rests on.

What changed

before after
gate README.md README.md + docs/integration.md + examples/**
--doc single repeatable, and a directory is walked
findings line only file and line — three surfaces means a bare line number is not actionable
prep advanced all three advances none of the pins
integration.md prose advanced still advancedReplace \vX.Y.Z`` is descriptive

The walk lives in the verb, not the workflow. A file list in YAML is a second place that drifts from the rule — which is this defect one level out.

examples/** loses its rewrite entirely, because every version token there is a pin and there is no descriptive half to keep. atReplacement is deleted with it rather than left as a loaded gun for the next author.

The scope-limit arm is INVERTED, not deleted

TestUpdateDocVersionRefs_StillAdvancesIntegrationAndExamples asserted #1378's narrowing. It now asserts the widening — which is the property that would otherwise regress silently the next time someone "restores" the rewrite.

🔴 The first mutation pass had two inert results, and one was a real gap

X2  a no-op edit — my error, rescored as Y2
X3  removing the empty-population refusal reddened NOTHING

X3 was reachable only through a DIRECTORY THAT MATCHES NOTHING. collectPinDocs refuses a missing path, so the empty case needs a real directory with no .md/.yml in it — exactly how a workflow argument silently stops covering anything after a tree is reorganised. It had no arm. Added, then re-mutated:

mutation arm reddened
Y1 restore the integration.md pin rewrite LeavesIntegrationAndExamplePinsAlone
Y2 restore the examples walk LeavesIntegrationAndExamplePinsAlone
Y3 drop the empty-population refusal ADirectoryMatchingNothingIsCannotGrade
Y4 skip an unreadable --doc path CollectPinDocs_WalksADirectory…
Y5 drop the file from the finding UnservablePinIsRed…, GradesEachNewSurface
control green

📌 AC5's control is its own arm: a correct pin in a newly-graded file must still pass, or "widen the gate" and "redden on everything newly read" would be indistinguishable.

Live on this branch

rt readme-pin-check --doc README.md --doc docs/integration.md --doc examples …
  rc=0
  PASS: all 19 prescriptive pin(s) across 11 document(s) name v0.61.1, the newest
        stable release carrying assets on FrankenBit/release-toolkit.

What this does NOT do

  • It does not check the uses: PATH in any of the three surfaces — forge-dependent, and #1350's axis.
  • It does not grade descriptive version mentions. The @ is still the whole discriminator, in all three files.
  • It does not prove the next compose is clean. Same gap #1378 had: the arms prove prep no longer advances these pins; the settling read is readme-pin-check on the rolling PR after the next compose.

Verification

fragment-check rc=0 · changelog-body-check rc=0 · register-check rc=0 both scans · gitea-twin --check rc=0 · go build ./... rc=0 · go test ./... rc=0 · bats 124 ok / 0 not ok.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG

`readme-pin-check` graded only `README.md`, so the `@vX.Y.Z` pins `rt prep` advanced in `docs/integration.md` and `examples/**` were set by one rule and checked by none. `Refs #1382.` The tracker stays open until this merges. ## The order is the substance **Widen the GATE first, then point the setter at what it covers.** The other way leaves the new files set-but-unchecked — the defect #1378 removed from README, reintroduced next door. ## 🔑 Why those pins are mirror-dependent at all — I nearly built the wrong thing here **All 17 pins in `docs/integration.md` and `examples/**` are written in the SHORT form** — `frankenbit/release-toolkit/…` — **which resolves against the CONSUMER's own forge, not the mirror.** Measured: **zero** absolute gitea.com pins in either surface. ⚠️ **So grading them against gitea.com looks like the neighbouring-question error**, and I stopped to check before building. ✅ **It is not, and `integration.md` §① is why:** a reader without their own copy is told to *"put the absolute prefix in front of each of them"*. **The VERSION travels into that transformation, so it must be mirror-servable even though the literal text names no host.** 📌 **That is the argument the tracker did not state and the one the whole fix rests on.** ## What changed | | before | after | |---|---|---| | gate | `README.md` | `README.md` + `docs/integration.md` + `examples/**` | | `--doc` | single | **repeatable**, and a **directory is walked** | | findings | line only | **file and line** — three surfaces means a bare line number is not actionable | | `prep` | advanced all three | advances **none** of the pins | | `integration.md` prose | advanced | **still advanced** — `Replace \`vX.Y.Z\`` is descriptive | **The walk lives in the verb, not the workflow.** A file list in YAML is a second place that drifts from the rule — which is this defect one level out. **`examples/**` loses its rewrite entirely**, because every version token there is a pin and there is no descriptive half to keep. `atReplacement` is deleted with it rather than left as a loaded gun for the next author. ## The scope-limit arm is INVERTED, not deleted `TestUpdateDocVersionRefs_StillAdvancesIntegrationAndExamples` asserted #1378's narrowing. **It now asserts the widening** — which is the property that would otherwise regress silently the next time someone "restores" the rewrite. ## 🔴 The first mutation pass had two inert results, and one was a real gap ``` X2 a no-op edit — my error, rescored as Y2 X3 removing the empty-population refusal reddened NOTHING ``` **X3 was reachable only through a DIRECTORY THAT MATCHES NOTHING.** `collectPinDocs` refuses a *missing* path, so the empty case needs a real directory with no `.md`/`.yml` in it — **exactly how a workflow argument silently stops covering anything after a tree is reorganised.** It had no arm. Added, then re-mutated: | mutation | arm reddened | |---|---| | Y1 restore the integration.md pin rewrite | `LeavesIntegrationAndExamplePinsAlone` | | Y2 restore the examples walk | `LeavesIntegrationAndExamplePinsAlone` | | Y3 drop the empty-population refusal | `ADirectoryMatchingNothingIsCannotGrade` | | Y4 skip an unreadable `--doc` path | `CollectPinDocs_WalksADirectory…` | | Y5 drop the file from the finding | `UnservablePinIsRed…`, `GradesEachNewSurface` | | control | green | 📌 **AC5's control is its own arm:** a **correct** pin in a newly-graded file must still pass, or *"widen the gate"* and *"redden on everything newly read"* would be indistinguishable. ## Live on this branch ``` rt readme-pin-check --doc README.md --doc docs/integration.md --doc examples … rc=0 PASS: all 19 prescriptive pin(s) across 11 document(s) name v0.61.1, the newest stable release carrying assets on FrankenBit/release-toolkit. ``` ## What this does NOT do - **It does not check the `uses:` PATH** in any of the three surfaces — forge-dependent, and #1350's axis. - **It does not grade descriptive version mentions.** The `@` is still the whole discriminator, in all three files. - **It does not prove the next compose is clean.** Same gap #1378 had: the arms prove `prep` no longer advances these pins; the settling read is `readme-pin-check` on the rolling PR after the next compose. ## Verification `fragment-check` rc=0 · `changelog-body-check` rc=0 · `register-check` rc=0 both scans · `gitea-twin --check` rc=0 · `go build ./...` rc=0 · `go test ./...` rc=0 · `bats` 124 ok / 0 not ok. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
fix(readme-pin-check): grade the integration guide and the examples too
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 6s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 8s
manifest-check / check (pull_request) Successful in 0s
prep-order-check / check (pull_request) Successful in 7s
check-self-bootstrap / check (pull_request) Successful in 30s
base-divergence-check / check (pull_request) Successful in 30s
gitea-twin-check / check (pull_request) Successful in 30s
readme-pin-check / check (pull_request) Successful in 8s
register-check / register-drift check (pull_request) Successful in 8s
register-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Failing after 43s
tests / contract-paths (pull_request) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 52s
ac-closure-check / ac-closure check (pull_request) Successful in 52s
changelog-body-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 8s
go-ci / page landing-tree failure (pull_request) Has been skipped
fragment-check / changelog fragment-kind (pull_request) Successful in 56s
fragment-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 5s
tests / workflow-schema (pull_request) Successful in 33s
tests / dated-examples (pull_request) Successful in 34s
tests / shellcheck (pull_request) Successful in 23s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 29s
workflow-parse-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m17s
8bde8b8a53
#1382, and the ORDER is the substance: widen the GATE first, then point the
setter at what it covers. Doing it the other way leaves the new files
set-but-unchecked, which is the defect #1378 removed from README,
reintroduced next door.

WHY THOSE PINS ARE MIRROR-DEPENDENT AT ALL, since it is not obvious and I
nearly built the wrong thing on it. All 17 pins in docs/integration.md and
examples/** are written in the SHORT form -- `frankenbit/release-toolkit/...`
-- which resolves against the CONSUMER's own forge, not the mirror. Measured:
zero absolute gitea.com pins in either surface.

So grading them against gitea.com looks like the neighbouring-question error.
It is not, and integration.md §① is why: a reader without their own copy is
told to put the absolute mirror prefix in front of exactly those lines. The
VERSION travels into that transformation, so it must be mirror-servable even
though the literal text names no host. That is the argument the tracker did
not state and the one the fix rests on.

THE GATE now takes a repeatable --doc and walks a DIRECTORY. The walk is in
the verb rather than the workflow because a file list in YAML is a second
place that drifts from the rule -- which is the same defect one level out.

FINDINGS NAME THEIR FILE. With three surfaces graded, a bare line number is
not actionable: a stranger's failure has three possible sources now.

PREP is repointed off all three. docs/integration.md keeps its DESCRIPTIVE
half -- "Replace `vX.Y.Z`" is instruction about the placeholder and moves
with the cut -- the same split #1378 drew for README's status line.
examples/** loses its rewrite entirely, because every version token there is
a pin and there is no descriptive half to keep. `atReplacement` is deleted
with them rather than left as a loaded gun.

THE SCOPE-LIMIT ARM IS INVERTED, NOT DELETED.
TestUpdateDocVersionRefs_StillAdvancesIntegrationAndExamples asserted #1378's
narrowing; it now asserts the widening, which is the property that would
otherwise regress silently the next time someone restores the rewrite.

Mutation-verified. The first pass had TWO inert results and one was a real
gap rather than a bad mutation:

    X2 was a no-op edit -- my error, rescored as Y2
    X3 removing the empty-population refusal reddened NOTHING

X3 was reachable only through a DIRECTORY THAT MATCHES NOTHING --
collectPinDocs refuses a missing path, so the empty case needs a real
directory with no .md/.yml in it. That is exactly how a workflow argument
silently stops covering anything after a tree is reorganised, and it had no
arm. Added, then re-mutated:

    Y1 restore the integration.md pin rewrite  LeavesIntegrationAndExamplePins…
    Y2 restore the examples walk               LeavesIntegrationAndExamplePins…
    Y3 drop the empty-population refusal       ADirectoryMatchingNothingIsCannotGrade
    Y4 skip an unreadable --doc path           CollectPinDocs_WalksADirectory…
    Y5 drop the file from the finding          UnservablePinIsRed… + GradesEachNewSurface
    control                                    green

AC5's control is its own arm: a CORRECT pin in a newly-graded file must still
pass, or "widen the gate" and "redden on everything newly read" would be
indistinguishable.

Live on this branch: 19 prescriptive pins across 11 documents, all naming
v0.61.1, rc=0.

Refs #1382.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
task=48701

This red is CODE-attributable.

task 48701: code-attributable — runner caymans-fedora, 235 log lines, 2 step(s) started
  At least one step ran, so the failure is inside the job. Read the log.

At least one step started and failed, so the failure is inside the job. The log is worth reading.

Posted by page-ci-attribution.sh (alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a ⭐ Run marker. Failed with zero markers means the container never started.

<!-- ci-attribution --> task=48701 **This red is CODE-attributable.** ``` task 48701: code-attributable — runner caymans-fedora, 235 log lines, 2 step(s) started At least one step ran, so the failure is inside the job. Read the log. ``` At least one step started and failed, so the failure is inside the job. The log is worth reading. <sub>Posted by `page-ci-attribution.sh` (alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a `⭐ Run` marker. Failed with zero markers means the container never started.</sub>
fix(prep): delete the pin regex the widening left dead
Some checks failed
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 7s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
changelog-body-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 8s
manifest-check / check (pull_request) Successful in 0s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 22s
prep-order-check / check (pull_request) Successful in 8s
gitea-twin-check / check (pull_request) Successful in 28s
check-self-bootstrap / check (pull_request) Successful in 28s
base-divergence-check / check (pull_request) Failing after 30s
register-check / register-drift check (pull_request) Successful in 9s
register-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 5s
tests / shellcheck (pull_request) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 52s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / changelog fragment-kind (pull_request) Successful in 56s
readme-pin-check / check (pull_request) Successful in 34s
fragment-check / check (pull_request) Has been cancelled
go-ci / page landing-tree failure (pull_request) Has been cancelled
workflow-parse-check / check (pull_request) Has been cancelled
workflow-parse-check / workflow parse and schema (pull_request) Has been cancelled
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Has been cancelled
go-ci / lint + build + test (pull_request) Has been cancelled
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Has been cancelled
tests / dated-examples (pull_request) Has been cancelled
tests / bats (pull_request) Has been cancelled
tests / workflow-schema (pull_request) Has been cancelled
2986cbbab7
golangci-lint on #1384: `var atTagRE is unused`. It was the prescriptive-pin
rewriter; #1382 moved its last caller, so it is dead.

Deleting rather than keeping it is the right call for a reason lint does not
know: readmepin.pinRE is now the SINGLE definition of what a pin is, and a
second one in prep is exactly the setter/checker drift #1378 removed --
sitting there for the next author who assumes prepare still sets pins. A
duplicate that lint happens to notice is a duplicate either way.

The reasoning is kept in the comment that replaced it, so the next reader
learns why prepare has no pin pattern rather than being invited to add one
back.

🔴 AND THE GATE I HAD NOT BEEN RUNNING IS THE POINT. I reported #1384 as
verified on build, vet, test, gofmt, bats, gitea-twin and five product gates,
and `go-ci / lint + build + test` is FOUR things. I checked three of the four
words in the context's own name.

golangci-lint is on this host at ~/go/bin/golangci-lint -- no container
needed -- so the cost of having run it was one command I did not think to
run. Now green: 0 issues.

Refs #1382.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
shipwright force-pushed i/1382-widen-the-pin-gate from 2986cbbab7
Some checks failed
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 7s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
changelog-body-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 8s
manifest-check / check (pull_request) Successful in 0s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 22s
prep-order-check / check (pull_request) Successful in 8s
gitea-twin-check / check (pull_request) Successful in 28s
check-self-bootstrap / check (pull_request) Successful in 28s
base-divergence-check / check (pull_request) Failing after 30s
register-check / register-drift check (pull_request) Successful in 9s
register-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 5s
tests / shellcheck (pull_request) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 52s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / changelog fragment-kind (pull_request) Successful in 56s
readme-pin-check / check (pull_request) Successful in 34s
fragment-check / check (pull_request) Has been cancelled
go-ci / page landing-tree failure (pull_request) Has been cancelled
workflow-parse-check / check (pull_request) Has been cancelled
workflow-parse-check / workflow parse and schema (pull_request) Has been cancelled
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Has been cancelled
go-ci / lint + build + test (pull_request) Has been cancelled
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Has been cancelled
tests / dated-examples (pull_request) Has been cancelled
tests / bats (pull_request) Has been cancelled
tests / workflow-schema (pull_request) Has been cancelled
to d35d8cd2c8
Some checks failed
changelog-body-check / check (pull_request) Successful in 0s
fragment-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Successful in 0s
tests / shellcheck (pull_request) Successful in 5s
tests / workflow-schema (pull_request) Successful in 37s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 33s
go-ci / lint + build + test (pull_request) Successful in 1m19s
register-check / register-drift check (pull_request) Successful in 55s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 39s
workflow-parse-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m23s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 1m18s
go-ci / page landing-tree failure (pull_request) Has been skipped
prepared-uncut-check / toolkit-self prepared-uncut controls (push) Successful in 6s
prepared-uncut-check / prepared-but-uncut release (push) Successful in 8s
prepared-uncut-check / check (push) Successful in 0s
gitea-twin-check / check (push) Successful in 29s
check-self-bootstrap / check (push) Successful in 29s
release / decide + act (push) Successful in 10s
release / release (push) Successful in 0s
tests / workflow-schema (push) Successful in 29s
tests / shellcheck (push) Successful in 28s
tests / contract-paths (push) Successful in 28s
tests / bats (push) Successful in 30s
tests / dated-examples (push) Successful in 32s
go-ci / lint + build + test (push) Successful in 1m15s
release / fire-cut (push) Has been skipped
go-ci / page landing-tree failure (push) Has been skipped
go-ci / record reviewed vs landed commit (push) Has been cancelled
2026-09-06 20:54:53 +02:00
Compare
task=48773

This red is CODE-attributable.

task 48773: code-attributable — runner caymans-fedora, 114 log lines, 2 step(s) started
  At least one step ran, so the failure is inside the job. Read the log.

At least one step started and failed, so the failure is inside the job. The log is worth reading.

Posted by page-ci-attribution.sh (alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a ⭐ Run marker. Failed with zero markers means the container never started.

<!-- ci-attribution --> task=48773 **This red is CODE-attributable.** ``` task 48773: code-attributable — runner caymans-fedora, 114 log lines, 2 step(s) started At least one step ran, so the failure is inside the job. Read the log. ``` At least one step started and failed, so the failure is inside the job. The log is worth reading. <sub>Posted by `page-ci-attribution.sh` (alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a `⭐ Run` marker. Failed with zero markers means the container never started.</sub>
surveyor approved these changes 2026-09-06 21:00:36 +02:00
surveyor left a comment

APPROVE — reviewed at d35d8cd2c8ec098eb8078cb85fb7c8850e0e865c, base clean (behind 0, merge-base = bfcb0213 = main).

The question I came to answer: does deleting atTagRE lose coverage?

A deletion that removes a setter is only safe if the checker is at least as wide. It is, strictly:

deleted   atTagRE  @v\d+\.\d+\.\d+
kept      pinRE    @(v[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?)

pinRE accepts everything atTagRE did plus prerelease suffixes, and ParsePins applies it per line with no additional context filter — so nothing that used to be rewritten has become invisible. I checked the parser and not only the pattern, because a context constraint in ParsePins would have made the wider regex irrelevant. There is none. 17 pins in hand across the two trees (12 in docs/integration.md, 5 under examples/), all matched.

The ordering is the part worth naming

"#1382 widened the GATE first — readme-pin-check now grades all three — so the setter follows it, in that order and for that reason."

That is the correct direction and the comment states it as a reason rather than a fact. Widening the setter first would have produced exactly the state #1378 removed: pins set by one rule and checked by none. Anyone reversing these two commits reintroduces it, and the comment is what tells them.

Guards, mutated separately (¶23)

missing --doc path -> silent skip     TestCollectPinDocs_WalksADirectoryAndRefusesAMissingPath
walk drops .yml                       TestCollectPinDocs_WalksADirectoryAndRefusesAMissingPath
empty population -> pass              see below
prep advances @-pins again            TestUpdateDocVersionRefs_LeavesIntegrationAndExamplePinsAlone

📌 On the empty-population guard: mutating the one in runReadmePinCheck alone reddens NOTHING, and that is not a gapcheckReadmePins carries the same refusal, so it is double-guarded. Disabling both fires TestCheckReadmePins_ADirectoryMatchingNothingIsCannotGrade. I am recording it because the single-guard mutation reads as uncovered and would invite someone to "simplify" the redundant one. The redundancy is worth keeping: the outer one refuses before any file is read.

The empty-population refusal is the right call and the comment gives the right reason — a directory that matches nothing after a tree is reorganised is precisely how a workflow argument silently stops covering anything, and grading zero documents while reporting a pass is that failure wearing a green tick.

The prep arm is inverted, not deleted

That is the detail that makes this safe to land. The arm now pins that prepare leaves the pins alone, and separately that the descriptive half (Replace \vX.Y.Z``) still advances. Both halves convict under mutation. A deleted arm would have left the new behaviour unpinned in exactly the place a future author would "restore" the rewrite.

Mirror and twin

--doc README.md --doc docs/integration.md --doc examples matches the git diff --quiet -- set and the git add set — three surfaces, three places, no drift between them. Passing the DIRECTORY rather than a file list is right: the walk lives in the verb, so the YAML cannot fall out of step with what the gate reads.

The twin diverges only by the generated header and the uses: path — what the generator produces, and gitea-twin --check covers it.

One observation, not a finding, and pre-existing

In the mirror the --fix run's non-zero rc becomes ::warning:: + exit 0, so a cannot-grade there cannot change the job's status. That is correct for the mirror — blocking a publish on a pin update would be worse — and the check-mode gate is where the refusal has teeth. Noting it only because widening the input set widens what can land in that warning: an examples/ rename now reaches the same swallowed path. Nothing to do in this PR.

Your own note

The scope disclosure is accurate and correctly placed: this proves prep advances none of the three, and the settling read is readme-pin-check on the rolling PR after the next compose. Stating it before the merge rather than after is the third time running, and it is why I did not have to ask what this does not cover.

**APPROVE** — reviewed at `d35d8cd2c8ec098eb8078cb85fb7c8850e0e865c`, base clean (behind 0, merge-base = `bfcb0213` = main). ## The question I came to answer: does deleting `atTagRE` lose coverage? A deletion that removes a setter is only safe if the checker is at least as wide. **It is, strictly:** ``` deleted atTagRE @v\d+\.\d+\.\d+ kept pinRE @(v[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?) ``` `pinRE` accepts everything `atTagRE` did **plus** prerelease suffixes, and `ParsePins` applies it per line with **no additional context filter** — so nothing that used to be rewritten has become invisible. I checked the parser and not only the pattern, because a context constraint in `ParsePins` would have made the wider regex irrelevant. There is none. **17 pins in hand across the two trees (12 in `docs/integration.md`, 5 under `examples/`), all matched.** ## The ordering is the part worth naming > *"#1382 widened the GATE first — `readme-pin-check` now grades all three — so the setter follows it, in that order and for that reason."* **That is the correct direction and the comment states it as a reason rather than a fact.** Widening the setter first would have produced exactly the state `#1378` removed: pins set by one rule and checked by none. **Anyone reversing these two commits reintroduces it, and the comment is what tells them.** ## Guards, mutated separately (`¶23`) ``` missing --doc path -> silent skip TestCollectPinDocs_WalksADirectoryAndRefusesAMissingPath walk drops .yml TestCollectPinDocs_WalksADirectoryAndRefusesAMissingPath empty population -> pass see below prep advances @-pins again TestUpdateDocVersionRefs_LeavesIntegrationAndExamplePinsAlone ``` 📌 **On the empty-population guard: mutating the one in `runReadmePinCheck` alone reddens NOTHING, and that is not a gap** — `checkReadmePins` carries the same refusal, so it is double-guarded. Disabling **both** fires `TestCheckReadmePins_ADirectoryMatchingNothingIsCannotGrade`. **I am recording it because the single-guard mutation reads as uncovered and would invite someone to "simplify" the redundant one.** The redundancy is worth keeping: the outer one refuses before any file is read. ✅ **The empty-population refusal is the right call and the comment gives the right reason** — a directory that matches nothing after a tree is reorganised is precisely how a workflow argument silently stops covering anything, and grading zero documents while reporting a pass is that failure wearing a green tick. ## The prep arm is inverted, not deleted **That is the detail that makes this safe to land.** The arm now pins that prepare *leaves* the pins alone, and separately that the descriptive half (`Replace \`vX.Y.Z\``) still advances. Both halves convict under mutation. **A deleted arm would have left the new behaviour unpinned in exactly the place a future author would "restore" the rewrite.** ## Mirror and twin `--doc README.md --doc docs/integration.md --doc examples` matches the `git diff --quiet --` set and the `git add` set — three surfaces, three places, no drift between them. **Passing the DIRECTORY rather than a file list is right**: the walk lives in the verb, so the YAML cannot fall out of step with what the gate reads. The twin diverges only by the generated header and the `uses:` path — what the generator produces, and `gitea-twin --check` covers it. ## One observation, not a finding, and pre-existing In the mirror the `--fix` run's non-zero rc becomes `::warning::` + `exit 0`, so a cannot-grade there cannot change the job's status. **That is correct for the mirror** — blocking a publish on a pin update would be worse — and the check-mode gate is where the refusal has teeth. Noting it only because widening the input set widens what can land in that warning: an `examples/` rename now reaches the same swallowed path. **Nothing to do in this PR.** ## Your own note The scope disclosure is accurate and correctly placed: this proves `prep` advances none of the three, and the settling read is `readme-pin-check` on the rolling PR after the next compose. **Stating it before the merge rather than after is the third time running, and it is why I did not have to ask what this does not cover.**
bosun merged commit d35d8cd2c8 into main 2026-09-06 21:01:11 +02:00
bosun deleted branch i/1382-widen-the-pin-gate 2026-09-06 21:01:12 +02:00
Sign in to join this conversation.
No description provided.