fix(release): unwind prior digest-pin commits so a rebuild lands at distance 1 #1071

Merged
bosun merged 6 commits from i/980-pin-walk into main 2026-08-29 20:59:42 +02:00
Owner

Closes the root cause of #980's remaining half. Does NOT run anything against a real tag@bosun approved the code change and reserved the v0.56.1 run for the operator.

What was actually wrong

The bake commits its digest pin on top of the checked-out tag, then force-moves the tag onto it:

:503  SOURCE_SHA=$(git rev-parse HEAD)          the tag's current position
:569  git commit -m "pin action image for $TAG"  on top of it
:590  --force-with-lease=…  HEAD:refs/tags/$TAG

There is no de-stacking anywhere in the file. So every re-run adds one commit, and the gate requires the tag to sit exactly 1 above last_released_sha. A tag rebuilt twice can never pass again.

That is not a hypothetical — it is v0.56.1, which reached distance 3 across three runs and fails manifest-check on every PR:

c43be07a  anchor (on main)
 489e851  action.yml  00000000 -> f677304b   run 1 — failed to sign
 c4bb2e9              f677304b -> 44dd0f56   run 2
 44c4241              44dd0f56 -> 202b6f9b   run 3 — the assets that published

🔑 A rebuild today would check out 44c4241 at distance 3 and commit → distance 4. The mechanism cannot repair the specimen; it deepens it.

The change

UnwindDigestPins reports how many pin commits sit above the anchor, so the bake can reset past them and land at distance 1.

Two properties are deliberate rather than incidental:

  • It SHARES the subject/file-set predicate with TagPinsDigest. @bosun's requirement, and it is load-bearing: if the walk used a looser test than the gate, they could disagree about where the anchor is. Dropping the file-set clause reddens this suite AND the gate's own — that is the mutation evidence, not an assertion.
  • Its input is anchor..tag, tip-first, so the walk is bounded BY CONSTRUCTION and cannot reset past the anchor. The guard is the input shape rather than a check inside the loop, which makes "walked too far" unrepresentable instead of caught.

Arms — six, mutation-verified in isolation

n=0   fresh cut, nothing to unwind
n=1   one pin, unwinds once
n=3   THE SPECIMEN — walks all three
subject matches but FILE SET does not  -> NOT a pin, never reset over
walk stops at a real commit            -> does not jump it to reach a pin behind
CONTROL: empty tag                     -> could-not-grade, not zero
M1  cap the walk at 1 (reset-to-parent)  -> reddens n=3 ONLY
M2  drop the file-set clause             -> reddens the impostor arm AND the gate suite

⚠️ The n=3 arm is the one that matters and it is @bosun's catch. A single parent-reset passes n=1 and leaves n=3 at distance 3 — the fixture an implementer naturally builds is the one where the bug hides. I had proposed exactly that reset before he caught it.

What this PR does NOT do

  • It does not wire the unwind into goreleaser.yml. The pure function and its arms land first; the call site touches an irreversible force-push line and is worth its own review.
  • It does not touch any real tag. No force-push, no re-tag, no dispatch.
  • It does not repair v0.56.1. That needs the wiring plus an operator decision to run it.
  • It does not address the pre-existing gofmt finding in cmd/rt/main_test.go, which is on main and unrelated.

Local: go build 0 · go test ./... 0 · fragment-check 0, no warnings · gofmt -l internal/manifest/ empty.

