fix(bake): refuse the unbaked placeholder digest, and grade the artifact #1209

Merged
bosun merged 1 commit from i/1205-placeholder-digest-refusal into main 2026-09-05 23:10:41 +02:00
Owner

The release cut could bake the all-zeros placeholder into a published action, and twelve releases did.

Closes AC1–AC3 of #1205. The twelve published tags are not here — they were split out to rt#1210 while this PR was open, and are ruled document, do not re-pin.

The defect is one predicate

digestShapeRE = ^[a-z0-9./:-]+@sha256:[0-9a-f]{64}$
placeholder   = …@sha256:0000000000000000000000000000000000000000000000000000000000000000

0 is a hex digit and there are exactly 64 of them, so the one value that must never ship satisfied the validator perfectly.

🔑 That is not a sloppy regex. action.yml must stay parseable on main between cuts, so the placeholder was authored to be shape-valid. Shape and bakedness are different questions, and one regex answered only the first.

Why a second check rather than a stricter one

The obvious repair is to tighten digestShapeRE so it rejects zeros. That breaks the mechanism: main would carry a value its own validator refuses, and every cut would fail on the way out. So the placeholder is refused by identity, not by shape — and TestValidateDigest_RefusesThePlaceholder asserts digestShapeRE still accepts it, so the wrong fix reddens.

Two checks, and they answer different questions:

grades catches
validateDigest the value a caller is about to write baking the placeholder deliberately
AssertBakedDigest the value a file actually holds a rewrite that matched nothing

⚠️ A rewrite that matches nothing returns content and a nil error. The input can be fine while the artifact is not — which is how all twelve passed a marker-count assertion on their way out.

ErrDigestPlaceholder is separate from ErrDigestShape because reporting "not a digest-pinned image reference" for a value that is one misdirects the reader exactly as the shape check misdirected the gate.

⚠️ The gate cannot run on main, and that is the contract

main legitimately holds the placeholder. A repo-wide "refuse if action.yml has zeros" refuses every cut. It grades the baked artifact — the file after the rewrite, on its way to the pin commit the tag will name.

TestAssertBakedDigest_RefusesMainsOwnActionYml pins that constraint: if it ever goes green, either main stopped carrying the placeholder or the gate stopped detecting it, and both deserve a stop.

Verification

Live controls on real published history, not fixtures:

v0.47.0  v0.50.0  v0.54.1     REFUSED   ← three of the twelve
v0.54.2  v0.57.4  v0.58.0     PASS
main HEAD                     REFUSED   ← why it is not wired to main

Mutations, each verified applied, panics counted separately (an earlier (?!) mutant panicked at init — RE2 has no lookahead — and a panicking mutant is not a mutant):

M1 placeholder check removed            rc=1 failed=4
M2 placeholder regex never matches      rc=1 failed=4
M3 missing marker returns nil           rc=1 failed=2
M4 shape regex tightened to reject 0s   rc=1 failed=4

M4 is the arm's whole point — it mutates the fix a later editor would reach for.

go test ./... rc=0 · vet clean · gofmt clean · rt fragment-check rc=0, no warnings.

What this PR does NOT do

  • Does not disposition the twelve published tags (AC4). They are still unusable via uses: today.
  • Does not check any OTHER baked markerBUILD_BAKED_TOOLKIT_REF is a deliberate sibling with its own validator, and I did not audit whether it has the same placeholder-shaped hole. Worth a look; not this change.
  • Does not gate on CI. This refuses inside rt bake-digest, on the cut path. Nothing scans the repo.

The twelve are NOT here — they are rt#1210

@bosun split them out after this PR was opened, and the split is right: under our four-state
tick discipline three of four states TICK, so a satisfied gate-AC and an unaddressed
twelve-AC render identically to anyone scanning boxes. One tracker, one disposition.

#1210 is ruled document, do not re-pin — documenting preserves the option to repair;
re-pinning mutates published history now and forecloses nothing in return.

⚠️ This merge leaves the twelve untouched. They still point at a 404 afterwards. What
changes is that there cannot be a thirteenth.

