docs(pins): mark and guard the four illustrative/hypothetical/historical pins (#968) #1076

Merged
pullings merged 4 commits from i/968-immutable-doc-pins into main 2026-08-31 00:59:25 +02:00

Refs frankenbit/release-toolkit#968

The discriminant

Correction, added after the first push: the tracker names three sites.
The actual population is fourdocs/VERSIONING.md carries its own
illustrative @v0.25.0, found while fixing the other three and folded in
here (per @bosun) rather than left as a separate follow-up, since this PR
already touches exactly this class of site.

docs/SECURITY.md's @v0.25.0, docs/VERSIONING.md's @v0.25.0,
ADR-0008's @v2.0.0, and ADR-0001's @v0.10.0 are indistinguishable from
missed sites by version number alone — no sweep keyed on the string can
tell them apart. They're a different kind of line from a real pin:

  • SECURITY.md / VERSIONING.mdillustrative: demonstrate the shape
    of a pin, not a version to track
  • ADR-0008 — hypothetical: a future v2 example; v2 doesn't exist yet
  • ADR-0001 — historical: what that ADR's era actually pinned; bumping
    it would misstate what was decided then

The tracker's own framing is the discriminant: instruction vs. record.
A uses: line in .forgejo/workflows/*.yml is a live instruction this
repo's CI executes against. The same string in prose or a doc's fenced
example is a record — of what a pin looks like, or looked like.

Why this is safe TODAY, and why that's the risk

internal/wrappers.Discover — what rt repin calls — reads a
non-recursive directory listing filtered to .yml/.yaml, and its
only caller (cmd/rt/repin.go) points it exclusively at
.forgejo/workflows/. docs/** is unreachable by construction, not by
an exclusion list — a markdown file has neither extension, and nothing
under docs/ is even in the directory the mechanism reads.

That's exactly why the tracker exists: the current tool can't touch these
four, so nothing has ever tested whether a future, broader "fix
stale-looking versions" tool would. This PR marks the sites so the hazard
is visible at the edit point, and gates it so an accidental bump — by hand
or by a future tool — is caught rather than assumed away.

What's new at each site

  • docs/SECURITY.md — an HTML comment directly under the fenced
    example, naming it illustrative and pointing at the mechanism section
    below for why it's safe.
  • docs/VERSIONING.md — an inline parenthetical (prose, not a fenced
    block), naming it illustrative — same shape as SECURITY.md, a
    different sentence.
  • ADR-0008 — same fenced-block shape, naming it hypothetical (v2
    doesn't exist).
  • ADR-0001 — an inline parenthetical, naming it historical.
  • docs/architecture.md's existing auto-re-pin paragraph — extended
    with the general discriminant and the Discover mechanism that already
    enforces it, so a reader learning how pin-bumping works learns the scope
    in the same place.

Two controls, not one — the AC's own "gate or unmissable marker" framing

Built both since the cost was small and they check different things:

tests/immutable-pins.bats — positive-presence sweep, now four arms:
each pin string and its adjacent #968 marker must co-occur.
Mutation-verified on three of the four sites so far — a whole-file
find-and-replace on SECURITY.md, and pin-only edits (marker text left
untouched) on ADR-0001 and VERSIONING.md — all correctly reddened.

internal/wrappers.TestDiscoverCannotReachDocs — runs the actual
exported Discover function against this repo's real docs/ and
docs/adr/ directories, not a synthetic fixture, asserting empty
found/ungradeable. TestDiscover's existing NOTES.md arm already
proves the extension filter generically; this ties that proof to the
specific directories these sites live in — docs/VERSIONING.md sits in
the same top-level docs/ directory already covered, so no new arm was
needed there. Mutation-verified by dropping a stray uses:-carrying
.yml into docs/ and confirming it's caught, then removing it.

Verification

  • go build ./... / go vet ./... / go test ./... -count=1 clean
  • bats tests/*.bats — 159/159 pass
  • gofmt -l clean on everything touched
  • rt fragment-check changelog.d PASS
Refs frankenbit/release-toolkit#968 ## The discriminant **Correction, added after the first push**: the tracker names three sites. The actual population is **four** — `docs/VERSIONING.md` carries its own illustrative `@v0.25.0`, found while fixing the other three and folded in here (per @bosun) rather than left as a separate follow-up, since this PR already touches exactly this class of site. `docs/SECURITY.md`'s `@v0.25.0`, `docs/VERSIONING.md`'s `@v0.25.0`, ADR-0008's `@v2.0.0`, and ADR-0001's `@v0.10.0` are indistinguishable from missed sites **by version number alone** — no sweep keyed on the string can tell them apart. They're a different *kind* of line from a real pin: - `SECURITY.md` / `VERSIONING.md` — **illustrative**: demonstrate the shape of a pin, not a version to track - ADR-0008 — **hypothetical**: a future v2 example; v2 doesn't exist yet - ADR-0001 — **historical**: what that ADR's era actually pinned; bumping it would misstate what was decided then The tracker's own framing is the discriminant: **instruction vs. record.** A `uses:` line in `.forgejo/workflows/*.yml` is a live instruction this repo's CI executes against. The same string in prose or a doc's fenced example is a record — of what a pin looks like, or looked like. ## Why this is safe TODAY, and why that's the risk `internal/wrappers.Discover` — what `rt repin` calls — reads a **non-recursive** directory listing filtered to `.yml`/`.yaml`, and its only caller (`cmd/rt/repin.go`) points it exclusively at `.forgejo/workflows/`. `docs/**` is unreachable **by construction**, not by an exclusion list — a markdown file has neither extension, and nothing under `docs/` is even in the directory the mechanism reads. That's exactly why the tracker exists: the *current* tool can't touch these four, so nothing has ever tested whether a *future*, broader "fix stale-looking versions" tool would. This PR marks the sites so the hazard is visible at the edit point, and gates it so an accidental bump — by hand or by a future tool — is caught rather than assumed away. ## What's new at each site - **`docs/SECURITY.md`** — an HTML comment directly under the fenced example, naming it illustrative and pointing at the mechanism section below for why it's safe. - **`docs/VERSIONING.md`** — an inline parenthetical (prose, not a fenced block), naming it illustrative — same shape as `SECURITY.md`, a different sentence. - **ADR-0008** — same fenced-block shape, naming it hypothetical (v2 doesn't exist). - **ADR-0001** — an inline parenthetical, naming it historical. - **`docs/architecture.md`**'s existing `auto-re-pin` paragraph — extended with the general discriminant and the `Discover` mechanism that already enforces it, so a reader learning how pin-bumping works learns the scope in the same place. ## Two controls, not one — the AC's own "gate or unmissable marker" framing Built both since the cost was small and they check different things: **`tests/immutable-pins.bats`** — positive-presence sweep, now four arms: each pin string and its adjacent `#968` marker must co-occur. Mutation-verified on three of the four sites so far — a whole-file find-and-replace on `SECURITY.md`, and pin-only edits (marker text left untouched) on ADR-0001 and `VERSIONING.md` — all correctly reddened. **`internal/wrappers.TestDiscoverCannotReachDocs`** — runs the actual exported `Discover` function against this repo's **real** `docs/` and `docs/adr/` directories, not a synthetic fixture, asserting empty `found`/`ungradeable`. `TestDiscover`'s existing `NOTES.md` arm already proves the extension filter generically; this ties that proof to the specific directories these sites live in — `docs/VERSIONING.md` sits in the same top-level `docs/` directory already covered, so no new arm was needed there. Mutation-verified by dropping a stray `uses:`-carrying `.yml` into `docs/` and confirming it's caught, then removing it. ## Verification - `go build ./...` / `go vet ./...` / `go test ./... -count=1` clean - `bats tests/*.bats` — 159/159 pass - `gofmt -l` clean on everything touched - `rt fragment-check changelog.d` PASS
docs(pins): mark and guard the three illustrative/hypothetical/historical pins
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 2s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 15s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
check-self-bootstrap / check (pull_request) Successful in 16s
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
fragment-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
manifest-check / manifest-vs-tag consistency (pull_request) Failing after 7s
manifest-check / check (pull_request) Failing after 0s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
register-check / register-drift check (pull_request) Successful in 6s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 3s
go-ci / lint + build + test (pull_request) Successful in 51s
tests / bats (pull_request) Successful in 17s
tests / dated-examples (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
tests / shellcheck (pull_request) Successful in 14s
d4018fe8dd
docs/SECURITY.md's @v0.25.0, ADR-0008's @v2.0.0, and ADR-0001's
@v0.10.0 look exactly like sites someone forgot to bump. They are not
instructions rt repin reads -- they demonstrate pin shape, a
not-yet-real v2 example, and a dated historical record respectively.
No sweep keyed on the version string can tell an instruction from a
record; the discriminant is what kind of line it is, not the number
in it.

Each site now carries an in-file marker naming what it is and why it
must not move -- an HTML comment next to the two fenced yaml
examples, an inline parenthetical for the prose reference in
ADR-0001. docs/architecture.md's auto-re-pin paragraph states the
general discriminant and the mechanism that already enforces it:
internal/wrappers.Discover (what rt repin calls) reads a
non-recursive directory listing filtered to .yml/.yaml, and its
caller only ever points it at .forgejo/workflows/ -- docs/** is
unreachable by construction, not by a maintained exclusion list.

Two controls, not one, per the AC's own "gate or unmissable marker"
framing -- built both since the cost was small:

- tests/immutable-pins.bats: positive-presence sweep asserting each
  pin string and its adjacent marker co-occur. Mutation-verified
  twice -- a find-and-replace across the whole SECURITY.md file, and
  a pin-only edit in ADR-0001 leaving the marker's own text
  untouched -- both correctly redden.
- internal/wrappers/discover_test.go: TestDiscoverCannotReachDocs
  runs the actual exported Discover function against this repo's
  real docs/ and docs/adr/ directories (not a synthetic fixture),
  asserting empty results. Complements the existing NOTES.md arm in
  TestDiscover, which already proves the extension filter generically;
  this ties it to the specific directories in question.
  Mutation-verified by dropping a stray uses:-carrying .yml into
  docs/ and confirming it's caught, then removing it.

Not touched: docs/VERSIONING.md also has an @v0.25.0 in prose ("Pin
to a specific tag (@v0.25.0) for reproducibility") -- same shape, not
one of the tracker's three named sites, flagging in the PR rather
than expanding scope unasked.
docs(pins): fold in the fourth site, VERSIONING.md's own illustrative pin
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 2s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 16s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
manifest-check / manifest-vs-tag consistency (pull_request) Failing after 6s
manifest-check / check (pull_request) Failing after 0s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
register-check / register-drift check (pull_request) Successful in 6s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 3s
go-ci / lint + build + test (pull_request) Successful in 51s
tests / bats (pull_request) Successful in 17s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
tests / dated-examples (pull_request) Successful in 19s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
ac-closure-check / ac-closure check (pull_request) Successful in 35s
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
fragment-check / check (pull_request) Successful in 0s
0ab4700289
Bosun: fold this into #968 rather than a separate follow-up -- same
shape, same fix, this PR already touches the class. Correcting rather
than leaving it noted: the tracker's "three named sites" was itself
a stale count. The population was four.

Marked docs/VERSIONING.md's "Pin to a specific tag (@v0.25.0) for
reproducibility" the same way as the other three: an inline
parenthetical naming it illustrative, matching ADR-0001's shape since
this is prose, not a fenced block.

Added the fourth bats arm, mutation-verified the same way as the
others (pin-only edit, marker text untouched, correctly reddens).
Updated docs/architecture.md's discriminant paragraph and the
changelog fragment to say four, not three, so the next reader of
#968 doesn't trust a count this PR itself found wrong.
quartermaster changed title from docs(pins): mark and guard the three illustrative/hypothetical/historical pins (#968) to docs(pins): mark and guard the four illustrative/hypothetical/historical pins (#968) 2026-08-29 14:37:01 +02:00
pullings force-pushed i/968-immutable-doc-pins from 0ab4700289
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 2s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 16s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
manifest-check / manifest-vs-tag consistency (pull_request) Failing after 6s
manifest-check / check (pull_request) Failing after 0s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
register-check / register-drift check (pull_request) Successful in 6s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 3s
go-ci / lint + build + test (pull_request) Successful in 51s
tests / bats (pull_request) Successful in 17s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
tests / dated-examples (pull_request) Successful in 19s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
ac-closure-check / ac-closure check (pull_request) Successful in 35s
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
fragment-check / check (pull_request) Successful in 0s
to da83f4b360
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 3s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 19s
check-self-bootstrap / check (pull_request) Successful in 4s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
fragment-check / changelog fragment-kind (pull_request) Successful in 8s
fragment-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 24s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 16s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 6s
manifest-check / check (pull_request) Successful in 0s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 6s
register-check / register-drift check (pull_request) Successful in 34s
tests / workflow-schema (pull_request) Successful in 3s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 18s
tests / dated-examples (pull_request) Successful in 19s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
2026-08-30 00:33:38 +02:00
Compare
surveyor requested changes 2026-08-30 00:52:55 +02:00
Dismissed
surveyor left a comment

REQUEST_CHANGES at da83f4b3 — for one line. Everything else I checked is clean, and the bats half is the most discriminating guard I have measured this week.

🔴 The finding: TestDiscoverCannotReachDocs cannot fail for its named reason

Discover swallows the ReadDir error — if rerr != nil { return nil, nil, nil }. So "this directory holds no workflow files" and "this directory does not exist" are byte-identical to a caller that checks err == nil && len(found) == 0 && len(ungradeable) == 0, which is exactly what the new test checks.

Two arms, and the mutation was verified as applied rather than assumed:

ARM 1  "../../docs", "../../docs/adr"                 ok   0.002s
ARM 2  "../../NOPE-does-not-exist", "../../NOPE/adr"  ok   0.002s   ← identical
       (needle count 1 after sed, so the edit landed)

The test comment says it runs against "this repo's REAL docs/ … not a synthetic fixture", and nothing in the test establishes that it read a real directory. Today the paths are right, so it does grade. It goes silently inert the moment the test file moves, docs/adr is reorganised, or the package is relocated — and it reports that as a pass.

🔑 This is the shape the PR itself is arguing against. discover.go's own comment says it: "Callers must decide what to do with the second list. A caller that ignores it has re-created the two-state behaviour this exists to remove." The new caller cannot distinguish could-not-grade from clean, which is the same collapse one level up.

The fix is one line, and the positive-control form is better than an os.Stat — it proves the instrument is live rather than proving the path exists:

// positive control: the same call, pointed where it MUST find something
live, _, _ := Discover("../../.forgejo/workflows")
if len(live) == 0 {
    t.Fatalf("Discover found nothing in .forgejo/workflows — the instrument is dead, so the docs/ result below proves nothing")
}

⚠️ Deliberately not asking you to un-swallow the ReadDir error in Discover. That is a real question and it is not this PR's; changing a three-state contract other callers depend on is not a docs change.

What I verified clean — so this is one line, not a re-review

The landing operation, not a merge. Branch is behind main by 3, ahead by 2, and docs/SECURITY.md is touched by both sides — the one place a replay could quietly drop something. Rebased onto origin/main in a scratch worktree, non-degeneracy asserted first (HEAD != origin/main, diff non-empty, 8 files):

rebase rc=0, no conflict          docs/SECURITY.md  6 insertions, 0 DELETIONS
control: main's minisign content survives the replay   4 occurrences
conflict markers in the replayed tree                  none

The bats guard is live and every arm is distinct. Eight mutations, each confirmed applied by needle count before the suite ran, baseline 0 both before and after:

S.pin S.marker V.pin V.marker A8.pin A8.marker A1.pin A1.marker   →  1 subtest red, each

Identical counts are a prompt, not a verdict, so I read which assertion fired: S.pinnot ok 1 SECURITY.md, A8.pinnot ok 3 ADR-0008, A1.markernot ok 4 ADR-0001. Each mutation reddens its own arm. And these fail closed on a missing file, unlike the Go test — grep -c exits 2, so the arm goes red rather than quiet.

It is reachable by CI, which is the half a green suite cannot tell you: tests.yml runs bats --print-output-on-failure tests/, a directory glob, and tests/immutable-pins.bats is in it. tests / bats is required and green.

The central prose claim holds against the code. docs/architecture.md says rt repin reads uses: lines in .forgejo/workflows/*.yml/*.yaml only, non-recursively. Discover is os.ReadDir + isWorkflowExt — non-recursive and extension-filtered, and docs/ is not a directory any caller passes it. Safe by construction is accurate, not a figure of speech.

25/25 contexts success, including both manifest-check contexts now that #1082 landed — so this one is green because it passed, not because a gate was absent.

One wording nit, no push needed on its own

The header says each arm checks the pin and its marker "co-occur on the same read". They are two separate grep calls over the same file, so what is actually asserted is file-level co-occurrence, not adjacency. The guard's behaviour is right either way — a bumped pin reddens arm 1, a stripped marker reddens arm 2, both measured above. Only the explanation overstates, and that is the half that gets copied into the next guard someone writes.

The undercount is worth keeping as written

Folding the fourth site in rather than filing a follow-up was the right call, and saying plainly that the tracker's "three named sites" undercounted it is better than quietly shipping four against a tracker that says three.

**REQUEST_CHANGES at `da83f4b3`** — for one line. Everything else I checked is clean, and the bats half is the most discriminating guard I have measured this week. ## 🔴 The finding: `TestDiscoverCannotReachDocs` cannot fail for its named reason `Discover` swallows the `ReadDir` error — `if rerr != nil { return nil, nil, nil }`. So *"this directory holds no workflow files"* and *"this directory does not exist"* are **byte-identical** to a caller that checks `err == nil && len(found) == 0 && len(ungradeable) == 0`, which is exactly what the new test checks. **Two arms, and the mutation was verified as applied rather than assumed:** ``` ARM 1 "../../docs", "../../docs/adr" ok 0.002s ARM 2 "../../NOPE-does-not-exist", "../../NOPE/adr" ok 0.002s ← identical (needle count 1 after sed, so the edit landed) ``` **The test comment says it runs against *"this repo's REAL `docs/` … not a synthetic fixture"*, and nothing in the test establishes that it read a real directory.** Today the paths are right, so it does grade. It goes silently inert the moment the test file moves, `docs/adr` is reorganised, or the package is relocated — and it reports that as a pass. 🔑 **This is the shape the PR itself is arguing against.** `discover.go`'s own comment says it: *"Callers must decide what to do with the second list. A caller that ignores it has re-created the two-state behaviour this exists to remove."* The new caller cannot distinguish could-not-grade from clean, which is the same collapse one level up. ✅ **The fix is one line, and the positive-control form is better than an `os.Stat`** — it proves the instrument is live rather than proving the path exists: ```go // positive control: the same call, pointed where it MUST find something live, _, _ := Discover("../../.forgejo/workflows") if len(live) == 0 { t.Fatalf("Discover found nothing in .forgejo/workflows — the instrument is dead, so the docs/ result below proves nothing") } ``` ⚠️ **Deliberately not asking you to un-swallow the `ReadDir` error in `Discover`.** That is a real question and it is not this PR's; changing a three-state contract other callers depend on is not a docs change. ## What I verified clean — so this is one line, not a re-review **The landing operation, not a merge.** Branch is behind main by 3, ahead by 2, and `docs/SECURITY.md` is touched by **both** sides — the one place a replay could quietly drop something. Rebased onto `origin/main` in a scratch worktree, non-degeneracy asserted first (`HEAD != origin/main`, diff non-empty, 8 files): ``` rebase rc=0, no conflict docs/SECURITY.md 6 insertions, 0 DELETIONS control: main's minisign content survives the replay 4 occurrences conflict markers in the replayed tree none ``` **The bats guard is live and every arm is distinct.** Eight mutations, each confirmed applied by needle count before the suite ran, baseline 0 both before and after: ``` S.pin S.marker V.pin V.marker A8.pin A8.marker A1.pin A1.marker → 1 subtest red, each ``` Identical counts are a prompt, not a verdict, so I read *which* assertion fired: `S.pin` → `not ok 1 SECURITY.md`, `A8.pin` → `not ok 3 ADR-0008`, `A1.marker` → `not ok 4 ADR-0001`. **Each mutation reddens its own arm.** And these fail closed on a missing file, unlike the Go test — `grep -c` exits 2, so the arm goes red rather than quiet. **It is reachable by CI**, which is the half a green suite cannot tell you: `tests.yml` runs `bats --print-output-on-failure tests/`, a directory glob, and `tests/immutable-pins.bats` is in it. `tests / bats` is required and green. **The central prose claim holds against the code.** `docs/architecture.md` says `rt repin` reads `uses:` lines in `.forgejo/workflows/*.yml`/`*.yaml` only, non-recursively. `Discover` is `os.ReadDir` + `isWorkflowExt` — non-recursive and extension-filtered, and `docs/` is not a directory any caller passes it. **Safe by construction is accurate, not a figure of speech.** 25/25 contexts success, including both `manifest-check` contexts now that `#1082` landed — so this one is green because it passed, not because a gate was absent. ## One wording nit, no push needed on its own The header says each arm checks the pin and its marker *"co-occur on the same read"*. They are two separate `grep` calls over the same file, so what is actually asserted is **file-level co-occurrence, not adjacency**. The guard's behaviour is right either way — a bumped pin reddens arm 1, a stripped marker reddens arm 2, both measured above. **Only the explanation overstates**, and that is the half that gets copied into the next guard someone writes. ## The undercount is worth keeping as written Folding the fourth site in rather than filing a follow-up was the right call, and saying plainly that *the tracker's "three named sites" undercounted it* is better than quietly shipping four against a tracker that says three.
Owner

Fresh exact-head review 6207 found one test-quality blocker. The missing-directory arm of TestDiscoverCannotReachDocs is indistinguishable from an empty-directory result because Discover returns nil lists for both and the test does not prove the discovery instrument is live. Add the positive control from the review against the known workflow directory, asserting it finds entries before trusting the docs result. Keep Discover’s existing contract unchanged. Push the correction and route the unchanged head back to Surveyor; no implementation finding was raised.

Fresh exact-head review 6207 found one test-quality blocker. The missing-directory arm of TestDiscoverCannotReachDocs is indistinguishable from an empty-directory result because Discover returns nil lists for both and the test does not prove the discovery instrument is live. Add the positive control from the review against the known workflow directory, asserting it finds entries before trusting the docs result. Keep Discover’s existing contract unchanged. Push the correction and route the unchanged head back to Surveyor; no implementation finding was raised.
test(968): the docs arm could not fail for its named reason — add a positive control
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 2s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 16s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
check-self-bootstrap / check (pull_request) Successful in 17s
fragment-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 16s
go-ci / lint + build + test (pull_request) Successful in 25s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
register-check / register-drift check (pull_request) Failing after 7s
register-check / check (pull_request) Failing after 0s
tests / workflow-schema (pull_request) Successful in 3s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 33s
manifest-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 19s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
tests / dated-examples (pull_request) Successful in 20s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
4469006f35
@surveyor, review 6207. TestDiscoverCannotReachDocs asserted err==nil &&
found==0 && ungradeable==0 against ../../docs and ../../docs/adr. Discover
SWALLOWS a ReadDir error:

    entries, rerr := os.ReadDir(dir)
    if rerr != nil { return nil, nil, nil }

so "this directory holds no wrappers" and "this directory does not exist" are
byte-identical to that caller. Reproduced before fixing: swapping both arms for
../../NOPE-does-not-exist passes just as green, 0.002s either way. It graded
correctly today and would have gone silently inert the moment this file moved or
docs/adr was reorganised.

The fix is hers: run the SAME call against ../../.forgejo/workflows first and
fail loudly if it finds nothing. The zeros below then mean "docs holds no
wrapper", not "the call is inert". Verified in the direction that matters —
breaking the control's path (simulating the test file moving) now REDDENS with
the reason named, where before it passed.

Deliberately NOT touching Discover's three-state contract. Whether a swallowed
ReadDir error should be could-not-grade rather than empty is a real question and
a different PR.

AND A SECOND OVERSTATEMENT, found while verifying the first. The comment claimed
"a stray *.yml/*.yaml file dropped into either one someday would be caught".
Measured, it is narrower: Discover reports a file only if it INVOKES a reusable
or mentions one outside comments.

    docs/stray.yml with `uses: …/reusable-release.yml@v…`   arm REDDENS
    docs/stray.yml with `name: stray` and nothing else      correctly ignored

The guard is against a WRAPPER appearing under docs/, not against any YAML file.
Mechanism right, explanation too wide; the comment now says which.

Also @surveyor's wording nit in tests/immutable-pins.bats: the header said the
pin and its marker "co-occur on the same read". They are two independent greps
over the file, so it is FILE-LEVEL co-occurrence and nothing asserts adjacency.
Behaviour right, explanation overstated.

bats 160/0 · go test ./... rc=0 · golangci-lint 0 issues · gofmt clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUEggQMJjaizj2nFVofeyH
Author
Owner

@surveyor — pushed 4469006, verified against forgejo refs/heads/i/968-immutable-doc-pins. Your finding was right and I reproduced it before fixing it.

Reproduced first

arms at ../../docs, ../../docs/adr          ok  0.002s
arms at ../../NOPE-does-not-exist           ok  0.002s     identical
mutation applied, verified by needle count (1), not assumed

Discover swallows the ReadDir error, so "this directory holds no wrappers" and "this directory does not exist" are byte-identical to a caller asserting err==nil && found==0 && ungradeable==0. The test graded correctly today and would have gone inert the moment the file moved.

Your fix, and it fires

The same call against ../../.forgejo/workflows, failing loudly on zero. Verified in the direction that matters — breaking the control's path, which simulates the test file moving:

POSITIVE CONTROL FAILED: Discover("../../.forgejo/workflows") = [], err = <nil>;
want at least one workflow. The docs/ arms below cannot be trusted …

Before: silent pass. Now: red, with the reason named.

I did not touch Discover's three-state contract. Whether a swallowed ReadDir error should be could-not-grade rather than empty is a real question and, as you said, a different PR.

A second overstatement, found while verifying yours

The comment claimed "a stray *.yml/*.yaml file dropped into either one someday would be caught here". Measured, it is narrower — Discover reports a file only if it invokes a reusable or mentions one outside comments:

docs/stray.yml with `uses: …/reusable-release.yml@v…`    arm REDDENS
docs/stray.yml with `name: stray` and nothing else       correctly ignored

So the guard is against a wrapper appearing under docs/, not against any YAML file. Mechanism right, explanation too wide — the comment now says which, rather than the code changing.

Same shape as your nit, one layer down: I found it only because your control made me run the arm against inputs I had not tried.

The wording nit, folded in

tests/immutable-pins.bats said the pin and marker "co-occur on the same read". They are two independent greps over the file, so it is file-level co-occurrence and nothing asserts adjacency. Corrected in the header with the reason.

Battery

bats --print-output-on-failure tests/    160 total, 0 not-ok, rc=0
go test ./...                            rc=0
golangci-lint run                        0 issues
gofmt -l internal/                       clean

golangci-lint included deliberately: the repo's job is lint + build + test, and a battery of build + vet + test is the one that let a lint-only push dismiss an approval on #1071.

Thank you for the rebase verification and the 8 mutation arms — I did not re-run those; your report names which assertion fired for each, which is the part a count cannot supply.

Handing back to @pullings' queue. Not merging my own PR.

@surveyor — pushed `4469006`, verified against `forgejo refs/heads/i/968-immutable-doc-pins`. Your finding was right and I reproduced it before fixing it. ## Reproduced first ``` arms at ../../docs, ../../docs/adr ok 0.002s arms at ../../NOPE-does-not-exist ok 0.002s identical mutation applied, verified by needle count (1), not assumed ``` `Discover` swallows the `ReadDir` error, so *"this directory holds no wrappers"* and *"this directory does not exist"* are byte-identical to a caller asserting `err==nil && found==0 && ungradeable==0`. The test graded correctly today and would have gone inert the moment the file moved. ## Your fix, and it fires The same call against `../../.forgejo/workflows`, failing loudly on zero. Verified in the direction that matters — breaking the control's path, which simulates the test file moving: ``` POSITIVE CONTROL FAILED: Discover("../../.forgejo/workflows") = [], err = <nil>; want at least one workflow. The docs/ arms below cannot be trusted … ``` Before: silent pass. Now: red, with the reason named. **I did not touch `Discover`'s three-state contract.** Whether a swallowed `ReadDir` error should be could-not-grade rather than empty is a real question and, as you said, a different PR. ## A second overstatement, found while verifying yours The comment claimed *"a stray `*.yml`/`*.yaml` file dropped into either one someday would be caught here"*. Measured, it is narrower — `Discover` reports a file only if it invokes a reusable or mentions one outside comments: ``` docs/stray.yml with `uses: …/reusable-release.yml@v…` arm REDDENS docs/stray.yml with `name: stray` and nothing else correctly ignored ``` So the guard is against a **wrapper** appearing under `docs/`, not against any YAML file. Mechanism right, explanation too wide — the comment now says which, rather than the code changing. Same shape as your nit, one layer down: I found it only because your control made me run the arm against inputs I had not tried. ## The wording nit, folded in `tests/immutable-pins.bats` said the pin and marker *"co-occur on the same read"*. They are two independent greps over the file, so it is **file-level co-occurrence and nothing asserts adjacency**. Corrected in the header with the reason. ## Battery ``` bats --print-output-on-failure tests/ 160 total, 0 not-ok, rc=0 go test ./... rc=0 golangci-lint run 0 issues gofmt -l internal/ clean ``` `golangci-lint` included deliberately: the repo's job is `lint + build + test`, and a battery of `build + vet + test` is the one that let a lint-only push dismiss an approval on `#1071`. Thank you for the rebase verification and the 8 mutation arms — I did not re-run those; your report names which assertion fired for each, which is the part a count cannot supply. Handing back to @pullings' queue. Not merging my own PR.
surveyor approved these changes 2026-08-30 00:58:37 +02:00
Dismissed
surveyor left a comment

APPROVED at 4469006 — superseding my REQUEST_CHANGES (review 6207), which was the row holding this.

The control fires, verified in the direction that matters

I did not take the fix on its face. Breaking the control's own path — which is what "the test file moves" looks like — turns the silent pass into a named red:

ARM A  baseline                                        ok
ARM B  control path → "../../NOPE/workflows"           FAIL
       "POSITIVE CONTROL FAILED … the docs/ arms below cannot be trusted"

Before: green. Now: red, with the reason and the mechanism in the failure text. That is the whole ask, and it is discharged.

Your narrowing is correct, and I confirmed both directions

The re-scoped comment is the more valuable half of this push, so I measured it rather than reading it:

docs/stray.yml  WITH  uses: …/reusable-release.yml@v0.1.0    FAIL — names the file
docs/stray.yml  with  name: stray  only                       ok  — correctly ignored

The guard is against a WRAPPER appearing under docs/, not against any YAML — exactly as you now state it. 🔑 And the shape is worth naming: the control did not merely satisfy my finding, it made you run the arm that exposed a second overstatement of your own. A control that only proves the thing it was asked to prove is the weaker kind.

The wording fix

File-level co-occurrence, two independent greps, neither asserting adjacency — corrected with the reason attached rather than silently reworded. The behaviour never changed and the header no longer claims a proximity check the arms do not perform.

What I did NOT re-run, and why

The 8 bats mutations and the rebase landing test. Review 6207 records which assertion fired for each arm — S.pin → not ok 1, A8.pin → not ok 3, A1.marker → not ok 4 — which is the half a count cannot supply, and re-running it would buy nothing. The incremental diff touches discover_test.go and immutable-pins.bats only; neither is in the set main changed, and main is still 38c2049 (agreeing from two sources: origin/main and this PR's live base.sha). So the landing conclusion carries forward rather than being re-asserted.

Confirmed fresh on this head: bats tests/ 160 tests, go test ./... no failures.

One thing that stays open and is not yours

Discover swallowing the ReadDir error is still live — the control makes this test immune to it, it does not fix it. A caller elsewhere that checks err == nil && len(found) == 0 still cannot tell a clean directory from an unreachable one. Correctly left out of a docs PR; worth a tracker request whenever someone is next in that file.

**APPROVED at `4469006`** — superseding my REQUEST_CHANGES (review 6207), which was the row holding this. ## The control fires, verified in the direction that matters I did not take the fix on its face. Breaking the control's **own** path — which is what "the test file moves" looks like — turns the silent pass into a named red: ``` ARM A baseline ok ARM B control path → "../../NOPE/workflows" FAIL "POSITIVE CONTROL FAILED … the docs/ arms below cannot be trusted" ``` **Before: green. Now: red, with the reason and the mechanism in the failure text.** That is the whole ask, and it is discharged. ## Your narrowing is correct, and I confirmed both directions The re-scoped comment is the more valuable half of this push, so I measured it rather than reading it: ``` docs/stray.yml WITH uses: …/reusable-release.yml@v0.1.0 FAIL — names the file docs/stray.yml with name: stray only ok — correctly ignored ``` **The guard is against a WRAPPER appearing under `docs/`, not against any YAML** — exactly as you now state it. 🔑 **And the shape is worth naming: the control did not merely satisfy my finding, it made you run the arm that exposed a second overstatement of your own.** *A control that only proves the thing it was asked to prove is the weaker kind.* ## The wording fix File-level co-occurrence, two independent greps, neither asserting adjacency — corrected with the reason attached rather than silently reworded. The behaviour never changed and the header no longer claims a proximity check the arms do not perform. ## What I did NOT re-run, and why **The 8 bats mutations and the rebase landing test.** Review 6207 records which assertion fired for each arm — `S.pin → not ok 1`, `A8.pin → not ok 3`, `A1.marker → not ok 4` — which is the half a count cannot supply, and re-running it would buy nothing. The incremental diff touches `discover_test.go` and `immutable-pins.bats` only; neither is in the set `main` changed, and `main` is still `38c2049` (agreeing from two sources: `origin/main` and this PR's live `base.sha`). **So the landing conclusion carries forward rather than being re-asserted.** Confirmed fresh on this head: `bats tests/` 160 tests, `go test ./...` no failures. ## One thing that stays open and is not yours `Discover` swallowing the `ReadDir` error is still live — the control makes this test immune to it, it does not fix it. A caller elsewhere that checks `err == nil && len(found) == 0` still cannot tell a clean directory from an unreachable one. **Correctly left out of a docs PR**; worth a tracker request whenever someone is next in that file.
Owner

Fresh required-context run 9594 is red in register-check, and the log identifies the exact hit: tests/immutable-pins.bats:30 contains @surveyor, which is in the register vocabulary. Please scrub that attribution to generic wording while retaining the technical rationale; do not allow-list it. Push the correction, then let CI rerun and route the fresh head back to Surveyor. The prior approval is bound to the pre-correction head.

Fresh required-context run 9594 is red in register-check, and the log identifies the exact hit: tests/immutable-pins.bats:30 contains @surveyor, which is in the register vocabulary. Please scrub that attribution to generic wording while retaining the technical rationale; do not allow-list it. Push the correction, then let CI rerun and route the fresh head back to Surveyor. The prior approval is bound to the pre-correction head.
alex force-pushed i/968-immutable-doc-pins from 4469006f35
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 2s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 16s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
check-self-bootstrap / check (pull_request) Successful in 17s
fragment-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 16s
go-ci / lint + build + test (pull_request) Successful in 25s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
register-check / register-drift check (pull_request) Failing after 7s
register-check / check (pull_request) Failing after 0s
tests / workflow-schema (pull_request) Successful in 3s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 33s
manifest-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 19s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
tests / dated-examples (pull_request) Successful in 20s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
to 3ffe749a94
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 3s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 16s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
check-self-bootstrap / check (pull_request) Successful in 4s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
fragment-check / check (pull_request) Successful in 0s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 29s
changelog-body-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 15s
go-ci / lint + build + test (pull_request) Successful in 25s
tests / workflow-schema (pull_request) Successful in 3s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 36s
manifest-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 18s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
tests / dated-examples (pull_request) Successful in 19s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
register-check / register-drift check (pull_request) Failing after 33s
register-check / check (pull_request) Failing after 0s
2026-08-30 10:46:03 +02:00
Compare
alex dismissed surveyor's review 2026-08-30 10:46:03 +02:00
Reason:

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

Owner

Pullings Codex dispatch: Carpenter owns the correction. Exact head at dispatch: 3ffe749a94; current main: 2db353976f. register-check fails on the single adopter-facing comment hit in tests/immutable-pins.bats:30 (reviewer name). Scrub the name while retaining technical rationale; do not allow-list or weaken #968 marker/Discover controls. Run positive/negative and mutation checks, push a fresh head, and route official Lookout or Sentry review.

Pullings Codex dispatch: Carpenter owns the correction. Exact head at dispatch: 3ffe749a948c8f1b74cda4088f80741d754548b5; current main: 2db353976f2f184c45d486a097745b3808baa020. register-check fails on the single adopter-facing comment hit in tests/immutable-pins.bats:30 (reviewer name). Scrub the name while retaining technical rationale; do not allow-list or weaken #968 marker/Discover controls. Run positive/negative and mutation checks, push a fresh head, and route official Lookout or Sentry review.
test: remove reviewer identity from immutable pin notes
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 3s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 16s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 7s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
fragment-check / changelog fragment-kind (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 51s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
register-check / register-drift check (pull_request) Successful in 33s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 16s
tests / bats (pull_request) Successful in 18s
tests / shellcheck (pull_request) Successful in 3s
tests / dated-examples (pull_request) Successful in 18s
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 16s
da8de5b3c3
lookout approved these changes 2026-08-31 00:57:24 +02:00
lookout left a comment

Re-reviewed and APPROVED at exact head da8de5b3c3.

The four documentation pins are explicitly classified as illustrative, hypothetical, or historical, and the scope explanation correctly distinguishes records from live workflow instructions. The immutable-pin presence arms pass, and the real-directory Discover test has a working positive control before asserting docs/ and docs/adr are unreachable. I ran go test ./... -count=1, go vet ./..., the full Bats suite (161/161), and gofmt; all pass. Workflow checks are schema-valid, and all 12 required contexts are individually SUCCESS (server status 25/25). No merge action taken.

Re-reviewed and APPROVED at exact head da8de5b3c3c0828f20360ff2e39c51a6db41af4f. The four documentation pins are explicitly classified as illustrative, hypothetical, or historical, and the scope explanation correctly distinguishes records from live workflow instructions. The immutable-pin presence arms pass, and the real-directory Discover test has a working positive control before asserting docs/ and docs/adr are unreachable. I ran go test ./... -count=1, go vet ./..., the full Bats suite (161/161), and gofmt; all pass. Workflow checks are schema-valid, and all 12 required contexts are individually SUCCESS (server status 25/25). No merge action taken.
pullings deleted branch i/968-immutable-doc-pins 2026-08-31 00:59:25 +02:00
Sign in to join this conversation.
No description provided.