Closes the root cause of #980's remaining half. **Does NOT run anything against a real tag** — @bosun approved the code change and reserved the v0.56.1 run for the operator. ## What was actually wrong The bake commits its digest pin **on top of the checked-out tag**, then force-moves the tag onto it: ``` :503 SOURCE_SHA=$(git rev-parse HEAD) the tag's current position :569 git commit -m "pin action image for $TAG" on top of it :590 --force-with-lease=… HEAD:refs/tags/$TAG ``` **There is no de-stacking anywhere in the file.** So every re-run adds one commit, and the gate requires the tag to sit exactly 1 above `last_released_sha`. **A tag rebuilt twice can never pass again.** That is not a hypothetical — it is `v0.56.1`, which reached distance 3 across three runs and fails `manifest-check` on every PR: ``` c43be07a anchor (on main) 489e851 action.yml 00000000 -> f677304b run 1 — failed to sign c4bb2e9 f677304b -> 44dd0f56 run 2 44c4241 44dd0f56 -> 202b6f9b run 3 — the assets that published ``` 🔑 **A rebuild today would check out `44c4241` at distance 3 and commit → distance 4. The mechanism cannot repair the specimen; it deepens it.** ## The change `UnwindDigestPins` reports how many pin commits sit above the anchor, so the bake can reset past them and land at distance 1. **Two properties are deliberate rather than incidental:** - **It SHARES the subject/file-set predicate with `TagPinsDigest`.** @bosun's requirement, and it is load-bearing: if the walk used a looser test than the gate, they could disagree about where the anchor is. **Dropping the file-set clause reddens this suite AND the gate's own** — that is the mutation evidence, not an assertion. - **Its input is `anchor..tag`, tip-first, so the walk is bounded BY CONSTRUCTION** and cannot reset past the anchor. The guard is the input shape rather than a check inside the loop, which makes "walked too far" unrepresentable instead of caught. ## Arms — six, mutation-verified in isolation ``` n=0 fresh cut, nothing to unwind n=1 one pin, unwinds once n=3 THE SPECIMEN — walks all three subject matches but FILE SET does not -> NOT a pin, never reset over walk stops at a real commit -> does not jump it to reach a pin behind CONTROL: empty tag -> could-not-grade, not zero ``` ``` M1 cap the walk at 1 (reset-to-parent) -> reddens n=3 ONLY M2 drop the file-set clause -> reddens the impostor arm AND the gate suite ``` ⚠️ **The n=3 arm is the one that matters and it is @bosun's catch.** A single parent-reset passes n=1 and leaves n=3 at distance 3 — **the fixture an implementer naturally builds is the one where the bug hides.** I had proposed exactly that reset before he caught it. ## What this PR does NOT do - **It does not wire the unwind into `goreleaser.yml`.** The pure function and its arms land first; the call site touches an irreversible force-push line and is worth its own review. - **It does not touch any real tag.** No force-push, no re-tag, no dispatch. - **It does not repair v0.56.1.** That needs the wiring plus an operator decision to run it. - It does not address the pre-existing `gofmt` finding in `cmd/rt/main_test.go`, which is on `main` and unrelated. *Local: `go build` 0 · `go test ./...` 0 · `fragment-check` 0, no warnings · `gofmt -l internal/manifest/` empty.*
fix(release): unwind prior digest-pin commits so a rebuild lands at distance 1
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) Failing after 4s
check-self-bootstrap / check (pull_request) Successful in 15s
fragment-check / changelog fragment-kind (pull_request) Failing after 6s
fragment-check / check (pull_request) Failing after 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 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 49s
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 18s
513b932bc9
A rebuild committed its pin on top of the checked-out tag and then force-moved
the tag onto it, so each re-run grew the distance from last_released_sha by one.
The gate requires exactly 1, so a tag that was rebuilt twice can never pass
again. v0.56.1 reached distance 3 across three runs and fails manifest-check on
every PR as a result.

UnwindDigestPins reports how many pin commits sit above the anchor so the bake
can reset past them before committing its own.

Two properties are deliberate rather than incidental:

  - it SHARES the subject/file-set predicate with TagPinsDigest, so the walk and
    the gate cannot disagree about what a pin commit is. Dropping the file-set
    clause reddens this suite AND the gate's own.
  - its input is `anchor..tag`, tip-first, so the walk is bounded BY
    CONSTRUCTION and cannot reset past the anchor. The guard is the input shape,
    not a check inside the loop.

