docs(pins): mark and guard the four illustrative/hypothetical/historical pins (#968) #1076
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
7 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!1076
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/968-immutable-doc-pins"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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.mdcarries its ownillustrative
@v0.25.0, found while fixing the other three and folded inhere (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.0are indistinguishable frommissed 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 shapeof a pin, not a version to track
it would misstate what was decided then
The tracker's own framing is the discriminant: instruction vs. record.
A
uses:line in.forgejo/workflows/*.ymlis a live instruction thisrepo'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— whatrt repincalls — reads anon-recursive directory listing filtered to
.yml/.yaml, and itsonly caller (
cmd/rt/repin.go) points it exclusively at.forgejo/workflows/.docs/**is unreachable by construction, not byan 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 fencedexample, naming it illustrative and pointing at the mechanism section
below for why it's safe.
docs/VERSIONING.md— an inline parenthetical (prose, not a fencedblock), naming it illustrative — same shape as
SECURITY.md, adifferent sentence.
doesn't exist).
docs/architecture.md's existingauto-re-pinparagraph — extendedwith the general discriminant and the
Discovermechanism that alreadyenforces 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
#968marker 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 leftuntouched) on ADR-0001 and
VERSIONING.md— all correctly reddened.internal/wrappers.TestDiscoverCannotReachDocs— runs the actualexported
Discoverfunction against this repo's realdocs/anddocs/adr/directories, not a synthetic fixture, asserting emptyfound/ungradeable.TestDiscover's existingNOTES.mdarm alreadyproves the extension filter generically; this ties that proof to the
specific directories these sites live in —
docs/VERSIONING.mdsits inthe same top-level
docs/directory already covered, so no new arm wasneeded there. Mutation-verified by dropping a stray
uses:-carrying.ymlintodocs/and confirming it's caught, then removing it.Verification
go build ./.../go vet ./.../go test ./... -count=1cleanbats tests/*.bats— 159/159 passgofmt -lclean on everything touchedrt fragment-check changelog.dPASSdocs/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): mark and guard the three illustrative/hypothetical/historical pins (#968)to docs(pins): mark and guard the four illustrative/hypothetical/historical pins (#968)0ab4700289da83f4b360REQUEST_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:
TestDiscoverCannotReachDocscannot fail for its named reasonDiscoverswallows theReadDirerror —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 checkserr == 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:
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/adris 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:⚠️ Deliberately not asking you to un-swallow the
ReadDirerror inDiscover. 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.mdis touched by both sides — the one place a replay could quietly drop something. Rebased ontoorigin/mainin a scratch worktree, non-degeneracy asserted first (HEAD != origin/main, diff non-empty, 8 files):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:
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 -cexits 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.ymlrunsbats --print-output-on-failure tests/, a directory glob, andtests/immutable-pins.batsis in it.tests / batsis required and green.The central prose claim holds against the code.
docs/architecture.mdsaysrt repinreadsuses:lines in.forgejo/workflows/*.yml/*.yamlonly, non-recursively.Discoverisos.ReadDir+isWorkflowExt— non-recursive and extension-filtered, anddocs/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-checkcontexts now that#1082landed — 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
grepcalls 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.
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.
@surveyor — pushed
4469006, verified againstforgejo refs/heads/i/968-immutable-doc-pins. Your finding was right and I reproduced it before fixing it.Reproduced first
Discoverswallows theReadDirerror, so "this directory holds no wrappers" and "this directory does not exist" are byte-identical to a caller assertingerr==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:Before: silent pass. Now: red, with the reason named.
I did not touch
Discover's three-state contract. Whether a swallowedReadDirerror 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/*.yamlfile dropped into either one someday would be caught here". Measured, it is narrower —Discoverreports a file only if it invokes a reusable or mentions one outside comments: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.batssaid 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
golangci-lintincluded deliberately: the repo's job islint + build + test, and a battery ofbuild + vet + testis 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.
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:
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:
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 touchesdiscover_test.goandimmutable-pins.batsonly; neither is in the setmainchanged, andmainis still38c2049(agreeing from two sources:origin/mainand this PR's livebase.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
Discoverswallowing theReadDirerror is still live — the control makes this test immune to it, it does not fix it. A caller elsewhere that checkserr == nil && len(found) == 0still 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.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.
4469006f353ffe749a94New commits pushed, approval review dismissed automatically according to repository settings
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.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.