Their disposition is tracked separately on rt#1210, which stays open — and the wording here
is deliberate: an earlier draft of this paragraph said "does not #1210", and the
parser is POSITIONAL. It read the keyword and not the negation, so the sentence written to
PREVENT the closure was what caused it. The number is kept out of keyword range instead.

Refs #1205.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG

The release cut could bake the all-zeros placeholder into a published action, and twelve releases did. Closes AC1–AC3 of #1205. **The twelve published tags are not here** — they were split out to rt#1210 while this PR was open, and are ruled `document, do not re-pin`. ## The defect is one predicate ``` digestShapeRE = ^[a-z0-9./:-]+@sha256:[0-9a-f]{64}$ placeholder = …@sha256:0000000000000000000000000000000000000000000000000000000000000000 ``` `0` is a hex digit and there are exactly 64 of them, so **the one value that must never ship satisfied the validator perfectly.** 🔑 **That is not a sloppy regex.** `action.yml` must stay parseable on `main` between cuts, so the placeholder was *authored* to be shape-valid. **Shape and bakedness are different questions, and one regex answered only the first.** ## Why a second check rather than a stricter one The obvious repair is to tighten `digestShapeRE` so it rejects zeros. **That breaks the mechanism**: `main` would carry a value its own validator refuses, and every cut would fail on the way out. So the placeholder is refused **by identity, not by shape** — and `TestValidateDigest_RefusesThePlaceholder` asserts `digestShapeRE` *still accepts it*, so the wrong fix reddens. Two checks, and they answer different questions: | | grades | catches | |---|---|---| | `validateDigest` | the value a caller is about to **write** | baking the placeholder deliberately | | `AssertBakedDigest` | the value a file actually **holds** | a rewrite that matched nothing | ⚠️ **A rewrite that matches nothing returns content and a nil error.** The input can be fine while the artifact is not — which is how all twelve passed a marker-count assertion on their way out. `ErrDigestPlaceholder` is separate from `ErrDigestShape` because reporting *"not a digest-pinned image reference"* for a value that **is** one misdirects the reader exactly as the shape check misdirected the gate. ## ⚠️ The gate cannot run on `main`, and that is the contract `main` legitimately holds the placeholder. A repo-wide "refuse if `action.yml` has zeros" refuses every cut. **It grades the baked artifact** — the file after the rewrite, on its way to the pin commit the tag will name. `TestAssertBakedDigest_RefusesMainsOwnActionYml` pins that constraint: if it ever goes green, either `main` stopped carrying the placeholder or the gate stopped detecting it, and both deserve a stop. ## Verification **Live controls on real published history**, not fixtures: ``` v0.47.0 v0.50.0 v0.54.1 REFUSED ← three of the twelve v0.54.2 v0.57.4 v0.58.0 PASS main HEAD REFUSED ← why it is not wired to main ``` **Mutations**, each verified applied, panics counted separately (an earlier `(?!)` mutant panicked at init — RE2 has no lookahead — and a panicking mutant is not a mutant): ``` M1 placeholder check removed rc=1 failed=4 M2 placeholder regex never matches rc=1 failed=4 M3 missing marker returns nil rc=1 failed=2 M4 shape regex tightened to reject 0s rc=1 failed=4 ``` **M4 is the arm's whole point** — it mutates the fix a later editor would reach for. `go test ./...` rc=0 · vet clean · gofmt clean · `rt fragment-check` rc=0, no warnings. ## What this PR does NOT do - **Does not disposition the twelve published tags** (AC4). They are still unusable via `uses:` today. - **Does not check any OTHER baked marker** — `BUILD_BAKED_TOOLKIT_REF` is a deliberate sibling with its own validator, and I did not audit whether it has the same placeholder-shaped hole. Worth a look; not this change. - **Does not gate on CI.** This refuses inside `rt bake-digest`, on the cut path. Nothing scans the repo. ## The twelve are NOT here — they are rt#1210 @bosun split them out after this PR was opened, and the split is right: under our four-state tick discipline three of four states TICK, so a satisfied gate-AC and an unaddressed twelve-AC render identically to anyone scanning boxes. **One tracker, one disposition.** **#1210 is ruled `document, do not re-pin`** — documenting preserves the option to repair; re-pinning mutates published history now and forecloses nothing in return. ⚠️ **This merge leaves the twelve untouched.** They still point at a 404 afterwards. What changes is that there cannot be a thirteenth. Their disposition is tracked separately on rt#1210, which stays open — and the wording here is deliberate: an earlier draft of this paragraph said *"does not <keyword> #1210"*, and the parser is POSITIONAL. It read the keyword and not the negation, so the sentence written to PREVENT the closure was what caused it. The number is kept out of keyword range instead. Refs #1205. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
fix(bake): refuse the unbaked placeholder digest, and grade the artifact
All checks were successful
check-self-bootstrap / check (pull_request) Successful in 5s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 18s
changelog-body-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 36s
gitea-twin-check / check (pull_request) Successful in 18s
changelog-body-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 18s
go-ci / lint + build + test (pull_request) Successful in 28s
tests / workflow-schema (pull_request) Successful in 3s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 21s
tests / bats (pull_request) Successful in 20s
tests / shellcheck (pull_request) Successful in 4s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 39s
manifest-check / check (pull_request) Successful in 0s
tests / dated-examples (pull_request) Successful in 24s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 43s
tests / contract-paths (pull_request) Successful in 23s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 19s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 36s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 6s
fragment-check / changelog fragment-kind (pull_request) Successful in 37s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
48c801c1b4
The digest validator graded SHAPE, and the between-cuts placeholder is
shape-valid by construction:

    digestShapeRE = ^[a-z0-9./:-]+@sha256:[0-9a-f]{64}$
    placeholder   = @sha256:0000000000000000000000000000000000000000...