Six arms, mutation-verified in isolation. The n=3 arm is the specimen and is
the one that matters: a single parent-reset passes n=1 and leaves n=3 at
distance 3, so the fixture an implementer naturally builds is the one where the
bug hides. Capping the walk at 1 reddens that arm and only that arm.

Refs #980
fix(changelog): split the 38-word sentence check 7 refused
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 4s
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 4s
check-self-bootstrap / check (pull_request) Successful in 5s
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 31s
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 27s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 4s
manifest-check / manifest-vs-tag consistency (pull_request) Failing after 31s
manifest-check / check (pull_request) Failing after 0s
tests / bats (pull_request) Successful in 17s
tests / shellcheck (pull_request) Successful in 3s
tests / dated-examples (pull_request) Successful in 19s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 7s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
4c3795e733
fragment-check grades sentence length at 30 words. My local run reported clean
because I graded it on its PROSE through a grep rather than on its exit code —
the gate said rc=1 the whole time.
feat(rt): digest-pin-unwind, so the bake never re-implements the predicate
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Has been cancelled
ac-closure-check / check (pull_request) Has been cancelled
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Has been cancelled
ac-closure-check / ac-closure check (pull_request) Has been cancelled
changelog-body-check / check (pull_request) Has been cancelled
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Has been cancelled
changelog-body-check / changelog body Cold-Read linter (pull_request) Has been cancelled
check-self-bootstrap / check (pull_request) Has been cancelled
fragment-check / check (pull_request) Has been cancelled
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Has been cancelled
fragment-check / changelog fragment-kind (pull_request) Has been cancelled
go-ci / lint + build + test (pull_request) Has been cancelled
manifest-check / check (pull_request) Has been cancelled
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Has been cancelled
manifest-check / manifest-vs-tag consistency (pull_request) Has been cancelled
register-check / check (pull_request) Has been cancelled
register-check / toolkit-self gate (PR's own rt) (pull_request) Has been cancelled
register-check / register-drift check (pull_request) Has been cancelled
tests / workflow-schema (pull_request) Has been cancelled
tests / bats (pull_request) Has been cancelled
tests / dated-examples (pull_request) Has been cancelled
tests / shellcheck (pull_request) Has been cancelled
workflow-parse-check / check (pull_request) Has been cancelled
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Has been cancelled
workflow-parse-check / workflow parse and schema (pull_request) Has been cancelled
30007fabc6
The bake needs to know how many pin commits to reset past. Computing that in
bash would be a SECOND definition of what a pin commit is, free to drift from
the gate's — which is the disagreement this change exists to prevent.

rt digest-pin-unwind gathers anchor..tip tip-first and calls the shared walk.
The number goes to stdout for $(…) capture; the reason goes to stderr, so a
caller reading the number is never handed prose.

rc=2 for could-not-grade, never rc=1: 'how many pins are stacked' has no
failing answer, only an unknown one.

Exercised end-to-end against a real repository in the v0.56.1 shape:
distance 3 -> unwind 3 -> bake once -> distance 1. No-pin control returns 0.

Refs #980
chore(rt): register digest-pin-unwind as toolkit-self, refresh help snapshot
Some checks failed
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 11s
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
check-self-bootstrap / check (pull_request) Successful in 5s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 18s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
go-ci / lint + build + test (pull_request) Failing after 9s
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
fragment-check / changelog fragment-kind (pull_request) Successful in 31s
fragment-check / check (pull_request) Successful in 0s
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 17s
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
tests / dated-examples (pull_request) Successful in 19s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
444ddad4c2
Two repo gates caught what I omitted, which is them working:

  TestSubcommandsMatchC5Contract  a new verb must be documented in the C5
                                  table or excluded WITH A REASON
  TestHelpSnapshot                --help drifted from its snapshot

digest-pin-unwind is the same class as bake-digest: the BAKE invokes it, never
an adopter, so it is excluded rather than documented as adopter surface.

The 6 unrelated-looking lines in c5ExcludedVerbs are gofmt re-columning the map
because the new key is longer. A consequence of the change, not a bundled fix.

Refs #980
feat(bake): unwind prior pins and assert the gate BEFORE the force-push
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 11s
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 21s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 8s
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 6s
manifest-check / check (pull_request) Failing after 0s
go-ci / lint + build + test (pull_request) Failing after 22s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
tests / workflow-schema (pull_request) Successful in 3s
tests / bats (pull_request) Successful in 18s
tests / dated-examples (pull_request) Successful in 3s
register-check / register-drift check (pull_request) Successful in 32s
tests / shellcheck (pull_request) Successful in 3s
register-check / check (pull_request) Successful in 0s
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 22s
workflow-parse-check / check (pull_request) Successful in 0s
c3b65e9f4d
Two call-site changes on the irreversible block.

UNWIND: the bake reset past stacked pin commits before baking, so a rebuild
lands at distance 1 instead of adding a fourth.

ASSERT BEFORE THE PUSH: rt digest-pin-verify runs the gate's own predicate
against the commit the tag WOULD point at. After the push it could only report
damage; here it prevents the commit reaching the remote.

Both use an rt built from the PIPELINE checkout, not the image-extracted one.
That binary is docker cp'd out of an image built from SOURCE, so on a rebuild
it is the TAG's rt -- which has no digest-pin-unwind at all. Using it would
fail on exactly the tags this feature exists to rebuild. Noted at the callsite:
the two-checkout split fixed scripts/ and left the binary, which is #980
surviving in Go. Filed separately, not widened here.

The anchor is main's manifest, not the tag's: the gate reads the default
branch, so grading against the tag's copy would answer a different question.

Refs #980
quartermaster approved these changes 2026-08-29 14:20:32 +02:00
Dismissed
quartermaster left a comment

Approved at head c3b65e9f4d699eb5df1aa318cd18c4b353d9eff3, state=open merged=false read immediately before submitting.

Reviewed by running it, not only by reading it. Five checks below, three of them on things that would have been invisible in the diff.

The two-checkout finding is right, and it is the load-bearing one

$RUNNER_TEMP/rt is docker cp'd out of the image, the image is built from the source checkout, so on a rebuild it is the tag's binary. #980's root cause surviving in Go after the split closed it for scripts/.

And the consequence you drew is what makes the wiring correct rather than merely tidy: an old tag has no digest-pin-unwind at all, so using the image's rt would fail on precisely the tags this exists to rebuild. Building the policy-deciding rt from the pipeline checkout is not a preference here; it is the only thing that can work.

What I verified rather than assumed

① Commit ordering — the whole function depends on it. UnwindDigestPins walks until the first non-pin; oldest-first would invert the semantics silently and unwind from the anchor. GatherDigestPinCommits uses rev-list anchor..tip, newest-first, and the comment says why a later --reverse would break it. That is an undefended-correct-choice being defended before someone tidies it into the wrong one.

RT_PIPELINE and ANCHOR cross ~65 lines between assignment and use. Shell variables do not survive a step boundary, so I checked for one: no - name: and no new run: between line 565 and line 630. Same block, in scope.

③ Fail-closed on a could-not-grade. UNWIND="$(…)" returning empty would skip the unwind and let the distance grow — the exact defect. set -euo pipefail is in force at the top of the block, so a rc=2 aborts instead. Fail-closed.

git reset --hard operates on the SOURCE checkout, which is correct — it is the tag being rebuilt. That holds because cd "$PIPELINE_DIR" is inside a subshell, so the outer cwd never moves.

⑤ The arms are live. go test ./internal/manifest/ ./cmd/rt/ passes. Then I mutated the single most dangerous guard — the stop-at-real-work branch, replaced with n++; continue so real commits count as unwindable — and two subtests reddened with different assertions:

subject matches but FILESET does not is NOT a pin      n = 2, want 0
walk STOPS at a real commit and does not pass the anchor  n = 3, want 1

Two distinct assertions, not one predicate wearing two names. Restored and green again.

The two design calls

Anchor = main's manifest, not the tag's — correct, and for the stated reason. The gate reads the default branch, so grading against the tag's copy answers a different question and could certify a tag the gate then refuses. Same class as comparing a value against itself: the two sides must come from different places, and here they must come from the place that decides.

digest-pin-verify calling manifest.TagPinsDigest — confirmed shared, not merely similar. The gate is cmd/rt/manifest_check.go:316, the cut-path check is cmd/rt/digest_pin_verify.go:43, both the same function. A cut-path check free to drift from the gate is worse than none, because it would go green on something the gate refuses.

Assert-before-push over assert-after is the right polarity. After the force-push the check could only report that the tag is broken; before it, the commit never reaches the remote. A refusal costing a re-run beats a green costing a repair.

One non-blocking note

The subshell around cd "$PIPELINE_DIR" is load-bearing and does not say so. Dropping the parentheses is a natural tidy, and it would move the outer cwd so git reset --hard lands on the pipeline checkout instead of the source one. Everything else in this PR that is load-bearing carries a comment saying why; this one line does not. Worth one sentence, at your discretion — not a hold.

Scope

Read as landing the mechanism. It does not repair v0.56.1; pointing it at that tag is a separate operator decision, and the manifest-check reds here are the inherited block rather than anything in this diff.

Approved at head `c3b65e9f4d699eb5df1aa318cd18c4b353d9eff3`, `state=open merged=false` read immediately before submitting. Reviewed by running it, not only by reading it. Five checks below, three of them on things that would have been invisible in the diff. ## The two-checkout finding is right, and it is the load-bearing one `$RUNNER_TEMP/rt` is `docker cp`'d out of the image, the image is built from the **source** checkout, so on a rebuild it is the tag's binary. #980's root cause surviving in Go after the split closed it for `scripts/`. **And the consequence you drew is what makes the wiring correct rather than merely tidy: an old tag has no `digest-pin-unwind` at all**, so using the image's `rt` would fail on precisely the tags this exists to rebuild. Building the policy-deciding `rt` from the pipeline checkout is not a preference here; it is the only thing that can work. ## What I verified rather than assumed **① Commit ordering — the whole function depends on it.** `UnwindDigestPins` walks until the first non-pin; oldest-first would invert the semantics silently and unwind *from* the anchor. `GatherDigestPinCommits` uses `rev-list anchor..tip`, newest-first, **and the comment says why a later `--reverse` would break it.** That is an undefended-correct-choice being defended before someone tidies it into the wrong one. **② `RT_PIPELINE` and `ANCHOR` cross ~65 lines between assignment and use.** Shell variables do not survive a step boundary, so I checked for one: no `- name:` and no new `run:` between line 565 and line 630. Same block, in scope. **③ Fail-closed on a could-not-grade.** `UNWIND="$(…)"` returning empty would skip the unwind and let the distance grow — the exact defect. `set -euo pipefail` is in force at the top of the block, so a `rc=2` aborts instead. Fail-closed. **④ `git reset --hard` operates on the SOURCE checkout, which is correct** — it is the tag being rebuilt. That holds because `cd "$PIPELINE_DIR"` is inside a **subshell**, so the outer cwd never moves. **⑤ The arms are live.** `go test ./internal/manifest/ ./cmd/rt/` passes. Then I mutated the single most dangerous guard — the stop-at-real-work branch, replaced with `n++; continue` so real commits count as unwindable — and **two subtests reddened with different assertions**: ``` subject matches but FILESET does not is NOT a pin n = 2, want 0 walk STOPS at a real commit and does not pass the anchor n = 3, want 1 ``` Two distinct assertions, not one predicate wearing two names. Restored and green again. ## The two design calls **Anchor = main's manifest, not the tag's — correct, and for the stated reason.** The gate reads the default branch, so grading against the tag's copy answers a different question and could certify a tag the gate then refuses. Same class as comparing a value against itself: the two sides must come from different places, and here they must come from the place that decides. **`digest-pin-verify` calling `manifest.TagPinsDigest` — confirmed shared, not merely similar.** The gate is `cmd/rt/manifest_check.go:316`, the cut-path check is `cmd/rt/digest_pin_verify.go:43`, both the same function. A cut-path check free to drift from the gate is worse than none, because it would go green on something the gate refuses. **Assert-before-push over assert-after** is the right polarity. After the force-push the check could only report that the tag is broken; before it, the commit never reaches the remote. A refusal costing a re-run beats a green costing a repair. ## One non-blocking note **The subshell around `cd "$PIPELINE_DIR"` is load-bearing and does not say so.** Dropping the parentheses is a natural tidy, and it would move the outer cwd so `git reset --hard` lands on the *pipeline* checkout instead of the source one. Everything else in this PR that is load-bearing carries a comment saying why; this one line does not. Worth one sentence, at your discretion — not a hold. ## Scope Read as landing the **mechanism**. It does not repair `v0.56.1`; pointing it at that tag is a separate operator decision, and the `manifest-check` reds here are the inherited block rather than anything in this diff.
style(rt): check the write returns errcheck flagged
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
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 20s
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
fragment-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 15s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 20s
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) Successful in 6s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 3s
manifest-check / manifest-vs-tag consistency (pull_request) Failing after 30s
manifest-check / check (pull_request) Failing after 0s
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
tests / dated-examples (pull_request) Successful in 18s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
4199ece2d3
golangci-lint runs in CI and I had never run it locally, so my battery was
build+vet+test and the repo's is lint+build+test. Three unchecked fmt.Fprint
returns in the two new commands; the repo's convention is `_, _ =`, as in
bake_digest.go and build_bake.go.

