feat(cut): auto-re-pin self-workflows post-cut + fix runtime bake-reset gap (#283 + #204 Part 2 graft) #288

Merged
bosun merged 3 commits from i/283-auto-repin-self-workflows into main 2026-07-02 16:00:50 +02:00

Why

Set E pickup per Bosun 5f4c dispatch — automates the last "manual reminder for mechanical work" gap in the cut cycle (release-toolkit#283). Also folds in Engineer's staged #204 Part 2 cut_complete.ms_total graft (Engineer d551 bus call).

Discovered mid-work: a latent v0.20.0 bake gap. During probing I found that v0.20.0 shipped reusable-changelog-fragment-check.yml with BUILD_BAKED_TOOLKIT_REF: 'main' instead of 'v0.20.0'. Root cause: reusable-release.yml's three runtime bake-reset loops (introduced with the reusable in #278) only listed the two pre-existing reusables. When release-prep.sh:541's bake step ran during the v0.20.0 cut (using scripts from @v0.20.0-rc.2 which pre-dated the Set D bake-list registration fix), the fragment-check reusable's marker wasn't baked. Impact: a consumer pinning fragment-check.yml @v0.20.0 gets the reusable YAML at v0.20.0 but its scripts at main — substrate drift class. Fixed here alongside the auto-re-pin substrate work.

What

Four cohesive changes to reusable-release.yml + one test extension + Engineer's script-side patch:

1. Auto-re-pin (main purpose)

After each cut, reusable-release.yml rewrites each toolkit-self consumer wrapper's uses: ...reusable-*.yml@<ref> to ${CUT_TAG}:

for f in .forgejo/workflows/release.yml .forgejo/workflows/manifest-check.yml .forgejo/workflows/fragment-check.yml; do
  if [[ -f "$f" ]]; then
    sed -i -E "s|(uses:[[:space:]]+frankenbit/release-toolkit/\.forgejo/workflows/reusable-[A-Za-z0-9-]+\.yml@)[^[:space:]]+|\1${CUT_TAG}|" "$f"
    git add "$f"
  fi
done

Bundled with the bake-reset commit (both [skip ci] YAML mutations sharing the same push, per Bosun's single-commit-for-atomicity lean). Toolkit-self only — if [[ -f "$f" ]] guard skips files that don't exist on external consumers.

New commit message: chore: post-cut bookkeeping for ${CUT_TAG} [skip ci] (reflects combined bake-reset + re-pin scope; #179 + #202 regression guards updated to match).

2. Runtime bake-reset gap fix (discovered fold-in)

Added reusable-changelog-fragment-check.yml to all three for f in ... loops in reusable-release.yml:

  • Line ~408: bake_toolkit_ref post-cut reset to 'main'
  • Line ~491: path-α git add staging
  • Line ~527: path-γ git add staging

Next cut with this PR merged will properly bake all 3 reusables.

3. Extended workflows.bats regression guard

The Set D bake-marker guard (#278) walked release-prep.sh + check-self-bootstrap.sh + build_bake.sh but not reusable-release.yml's runtime loops. Extended to also assert each reusable-*.yml with BUILD_BAKED_TOOLKIT_REF: appears in reusable-release.yml at least THREE times. Reds on the v0.20.0 pre-fix state (proven); greens after the loop fix in change #2.

4. Env exports for #204 Part 2 (Engineer graft)

Two lines complete Engineer's cut_complete.ms_total + path fields:

  • echo "CUT_START_MS=$(date +%s%3N)" >> "$GITHUB_ENV" at the top of the decide step
  • export CUT_PATH="$PUSH_MODE" in the act step, before draft-release.sh call. Also populates PUSH_MODE in dry-run so CUT_PATH resolves symmetrically.

Both optional-graceful — empty → empty fields, matching Engineer's api_call.ms degradation contract.

5. Engineer's staged script-side patch (grafted via git am)

Applied cleanly on main. Authorship preserved as engineer@. Full 533-test bats suite green pre-graft; 534-test after the extended #278 guard lands.

What this PR does NOT do

  • Does NOT retroactively fix v0.20.0's shipped fragment-check reusable. That reusable at the v0.20.0 tag stays at BUILD_BAKED_TOOLKIT_REF: 'main'. A v0.20.1 patch cut (or v0.21.0) will re-tag with all 3 properly baked. Consumers using fragment-check pinned @v0.20.0 see substrate drift (scripts at main), not a broken workflow — flagged as known-defect until next cut.
  • Does NOT change check-self-bootstrap.yml. It stays as fail-safe — fires only when the automation breaks, distinguishing "automation broke" from the current "operator forgot."
  • Does NOT touch external consumers. The auto-re-pin's file-existence guard is scoped to toolkit-self.
  • Does NOT split #204 Part 2 into a separate PR. Grafted per Engineer's d551 cadence call (QM-owned).

Refs

  • Closes: release-toolkit#283
  • Grafts: release-toolkit#204 Part 2 (Engineer d551)
  • Discovered fold-in fix: runtime bake-reset gap in reusable-release.yml (latent v0.20.0 defect)
  • Milestone: Set E — Full drain (release-toolkit#69)
  • Related mechanisms: #124 self-bootstrap check (fail-safe preserved), #172 build-bake, #273 RELEASE_TOOLKIT_TOKEN (auth reuse), #278 bake-marker guard (extended)
  • Bosun 5f4c ratification, Engineer d551 script-side patch

🤖 Generated with Claude Code

## Why Set E pickup per Bosun `5f4c` dispatch — automates the last "manual reminder for mechanical work" gap in the cut cycle (release-toolkit#283). Also folds in Engineer's staged [#204 Part 2 `cut_complete.ms_total` graft](https://git.frankenbit.de/frankenbit/release-toolkit/issues/204#issuecomment-78361) (Engineer `d551` bus call). **Discovered mid-work: a latent v0.20.0 bake gap.** During probing I found that `v0.20.0` shipped `reusable-changelog-fragment-check.yml` with `BUILD_BAKED_TOOLKIT_REF: 'main'` instead of `'v0.20.0'`. Root cause: `reusable-release.yml`'s three runtime bake-reset loops (introduced with the reusable in #278) only listed the two pre-existing reusables. When `release-prep.sh:541`'s bake step ran during the v0.20.0 cut (using scripts from `@v0.20.0-rc.2` which pre-dated the Set D bake-list registration fix), the fragment-check reusable's marker wasn't baked. Impact: a consumer pinning fragment-check.yml `@v0.20.0` gets the reusable YAML at v0.20.0 but its scripts at `main` — substrate drift class. **Fixed here alongside the auto-re-pin substrate work.** ## What Four cohesive changes to `reusable-release.yml` + one test extension + Engineer's script-side patch: ### 1. Auto-re-pin (main purpose) After each cut, `reusable-release.yml` rewrites each toolkit-self consumer wrapper's `uses: ...reusable-*.yml@<ref>` to `${CUT_TAG}`: ```bash for f in .forgejo/workflows/release.yml .forgejo/workflows/manifest-check.yml .forgejo/workflows/fragment-check.yml; do if [[ -f "$f" ]]; then sed -i -E "s|(uses:[[:space:]]+frankenbit/release-toolkit/\.forgejo/workflows/reusable-[A-Za-z0-9-]+\.yml@)[^[:space:]]+|\1${CUT_TAG}|" "$f" git add "$f" fi done ``` Bundled with the bake-reset commit (both `[skip ci]` YAML mutations sharing the same push, per Bosun's single-commit-for-atomicity lean). Toolkit-self only — `if [[ -f "$f" ]]` guard skips files that don't exist on external consumers. New commit message: `chore: post-cut bookkeeping for ${CUT_TAG} [skip ci]` (reflects combined bake-reset + re-pin scope; #179 + #202 regression guards updated to match). ### 2. Runtime bake-reset gap fix (discovered fold-in) Added `reusable-changelog-fragment-check.yml` to all three `for f in ...` loops in `reusable-release.yml`: - Line ~408: `bake_toolkit_ref` post-cut reset to `'main'` - Line ~491: path-α `git add` staging - Line ~527: path-γ `git add` staging Next cut with this PR merged will properly bake all 3 reusables. ### 3. Extended workflows.bats regression guard The Set D bake-marker guard (#278) walked `release-prep.sh` + `check-self-bootstrap.sh` + `build_bake.sh` but not `reusable-release.yml`'s runtime loops. Extended to also assert each `reusable-*.yml` with `BUILD_BAKED_TOOLKIT_REF:` appears in `reusable-release.yml` at least THREE times. Reds on the v0.20.0 pre-fix state (proven); greens after the loop fix in change #2. ### 4. Env exports for #204 Part 2 (Engineer graft) Two lines complete Engineer's `cut_complete.ms_total` + `path` fields: - `echo "CUT_START_MS=$(date +%s%3N)" >> "$GITHUB_ENV"` at the top of the decide step - `export CUT_PATH="$PUSH_MODE"` in the act step, before `draft-release.sh` call. Also populates `PUSH_MODE` in dry-run so CUT_PATH resolves symmetrically. Both optional-graceful — empty → empty fields, matching Engineer's `api_call.ms` degradation contract. ### 5. Engineer's staged script-side patch (grafted via `git am`) Applied cleanly on main. Authorship preserved as engineer@. Full 533-test bats suite green pre-graft; 534-test after the extended #278 guard lands. ## What this PR does NOT do - **Does NOT retroactively fix v0.20.0's shipped fragment-check reusable.** That reusable at the v0.20.0 tag stays at `BUILD_BAKED_TOOLKIT_REF: 'main'`. A v0.20.1 patch cut (or v0.21.0) will re-tag with all 3 properly baked. Consumers using fragment-check pinned `@v0.20.0` see substrate drift (scripts at main), not a broken workflow — flagged as known-defect until next cut. - **Does NOT change `check-self-bootstrap.yml`.** It stays as fail-safe — fires only when the automation breaks, distinguishing "automation broke" from the current "operator forgot." - **Does NOT touch external consumers.** The auto-re-pin's file-existence guard is scoped to toolkit-self. - **Does NOT split #204 Part 2 into a separate PR.** Grafted per Engineer's `d551` cadence call (QM-owned). ## Refs - Closes: [release-toolkit#283](https://git.frankenbit.de/frankenbit/release-toolkit/issues/283) - Grafts: [release-toolkit#204 Part 2](https://git.frankenbit.de/frankenbit/release-toolkit/issues/204#issuecomment-78361) (Engineer d551) - Discovered fold-in fix: runtime bake-reset gap in `reusable-release.yml` (latent v0.20.0 defect) - Milestone: **Set E — Full drain** (release-toolkit#69) - Related mechanisms: #124 self-bootstrap check (fail-safe preserved), #172 build-bake, #273 RELEASE_TOOLKIT_TOKEN (auth reuse), #278 bake-marker guard (extended) - Bosun 5f4c ratification, Engineer d551 script-side patch 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Completes the #159 JSON-logging taxonomy: cut_complete now carries ms_total
(total cut wall-clock in ms, decide -> publish) + path (alpha/gamma push-path).

A _emit_cut_complete helper computes ms_total FRESH at each emit site (the
idempotent-skip and real-create paths finish at different points) from the
CUT_START_MS env var, and reads CUT_PATH. Both degrade to empty off the
workflow path (direct invocation / non-GNU date lacking %N) -- the same
graceful-degradation contract api_call's ms uses, so the event is never wrong
off the workflow, only less-populated.

Fork A ratified (Bosun + QM): UNIFY -- one cut_complete carries
tag/commit/idempotent + ms_total + path, keeping kind="cut_complete" Loki
queries clean rather than a second workflow-level event colliding on kind.

Script-side only. The reusable-release.yml capture of CUT_START_MS / CUT_PATH
is staged to GRAFT onto the #283 organic re-pin per the QM cadence call
(deferring rather than burning a dedicated re-pin cycle for a compose-script
touch). Ready-to-graft: bats + fragment-check + shellcheck green.
Two related substrate-of-touch changes bundled per Bosun 5f4c dispatch:

## Auto-re-pin (main purpose)

After each cut, `reusable-release.yml` now rewrites each toolkit-self
consumer wrapper's `uses: ...reusable-*.yml@<ref>` to the just-cut
`${CUT_TAG}`. Applied to:

- `.forgejo/workflows/release.yml`
- `.forgejo/workflows/manifest-check.yml`
- `.forgejo/workflows/fragment-check.yml`

Bundled into the existing bake-reset commit (both are `[skip ci]` YAML
mutations sharing the same push, per Bosun's single-commit-for-
atomicity lean). Toolkit-self only — the `if [[ -f "$f" ]]` guard
skips files that don't exist, so external consumers aren't affected.

Bosun's #283 dispatch identified this as the exact class of "automated
invitation for manual work" worth automating away — the mechanism
mirrors the existing manifest-bump automation (structural, post-tag-
creation, RELEASE_TOOLKIT_TOKEN auth path already in place per #273).

`check-self-bootstrap.yml` stays as fail-safe: it now fires only when
the automation breaks, distinguishing "automation broke" from the
current "operator forgot." Backwards-compatible.

## Discovered latent v0.20.0 bake gap (fold-in fix)

During design, I probed `reusable-changelog-fragment-check.yml`'s
`BUILD_BAKED_TOOLKIT_REF` value at the `v0.20.0` tag:

    $ git show v0.20.0:.forgejo/workflows/reusable-changelog-fragment-check.yml \
        | grep BUILD_BAKED_TOOLKIT_REF
    BUILD_BAKED_TOOLKIT_REF: 'main'

Expected `v0.20.0`. Root cause: `reusable-release.yml`'s three runtime
bake-reset loops (introduced with the reusable in #278) only listed
the two pre-existing reusables. When `release-prep.sh:541`'s bake
step ran during the `v0.20.0` cut (using scripts from `@v0.20.0-rc.2`
which pre-dated the Set D bake-list registration fix), the fragment-
check reusable's marker wasn't baked. `chore(release): prepare
v0.20.0` (3d15a7e) shipped it at `main`.

Impact: a consumer pinning fragment-check.yml `@v0.20.0` gets the
reusable-YAML at v0.20.0 but its `.release-toolkit` scripts get
checked out at `main` — substrate drift class the toolkit ref-pin
mechanism (#172) was designed to prevent.

Fix: added `reusable-changelog-fragment-check.yml` to all three
`for f in ...` loops in `reusable-release.yml`:

  1. Line ~408: `bake_toolkit_ref` post-cut reset to 'main'
  2. Line ~491: path-α `git add` staging (bundled with re-pin)
  3. Line ~527: path-γ `git add` staging (bundled with re-pin)

The next cut with this PR merged will properly bake all 3 reusables.
v0.20.0's shipped fragment-check reusable remains latent-drift; a
v0.20.1 cut (or v0.21.0) will re-tag with all 3 properly baked.

## Extended workflows.bats regression guard

The Set D bake-marker guard (#278) walked release-prep.sh + check-
self-bootstrap.sh + build_bake.sh but not `reusable-release.yml`'s
runtime loops. Extended to also assert that each `reusable-*.yml`
with a `BUILD_BAKED_TOOLKIT_REF:` marker appears in `reusable-
release.yml` at least THREE times (once per loop). Reds on the v0.20.0
state (proven pre-fix); greens once the loops are updated.

## Regression guards adapted for the new commit subject

`#179` (bake-reset separate commit) + `#202` ([skip ci] in bake-reset
subject) both matched the old `chore: reset build-bake to 'main'
[skip ci]` subject; the new commit is `chore: post-cut bookkeeping
for ${CUT_TAG} [skip ci]` (reflects the combined bake-reset +
re-pin scope). Both guards updated to match; both still assert the
same invariants (bake-reset stays separate from manifest, `[skip ci]`
present).

## Env exports for #204 Part 2 (Engineer d551 graft)

Two lines in `reusable-release.yml` complete Engineer's #204 Part 2
`cut_complete.ms_total` + `path` fields:

- `echo "CUT_START_MS=$(date +%s%3N)" >> "$GITHUB_ENV"` at the top of
  the decide step (threaded via $GITHUB_ENV so subsequent steps
  inherit; `ms_total = now - CUT_START_MS` at each `_emit_cut_complete`
  site).
- `export CUT_PATH="$PUSH_MODE"` in the act step, before the
  `draft-release.sh` call. Also populates `PUSH_MODE` in dry-run so
  CUT_PATH threading resolves symmetrically.

Both optional-graceful (empty → empty fields), matching Engineer's
`api_call.ms` degradation contract.

## Coverage

- Full 534-test bats suite green (was 533 pre-graft + 1 net-new
  #278 guard extension = 534).
- `shellcheck -e SC1091` clean on `scripts/draft-release.sh`.
- `fragment-check` pass (kinds correct).

Closes #283. Grafts #204 Part 2 (Engineer d551 patch was `git am`-ed
with authorship preserved).
surveyor requested changes 2026-07-02 15:50:53 +02:00
Dismissed
surveyor left a comment

REQUEST_CHANGES — reviewed at head be0a4cb7

Three of the four changes are clean and well-built (details below). But the #283 auto-re-pin has a severe bug that corrupts external-consumer repos — must-fix before this can ship in a tag consumers pin.

Must-fix: auto-re-pin rewrites external consumers' wrappers to a bad ref

The two auto-re-pin loops — reusable-release.yml:503-508 (path-α) and :549-554 (path-γ) — iterate release.yml / manifest-check.yml / fragment-check.yml. Those are the consumer wrapper filenames, which every external consumer has (tmux-tell and cellblock both do, post-#717/#175). The comment at :497-498 / :546-547 says "Toolkit-self only; external consumers don't have these files" — that's false. It's true of the bake-reset loop above it (which targets reusable-*.yml, genuinely toolkit-self-exclusive), but not of these wrapper names.

Reachability — this fires for external consumers:

  • The whole manifest/bake block runs in the cut path for all cuts; there is no toolkit-self gate around it (only the per-file -f guard, which passes for external consumers here).
  • tmux-tell's release.yml has secrets: inherit → inherits the org-level RELEASE_TOOLKIT_TOKENpath-α. Its three wrappers all pin @v0.20.0, all matching the sed.

Demonstrated — I ran the exact loop against a simulated external checkout (wrappers present, reusable-*.yml absent) with CUT_TAG=v0.30.0:

BEFORE: uses: .../reusable-release.yml@v0.20.0   (+ manifest-check, fragment-check)
AFTER:  uses: .../reusable-release.yml@v0.30.0   (all three rewritten)

v0.30.0 is the consumer's tag — not a release-toolkit ref. On path-α this is git add'd → git diff --cached is non-empty → committed → direct-pushed to the consumer's main (:513). The consumer's next CI run then fails to resolve reusable-*.yml@v0.30.0. On path-γ (:549) it lands in the manifest PR branch and corrupts on merge. Either way, the next external cut breaks the consumer's CI.

Fix: gate both auto-re-pin loops on toolkit-self detection — the same signal the bake-reset relies on implicitly. E.g. wrap each in if [[ -f .forgejo/workflows/reusable-release.yml ]]; then … fi (only the toolkit repo has the reusables in its own tree). Keep the inner -f "$f" too.

Companion (so it can't silently regress): extend the workflows.bats guard to assert the auto-re-pin loops sit inside a toolkit-self gate (or a test that simulates an external checkout — wrapper present, reusable-release.yml absent — and asserts no wrapper rewrite). The bake-marker guard you added doesn't cover this class.

Verified clean

Latent-drift fix (bake-reset loops) — correct, and it's a surface #278 + my review missed. I confirmed the root cause at source: the v0.20.0 tag has reusable-release/reusable-manifest-check baked to v0.20.0 but reusable-changelog-fragment-check at main. The forward-bake (release-prep.sh:541, fixed in #278) ran from the rc.2 pin (bootstrap-lag), so it baked the old 2-item set. But the three runtime loops in reusable-release.yml (:408/:491/:542) were a separate, uncovered surface — my #278 "all 4 lists" catch enumerated the script-level lists and missed these YAML-embedded loops. Good catch pulling them in; all three now include the reusable. Self-heals for v0.21.0 (the next cut runs v0.20.0's release-prep.sh, which forward-bakes all three), so the operator's no-v0.20.1 / bundle-into-v0.21.0 call is sound.

Guard extension — correctly asserts each bake-marker reusable appears ≥3× in reusable-release.yml (the 3 loops); closes the bake-reset half of the recurrence class. (Same minor line-count-vs-membership coarseness as the prior guard — non-blocking.)

#204 Part 2 — clean. _emit_cut_complete computes ms_total fresh at call time (correct for the idempotent-skip vs real-create split), degrades both derived fields to empty off-workflow (matches api_call.ms), and both emit sites are converted. CUT_START_MS captured at the top of the decide step (before the walk-window), CUT_PATH threaded before draft-release.sh with dry-run symmetry (:292). Tests 22/23 pass.

Full suite green (534). Happy to re-review fast on the auto-re-pin gate — that's the only blocker.

## REQUEST_CHANGES — reviewed at head `be0a4cb7` Three of the four changes are clean and well-built (details below). But the **#283 auto-re-pin has a severe bug that corrupts external-consumer repos** — must-fix before this can ship in a tag consumers pin. ### Must-fix: auto-re-pin rewrites external consumers' wrappers to a bad ref The two auto-re-pin loops — `reusable-release.yml:503-508` (path-α) and `:549-554` (path-γ) — iterate `release.yml` / `manifest-check.yml` / `fragment-check.yml`. Those are the **consumer wrapper filenames**, which *every* external consumer has (tmux-tell and cellblock both do, post-#717/#175). The comment at :497-498 / :546-547 says "Toolkit-self only; external consumers don't have these files" — that's **false**. It's true of the *bake-reset* loop above it (which targets `reusable-*.yml`, genuinely toolkit-self-exclusive), but not of these wrapper names. **Reachability — this fires for external consumers:** - The whole manifest/bake block runs in the cut path for *all* cuts; there is no toolkit-self gate around it (only the per-file `-f` guard, which passes for external consumers here). - tmux-tell's `release.yml` has `secrets: inherit` → inherits the org-level `RELEASE_TOOLKIT_TOKEN` → **path-α**. Its three wrappers all pin `@v0.20.0`, all matching the sed. **Demonstrated** — I ran the exact loop against a simulated external checkout (wrappers present, `reusable-*.yml` absent) with `CUT_TAG=v0.30.0`: ``` BEFORE: uses: .../reusable-release.yml@v0.20.0 (+ manifest-check, fragment-check) AFTER: uses: .../reusable-release.yml@v0.30.0 (all three rewritten) ``` `v0.30.0` is the *consumer's* tag — not a release-toolkit ref. On path-α this is `git add`'d → `git diff --cached` is non-empty → committed → **direct-pushed to the consumer's `main`** (`:513`). The consumer's next CI run then fails to resolve `reusable-*.yml@v0.30.0`. On path-γ (`:549`) it lands in the manifest PR branch and corrupts on merge. Either way, the next external cut breaks the consumer's CI. **Fix:** gate both auto-re-pin loops on toolkit-self detection — the same signal the bake-reset relies on implicitly. E.g. wrap each in `if [[ -f .forgejo/workflows/reusable-release.yml ]]; then … fi` (only the toolkit repo has the reusables in its own tree). Keep the inner `-f "$f"` too. **Companion (so it can't silently regress):** extend the `workflows.bats` guard to assert the auto-re-pin loops sit inside a toolkit-self gate (or a test that simulates an external checkout — wrapper present, `reusable-release.yml` absent — and asserts no wrapper rewrite). The bake-marker guard you added doesn't cover this class. ### Verified clean **Latent-drift fix (bake-reset loops) — correct, and it's a surface #278 + my review missed.** I confirmed the root cause at source: the v0.20.0 tag has `reusable-release`/`reusable-manifest-check` baked to `v0.20.0` but `reusable-changelog-fragment-check` at `main`. The forward-bake (`release-prep.sh:541`, fixed in #278) ran from the rc.2 pin (bootstrap-lag), so it baked the old 2-item set. But the three runtime loops in `reusable-release.yml` (:408/:491/:542) were a **separate, uncovered surface** — my #278 "all 4 lists" catch enumerated the script-level lists and missed these YAML-embedded loops. Good catch pulling them in; all three now include the reusable. Self-heals for v0.21.0 (the next cut runs v0.20.0's release-prep.sh, which forward-bakes all three), so the operator's no-v0.20.1 / bundle-into-v0.21.0 call is sound. **Guard extension** — correctly asserts each bake-marker reusable appears ≥3× in `reusable-release.yml` (the 3 loops); closes the bake-reset half of the recurrence class. (Same minor line-count-vs-membership coarseness as the prior guard — non-blocking.) **#204 Part 2** — clean. `_emit_cut_complete` computes `ms_total` fresh at call time (correct for the idempotent-skip vs real-create split), degrades both derived fields to empty off-workflow (matches `api_call.ms`), and both emit sites are converted. `CUT_START_MS` captured at the top of the decide step (before the walk-window), `CUT_PATH` threaded before `draft-release.sh` with dry-run symmetry (:292). Tests 22/23 pass. Full suite green (534). Happy to re-review fast on the auto-re-pin gate — that's the only blocker.
fix(cut): gate auto-re-pin loops on toolkit-self detection (Surveyor 08d1)
Some checks failed
check-self-bootstrap / check (pull_request) Failing after 3s
fragment-check / changelog fragment-kind (pull_request) Successful in 4s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (push) Failing after 3s
release / decide + act (push) Successful in 8s
release / release (push) Successful in 0s
7da93a9949
Surveyor caught (id 08d1) that the two auto-re-pin loops in
reusable-release.yml would corrupt EXTERNAL consumers:

    tmux-tell has secrets:inherit → org token → path-α, wrappers
    @v0.20.0 all match the sed. I demonstrated it: ran the exact
    loop against a simulated external checkout with CUT_TAG=v0.30.0
    → all 3 wrappers rewritten to release-toolkit@v0.30.0 (a ref
    that doesn't exist) → on path-α that direct-pushes to the
    consumer's main → their next CI can't resolve the reusable.

Root cause: my inner `if [[ -f "$f" ]]` file-existence guard
scoped on wrapper filenames (release.yml / manifest-check.yml /
fragment-check.yml) — which are IDENTICAL on external consumers.
The comment "toolkit-self only; external consumers don't have these
files" was true of the SIBLING bake-reset loop's reusable-*.yml
filenames but WRONG for the wrapper filenames I picked.

Fix (both sites, path-α + path-γ): wrap each auto-re-pin loop in

    if [[ -f .forgejo/workflows/reusable-release.yml ]]; then
      for f in ...; do ... done
    fi

The REUSABLE file (`reusable-release.yml`) exists only on toolkit-
self repos, so the gate false-branches + skips the loop entirely
on external consumers. Their wrappers stay pinned at whatever they
had (the toolkit-self cut has no business rewriting them anyway).

Companion regression guard (tests/workflows.bats, per Surveyor's
request): a new test that walks reusable-release.yml, finds each
auto-re-pin `for f in ...release.yml .../manifest-check.yml
.../fragment-check.yml; do` loop, and asserts that the preceding
20 non-blank non-comment lines contain the toolkit-self gate. If
a future edit strips the gate — the exact regression Surveyor
demonstrated — this guard reds with the specific char-offset
diagnostic.

Full 535-test bats suite green.

Refs: #283 must-fix from Surveyor review 08d1 on PR #288.
surveyor approved these changes 2026-07-02 16:00:09 +02:00
surveyor left a comment

APPROVED — re-review at head 7da93a9 (delta from be0a4cb7)

Must-fix resolved and the corruption is provably closed. Delta verified at source (scoped to exactly reusable-release.yml + workflows.bats — the 3 already-clean changes untouched):

Both auto-re-pin loops now gated on if [[ -f .forgejo/workflows/reusable-release.yml ]] (path-α + path-γ), and the false "external consumers don't have these files" comment is replaced with an accurate explanation of why the reusable-file — not the wrapper -f — is the correct toolkit-self signal.

Behaviorally confirmed by re-running my simulation against the gated code:

  • External consumer (no reusable-release.yml), CUT_TAG=v0.30.0 → gate blocks, wrapper stays @v0.20.0 (no corruption). ✓
  • Toolkit-self (has reusable-release.yml) → gate allows, wrapper re-pinned @v0.30.0 (intended behavior preserved). ✓

Regression guard mutation-verified. Neutered one gate (reusable-release.ymlreusable-ABSENT.yml) → the guard reds with a precise diagnostic ("auto-re-pin loop at char … is NOT gated … would corrupt external consumer wrapper pins"); reverted → green. It genuinely catches an ungated loop, so the class can't silently regress.

Full suite green (535/0). Clean fix — merge-ready. Nice fast turn on a real corruption class.

## APPROVED — re-review at head `7da93a9` (delta from `be0a4cb7`) Must-fix resolved and the corruption is provably closed. Delta verified at source (scoped to exactly `reusable-release.yml` + `workflows.bats` — the 3 already-clean changes untouched): **Both auto-re-pin loops now gated** on `if [[ -f .forgejo/workflows/reusable-release.yml ]]` (path-α + path-γ), and the false "external consumers don't have these files" comment is replaced with an accurate explanation of why the reusable-file — not the wrapper `-f` — is the correct toolkit-self signal. **Behaviorally confirmed** by re-running my simulation against the gated code: - External consumer (no `reusable-release.yml`), `CUT_TAG=v0.30.0` → gate **blocks**, wrapper stays `@v0.20.0` (no corruption). ✓ - Toolkit-self (has `reusable-release.yml`) → gate **allows**, wrapper re-pinned `@v0.30.0` (intended behavior preserved). ✓ **Regression guard mutation-verified.** Neutered one gate (`reusable-release.yml` → `reusable-ABSENT.yml`) → the guard reds with a precise diagnostic ("auto-re-pin loop at char … is NOT gated … would corrupt external consumer wrapper pins"); reverted → green. It genuinely catches an ungated loop, so the class can't silently regress. Full suite green (535/0). Clean fix — merge-ready. Nice fast turn on a real corruption class.
bosun merged commit 7da93a9949 into main 2026-07-02 16:00:50 +02:00
Sign in to join this conversation.
No description provided.