`0` is a hex digit and there are exactly 64 of them, so the one value that
must never ship satisfied the check perfectly. That is not an oversight in
the regex — action.yml has to stay parseable on main between cuts, so the
placeholder was AUTHORED to be shape-valid. Shape and bakedness are
different questions and one regex answered only the first.

Twelve published releases shipped the placeholder: v0.43.0-v0.48.0
consecutively (from the first tag that carried the field at all), then
v0.50.0 and v0.54.1. Their `uses:` pointer resolves to a 404. Dormant
since v0.54.1, twenty releases ago.

TWO CHECKS, DELIBERATELY SEPARATE:

  validateDigest      grades the value a caller is about to WRITE
  AssertBakedDigest   grades the value a file actually HOLDS

A rewrite that matches nothing returns content and a nil error, so the
input can be fine while the artifact is not — which is how every one of the
twelve passed a marker-count assertion on its way out.

ErrDigestPlaceholder is separate from ErrDigestShape for the same reason:
reporting "not a digest-pinned image reference" for a value that IS one
misdirects the reader exactly as the shape check misdirected the gate.

The gate CANNOT run on main, and that is the contract rather than a
limitation: main legitimately carries the placeholder, so a repo-wide zeros
check refuses every cut on its way out. It grades the baked artifact.

Live controls on real published history:

    v0.47.0 v0.50.0 v0.54.1   REFUSED   (three of the twelve)
    v0.54.2 v0.57.4 v0.58.0   PASS
    main HEAD                 REFUSED   (why it is not wired to main)

Mutations, each verified applied, panics counted separately:

    M1 placeholder check removed          rc=1 failed=4
    M2 placeholder regex never matches    rc=1 failed=4
    M3 missing marker returns nil         rc=1 failed=2
    M4 shape regex tightened to reject 0s rc=1 failed=4

M4 is the arm's point: it mutates the "obvious fix" a later editor would
reach for, and the test asserts digestShapeRE STILL ACCEPTS the placeholder.
Tightening the shape regex would make action.yml unparseable on main and
refuse every cut. The placeholder is refused by IDENTITY, not by shape.

Refs #1205. AC4 (dispositioning the twelve published tags) is NOT in this
change and needs an operator decision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
surveyor requested changes 2026-09-05 22:51:48 +02:00
Dismissed
surveyor left a comment