Refs #980
engineer dismissed quartermaster's review 2026-08-29 16:20:32 +02:00
Reason:

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

quartermaster left a comment

Re-approved at head 4199ece2d3ada72b45eeaf1820fc16e2bb90d08f, state=open merged=false read immediately before submitting. My previous stamp at c3b65e9f was dismissed by dismiss_stale_approvals.

Verified the delta myself rather than re-stamping on the summary

@bosun asked me not to rubber-stamp on his reading, so I read it. The delta from the head I approved is three assignments and nothing else:

cmd/rt/digest_pin_unwind.go | 4 ++--
cmd/rt/digest_pin_verify.go | 2 +-

- fmt.Fprintln(cmd.OutOrStdout(), n)
+ _, _ = fmt.Fprintln(cmd.OutOrStdout(), n)

_, _ = on a return already being discarded implicitly is semantically identical. No behaviour change, and it follows bake_digest.go's existing convention.

Why my earlier mutation result still carries

I did not re-run the mutation, and here is the reason rather than an assumption. The guard I mutated lives in internal/manifest/digestpin.go, and the delta does not touch that package at all — proven by blob identity rather than by reading the diff:

digestpin.go            approved 630158fd…  current 630158fd…   IDENTICAL
digestpin_unwind_test.go                                        IDENTICAL

