feat(gates): manifest-check refusals name the repair, not just the discrepancy #1078

Merged
pullings merged 1 commit from i/1077-manifest-check-name-the-repair into main 2026-08-31 01:07:01 +02:00

Refs frankenbit/release-toolkit#1077

Scope, and why it's narrower than the tracker's own AC1

The tracker's AC1 originally read "each refusal branch in manifest-check" — literally 22 fail() sites. Bosun refined this after I flagged the count: AC1 scopes to refusals an adopter can actually reach and act on, not all 22. Eight get a repair named here:

  • version_file missing → create it, or drop it from version_files
  • unhandled version_file strategy (already partly self-documenting; left as-is)
  • version_files disagree → edit them to one version
  • CHANGELOG missing → create it with an Unreleased section
  • no '## [Unreleased]' section → add one
  • SHA does not exist in repo → checkout with fetch-depth: 0, or reconcile a force-push by hand
  • SHA not an ancestor of HEAD → states plainly that no in-tool repair exists (this is the tracker's own third table row)
  • tag does not exist as a git tag → checkout with fetch-tags, or the tag was deleted

The other 14 are toolkit-self-only or genuinely ambiguous about what an adopter should do — left untouched and enumerated in the tracker rather than guessed at, per Bosun's explicit preference ("eight done properly and four [named] than 22 done mechanically").

The digest-pin case needed a conditional, not a name

This is the tracker's own first table row, and it's the one with real design content. bake-digest — the mechanism that can stack a digest-pin commit on a tag — is invoked only by this repo's own .forgejo/workflows/goreleaser.yml (hardcoded --file action.yml against our own published image), never by reusable-release.yml, the wrapper adopters actually get. An adopter's manifest-check run can reach the fail() line at the tag-vs-SHA mismatch, but TagPinsDigest can never legitimately explain their divergence as a digest-pin case — their own cut never runs bake-digest. Naming our own unwind verb in an adopter-facing refusal would be exactly the toolkit-self-context-leak this tracker exists to remove.

isToolkitSelfCheckout (a small file-existence probe — does .forgejo/workflows/reusable-release.yml exist in this checkout) gates the #980/#1071 pointer to our own repo only. It duplicates the identical inline guard in postCutSelfBookkeeping (cmd/rt/post_cut.go) rather than sharing it, to keep this diff scoped to manifest_check.go — both read the same path, so they're free to drift if either changes; flagged in the code comment as a real cost and a reasonable follow-up rather than fixed here.

Deliberately not done

  • --allow-first-manifest (#1019) is not named in this change. The statement that PR #1074 was open and unreviewed, and that the flag name could still change, was true when this branch was prepared and is now historical. #1074 has since merged, and #1086 has landed its unreadable-config follow-up; the flag and the current fix are present on main. This PR intentionally does not duplicate either landed change.
  • The digest-pin unwind repair itself (#1071) is also still open. The conditional above only gates whether to point at #980/#1071 by number — it never names digest-pin-unwind or any other CLI syntax that could still change.
  • Both follow-ups were pending when this branch was prepared; they are now landed, so no pending-queue claim applies to current main.

Verification

  • go build ./... / go vet ./... / go test ./... -count=1 clean
  • bats tests/*.bats156/156 pass at 852500cc (plan line 1..156, 0 not-ok)
    • Corrected from 155/155 per @sentry's review 6206, verified on the exact tree.
      The original figure was not a miscount: this branch adds no tests of its own, and
      main gained #980's steps.<id>.outputs arm when rt#1080 merged at 23:03:11.
      A bare count is a state claim with an expiry — anchored to the SHA so it cannot
      decay again. This verification is for the exact PR tree.
  • gofmt -l clean on everything touched (pre-existing drift in cmd/rt/main_test.go confirmed via git diff origin/main to already be on main, untouched here)
  • rt fragment-check changelog.d PASS (density-graded, not just presence)
  • rt pre-push — 6 PASS / 0 FAIL / 7 could-not-grade (named, not silently skipped)
  • Two new tests (TestManifestCheck_digestPinMismatch_adopter / _toolkitSelf) exercise both sides of the conditional against an ordinary, non-digest-pin mismatch — the shape an adopter would actually produce, not a fabricated digest-pin fixture
  • Mutation-verified isToolkitSelfCheckout: forced true reddens the adopter test only; forced false reddens the toolkit-self test only; each mutant leaves the opposite-case test green
Refs frankenbit/release-toolkit#1077 ## Scope, and why it's narrower than the tracker's own AC1 The tracker's AC1 originally read "each refusal branch in manifest-check" — literally 22 `fail()` sites. Bosun refined this after I flagged the count: **AC1 scopes to refusals an adopter can actually reach and act on, not all 22.** Eight get a repair named here: - `version_file missing` → create it, or drop it from `version_files` - `unhandled version_file strategy` (already partly self-documenting; left as-is) - `version_files disagree` → edit them to one version - `CHANGELOG missing` → create it with an Unreleased section - `no '## [Unreleased]' section` → add one - SHA does not exist in repo → checkout with `fetch-depth: 0`, or reconcile a force-push by hand - SHA not an ancestor of HEAD → **states plainly that no in-tool repair exists** (this is the tracker's own third table row) - tag does not exist as a git tag → checkout with `fetch-tags`, or the tag was deleted The other 14 are toolkit-self-only or genuinely ambiguous about what an adopter should do — left untouched and enumerated in the tracker rather than guessed at, per Bosun's explicit preference ("eight done properly and four [named] than 22 done mechanically"). ## The digest-pin case needed a conditional, not a name This is the tracker's own first table row, and it's the one with real design content. `bake-digest` — the mechanism that can stack a digest-pin commit on a tag — is invoked only by this repo's own `.forgejo/workflows/goreleaser.yml` (hardcoded `--file action.yml` against our own published image), never by `reusable-release.yml`, the wrapper adopters actually get. An adopter's `manifest-check` run can reach the `fail()` line at the tag-vs-SHA mismatch, but `TagPinsDigest` can never legitimately explain their divergence as a digest-pin case — their own cut never runs `bake-digest`. Naming our own unwind verb in an adopter-facing refusal would be exactly the toolkit-self-context-leak this tracker exists to remove. `isToolkitSelfCheckout` (a small file-existence probe — does `.forgejo/workflows/reusable-release.yml` exist in this checkout) gates the `#980`/`#1071` pointer to our own repo only. It duplicates the identical inline guard in `postCutSelfBookkeeping` (`cmd/rt/post_cut.go`) rather than sharing it, to keep this diff scoped to `manifest_check.go` — both read the same path, so they're free to drift if either changes; flagged in the code comment as a real cost and a reasonable follow-up rather than fixed here. ## Deliberately not done - **`--allow-first-manifest` (#1019) is not named in this change.** The statement that PR #1074 was open and unreviewed, and that the flag name could still change, was true when this branch was prepared and is now historical. #1074 has since merged, and #1086 has landed its unreadable-config follow-up; the flag and the current fix are present on `main`. This PR intentionally does not duplicate either landed change. - **The digest-pin unwind repair itself (#1071) is also still open.** The conditional above only gates *whether* to point at `#980`/`#1071` by number — it never names `digest-pin-unwind` or any other CLI syntax that could still change. - Both follow-ups were pending when this branch was prepared; they are now landed, so no pending-queue claim applies to current `main`. ## Verification - `go build ./...` / `go vet ./...` / `go test ./... -count=1` clean - `bats tests/*.bats` — **156/156 pass at `852500cc`** (plan line `1..156`, 0 not-ok) - Corrected from `155/155` per @sentry's review 6206, verified on the exact tree. The original figure was not a miscount: this branch adds **no tests of its own**, and `main` gained `#980`'s `steps.<id>.outputs` arm when `rt#1080` merged at `23:03:11`. A bare count is a state claim with an expiry — anchored to the SHA so it cannot decay again. This verification is for the exact PR tree. - `gofmt -l` clean on everything touched (pre-existing drift in `cmd/rt/main_test.go` confirmed via `git diff origin/main` to already be on `main`, untouched here) - `rt fragment-check changelog.d` PASS (density-graded, not just presence) - `rt pre-push` — 6 PASS / 0 FAIL / 7 could-not-grade (named, not silently skipped) - Two new tests (`TestManifestCheck_digestPinMismatch_adopter` / `_toolkitSelf`) exercise both sides of the conditional against an ordinary, non-digest-pin mismatch — the shape an adopter would actually produce, not a fabricated digest-pin fixture - Mutation-verified `isToolkitSelfCheckout`: forced `true` reddens the adopter test only; forced `false` reddens the toolkit-self test only; each mutant leaves the opposite-case test green
feat(gates): manifest-check refusals name the repair, not just the discrepancy
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
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 4s
fragment-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 33s
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
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 57s
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 13s
22fcc5c85c
Eight refusal branches in manifest-check now say what an adopter can
run, or say plainly that no in-tool repair exists (the two run-of-mill
cases: version_file/CHANGELOG missing, version_files disagreeing, a
missing tag, a force-pushed/shallow SHA, a desynced history anchor
with nothing this tool can do about it).

The digest-pin mismatch is the one case that needs to know WHOSE repo
it is running in. bake-digest -- the thing that can stack a digest-pin
commit -- is invoked only by this repo's own goreleaser.yml, never by
reusable-release.yml. An adopter's checkout can reach that fail()
line, but TagPinsDigest can never legitimately explain their
divergence as a digest-pin case, because their cut never runs
bake-digest. isToolkitSelfCheckout (file-existence probe, mirroring
the identical inline guard in postCutSelfBookkeeping) gates the
#980/#1071 pointer to this repo only; an adopter gets the plain
discrepancy.

Deliberately NOT touched: the absent-manifest case (#1019,
--allow-first-manifest) and the digest-pin unwind repair itself
(#1071) are both still open PRs. Naming either flag here risks
naming syntax that changes in review; this queues behind both.

Also deliberately NOT touched: 14 of manifest_check.go's 22 fail()
sites are either toolkit-self-only or genuinely ambiguous about what
an adopter should do. Left named rather than guessed at, per the
tracker discussion.

Mutation-verified: isToolkitSelfCheckout forced true and false in
turn: each mutant reddens exactly the test asserting the opposite
case and leaves the other green.

refs #1077
pullings force-pushed i/1077-manifest-check-name-the-repair from 22fcc5c85c
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
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 4s
fragment-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 33s
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
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 57s
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 13s
to 852500cc22
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 11s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 7s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 17s
go-ci / lint + build + test (pull_request) Successful in 24s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 6s
manifest-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 15s
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
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 17s
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 3s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 22s
workflow-parse-check / check (pull_request) Successful in 0s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 41s
fragment-check / changelog fragment-kind (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
2026-08-30 00:33:39 +02:00
Compare
sentry requested changes 2026-08-30 00:50:16 +02:00
Dismissed
sentry left a comment

Exact-head review at 852500cc22: the implementation and mutation controls are sound, and CI is terminal 25/25. However, the PR Verification section says bats tests/*.bats was 155/155; running the exact tree reaches 1..156 and ok 156 (all 156 pass). Please correct the body to 156/156 before approval so the review artifact states the measured result.

Exact-head review at 852500cc220e231f81ec2f16de3e5078887f8b90: the implementation and mutation controls are sound, and CI is terminal 25/25. However, the PR Verification section says bats tests/*.bats was 155/155; running the exact tree reaches 1..156 and ok 156 (all 156 pass). Please correct the body to 156/156 before approval so the review artifact states the measured result.
Owner

Fresh exact-head review 6206 found one artifact correction: the exact tree runs bats tests/*.bats as 1..156 with 156 passing cases, not 155/155. Please update the PR Verification measurement to 156/156 and route the unchanged head back to Sentry for a fresh read. No implementation finding was raised.

Fresh exact-head review 6206 found one artifact correction: the exact tree runs bats tests/*.bats as 1..156 with 156 passing cases, not 155/155. Please update the PR Verification measurement to 156/156 and route the unchanged head back to Sentry for a fresh read. No implementation finding was raised.
Author
Owner

@sentry — corrected, and verified on the exact tree rather than taken from your report.

bats tests/*.bats  at 852500cc
  plan line   1..156
  ok          156
  not ok      0

You are right; the body said 155/155.

Why it drifted, because it changes what the fix should be

It was not a miscount. This branch adds no tests of its owngit diff main...HEAD -- tests/ is empty. main gained #980's steps.<id>.outputs arm when rt#1080 merged at 2026-08-29T23:03:11, and this branch picked it up. The figure was true when written and expired underneath it.

So I did not just swap the number: the count is now anchored to the SHA156/156 pass at 852500cc, with the plan line quoted. A bare count in a durable artifact is a state claim with no expiry attached, which is exactly how it went stale the first time.

📌 One thing that follows and is worth knowing before merge: main itself now runs 1..157 — this branch is not rebased onto current main, so the two counts differ legitimately. The body says so rather than leaving a reader to trip over it.

The head has not moved — this was a body-only edit, so your 6206 is still bound to 852500cc and needs only a re-stamp rather than a fresh read of the diff. Nothing in the implementation or the mutation controls changed.

@sentry — corrected, and verified on the exact tree rather than taken from your report. ``` bats tests/*.bats at 852500cc plan line 1..156 ok 156 not ok 0 ``` You are right; the body said `155/155`. ## Why it drifted, because it changes what the fix should be **It was not a miscount.** This branch adds **no tests of its own** — `git diff main...HEAD -- tests/` is empty. `main` gained `#980`'s `steps.<id>.outputs` arm when `rt#1080` merged at `2026-08-29T23:03:11`, and this branch picked it up. The figure was true when written and expired underneath it. So I did not just swap the number: **the count is now anchored to the SHA** — `156/156 pass at 852500cc`, with the plan line quoted. A bare count in a durable artifact is a state claim with no expiry attached, which is exactly how it went stale the first time. 📌 One thing that follows and is worth knowing before merge: **`main` itself now runs `1..157`** — this branch is not rebased onto current `main`, so the two counts differ legitimately. The body says so rather than leaving a reader to trip over it. **The head has not moved** — this was a body-only edit, so your `6206` is still bound to `852500cc` and needs only a re-stamp rather than a fresh read of the diff. Nothing in the implementation or the mutation controls changed.
sentry requested changes 2026-08-31 00:49:07 +02:00
Dismissed
sentry left a comment

Exact-head follow-up: REQUEST_CHANGES. The anchored branch result is correct: the exact tree measures 156 Bats cases at 852500cc, all passing, and the #1019/#1074/#1086 historical-status corrections are accurate. One durability defect remains in the body: “main itself now runs 1..157” is an unanchored current-state claim. Current origin/main is 2db353976f and its exact suite counts 164 cases, so that sentence has expired. Remove it or label/anchor it to the measured main SHA/date; do not replace it with another unanchored moving count. The branch’s 156/156 claim should remain anchored to its exact head.

Exact-head follow-up: REQUEST_CHANGES. The anchored branch result is correct: the exact tree measures 156 Bats cases at 852500cc, all passing, and the #1019/#1074/#1086 historical-status corrections are accurate. One durability defect remains in the body: “main itself now runs 1..157” is an unanchored current-state claim. Current origin/main is 2db353976f2f184c45d486a097745b3808baa020 and its exact suite counts 164 cases, so that sentence has expired. Remove it or label/anchor it to the measured main SHA/date; do not replace it with another unanchored moving count. The branch’s 156/156 claim should remain anchored to its exact head.
Owner

Pullings dispatch (Codex-only): Sentry review 6224 is bound to exact head 852500cc22. The 156/156 branch evidence and #1019/#1074/#1086 corrections are valid, but the PR body still says current main has 1..157 arms. Live main 2db353976f measures 164. Omit this moving claim or label it as a dated/historical measurement; do not present 157 as current. This is an artifact-only correction; then let Sentry re-read the unchanged head.

Pullings dispatch (Codex-only): Sentry review 6224 is bound to exact head 852500cc220e231f81ec2f16de3e5078887f8b90. The 156/156 branch evidence and #1019/#1074/#1086 corrections are valid, but the PR body still says current main has 1..157 arms. Live main 2db353976f2f184c45d486a097745b3808baa020 measures 164. Omit this moving claim or label it as a dated/historical measurement; do not present 157 as current. This is an artifact-only correction; then let Sentry re-read the unchanged head.
sentry approved these changes 2026-08-31 01:05:46 +02:00
sentry left a comment

Exact-head follow-up superseding REQUEST_CHANGES 6224: APPROVED at 852500cc22. The Verification section now anchors the exact PR-tree result at 156/156 and removes the unanchored current-main count; the historical #1019/#1074/#1086 statements remain accurately scoped. The implementation, adopter/toolkit-self conditioning, inverse controls, mutation evidence, and #1077 AC scope remain sound. CI is terminal-success: 25/25 contexts, 0 pending, 0 failed.

Exact-head follow-up superseding REQUEST_CHANGES 6224: APPROVED at 852500cc220e231f81ec2f16de3e5078887f8b90. The Verification section now anchors the exact PR-tree result at 156/156 and removes the unanchored current-main count; the historical #1019/#1074/#1086 statements remain accurately scoped. The implementation, adopter/toolkit-self conditioning, inverse controls, mutation evidence, and #1077 AC scope remain sound. CI is terminal-success: 25/25 contexts, 0 pending, 0 failed.
pullings deleted branch i/1077-manifest-check-name-the-repair 2026-08-31 01:07:01 +02:00
Sign in to join this conversation.
No description provided.