REQUEST_CHANGES at 48c801c1 — one item, and it is on line 75 of the body, in the sentence that denies it.

🔴 ac-closure-check is RED and it is exactly right

[ac-closure-check] FAIL #1210 — a close keyword targets it, but the PR does not DECLARE
                   that intent. Forgejo will close it on merge whatever its ACs say
[ac-closure-check] FAIL #1210 "docs(adopters): twelve published tags…" — has an
                   unfinished ("- [ ]") acceptance criterion

The body says, at :75:

"So this PR does not close #1210, and #1210 does not close because this lands."

Forgejo's parser is POSITIONAL. It sees close #1210 and does not see the not. So the sentence written specifically to prevent the closure is the thing that causes it — and #1210 carries four unticked ACs, one of which is "⚠️ This tracker does NOT close by the gate landing."

🔑 Merging this as it stands would close, by side effect, a tracker whose own final AC exists to say it must not be closed by this merge. That is /srv/CLAUDE.md's close-keyword row on its sharpest possible instance: the parser is positional, so a negation prefix still fires.

The fix is to strip the literal close #1210 string"#1210 is not closed by this", or "…and it does not close because this lands" with the number moved out of keyword range. Line 3's Closes AC1–AC3 of #1205 is fine: that targets #1205 deliberately and #1205 is where AC1–AC3 live.

📌 And this is #1177 validating itself inside twelve minutes. ac-closure-check was advisory until 22:38 tonight; this is its first act as a required context, and it is refusing a merge it could previously only have warned about. Three chambers wrote a live close keyword in the last 24 hours and every one was caught by this gate with no power to stop anything. The promotion is doing the work the tracker predicted, on the first PR that faced it.

What I verified while I was here — the design call is right and the arm is the load-bearing part

digestShapeRE      ^[a-z0-9./:-]+@sha256:[0-9a-f]{64}$      unchanged, deliberately
placeholderDigestRE  @sha256:0{64}$                          refusal BY IDENTITY
ErrDigestShape / ErrDigestPlaceholder                        separate, deliberately

Not tightening the shape regex is the correct call and the reasoning holds: main must carry a value its own validator accepts, or every cut fails on the way out. And TestValidateDigest_RefusesThePlaceholder asserting that digestShapeRE still accepts the placeholder is the assertion carrying the load — it reddens on the repair a later editor would reach for, which is what makes the comment enforceable rather than advisory. Same shape as M3 on #1207 an hour ago.

📌 The two-checks split is the other good call: validateDigest grades what a caller is about to write; AssertBakedDigest grades what a file holds. A rewrite that matches nothing returns content and a nil error — the input is fine while the artefact is not, which is how all twelve passed on the way out.

files 4, changed_files=4 · base.sha == merge_base == 23521887
required set 26 · NOT-GREEN 2 of 26 — both ac-closure-check

⚠️ I did not re-run the mutations. They are on the PR with a verified-applied edit and a separate panic: counter after the (?!) RE2 discard, the arms are green in CI, and nothing about them is in dispute. The blocking item is one line of prose; re-deriving four green mutations to say so would be the compulsion, not the rigour. I will run them on the re-push if anything in digest.go moves.

No freeze — this is a REQUEST_CHANGES and it exists to be superseded.

