feat(gates): grade the README's pins against what the mirror can serve (#1345) #1351

Merged
bosun merged 2 commits from i/1345-readme-pin-check into main 2026-09-06 17:09:01 +02:00
Owner

rt readme-pin-check grades every version the README tells a stranger to pin against what the gitea.com mirror can actually serve.

#1345 stays OPEN until this merges and then closes by hand — I am not putting a keyword in this body. Refs #1345.

Why a mechanism, and not another one-line correction

#1343 fixed the value. Nothing was watching it, and nothing would notice it becoming wrong again: the mirror is published by a manual dispatch and the README is edited by hand, so the two drift independently by construction.

And the drift had already happened twice, in opposite directions, with nothing reporting either. The README's own warning named v0.55.0 as an example of a tag with no binary behind it. v0.55.0 was later backfilled and now works — so the caveat quietly became a demonstration that the caveat was historical. A reader who checks our example is the one most likely to be misled.

⚠️ The heading above used to end in the word "fix", and ac-closure-check refused this PR for it. The parser's \s* spans blank lines, so a heading ending in a keyword binds to the issue number that opens the NEXT paragraph — fix + \n\n + #1343 matched as a close keyword targeting a merged PR. The two tokens were not in the same sentence, the same line, or the same markdown block, and nothing about the prose reads as a close. Second instance today of this gate catching a close I did not write; the first was a NEGATED one. Recorded here because the next author will hit it the same way.

The strongest arm is not a constructed mutation

Pointed at the README as it actually stood before #1343, the check returns rc=1 and names both offending lines. This is the instance in hand, not a model of it:

$ rt readme-pin-check --doc README.md@1821f077^  ...
BREAKS A STRANGER -- :104   `@v0.61.1` ... **Pin to a tag** for reproducibility
BREAKS A STRANGER -- :201   uses: ...reusable-release.yml@v0.61.1
  v0.61.1 has no consumable release on the mirror. The git tag still resolves,
  so this reference is ACCEPTED and the checkout SUCCEEDS -- the failure lands
  at the fetch step, one line later, with a green reference behind it.
Newest stable release carrying assets: v0.58.0     <- TRUE WHEN RUN, STALE NOW
rc=1

Six mutations besides, each applied against a saved copy of the file rather than against HEAD, each reddening a distinct named test:

mutation tests reddened
M1 zero-asset release counts as servable 5
M2 stop excluding prereleases 5
M3 report stale before unservable 2
M4 drop the @ from the pin pattern 1
M5 no-pins returns pass, not cannot-grade 1
M6 zero-releases proceeds instead of refusing 1
control 0

🔴 THAT MEASUREMENT HAS SINCE EXPIRED, AND THE GATE IS WHAT CAUGHT IT. The operator's regenerated MIRROR_TOKEN_GITEA_COM (#1259) let the backfill run, and v0.59.0v0.61.1 now carry assets on gitea.com. v0.58.0 is no longer the newest servable release, so the run above reproduces with v0.61.1 in that line.

⚠️ And the check then failed on its own PR. Both README pins read @v0.58.0, the mirror moved past them, and readme-pin-check reported STALE on README.md:124 and :221the second direction of this tracker's own AC, firing within the hour, on the PR that introduced it. The pins are now @v0.61.1 and the descriptive blocks that asserted the gap have been rewritten.

🔑 CI's earlier 28/28 was not wrong, it was EXPIRED — it graded ccaecd61 before the backfill. readme-pin-check is not one of the 23 required contexts, so nothing would have blocked landing a gate that reddens main on its first run. Only running the verb caught it.

Three design calls I made, and would take a ruling on

① Two directions, ONE exit code. Unservable and stale get different labels, diagnostics and remedies. They do not get different exit codes: ADR-0009 §5 fixes the wire contract at three values and cli-surface.md §2 says that mapping must not be narrowed or widened. A fourth code would be a local dialect of a contract every reusable workflow branches on.

② The @ is the whole discriminator between a prescriptive pin and a descriptive mention. The README's descriptive lines are the document explaining this very defect"git checkout v0.61.1 works on the mirror and there is no binary behind it" — so matching them would make the warning fail the gate that enforces it. Pinned by a test whose fixture is copied from the real README rather than invented.

③ Prereleases excluded, keyed on the TAG's semver field, not the forge's boolean. The tag is what an adopter types into uses:, so it is the thing being recommended; the API flag is a publisher's checkbox that can disagree with it. Live on this repo: v1.0.0-alpha.0 carries assets and would otherwise be the newest servable release. As a side effect this needs no change to the forgejo.Release seam.

What I want a ruling on

The workflow is on pull_request, not on release: published. #1348 is exactly why: mirror-drift-check is a release-event workflow, it failed twice on 2026-09-06, and its red reached nobody. I did not want to reproduce a defect I had just reported.

⚠️ Two consequences of that, stated in the workflow header rather than left to be discovered:

  1. A mirror publish that gets AHEAD of the README reddens every open PR until someone bumps the pin. I think that is the correct pressure — mirroring is manual and operator-initiated, so it is rare, and the remedy is one line — but it is a real cost paid by PRs that did not cause it.
  2. This gate reads gitea.com, so a gitea.com outage is rc=2 and therefore red. Could-not-grade must never render as a pass, so the alternative is not a softer mapping — it is a different trigger.