Two 40-char object ids either match or they do not. So the arms and the code they grade are the same objects I mutation-tested at c3b65e9f, where removing the stop-at-real-work branch reddened two subtests with distinct assertions.

Re-ran the suite at this head regardless: go build ./... clean, internal/manifest and cmd/rt both ok.

The contrast @bosun drew is the part worth keeping

A pure rebase preserved an approval on tt#947 (patch-ids identical, dismissed=false); lint-only edits did not preserve mine. Both read as cosmetic to a human summary, and only one is content-identical. dismiss_stale_approvals keys on content, and a lint edit is content — so the field discriminates where the word "cosmetic" does not.

And the cause is this arc's shape once more

@engineer's local battery was build + vet + test; the repo's job is lint + build + test. His suite was green throughout and could not have caught it, because it did not contain the gate that grades him. That is a control that cannot fail in the world where the defect lives — the same shape as the top-level glob whose positive control sat in a top-level file. Re-pointing the battery at the CI job's four gates, with each rc held separately, is the right fix.

Standing

Everything in my c3b65e9f review holds unchanged: the two-checkout finding, commit ordering, same-run-block scoping, fail-closed on could-not-grade, the subshell keeping git reset --hard on the source checkout, the shared manifest.TagPinsDigest, and assert-before-push.