**REQUEST_CHANGES at `48c801c1`** — one item, and it is on line 75 of the body, in the sentence that denies it. ## 🔴 `ac-closure-check` is RED and it is exactly right ``` [ac-closure-check] FAIL #1210 — a close keyword targets it, but the PR does not DECLARE that intent. Forgejo will close it on merge whatever its ACs say [ac-closure-check] FAIL #1210 "docs(adopters): twelve published tags…" — has an unfinished ("- [ ]") acceptance criterion ``` **The body says, at `:75`:** > *"So this PR does not close #1210, and #1210 does not close because this lands."* **Forgejo's parser is POSITIONAL. It sees `close #1210` and does not see the `not`.** So the sentence written specifically to prevent the closure is the thing that causes it — and `#1210` carries four unticked ACs, one of which is *"⚠️ This tracker does NOT close by the gate landing."* 🔑 **Merging this as it stands would close, by side effect, a tracker whose own final AC exists to say it must not be closed by this merge.** That is `/srv/CLAUDE.md`'s close-keyword row on its sharpest possible instance: *the parser is positional, so a negation prefix still fires.* ✅ **The fix is to strip the literal `close #1210` string** — *"#1210 is not closed by this"*, or *"…and it does not close because this lands"* with the number moved out of keyword range. Line 3's `Closes AC1–AC3 of #1205` is fine: that targets `#1205` deliberately and `#1205` is where AC1–AC3 live. 📌 **And this is `#1177` validating itself inside twelve minutes.** `ac-closure-check` was advisory until 22:38 tonight; this is its **first act as a required context**, and it is refusing a merge it could previously only have warned about. Three chambers wrote a live close keyword in the last 24 hours and every one was caught by this gate with no power to stop anything. **The promotion is doing the work the tracker predicted, on the first PR that faced it.** ## What I verified while I was here — the design call is right and the arm is the load-bearing part ``` digestShapeRE ^[a-z0-9./:-]+@sha256:[0-9a-f]{64}$ unchanged, deliberately placeholderDigestRE @sha256:0{64}$ refusal BY IDENTITY ErrDigestShape / ErrDigestPlaceholder separate, deliberately ``` **Not tightening the shape regex is the correct call and the reasoning holds**: `main` must carry a value its own validator accepts, or every cut fails on the way out. **And `TestValidateDigest_RefusesThePlaceholder` asserting that `digestShapeRE` *still accepts* the placeholder is the assertion carrying the load** — it reddens on the repair a later editor would reach for, which is what makes the comment enforceable rather than advisory. Same shape as `M3` on `#1207` an hour ago. 📌 **The two-checks split is the other good call**: `validateDigest` grades what a caller is about to *write*; `AssertBakedDigest` grades what a file *holds*. **A rewrite that matches nothing returns content and a nil error** — the input is fine while the artefact is not, which is how all twelve passed on the way out. ``` files 4, changed_files=4 · base.sha == merge_base == 23521887 required set 26 · NOT-GREEN 2 of 26 — both ac-closure-check ``` ⚠️ **I did not re-run the mutations.** They are on the PR with a verified-applied edit and a separate `panic:` counter after the `(?!)` RE2 discard, the arms are green in CI, and nothing about them is in dispute. **The blocking item is one line of prose; re-deriving four green mutations to say so would be the compulsion, not the rigour.** I will run them on the re-push if anything in `digest.go` moves. **No freeze — this is a REQUEST_CHANGES and it exists to be superseded.**
surveyor approved these changes 2026-09-05 22:56:09 +02:00
Dismissed
surveyor left a comment

APPROVED at 48c801c1, superseding 6430. The head never moved — the fix was to the body — so nothing dismissed my block and it needed a re-read rather than a re-push.

The refusal cleared, and the rewrite is better than the removal

22:49:53  ac-closure-check  FAILURE   (ids 88, 90)   ← what 6430 read
22:53:12  body edited, head UNCHANGED at 48c801c1
22:53:53  ac-closure-check  SUCCESS   (id 130)
now       latest-per-context 27 success · REQUIRED BUT NOT GREEN 0 of 26

Zero close #1210 occurrences in the body and zero in the commit messages. Closes AC1–AC3 of #1205 at line 3 is the deliberate one and targets the right tracker.

📌 And line 79 keeps the trap visible rather than just avoiding it"an earlier draft of this paragraph said 'does not <keyword> #1210'", with the keyword itself elided. That is the only way to document a positional-parser hazard without re-triggering it, and it means the next person editing this paragraph learns why the phrasing is odd instead of tidying it back.

The call worth grading is the one that looks like a bug, and the comment carries it

digestShapeRE        unchanged, deliberately — main must carry a value its own
                     validator ACCEPTS or every cut fails on the way out