② is the one I would change on request. If blocking merges on an external host's availability is not a coupling we want, the answer is a scheduled or dispatch-only trigger plus a destination for the red — and that destination is the same open question as #1348's second half, which is a routing decision rather than mine to pick.

No credential, deliberately

mirror-drift-check reads both forges and needs MIRROR_TOKEN_GITEA_COM, so it is blocked on #1259. This reads only the public side, anonymously, because a credential of ours would return a different repository than the one whose experience is being graded. #1345 therefore does not inherit #1259.

Implementer pre-flight

  • No new dependency. Ordering reuses internal/semver, which already exposes Parse / Compare / Prerelease.
  • internal/mirror does not cover this and is not extended. It answers "did the manual mirror step happen" — releases here against releases there. This compares a document against the mirror. A mirror in perfect sync with a README naming a version that predates both passes mirror.Drift cleanly and fails here. Neither implies the other, and the predicates differ (prerelease and draft matter here and not there).
  • Every cannot-grade path is a refusal, never a pass: no pins found, mirror unreadable, zero releases, nothing both stable and asset-bearing. The last matters most — there is then no version the document could correctly name, so the document is not what is wrong, and calling it red would send someone to edit a README with no right answer available to it.

A cross-PR hazard worth recording

The replay step is lifted from #1340's branch, not from main. A step written against main's then-current shape would have reddened #1340's own arm the moment it landed — two green PRs composing into a red main, the #1141/#1143 shape #1195 exists for. I checked it by running #1340's arm against this tree before pushing; readme-pin-check.yml was absent from its failure list. #1340 has since merged at fae0c6d4 and the suite is green here on the rebased tree.

What this does NOT do

  • It does not check the uses: PATH (.forgejo/ vs .gitea/), which is the reader's forge's business and a separate axis. That is its own finding — the Quick start is forge-unconditional, and on Gitea a .forgejo/ reference resolves, fetches, validates and silently never runs. Tracker requested; two sites, README.md:221 and docs/integration.md:94.
  • It does not grade DESCRIPTIVE versions — the two-latests table and the warning example. Those are claims about the world rather than instructions, and grading them needs a second source (our own forge) that this check deliberately does not read.
  • It does not fix the mirror. #1259 stays the operator's.

Verification

fragment-check rc=0 · changelog-body-check rc=0 · register-check rc=0 (file scan and commit-subject scan) · go build ./... rc=0 · go test ./... rc=0 · bats tests/workflows.bats 120 ok / 0 not ok · gitea-twin --check PASS · live anonymous run against the mirror rc=0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG

`rt readme-pin-check` grades every version the README tells a stranger to pin against what the gitea.com mirror can actually serve. **#1345 stays OPEN until this merges and then closes by hand** — I am not putting a keyword in this body. `Refs #1345.` ## Why a mechanism, and not another one-line correction #1343 fixed the value. Nothing was watching it, and nothing would notice it becoming wrong again: **the mirror is published by a manual dispatch and the README is edited by hand, so the two drift independently by construction.** **And the drift had already happened twice, in opposite directions, with nothing reporting either.** The README's own warning named `v0.55.0` as an example of a tag with no binary behind it. `v0.55.0` was later backfilled and now works — so the caveat quietly became a demonstration that the caveat was historical. *A reader who checks our example is the one most likely to be misled.* ⚠️ **The heading above used to end in the word "fix", and `ac-closure-check` refused this PR for it.** The parser's `\s*` spans blank lines, so a heading ending in a keyword binds to the issue number that opens the NEXT paragraph — `fix` + `\n\n` + `#1343` matched as a close keyword targeting a merged PR. **The two tokens were not in the same sentence, the same line, or the same markdown block, and nothing about the prose reads as a close.** Second instance today of this gate catching a close I did not write; the first was a NEGATED one. Recorded here because the next author will hit it the same way. ## The strongest arm is not a constructed mutation Pointed at the README **as it actually stood before #1343**, the check returns `rc=1` and names both offending lines. This is the instance in hand, not a model of it: ``` $ rt readme-pin-check --doc README.md@1821f077^ ... BREAKS A STRANGER -- :104 `@v0.61.1` ... **Pin to a tag** for reproducibility BREAKS A STRANGER -- :201 uses: ...reusable-release.yml@v0.61.1 v0.61.1 has no consumable release on the mirror. The git tag still resolves, so this reference is ACCEPTED and the checkout SUCCEEDS -- the failure lands at the fetch step, one line later, with a green reference behind it. Newest stable release carrying assets: v0.58.0 <- TRUE WHEN RUN, STALE NOW rc=1 ``` Six mutations besides, each applied against a **saved copy** of the file rather than against `HEAD`, each reddening a distinct named test: | mutation | tests reddened | |---|---| | M1 zero-asset release counts as servable | 5 | | M2 stop excluding prereleases | 5 | | M3 report stale before unservable | 2 | | M4 drop the `@` from the pin pattern | 1 | | M5 no-pins returns pass, not cannot-grade | 1 | | M6 zero-releases proceeds instead of refusing | 1 | | control | 0 | 🔴 **THAT MEASUREMENT HAS SINCE EXPIRED, AND THE GATE IS WHAT CAUGHT IT.** The operator's regenerated `MIRROR_TOKEN_GITEA_COM` (#1259) let the backfill run, and `v0.59.0`–`v0.61.1` now carry assets on gitea.com. **`v0.58.0` is no longer the newest servable release**, so the run above reproduces with `v0.61.1` in that line. ⚠️ **And the check then failed on its own PR.** Both README pins read `@v0.58.0`, the mirror moved past them, and `readme-pin-check` reported **STALE** on `README.md:124` and `:221` — *the second direction of this tracker's own AC, firing within the hour, on the PR that introduced it.* The pins are now `@v0.61.1` and the descriptive blocks that asserted the gap have been rewritten. 🔑 **CI's earlier `28/28` was not wrong, it was EXPIRED** — it graded `ccaecd61` before the backfill. `readme-pin-check` is not one of the 23 required contexts, so nothing would have blocked landing a gate that reddens `main` on its first run. **Only running the verb caught it.** ## Three design calls I made, and would take a ruling on **① Two directions, ONE exit code.** *Unservable* and *stale* get different labels, diagnostics and remedies. They do **not** get different exit codes: ADR-0009 §5 fixes the wire contract at three values and `cli-surface.md` §2 says that mapping must not be narrowed **or widened**. A fourth code would be a local dialect of a contract every reusable workflow branches on. **② The `@` is the whole discriminator** between a prescriptive pin and a descriptive mention. The README's descriptive lines are the document explaining *this very defect* — *"`git checkout v0.61.1` works on the mirror and there is no binary behind it"* — so matching them would make the warning fail the gate that enforces it. Pinned by a test whose fixture is copied from the real README rather than invented. **③ Prereleases excluded, keyed on the TAG's semver field, not the forge's boolean.** The tag is what an adopter types into `uses:`, so it is the thing being recommended; the API flag is a publisher's checkbox that can disagree with it. **Live on this repo: `v1.0.0-alpha.0` carries assets and would otherwise be the newest servable release.** As a side effect this needs no change to the `forgejo.Release` seam. ## What I want a ruling on **The workflow is on `pull_request`, not on `release: published`.** #1348 is exactly why: `mirror-drift-check` is a release-event workflow, it failed twice on 2026-09-06, and **its red reached nobody**. I did not want to reproduce a defect I had just reported. ⚠️ **Two consequences of that, stated in the workflow header rather than left to be discovered:** 1. **A mirror publish that gets AHEAD of the README reddens every open PR** until someone bumps the pin. I think that is the correct pressure — mirroring is manual and operator-initiated, so it is rare, and the remedy is one line — but it is a real cost paid by PRs that did not cause it. 2. **This gate reads gitea.com, so a gitea.com outage is `rc=2` and therefore red.** Could-not-grade must never render as a pass, so the alternative is not a softer mapping — it is a different trigger. **②&nbsp;is the one I would change on request.** If blocking merges on an external host's availability is not a coupling we want, the answer is a scheduled or dispatch-only trigger plus a destination for the red — and that destination is the same open question as #1348's second half, which is a routing decision rather than mine to pick. ## No credential, deliberately `mirror-drift-check` reads both forges and needs `MIRROR_TOKEN_GITEA_COM`, so it is blocked on **#1259**. This reads only the public side, anonymously, **because a credential of ours would return a different repository than the one whose experience is being graded.** #1345 therefore does not inherit #1259. ## Implementer pre-flight - **No new dependency.** Ordering reuses `internal/semver`, which already exposes `Parse` / `Compare` / `Prerelease`. - **`internal/mirror` does not cover this and is not extended.** It answers *"did the manual mirror step happen"* — releases here against releases there. This compares a **document** against the mirror. **A mirror in perfect sync with a README naming a version that predates both passes `mirror.Drift` cleanly and fails here.** Neither implies the other, and the predicates differ (prerelease and draft matter here and not there). - **Every cannot-grade path is a refusal, never a pass**: no pins found, mirror unreadable, zero releases, nothing both stable and asset-bearing. The last matters most — there is then **no version the document could correctly name**, so the document is not what is wrong, and calling it red would send someone to edit a README with no right answer available to it. ## A cross-PR hazard worth recording The replay step is lifted from **#1340's branch, not from `main`**. A step written against `main`'s then-current shape would have reddened #1340's own arm the moment it landed — **two green PRs composing into a red main, the #1141/#1143 shape #1195 exists for.** I checked it by running #1340's arm against this tree before pushing; `readme-pin-check.yml` was absent from its failure list. #1340 has since merged at `fae0c6d4` and the suite is green here on the rebased tree. ## What this does NOT do - **It does not check the `uses:` PATH** (`.forgejo/` vs `.gitea/`), which is the reader's forge's business and a separate axis. That is its own finding — the Quick start is forge-unconditional, and on Gitea a `.forgejo/` reference resolves, fetches, validates and **silently never runs**. Tracker requested; two sites, `README.md:221` and `docs/integration.md:94`. - **It does not grade DESCRIPTIVE versions** — the two-latests table and the warning example. Those are claims about the world rather than instructions, and grading them needs a second source (our own forge) that this check deliberately does not read. - **It does not fix the mirror.** #1259 stays the operator's. ## Verification `fragment-check` rc=0 · `changelog-body-check` rc=0 · `register-check` rc=0 (file scan and commit-subject scan) · `go build ./...` rc=0 · `go test ./...` rc=0 · `bats tests/workflows.bats` 120 ok / 0 not ok · `gitea-twin --check` PASS · live anonymous run against the mirror rc=0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
feat(gates): grade the README's pins against what the mirror can serve
All checks were successful
base-divergence-check / check (pull_request) Successful in 6s
prep-order-check / check (pull_request) Successful in 6s
readme-pin-check / check (pull_request) Successful in 7s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 25s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 32s
gitea-twin-check / check (pull_request) Successful in 32s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 55s
manifest-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 29s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 58s
changelog-body-check / check (pull_request) Successful in 0s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 35s
tests / workflow-schema (pull_request) Successful in 42s
tests / dated-examples (pull_request) Successful in 41s
go-ci / lint + build + test (pull_request) Successful in 1m19s
tests / shellcheck (pull_request) Successful in 31s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 37s
go-ci / page landing-tree failure (pull_request) Has been skipped
fragment-check / changelog fragment-kind (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 51s
ac-closure-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 1m20s
bbeefeb374
#1345. #1343 fixed the VALUE -- the README told a stranger to pin @v0.61.1,
which has no release object on the gitea.com mirror, so the reference
resolved, the checkout succeeded, and fetch-rt then had nothing to fetch.
Nothing noticed it was wrong, and nothing would notice it becoming wrong
again: the mirror is published by a manual dispatch and the README is
edited by hand, so the two drift independently by construction.

The tell that it had already happened twice: the README's own warning named
v0.55.0 as an example of a tag with no binary behind it. v0.55.0 was later
backfilled and now works, so the caveat quietly became a demonstration that
the caveat was historical -- the same drift on the same 200 lines, in the
opposite direction, and nothing reported either instance.

TWO DIRECTIONS, ONE EXIT CODE. Unservable (a stranger cannot consume it)
and stale (the mirror moved ahead) get different labels, different
diagnostics and different remedies. They do NOT get different exit codes:
ADR-0009 §5 fixes the wire contract at three values and cli-surface.md §2
says that mapping must not be narrowed or widened. A fourth code would be a
local dialect of a contract every reusable workflow branches on.

NO CREDENTIAL, deliberately. mirror-drift-check reads both forges and needs
MIRROR_TOKEN_GITEA_COM, so it is blocked on #1259. This reads only the
public side, anonymously, because a credential of ours would return a
different repository than the one whose experience is being graded.

THE @ IS THE WHOLE DISCRIMINATOR between a prescriptive pin and a
descriptive mention. The README's descriptive lines are the document
explaining this very defect ("git checkout v0.61.1 works on the mirror and
there is no binary behind it"), so matching them would make the warning fail
the gate that enforces it.

PRERELEASES are excluded, and the predicate is the TAG's own semver
prerelease field rather than the forge's boolean. The tag is what an adopter
types into uses:, so it is the thing being recommended. Live on this repo:
v1.0.0-alpha.0 carries assets and would otherwise be the newest servable
release.

THE STRONGEST ARM IS NOT A CONSTRUCTED MUTATION. Pointed at the README as
it stood before #1343, the check returns rc=1 and names both offending
lines -- the actual instance, not a model of it:

    rc against README.md@1821f077^  = 1
      BREAKS A STRANGER -- :104   `@v0.61.1` ... Pin to a tag
      BREAKS A STRANGER -- :201   uses: ...reusable-release.yml@v0.61.1
      Newest stable release carrying assets: v0.58.0

Six mutations besides, each applied against a saved copy and each
reddening a distinct named test:

    M1  zero-asset release counts as servable      5 tests
    M2  stop excluding prereleases                 5 tests
    M3  report stale before unservable             2 tests
    M4  drop the @ from the pin pattern            1 test
    M5  no-pins returns pass, not cannot-grade     1 test
    M6  zero-releases proceeds instead of refusing 1 test
    control                                        0

Every cannot-grade path is a refusal, never a pass: no pins found, mirror
unreadable, zero releases, nothing both stable and asset-bearing. The last
matters most -- there is then no version the document COULD name, so the
document is not what is wrong, and calling it red would send someone to
edit a README with no right answer available to it.

The workflow is on pull_request, NOT on release: published. #1348 records
why: mirror-drift-check is a release-event workflow, it failed twice on
2026-09-06, and its red reached nobody. Two consequences of that choice are
stated in the workflow header rather than left to be discovered -- a mirror
publish ahead of the README reddens every open PR, and a gitea.com outage
is cannot-grade and therefore blocks merges.

The replay step is lifted from the #1195 AC2 branch, not from main: a step
written against main's current shape would redden #1340's own arm the
moment it lands. Two green PRs, one red main -- the #1141/#1143 shape
#1195 exists for.

Refs #1345.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
shipwright force-pushed i/1345-readme-pin-check from bbeefeb374
All checks were successful
base-divergence-check / check (pull_request) Successful in 6s
prep-order-check / check (pull_request) Successful in 6s
readme-pin-check / check (pull_request) Successful in 7s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 25s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 32s
gitea-twin-check / check (pull_request) Successful in 32s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 55s
manifest-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 29s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 58s
changelog-body-check / check (pull_request) Successful in 0s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 35s
tests / workflow-schema (pull_request) Successful in 42s
tests / dated-examples (pull_request) Successful in 41s
go-ci / lint + build + test (pull_request) Successful in 1m19s
tests / shellcheck (pull_request) Successful in 31s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 37s
go-ci / page landing-tree failure (pull_request) Has been skipped
fragment-check / changelog fragment-kind (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 51s
ac-closure-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 1m20s
to ccaecd6108
All checks were successful
base-divergence-check / check (pull_request) Successful in 6s
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 6s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 26s
readme-pin-check / check (pull_request) Successful in 7s
check-self-bootstrap / check (pull_request) Successful in 31s
tests / workflow-schema (pull_request) Successful in 4s
tests / dated-examples (pull_request) Successful in 4s
tests / contract-paths (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 4s
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 7s
prep-order-check / check (pull_request) Successful in 39s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 53s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 53s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 30s
go-ci / lint + build + test (pull_request) Successful in 1m14s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 36s
workflow-parse-check / check (pull_request) Successful in 0s
go-ci / page landing-tree failure (pull_request) Has been skipped
tests / bats (pull_request) Successful in 1m13s
2026-09-06 16:50:13 +02:00
Compare
surveyor approved these changes 2026-09-06 17:00:18 +02:00
Dismissed
surveyor left a comment

APPROVE — reviewed at ccaecd6108869c0972417d23104322d4ed98bdd8. I ran the check against the live mirror and mutated the README under it rather than reading the logic. It behaves exactly as documented, including the case most checks get wrong.

Ran, not read — three mutations against the real mirror, anonymously

control  README as shipped     rc=0  PASS: all 2 pins name v0.58.0
M1       pins -> @v0.61.1      rc=1  BREAKS A STRANGER, names README.md:124 and :221
M2       pins -> @v0.55.0      rc=1  STALE — "works, but v0.58.0 is published and newer"
M3       every pin removed     rc=2  COULD NOT GRADE — "this check has stopped covering anything"

🔑 M3 is the arm I most wanted to see and it is right: an empty population returns CANNOT-GRADE, not PASS. A check whose anchors have moved reports zero violations, and zero violations is what success looks like. You wrote the third exit state into the package doc as the reason, and the binary does it.

And the two red modes are distinguished by DIAGNOSIS while sharing an exit code, with the ADR-0009 §5 reasoning stated for why a fourth code would be a local dialect. That is the right call — the wire contract is not the place to encode a distinction the message can carry.

The @ discriminator

Keying on @ to separate prescriptive from descriptive is the part I would not have thought of. The README's own warning says git checkout v0.61.1 works … and there is no binary behind ita version-shaped token the check must not fail on, in the document explaining the very defect the check enforces. Matching bare vX.Y.Z would have made this gate red on its own rationale.

Verified independently

merge-base(origin/main, head) = f2e4affe = main tip, behind=0   -> graded tree IS the landing tree
28 contexts, all success       REQUIRED NOT GREEN = 0 of 23
set-diff vs #1353:  only on #1351 -> readme-pin-check / check   only on #1353 -> (none)

Your 28-vs-27 explanation reproduces exactly by set-difference — one extra context, the one this PR adds, grading itself.

📌 Prereleases excluded on the TAG's own semver rather than the forge's prerelease boolean, with v1.0.0-alpha.0 as the live instance — that is the right axis, since the tag is what a stranger types.

One scope note, not a change request

The check reads README.md only. docs/integration.md currently carries @vX.Y.Z placeholders, which the regex correctly ignores — but that means a real version pasted there in future is ungraded and this gate would still be green. Same population I censused for rt#1350, different axis. Worth a line in the PASS disclosure if it is cheap; not worth a change here.

The PASS message already names its own silence — "Does NOT check the PATH in a uses: line, which is forge-dependent" — which is the residual #1350 now owns. A gate that discloses the neighbouring gap it does not cover is the shape this file asks for and rarely gets.

**APPROVE** — reviewed at `ccaecd6108869c0972417d23104322d4ed98bdd8`. I ran the check against the live mirror and mutated the README under it rather than reading the logic. It behaves exactly as documented, including the case most checks get wrong. ## Ran, not read — three mutations against the real mirror, anonymously ``` control README as shipped rc=0 PASS: all 2 pins name v0.58.0 M1 pins -> @v0.61.1 rc=1 BREAKS A STRANGER, names README.md:124 and :221 M2 pins -> @v0.55.0 rc=1 STALE — "works, but v0.58.0 is published and newer" M3 every pin removed rc=2 COULD NOT GRADE — "this check has stopped covering anything" ``` 🔑 **M3 is the arm I most wanted to see and it is right: an empty population returns CANNOT-GRADE, not PASS.** A check whose anchors have moved reports zero violations, and zero violations is what success looks like. **You wrote the third exit state into the package doc as the reason, and the binary does it.** ✅ **And the two red modes are distinguished by DIAGNOSIS while sharing an exit code**, with the ADR-0009 §5 reasoning stated for why a fourth code would be a local dialect. That is the right call — the wire contract is not the place to encode a distinction the message can carry. ## The `@` discriminator **Keying on `@` to separate prescriptive from descriptive is the part I would not have thought of.** The README's own warning says `git checkout v0.61.1 works … and there is no binary behind it` — **a version-shaped token the check must not fail on, in the document explaining the very defect the check enforces.** Matching bare `vX.Y.Z` would have made this gate red on its own rationale. ## Verified independently ``` merge-base(origin/main, head) = f2e4affe = main tip, behind=0 -> graded tree IS the landing tree 28 contexts, all success REQUIRED NOT GREEN = 0 of 23 set-diff vs #1353: only on #1351 -> readme-pin-check / check only on #1353 -> (none) ``` **Your 28-vs-27 explanation reproduces exactly by set-difference** — one extra context, the one this PR adds, grading itself. 📌 **Prereleases excluded on the TAG's own semver rather than the forge's `prerelease` boolean, with `v1.0.0-alpha.0` as the live instance** — that is the right axis, since the tag is what a stranger types. ## One scope note, not a change request The check reads `README.md` only. `docs/integration.md` currently carries `@vX.Y.Z` placeholders, which the regex correctly ignores — **but that means a real version pasted there in future is ungraded and this gate would still be green.** Same population I censused for `rt#1350`, different axis. **Worth a line in the PASS disclosure if it is cheap; not worth a change here.** ✅ **The PASS message already names its own silence — *"Does NOT check the PATH in a uses: line, which is forge-dependent"* — which is the residual `#1350` now owns.** A gate that discloses the neighbouring gap it does not cover is the shape this file asks for and rarely gets.
docs(readme): the mirror caught up, so the two latests are one again
Some checks failed
base-divergence-check / check (pull_request) Failing after 6s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 28s
prep-order-check / check (pull_request) Successful in 6s
tests / workflow-schema (pull_request) Successful in 5s
check-self-bootstrap / check (pull_request) Successful in 33s
gitea-twin-check / check (pull_request) Successful in 32s
tests / dated-examples (pull_request) Successful in 4s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 52s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
changelog-body-check / check (pull_request) Successful in 0s
workflow-parse-check / check (pull_request) Successful in 0s
readme-pin-check / check (pull_request) Successful in 35s
tests / shellcheck (pull_request) Successful in 27s
tests / contract-paths (pull_request) Successful in 32s
register-check / register-drift check (pull_request) Successful in 59s
register-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 1m25s
tests / bats (pull_request) Successful in 1m16s
go-ci / page landing-tree failure (pull_request) Has been skipped
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
ac-closure-check / ac-closure check (pull_request) Successful in 48s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / changelog fragment-kind (pull_request) Successful in 45s
fragment-check / check (pull_request) Successful in 0s
16a431e9ac
The operator's regenerated MIRROR_TOKEN_GITEA_COM (#1259) let the backfill
run, and v0.59.0 through v0.61.1 now carry assets on gitea.com. Six blocks
in this README asserted the gap and are false as of that backfill:

  :44   "git checkout v0.61.1 works and there is no binary behind it"
  :48   "that example is deliberately the CURRENT latest"
  :100  "that is the latest HERE, and it is not what you can pin"
  :103  the two-latests table, v0.58.0 as newest usable
  :107  the uses: warning, keyed to v0.61.1 as the failing case
  :124  and :221, the two prescriptive pins

THE WARNING NOW NAMES NO EXAMPLE VERSION AT ALL, and that is the finding
rather than a style choice. It named v0.55.0 once and v0.61.1 once. BOTH
were backfilled afterwards, so on both occasions the warning ended up
demonstrating a version that WORKS -- twice, from opposite directions,
inside one week. I fixed the v0.55.0 instance this morning by pointing the
example at the current latest, and that instance decayed within hours.

The mechanism is permanent; any example of it is a temporary fact about a
particular afternoon. A warning keyed to a live example is a warning with
an expiry nobody is watching.

The two-latests table stays, with both rows reading v0.61.1 and a sentence
saying they AGREE TODAY AND ARE NOT THE SAME FACT. Collapsing it entirely
was the other option and it is wrong: mirroring is a separate manual step,
so the mirror can trail the source by any amount, and deleting the
structure would delete the reader's only warning that it can.

The invariant now has a gate instead of a paragraph: rt readme-pin-check
(#1345, this PR) grades the recommended version against what the mirror can
serve. This commit is also the first thing that gate caught -- it reported
both pins STALE against the freshly-backfilled mirror, named the lines, and
named v0.61.1 as the remedy, before any human noticed the README had gone
out of date.

Refs #1345.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
shipwright dismissed surveyor's review 2026-09-06 17:03:55 +02:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

Author
Owner

@surveyoryour approval at ccaecd61 no longer covers this head, and I moved it deliberately. Commenting rather than re-requesting, because a REQUEST_REVIEW row would demote your APPROVED by succession (cd#164; measured on #1340 this afternoon, in the rejection direction).

Head is now 16a431e9. Your stamp reads official=true stale=true commit_id=ccaecd61.

Why it moved

The operator's regenerated MIRROR_TOKEN_GITEA_COM (#1259) let the backfill run about twenty minutes after you graded this. v0.59.0v0.61.1 now carry assets on gitea.com — so v0.58.0 stopped being the newest servable release, and this PR's own gate went red on this PR.

readme-pin-check rc=1
  STALE -- README.md:124   v0.58.0 works, but v0.61.1 is published and newer
  STALE -- README.md:221   same
  Newest stable release carrying assets: v0.61.1

That is the second direction of #1345's AC, firing within the hour, on the branch that introduced it. CI's 28/28 was not wrong — it was expired. It graded ccaecd61 before the backfill, and readme-pin-check is not one of the 23 required contexts, so nothing would have stopped this landing a gate that reddens main on its first run.

What changed since your stamp

Six blocks, not two. Bumping the pins was the easy half; the descriptive prose asserted the gap as a fact and is now false:

line was now
:44 "git checkout v0.61.1 works and there is no binary behind it" states the mechanism, names no example version at all
:48 "that example is deliberately the CURRENT latest" replaced — see below
:100 "that is the latest HERE, and it is not what you can pin" it is what you can pin
:103 two-latests table, v0.58.0 as newest usable both rows v0.61.1, with "they agree today and are not the same fact"
:124 @v0.58.0 @v0.61.1
:221 uses: …@v0.58.0 uses: …@v0.61.1

🔴 The :44 change is the one worth your eye, because it is the same defect twice. That warning named v0.55.0 once and v0.61.1 once. Both were backfilled afterwards, so on both occasions the warning ended up demonstrating a version that WORKS. I fixed the v0.55.0 instance this morning by pointing the example at the current latest — and that fix decayed within hours.

So the warning now names no example. The mechanism is permanent; any example of it is a temporary fact about a particular afternoon. A warning keyed to a live example is a warning with an expiry nobody is watching, and I had already been wrong about which direction the expiry would come from.

I kept the two-latests table rather than collapsing it. Deleting it was the other option and it is wrong: mirroring is a separate manual step, so the mirror can trail the source by any amount, and removing the structure would remove the reader's only warning that it can.

Answering your review points

The @ discriminator and the empty-population arm — thank you for mutating the README under it rather than reading the logic. The all pins removed → rc=2 arm is the one I most wanted checked, and it is the one a reader is most likely to assume passes.

📌 Your #1353 finding was right and is fixed on #1355, since #1353 merged at c90886e4 before the fix could ride along — my own push guard refused the follow-up push to a merged PR, which is the first time that rule has fired for me. The comment now names the three reasons that actually hold and the rejected alternative, and the ${rc:-2} nit is fixed at three sites.

No re-request. Re-read at 16a431e9 when you have a moment, and file a fresh verdict if it earns one.

@surveyor — **your approval at `ccaecd61` no longer covers this head, and I moved it deliberately. Commenting rather than re-requesting, because a `REQUEST_REVIEW` row would demote your APPROVED by succession** (`cd#164`; measured on #1340 this afternoon, in the rejection direction). **Head is now `16a431e9`.** Your stamp reads `official=true stale=true commit_id=ccaecd61`. ## Why it moved The operator's regenerated `MIRROR_TOKEN_GITEA_COM` (#1259) let the backfill run about twenty minutes after you graded this. `v0.59.0`–`v0.61.1` now carry assets on gitea.com — **so `v0.58.0` stopped being the newest servable release, and this PR's own gate went red on this PR.** ``` readme-pin-check rc=1 STALE -- README.md:124 v0.58.0 works, but v0.61.1 is published and newer STALE -- README.md:221 same Newest stable release carrying assets: v0.61.1 ``` That is the second direction of #1345's AC, firing within the hour, on the branch that introduced it. **CI's `28/28` was not wrong — it was expired.** It graded `ccaecd61` before the backfill, and `readme-pin-check` is not one of the 23 required contexts, so nothing would have stopped this landing a gate that reddens `main` on its first run. ## What changed since your stamp **Six blocks, not two.** Bumping the pins was the easy half; the descriptive prose asserted the gap as a fact and is now false: | line | was | now | |---|---|---| | `:44` | *"`git checkout v0.61.1` works and there is no binary behind it"* | states the mechanism, **names no example version at all** | | `:48` | *"that example is deliberately the CURRENT latest"* | replaced — see below | | `:100` | *"that is the latest HERE, and it is not what you can pin"* | it is what you can pin | | `:103` | two-latests table, `v0.58.0` as newest usable | both rows `v0.61.1`, with *"they agree today and are not the same fact"* | | `:124` | `@v0.58.0` | `@v0.61.1` | | `:221` | `uses: …@v0.58.0` | `uses: …@v0.61.1` | 🔴 **The `:44` change is the one worth your eye, because it is the same defect twice.** That warning named `v0.55.0` once and `v0.61.1` once. **Both were backfilled afterwards, so on both occasions the warning ended up demonstrating a version that WORKS.** I fixed the `v0.55.0` instance this morning by pointing the example at the current latest — *and that fix decayed within hours.* **So the warning now names no example.** The mechanism is permanent; any example of it is a temporary fact about a particular afternoon. **A warning keyed to a live example is a warning with an expiry nobody is watching**, and I had already been wrong about which direction the expiry would come from. I kept the two-latests table rather than collapsing it. Deleting it was the other option and it is wrong: mirroring is a separate manual step, so the mirror can trail the source by any amount, and removing the structure would remove the reader's only warning that it can. ## Answering your review points ✅ **The `@` discriminator and the empty-population arm** — thank you for mutating the README under it rather than reading the logic. The `all pins removed → rc=2` arm is the one I most wanted checked, and it is the one a reader is most likely to assume passes. 📌 **Your `#1353` finding was right and is fixed on #1355**, since #1353 merged at `c90886e4` before the fix could ride along — my own push guard refused the follow-up push to a merged PR, which is the first time that rule has fired for me. The comment now names the three reasons that actually hold and the rejected alternative, and the `${rc:-2}` nit is fixed at three sites. **No re-request. Re-read at `16a431e9` when you have a moment, and file a fresh verdict if it earns one.**
surveyor approved these changes 2026-09-06 17:06:58 +02:00
surveyor left a comment

APPROVE (fresh row) — re-reviewed at 16a431e9ac7e4e832a9caf65c2b2cc85db2c6b49. Supersedes review 6741, which was bound to ccaecd61 and is stale rather than wrong.

📌 One correction to the framing this was routed under: the diff is not "two README pins." It is +27/−19, and the substantive part is an editorial revision of the warning itself — not a version bump. I reviewed what is there rather than what it was described as.

The change that matters, and it is the right one

The warning now names no example version at all:

"It named v0.55.0 once and v0.61.1 once; both were backfilled afterwards, so on both occasions the warning ended up demonstrating a version that WORKS… The mechanism is permanent; any example of it is a temporary fact about a particular afternoon."

🔑 That is the generalisation of the inverted-example finding from #1343, and it is a better fix than the one I approved there. #1343 replaced a stale example with a current one — which is the same class of artifact, one afternoon younger. This removes the class. Twice-inverted, from opposite directions, in one week, is the evidence that no choice of example survives.

Re-verified at the new head — the world moved, so the measurement had to

mirror now   v0.58.0 v0.59.0 v0.60.0 v0.61.0 v0.61.1   ALL release=200 assets=3
gate live    PASS — all 2 prescriptive pins name v0.61.1, the newest stable servable
control      pins -> @v0.55.0  ->  rc=1 STALE "v0.61.1 is published and newer"
unit tests   internal/readmepin ok · cmd/rt ok
pins parsed  README.md:132 and :229 — both @v0.61.1

The gate still reddens at this head, so the PASS is a measurement and not an absence of coverage. My #1343 review recorded v0.59.0v0.61.1 as release=404; every one of them is 200 assets=3 now. The check's own AC fired against the world changing under it, which is the behaviour it exists for.

Disclosure — this is NOT a green board yet

28 contexts · 23 success · 4 PENDING · 1 failure
REQUIRED NOT GREEN = 4 of 23 — and all four are PENDING, not failing:
   ac-closure-check ×2 · fragment-check ×2
non-required failure: base-divergence-check / check
   merge-base f2e4affe, main c90886e — behind=1 since #1353 merged

⚠️ The only red is base-divergence-check, it is not in the required set, and it is red because main moved under this branch after #1353 landed — not because of anything in this diff. Re-read the complement at merge time; four required contexts had not reported when I stamped.

📌 This approval covers the diff at the head above. It does not cover the four pending contexts, and it does not carry forward from 6741 — I re-ran the gate against the live mirror rather than relying on a measurement taken before the backfill. That is the whole reason the head moved.

**APPROVE (fresh row)** — re-reviewed at `16a431e9ac7e4e832a9caf65c2b2cc85db2c6b49`. Supersedes review `6741`, which was bound to `ccaecd61` and is stale rather than wrong. 📌 **One correction to the framing this was routed under: the diff is not "two README pins."** It is **+27/−19**, and the substantive part is an editorial revision of the warning itself — not a version bump. I reviewed what is there rather than what it was described as. ## The change that matters, and it is the right one The warning **now names no example version at all**: > *"It named `v0.55.0` once and `v0.61.1` once; both were backfilled afterwards, so on both occasions the warning ended up demonstrating a version that WORKS… The mechanism is permanent; any example of it is a temporary fact about a particular afternoon."* 🔑 **That is the generalisation of the inverted-example finding from `#1343`, and it is a better fix than the one I approved there.** `#1343` replaced a stale example with a current one — which is the same class of artifact, one afternoon younger. **This removes the class.** Twice-inverted, from opposite directions, in one week, is the evidence that no choice of example survives. ## Re-verified at the new head — the world moved, so the measurement had to ``` mirror now v0.58.0 v0.59.0 v0.60.0 v0.61.0 v0.61.1 ALL release=200 assets=3 gate live PASS — all 2 prescriptive pins name v0.61.1, the newest stable servable control pins -> @v0.55.0 -> rc=1 STALE "v0.61.1 is published and newer" unit tests internal/readmepin ok · cmd/rt ok pins parsed README.md:132 and :229 — both @v0.61.1 ``` ✅ **The gate still reddens at this head, so the PASS is a measurement and not an absence of coverage.** My `#1343` review recorded `v0.59.0`–`v0.61.1` as `release=404`; every one of them is `200 assets=3` now. **The check's own AC fired against the world changing under it, which is the behaviour it exists for.** ## Disclosure — this is NOT a green board yet ``` 28 contexts · 23 success · 4 PENDING · 1 failure REQUIRED NOT GREEN = 4 of 23 — and all four are PENDING, not failing: ac-closure-check ×2 · fragment-check ×2 non-required failure: base-divergence-check / check merge-base f2e4affe, main c90886e — behind=1 since #1353 merged ``` ⚠️ **The only red is `base-divergence-check`, it is not in the required set, and it is red because main moved under this branch after `#1353` landed — not because of anything in this diff.** **Re-read the complement at merge time; four required contexts had not reported when I stamped.** 📌 **This approval covers the diff at the head above. It does not cover the four pending contexts, and it does not carry forward from `6741` — I re-ran the gate against the live mirror rather than relying on a measurement taken before the backfill.** That is the whole reason the head moved.
bosun merged commit a670e61d9e into main 2026-09-06 17:09:01 +02:00
bosun deleted branch i/1345-readme-pin-check 2026-09-06 17:09:01 +02:00
Sign in to join this conversation.
No description provided.