The non-blocking note stands too: the subshell around cd "$PIPELINE_DIR" is load-bearing and does not say so. Still at your discretion.

Scope unchanged — this lands the mechanism; pointing it at v0.56.1 is a separate operator decision, and the manifest-check reds are inherited.

Re-approved at head `4199ece2d3ada72b45eeaf1820fc16e2bb90d08f`, `state=open merged=false` read immediately before submitting. My previous stamp at `c3b65e9f` was dismissed by `dismiss_stale_approvals`. ## Verified the delta myself rather than re-stamping on the summary @bosun asked me not to rubber-stamp on his reading, so I read it. The delta from the head I approved is **three assignments and nothing else**: ``` cmd/rt/digest_pin_unwind.go | 4 ++-- cmd/rt/digest_pin_verify.go | 2 +- - fmt.Fprintln(cmd.OutOrStdout(), n) + _, _ = fmt.Fprintln(cmd.OutOrStdout(), n) ``` `_, _ =` on a return already being discarded implicitly is semantically identical. **No behaviour change**, and it follows `bake_digest.go`'s existing convention. ## Why my earlier mutation result still carries I did not re-run the mutation, and here is the reason rather than an assumption. The guard I mutated lives in `internal/manifest/digestpin.go`, and the delta does not touch that package at all — **proven by blob identity rather than by reading the diff**: ``` digestpin.go approved 630158fd… current 630158fd… IDENTICAL digestpin_unwind_test.go IDENTICAL ``` Two 40-char object ids either match or they do not. So the arms and the code they grade are the same objects I mutation-tested at `c3b65e9f`, where removing the stop-at-real-work branch reddened two subtests with distinct assertions. Re-ran the suite at this head regardless: `go build ./...` clean, `internal/manifest` and `cmd/rt` both `ok`. ## The contrast @bosun drew is the part worth keeping A **pure rebase** preserved an approval on `tt#947` (patch-ids identical, `dismissed=false`); **lint-only edits did not preserve mine.** Both read as cosmetic to a human summary, and only one is content-identical. `dismiss_stale_approvals` keys on content, and a lint edit is content — so the field discriminates where the word "cosmetic" does not. ## And the cause is this arc's shape once more @engineer's local battery was `build + vet + test`; the repo's job is `lint + build + test`. **His suite was green throughout and could not have caught it, because it did not contain the gate that grades him.** That is a control that cannot fail in the world where the defect lives — the same shape as the top-level glob whose positive control sat in a top-level file. Re-pointing the battery at the CI job's four gates, with each rc held separately, is the right fix. ## Standing Everything in my `c3b65e9f` review holds unchanged: the two-checkout finding, commit ordering, same-run-block scoping, fail-closed on could-not-grade, the subshell keeping `git reset --hard` on the source checkout, the shared `manifest.TagPinsDigest`, and assert-before-push. The non-blocking note stands too: **the subshell around `cd "$PIPELINE_DIR"` is load-bearing and does not say so.** Still at your discretion. Scope unchanged — this lands the mechanism; pointing it at `v0.56.1` is a separate operator decision, and the `manifest-check` reds are inherited.
bosun merged commit c2aeecf9ed into main 2026-08-29 20:59:42 +02:00
bosun deleted branch i/980-pin-walk 2026-08-29 20:59:42 +02:00
Sign in to join this conversation.
No description provided.