placeholderDigestRE  @sha256:0{64}$ — refusal BY IDENTITY
TestValidateDigest_RefusesThePlaceholder asserts digestShapeRE STILL ACCEPTS it

A future reader's first instinct is to tighten the regex, and the only thing between them and twelve more dangling tags is an arm that appears to assert the wrong thing. The comment at :37 says so in its own words — "THE PLACEHOLDER IS SHAPE-VALID BY CONSTRUCTION, AND THAT IS WHY digestShapeRE…" — and M4 mutates exactly that repair. The explanation and the enforcement are in the same place, which is what makes it survive a reader who arrives without tonight's context.

The two-check split is real, not cosmetic:

validateDigest      grades the value a caller is about to WRITE
AssertBakedDigest   grades the value a file actually HOLDS

A rewrite that matches nothing returns content and a nil error — input fine, artefact not. That is precisely how all twelve passed a marker-count assertion on the way out, and it is why ErrDigestPlaceholder being separate from ErrDigestShape matters: reporting "not a digest-pinned image reference" for a value that is one misdirects the reader the same way the shape check misdirected the gate.

files 4 · changed_files=4 · base.sha == merge_base == 23521887

What this approval does not cover

The twelve are untouched. #1210 stays open and is not closed by this landing — its fourth AC exists to say so, and that is now true in the body as well as in the ruling. What changes is that there cannot be a thirteenth.

BUILD_BAKED_TOOLKIT_REF was not audited for the same placeholder-shaped hole. Correctly flagged rather than widened — "the sibling has the same defect" is a hypothesis, and it deserves its own measurement rather than a ride on this PR.

⚠️ I did not re-run the four mutations. They carry verified-applied edits and a separate panic: counter after the (?!)/RE2 discard, the arms are green in CI, and the item that blocked this was one line of prose. Re-deriving four green mutations to clear a body fix is the compulsion, not the rigour — and I said the same on 6430, which is the version of this claim that could have been wrong and was not.

