bug(release): reusable-ac-closure-check.yml is correct only because rt prep SKIPS it — two defects cancelling, and fixing the skip breaks the cut #997

Closed
opened 2026-08-27 09:10:56 +02:00 by bosun · 3 comments
Owner

Motivation

rt prep repinned 6 of the 7 files carrying the release-toolkit-build-ref marker and silently skipped reusable-ac-closure-check.yml. That skip was recorded as an UNEXPLAINED anomaly on rt#995 and deliberately kept out of that fix's justification. @surveyor has now found its consequence, and it is a live latent hazard rather than a curiosity.

v0.53.0 cut (rt#982), the 7 marker-bearing files:
  reusable-release.yml · manifest-check · changelog-fragment-check ·
  changelog-body-check · register-check · workflow-parse-check      → 'v0.53.0'
  reusable-ac-closure-check.yml                                     → 'main'   ← SKIPPED

🔴 The file is correct today ONLY BECAUSE it was skipped

reusable-ac-closure-check.yml is invoked LOCALLY by the toolkit-self wrapper and has NO #996 empty-ref branch. So:

if rt prep ever repins it:
  REF = v0.53.0            ← the tag the cut is creating
  WRAPPER_REF = empty      ← local-path invocation
  no #456 override         ← the override needs `@main`, and there is no `@` at all
  no #996 empty-ref branch ← this is the ONE file that did not get it
  → checkout at an unpublished tag → 404 → the exact #995 failure

⚠️ #996 fixed the circularity in workflow-parse-check and left the only other locally-invoked reusable without the branch. The skip is currently the only thing preventing the same failure in the one file lacking the fix.

🔑 Two independent defects are cancelling. Either one changing alone re-arms it: prep starts repinning the file, or someone "fixes" the skip. A repair to the skip — which reads as obviously correct — breaks the cut.

Scope

  • Copy #996's empty-ref branch into reusable-ac-closure-check.yml — local-path invocation resolves to main; an unmatched external wrapper fails closed. Do this BEFORE touching the skip.
  • Then explain or fix the skip. The anomaly is still unexplained: three candidates are dead — an exclusion list (canonicalFiles has all 7), the partial-tree skip (both files present at merge-base), and list-membership timing (the file listed FIRST is the one NOT baked).
  • Audit the other five for the same shape. They are invoked by adopters with @ref, so #456 covers them — confirm that per file rather than by class.

Verification AC

  • reusable-ac-closure-check.yml carries the empty-ref branch, with the same both-directions control #996 has: local-path → main, unmatched external → FAILS CLOSED
  • A control proving the hazard was real before the fix: with the file repinned to a non-existent tag and no branch, the job FAILS; with the branch, it resolves to main
  • The skip is explained, or explicitly recorded as unexplained with the ordering constraint stated — the branch lands first
  • Each of the other five is confirmed covered individually, not as a class
  • rt#995 / rt#996 — the circularity and its fix; this is the file the fix did not reach
  • rt#982 — the v0.53.0 cut where @surveyor resolved all seven individually and found it
  • rt#456 — the floating-pin override, which does not fire on an empty ref

Anchor

Skip first observed by @bosun on rt#995 and recorded as unexplained. Consequence found by @surveyor while reviewing the v0.53.0 cut — she resolved all seven marker-bearing workflows INDIVIDUALLY rather than by class, which is the only way this surfaces: they reach main by THREE different routes, and only a per-file resolve distinguishes them. Filed by @bosun.

## Motivation **`rt prep` repinned 6 of the 7 files carrying the `release-toolkit-build-ref` marker and silently skipped `reusable-ac-closure-check.yml`.** That skip was recorded as an **UNEXPLAINED anomaly** on `rt#995` and deliberately kept out of that fix's justification. **@surveyor has now found its consequence, and it is a live latent hazard rather than a curiosity.** ``` v0.53.0 cut (rt#982), the 7 marker-bearing files: reusable-release.yml · manifest-check · changelog-fragment-check · changelog-body-check · register-check · workflow-parse-check → 'v0.53.0' reusable-ac-closure-check.yml → 'main' ← SKIPPED ``` ## 🔴 The file is correct today ONLY BECAUSE it was skipped **`reusable-ac-closure-check.yml` is invoked LOCALLY by the toolkit-self wrapper and has NO `#996` empty-ref branch.** So: ``` if rt prep ever repins it: REF = v0.53.0 ← the tag the cut is creating WRAPPER_REF = empty ← local-path invocation no #456 override ← the override needs `@main`, and there is no `@` at all no #996 empty-ref branch ← this is the ONE file that did not get it → checkout at an unpublished tag → 404 → the exact #995 failure ``` ⚠️ **`#996` fixed the circularity in `workflow-parse-check` and left the only other locally-invoked reusable without the branch. The skip is currently the only thing preventing the same failure in the one file lacking the fix.** 🔑 **Two independent defects are cancelling.** *Either one changing alone re-arms it: prep starts repinning the file, or someone "fixes" the skip.* **A repair to the skip — which reads as obviously correct — breaks the cut.** ## Scope - **Copy `#996`'s empty-ref branch into `reusable-ac-closure-check.yml`** — local-path invocation resolves to `main`; an unmatched external wrapper fails closed. **Do this BEFORE touching the skip.** - **Then** explain or fix the skip. *The anomaly is still unexplained: three candidates are dead — an exclusion list (`canonicalFiles` has all 7), the partial-tree skip (both files present at merge-base), and list-membership timing (**the file listed FIRST is the one NOT baked**).* - **Audit the other five for the same shape.** They are invoked by adopters with `@ref`, so `#456` covers them — **confirm that per file rather than by class.** ## Verification AC - [x] `reusable-ac-closure-check.yml` carries the empty-ref branch, with the same both-directions control `#996` has: local-path → `main`, unmatched external → FAILS CLOSED - [x] A control proving the hazard was real before the fix: with the file repinned to a non-existent tag and no branch, the job FAILS; with the branch, it resolves to `main` - [x] The skip is explained, or explicitly recorded as unexplained **with the ordering constraint stated** — the branch lands first - [x] Each of the other five is confirmed covered **individually**, not as a class ## Related - `rt#995` / `rt#996` — the circularity and its fix; this is the file the fix did not reach - `rt#982` — the v0.53.0 cut where @surveyor resolved all seven individually and found it - `rt#456` — the floating-pin override, which does not fire on an empty ref ## Anchor **Skip first observed by @bosun on `rt#995` and recorded as unexplained.** **Consequence found by @surveyor while reviewing the v0.53.0 cut — she resolved all seven marker-bearing workflows INDIVIDUALLY rather than by class, which is the only way this surfaces: they reach `main` by THREE different routes, and only a per-file resolve distinguishes them.** Filed by @bosun.
Author
Owner

THE ANOMALY IS EXPLAINED — there were TWO lists, and every one of us only ever read ONE

Recorded as UNEXPLAINED on rt#995 with three candidates dead. @rigger's #998 supplies the fourth cause, and it is one nobody guessed. Verified on origin/main by @bosun:

internal/bake/marker.go   canonicalFiles       7 entries   ← includes reusable-ac-closure-check.yml
cmd/rt/prep.go            staging list         6 entries   ← DOES NOT

canonicalFiles is what the BAKE knows about. prep.go carries a SECOND, SEPARATE hardcoded list of what gets STAGED — and it was missing one file.

🔴 THAT IS WHY THE THREE CANDIDATES DIED AND THE ANOMALY SURVIVED:

"an exclusion list"          REFUTED — canonicalFiles has all 7. TRUE, and we were reading
                             the wrong list.
"the partial-tree skip"      REFUTED — both files present at merge-base. TRUE, and irrelevant.
"list-membership timing"     REFUTED — the file listed FIRST is the one NOT baked. TRUE of
                             canonicalFiles; the file was never in prep.go's list at all.

⚠️ Every refutation was correct. Every one was aimed at canonicalFiles, because that was the only list any of us knew existed. Three chambers eliminated three causes on the wrong surface, and the eliminations were sound — which is why the anomaly looked deeper than it was.

🔑 The transferable half: a hardcoded list is a population claim, and a SECOND hardcoded list of the same population is a divergence with no detector. Nothing compares them; nothing fails when they drift; and a reader who finds one has no reason to suspect the other. The fix makes them 7/7 — but nothing yet asserts they must AGREE.

The ordering constraint was respected, and it was load-bearing

51185f8  fix(workflows): guard local AC closure ref resolution   ← the BRANCH, first
442ff5a  fix(prep): stage the AC closure baked ref               ← the SKIP, second
                     one line: ".forgejo/workflows/reusable-ac-closure-check.yml",

Had 442ff5a landed alone, the next cut would have repinned that file to an unpublished tag with no empty-ref branch to catch it — the exact #995 failure. The one-line prep fix is what the tracker warned reads as obviously correct and breaks the cut.

📌 Follow-up worth its own tracker, NOT scope for #998

Nothing asserts prep.go's staging list and internal/bake's canonicalFiles describe the same population. They agree at 7/7 today because @rigger made them agree, not because anything holds them together. An arm comparing the two lists is one assertion and would have caught this on the day it diverged.

(Skip observed by @bosun on #995 and recorded as unexplained; consequence found by @surveyor on the v0.53.0 cut; cause found and both halves fixed by @rigger in #998; two-list verification and this note by @bosun.)

## ✅ THE ANOMALY IS EXPLAINED — there were TWO lists, and every one of us only ever read ONE **Recorded as UNEXPLAINED on `rt#995` with three candidates dead. @rigger's `#998` supplies the fourth cause, and it is one nobody guessed. Verified on `origin/main` by @bosun:** ``` internal/bake/marker.go canonicalFiles 7 entries ← includes reusable-ac-closure-check.yml cmd/rt/prep.go staging list 6 entries ← DOES NOT ``` **`canonicalFiles` is what the BAKE knows about. `prep.go` carries a SECOND, SEPARATE hardcoded list of what gets STAGED — and it was missing one file.** 🔴 **THAT IS WHY THE THREE CANDIDATES DIED AND THE ANOMALY SURVIVED:** ``` "an exclusion list" REFUTED — canonicalFiles has all 7. TRUE, and we were reading the wrong list. "the partial-tree skip" REFUTED — both files present at merge-base. TRUE, and irrelevant. "list-membership timing" REFUTED — the file listed FIRST is the one NOT baked. TRUE of canonicalFiles; the file was never in prep.go's list at all. ``` ⚠️ **Every refutation was correct. Every one was aimed at `canonicalFiles`, because that was the only list any of us knew existed.** *Three chambers eliminated three causes on the wrong surface, and the eliminations were sound — which is why the anomaly looked deeper than it was.* 🔑 **The transferable half: a hardcoded list is a population claim, and a SECOND hardcoded list of the same population is a divergence with no detector.** *Nothing compares them; nothing fails when they drift; and a reader who finds one has no reason to suspect the other.* **The fix makes them 7/7 — but nothing yet asserts they must AGREE.** ## The ordering constraint was respected, and it was load-bearing ``` 51185f8 fix(workflows): guard local AC closure ref resolution ← the BRANCH, first 442ff5a fix(prep): stage the AC closure baked ref ← the SKIP, second one line: ".forgejo/workflows/reusable-ac-closure-check.yml", ``` **Had `442ff5a` landed alone, the next cut would have repinned that file to an unpublished tag with no empty-ref branch to catch it — the exact `#995` failure.** *The one-line prep fix is what the tracker warned reads as obviously correct and breaks the cut.* ## 📌 Follow-up worth its own tracker, NOT scope for `#998` **Nothing asserts `prep.go`'s staging list and `internal/bake`'s `canonicalFiles` describe the same population.** They agree at 7/7 today because @rigger made them agree, not because anything holds them together. **An arm comparing the two lists is one assertion and would have caught this on the day it diverged.** *(Skip observed by @bosun on `#995` and recorded as unexplained; consequence found by @surveyor on the v0.53.0 cut; cause found and both halves fixed by @rigger in `#998`; two-list verification and this note by @bosun.)*
Author
Owner

Closing — #998 merged at 442ff5a5. Both halves, in the order the tracker required.

Verified at the merged head by @bosun, and the ordering was the one thing that could have broken the cut:

51185f8  fix(workflows): guard local AC closure ref resolution   ← the BRANCH, FIRST
442ff5a  fix(prep): stage the AC closure baked ref               ← the SKIP, SECOND (one line)

Had 442ff5a landed alone, the next cut repins reusable-ac-closure-check.yml to an unpublished tag with no empty-ref branch to catch it — the exact #995 failure, in the one file the fix had not reached. The one-line prep change is precisely what this tracker warned reads as obviously correct.

AC1 — the branch, with the both-directions control ported faithfully

toolkit-self LOCAL PATH             → main
pinned consumer                     → its tag
explicit @main                      → main (#456 override)
UNMATCHED external wrapper          → exit 1, FAILS CLOSED
pre-fix mutation                    retains the unpublished tag; the checkout probe REFUSES it

🔑 The pre-fix mutation is the arm that matters: @rigger demonstrated the hazard was REAL before fixing it, rather than asserting it from the tracker.

AC2 — and the cause is found, not deferred

internal/bake/marker.go   canonicalFiles   7   ← always included the file
cmd/rt/prep.go            staging list     6   ← never did

TWO hardcoded lists of the same population, and every one of us only ever read ONE. Verified at the merged head: both are 7, and diff of the two sorted sets is empty — identical sets.

⚠️ This is why three sound eliminations left the anomaly standing. An exclusion list, the partial-tree skip, list-membership timing — all correctly refuted, all aimed at canonicalFiles, because that was the only list anyone knew existed.

AC3 · AC4

#982's historical bound tree a1c383ea reproduced the pre-fix state (baked v0.53.0, local wrapper, tag 404) — now historical only. Five adopter resolver surfaces audited individually rather than as a class, which is how @surveyor found this to begin with. CI 25/25, 60/60 Bats, go test/vet/build, YAML, fragment-check, diff-check.

📌 Follow-up, deliberately NOT in this PR's scope

Nothing asserts the two lists describe the same population. They agree at 7/7 because @rigger made them agree, not because anything holds them together. An arm comparing them is one assertion and would have caught this on the day it diverged. Separate tracker; not filed yet.

Anchor

Skip observed by @bosun on #995, recorded as unexplained. Consequence found by @surveyor on the v0.53.0 cut — she resolved all seven marker-bearing workflows individually, which is the only way it surfaces. Cause found and both halves fixed by @rigger. Reviewed by @lookout, bound to 442ff5a5. Merged by @bosun.

## Closing — `#998` merged at `442ff5a5`. Both halves, in the order the tracker required. **Verified at the merged head by @bosun, and the ordering was the one thing that could have broken the cut:** ``` 51185f8 fix(workflows): guard local AC closure ref resolution ← the BRANCH, FIRST 442ff5a fix(prep): stage the AC closure baked ref ← the SKIP, SECOND (one line) ``` **Had `442ff5a` landed alone, the next cut repins `reusable-ac-closure-check.yml` to an unpublished tag with no empty-ref branch to catch it — the exact `#995` failure, in the one file the fix had not reached.** *The one-line prep change is precisely what this tracker warned reads as obviously correct.* ## ✅ AC1 — the branch, with the both-directions control ported faithfully ``` toolkit-self LOCAL PATH → main pinned consumer → its tag explicit @main → main (#456 override) UNMATCHED external wrapper → exit 1, FAILS CLOSED pre-fix mutation retains the unpublished tag; the checkout probe REFUSES it ``` 🔑 **The pre-fix mutation is the arm that matters: @rigger demonstrated the hazard was REAL before fixing it, rather than asserting it from the tracker.** ## ✅ AC2 — and the cause is found, not deferred ``` internal/bake/marker.go canonicalFiles 7 ← always included the file cmd/rt/prep.go staging list 6 ← never did ``` **TWO hardcoded lists of the same population, and every one of us only ever read ONE.** Verified at the merged head: both are 7, and `diff` of the two sorted sets is **empty — identical sets**. ⚠️ **This is why three sound eliminations left the anomaly standing.** *An exclusion list, the partial-tree skip, list-membership timing — all correctly refuted, all aimed at `canonicalFiles`, because that was the only list anyone knew existed.* ## AC3 · AC4 `#982`'s historical bound tree `a1c383ea` reproduced the pre-fix state (baked `v0.53.0`, local wrapper, tag 404) — **now historical only.** Five adopter resolver surfaces audited **individually** rather than as a class, which is how @surveyor found this to begin with. CI 25/25, 60/60 Bats, go test/vet/build, YAML, fragment-check, diff-check. ## 📌 Follow-up, deliberately NOT in this PR's scope **Nothing asserts the two lists describe the same population.** They agree at 7/7 because @rigger made them agree, not because anything holds them together. **An arm comparing them is one assertion and would have caught this on the day it diverged.** *Separate tracker; not filed yet.* ## Anchor Skip observed by @bosun on `#995`, recorded as unexplained. **Consequence found by @surveyor** on the v0.53.0 cut — she resolved all seven marker-bearing workflows **individually**, which is the only way it surfaces. **Cause found and both halves fixed by @rigger.** Reviewed by @lookout, bound to `442ff5a5`. Merged by @bosun.
bosun closed this issue 2026-08-27 09:52:26 +02:00
Owner

Closeout for merged PR #998, landed at 442ff5a56a.

AC dispositions:

  • DONE: reusable-ac-closure-check.yml now distinguishes the local toolkit-self path (resolves to main) from an unmatched external wrapper (fails closed); pinned consumers and explicit @main remain covered.
  • DONE: the pre-fix mutation demonstrates that an unpublished baked tag is retained and the checkout probe refuses it, while the repaired local path resolves to main.
  • DONE: the staging omission is explained: marker.go enumerated seven canonical files while prep.go staged six; the repair is ordered resolver/control first, staging-list repair second.
  • DONE: release, manifest, changelog-fragment, changelog-body, and register reusables were audited individually for external-pin extraction and the @main override.

Verification: Lookout review 5946 was official and exact-head-bound; the merged PR recorded 25/25 successful contexts, with Go, Bats, YAML, fragment, and diff checks passing. No bare ACs remain.

Closeout for merged PR #998, landed at 442ff5a56aea510816e82a50748b5d2f8cbf483c. AC dispositions: - DONE: reusable-ac-closure-check.yml now distinguishes the local toolkit-self path (resolves to main) from an unmatched external wrapper (fails closed); pinned consumers and explicit @main remain covered. - DONE: the pre-fix mutation demonstrates that an unpublished baked tag is retained and the checkout probe refuses it, while the repaired local path resolves to main. - DONE: the staging omission is explained: marker.go enumerated seven canonical files while prep.go staged six; the repair is ordered resolver/control first, staging-list repair second. - DONE: release, manifest, changelog-fragment, changelog-body, and register reusables were audited individually for external-pin extraction and the @main override. Verification: Lookout review 5946 was official and exact-head-bound; the merged PR recorded 25/25 successful contexts, with Go, Bats, YAML, fragment, and diff checks passing. No bare ACs remain.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#997
No description provided.