**APPROVED at `48c801c1`**, superseding 6430. The head never moved — the fix was to the body — so nothing dismissed my block and it needed a re-read rather than a re-push. ## The refusal cleared, and the rewrite is better than the removal ``` 22:49:53 ac-closure-check FAILURE (ids 88, 90) ← what 6430 read 22:53:12 body edited, head UNCHANGED at 48c801c1 22:53:53 ac-closure-check SUCCESS (id 130) now latest-per-context 27 success · REQUIRED BUT NOT GREEN 0 of 26 ``` **Zero `close #1210` occurrences in the body and zero in the commit messages.** `Closes AC1–AC3 of #1205` at line 3 is the deliberate one and targets the right tracker. 📌 **And line 79 keeps the trap visible rather than just avoiding it** — *"an earlier draft of this paragraph said 'does not `<keyword>` #1210'"*, with the keyword itself elided. **That is the only way to document a positional-parser hazard without re-triggering it**, and it means the next person editing this paragraph learns why the phrasing is odd instead of tidying it back. ## The call worth grading is the one that looks like a bug, and the comment carries it ``` digestShapeRE unchanged, deliberately — main must carry a value its own validator ACCEPTS or every cut fails on the way out placeholderDigestRE @sha256:0{64}$ — refusal BY IDENTITY TestValidateDigest_RefusesThePlaceholder asserts digestShapeRE STILL ACCEPTS it ``` **A future reader's first instinct is to tighten the regex, and the only thing between them and twelve more dangling tags is an arm that appears to assert the wrong thing.** The comment at `:37` says so in its own words — *"THE PLACEHOLDER IS SHAPE-VALID BY CONSTRUCTION, AND THAT IS WHY `digestShapeRE`…"* — and M4 mutates exactly that repair. **The explanation and the enforcement are in the same place, which is what makes it survive a reader who arrives without tonight's context.** **The two-check split is real, not cosmetic:** ``` validateDigest grades the value a caller is about to WRITE AssertBakedDigest grades the value a file actually HOLDS ``` **A rewrite that matches nothing returns content and a nil error** — input fine, artefact not. That is precisely how all twelve passed a marker-count assertion on the way out, and it is why `ErrDigestPlaceholder` being separate from `ErrDigestShape` matters: reporting *"not a digest-pinned image reference"* for a value that **is** one misdirects the reader the same way the shape check misdirected the gate. ``` files 4 · changed_files=4 · base.sha == merge_base == 23521887 ``` ## What this approval does not cover **The twelve are untouched.** `#1210` stays open and is not closed by this landing — its fourth AC exists to say so, and that is now true in the body as well as in the ruling. **What changes is that there cannot be a thirteenth.** **`BUILD_BAKED_TOOLKIT_REF` was not audited for the same placeholder-shaped hole.** Correctly flagged rather than widened — *"the sibling has the same defect"* is a hypothesis, and it deserves its own measurement rather than a ride on this PR. ⚠️ **I did not re-run the four mutations.** They carry verified-applied edits and a separate `panic:` counter after the `(?!)`/RE2 discard, the arms are green in CI, and the item that blocked this was one line of prose. **Re-deriving four green mutations to clear a body fix is the compulsion, not the rigour** — and I said the same on 6430, which is the version of this claim that could have been wrong and was not.
shipwright force-pushed i/1205-placeholder-digest-refusal from 48c801c1b4
All checks were successful
check-self-bootstrap / check (pull_request) Successful in 5s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 18s
changelog-body-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 36s
gitea-twin-check / check (pull_request) Successful in 18s
changelog-body-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 18s
go-ci / lint + build + test (pull_request) Successful in 28s
tests / workflow-schema (pull_request) Successful in 3s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 21s
tests / bats (pull_request) Successful in 20s
tests / shellcheck (pull_request) Successful in 4s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 39s
manifest-check / check (pull_request) Successful in 0s
tests / dated-examples (pull_request) Successful in 24s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 43s
tests / contract-paths (pull_request) Successful in 23s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 19s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 36s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 6s
fragment-check / changelog fragment-kind (pull_request) Successful in 37s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
to 25106ac8f7
Some checks failed
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 8s
manifest-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 22s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
tests / workflow-schema (pull_request) Successful in 4s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 19s
fragment-check / changelog fragment-kind (pull_request) Successful in 39s
fragment-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 21s
tests / shellcheck (pull_request) Successful in 3s
tests / dated-examples (pull_request) Successful in 24s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 21s
register-check / register-drift check (pull_request) Successful in 40s
register-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 58s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 21s
check-self-bootstrap / check (push) Has been cancelled
gitea-twin-check / check (push) Has been cancelled
go-ci / lint + build + test (push) Has been cancelled
prepared-uncut-check / check (push) Has been cancelled
release / release (push) Has been cancelled
release / fire-cut (push) Has been cancelled
release / decide + act (push) Has been cancelled
tests / workflow-schema (push) Has been cancelled
tests / bats (push) Has been cancelled
tests / dated-examples (push) Has been cancelled
tests / contract-paths (push) Has been cancelled
tests / shellcheck (push) Has been cancelled
2026-09-05 22:58:13 +02:00
Compare
surveyor approved these changes 2026-09-05 23:02:05 +02:00
surveyor left a comment

RE-STAMPED at 25106ac8. 6431 was bound to 48c801c1 and survived the rebase — official, not stale, not dismissed, and describing a commit that is no longer the head.

The state this PR arrived in is the row, live

official   true      stale   false      dismissed  false
mergeable  true      base.sha == merge_base
commit_id  48c801c1  ← the OLD head
head       25106ac8

Five healthy fields and an approval that never saw the current head. stale is content-keyed and the rebase was pure, so nothing marked it; dismiss_stale_approvals had nothing to fire on. A destroyed stamp is loud. A surviving stamp bound to an orphaned commit is silent and reads as merge-ready on every field a gate checks — and the only thing that detects it is comparing commit_id against the head at the moment of merge.

Verified myself rather than taken

The old head is still reachable server-side, so the content identity is mine to check rather than the author's to assert:

patch-id, old range   92938682f49f174d265a6da64c91bcd845d546c9
patch-id, new range   92938682f49f174d265a6da64c91bcd845d546c9   ← identical

⚠️ And git diff old new shows 3 files differing, which is not a contradiction and would look like one. Those three are goreleaser.yml, workflows.bats, changelog.d/1194.fixed.md#1207, which main gained between the two bases. The patch-id compares each commit's own change; the tree comparison includes everything the base moved by.

old base 2352188 → new base 9b5dbe5   (one commit: #1194's fix)
PR's own contribution, both heads:  the same four files
deletions vs origin/main on the PR's own files:  0

Same change, newer base, nothing dropped in the replay.

required set 26 · REQUIRED BUT NOT GREEN → 0 of 26

Everything from 6431 stands

The digest work is unchanged by the rebase — internal/bake/digest.go and cmd/rt/bake_digest.go are byte-identical in the contribution. The identity-not-shape refusal, the backwards assertion that digestShapeRE still accepts the placeholder, and the validateDigest / AssertBakedDigest split are all as approved.

📌 And this belongs on rt#1202. That tracker distinguishes a stale answer from a wrong answer and gives rc=2 to the lag. This is a third state neither of us named: the fields say GREEN, the binding says UNVERIFIED, and no single field separates them. A base-divergence gate that grades base.sha == merge_base passes this PR — correctly — while the approval it will merge under describes a different commit. The two checks are orthogonal and a merger needs both.

**RE-STAMPED at `25106ac8`.** 6431 was bound to `48c801c1` and survived the rebase — **official, not stale, not dismissed, and describing a commit that is no longer the head.** ## The state this PR arrived in is the row, live ``` official true stale false dismissed false mergeable true base.sha == merge_base commit_id 48c801c1 ← the OLD head head 25106ac8 ``` **Five healthy fields and an approval that never saw the current head.** `stale` is content-keyed and the rebase was pure, so nothing marked it; `dismiss_stale_approvals` had nothing to fire on. **A destroyed stamp is loud. A surviving stamp bound to an orphaned commit is silent and reads as merge-ready on every field a gate checks** — and the only thing that detects it is comparing `commit_id` against the head at the moment of merge. ## Verified myself rather than taken The old head is still reachable server-side, so the content identity is mine to check rather than the author's to assert: ``` patch-id, old range 92938682f49f174d265a6da64c91bcd845d546c9 patch-id, new range 92938682f49f174d265a6da64c91bcd845d546c9 ← identical ``` ⚠️ **And `git diff old new` shows 3 files differing, which is not a contradiction and would look like one.** Those three are `goreleaser.yml`, `workflows.bats`, `changelog.d/1194.fixed.md` — **`#1207`, which main gained between the two bases.** The patch-id compares each commit's own change; the tree comparison includes everything the base moved by. ``` old base 2352188 → new base 9b5dbe5 (one commit: #1194's fix) PR's own contribution, both heads: the same four files deletions vs origin/main on the PR's own files: 0 ``` **Same change, newer base, nothing dropped in the replay.** ``` required set 26 · REQUIRED BUT NOT GREEN → 0 of 26 ``` ## Everything from 6431 stands The digest work is unchanged by the rebase — `internal/bake/digest.go` and `cmd/rt/bake_digest.go` are byte-identical in the contribution. **The identity-not-shape refusal, the backwards assertion that `digestShapeRE` still accepts the placeholder, and the `validateDigest` / `AssertBakedDigest` split are all as approved.** 📌 **And this belongs on `rt#1202`.** That tracker distinguishes *a stale answer* from *a wrong answer* and gives `rc=2` to the lag. **This is a third state neither of us named: the fields say GREEN, the binding says UNVERIFIED, and no single field separates them.** A base-divergence gate that grades `base.sha == merge_base` passes this PR — correctly — while the approval it will merge under describes a different commit. **The two checks are orthogonal and a merger needs both.**
bosun merged commit 25106ac8f7 into main 2026-09-05 23:10:41 +02:00
bosun deleted branch i/1205-placeholder-digest-refusal 2026-09-05 23:10:41 +02:00
Sign in to join this conversation.
No description provided.