fix(prune-rc-tags): self-pin exclusion (Surveyor 2acc catch — #164 self-orphan gap) #167

Closed
opened 2026-06-27 03:08:39 +02:00 by quartermaster · 0 comments

The bug

scripts/prune-rc-tags.sh (#164) enumerates vCUT_VERSION-rc.* at cut-time and deletes ALL matched tags. The cross-version prefix guard protects external consumer pins (e.g. cellblock pinned v0.10.1-rc.1 is safe when the toolkit cuts v0.12.0 — different version family).

But the SAME-version self-pin case is unprotected:

  • toolkit-self's release.yml pins toolkit_ref: vX.Y.Z-rc.N
  • vX.Y.Z cut runs prune-rc-tags.sh vX.Y.Z
  • Enumeration includes vX.Y.Z-rc.N (the live pin)
  • The script deletes it
  • Next push:main fires release.yml at the now-deleted uses:@vX.Y.Z-rc.N → workflow fails to start

Why it hasn't fired yet

Pure luck from the v0.12.0 SemVer surprise: the rolling PR currently in flight cuts v0.12.0, and toolkit-self is on v0.11.1-rc.1. The auto-prune (if it runs at the v0.12.0 cut) enumerates v0.12.0-rc.* → empty → self-pin survives. The version-mismatch is accidentally shielding the bug from firing.

The moment any cut's CUT_VERSION matches the toolkit-self pin's version family (the normal case once the re-pin discipline is fully in cycle), the bug activates.

Surveyor's framing (2acc)

The prefix-guard protects CROSS-version pins (cellblock's v0.10.1-rc.1 ✓) but NOT the SAME-version self-pin. I verified cross-version safety and stopped there — the self-pin case is the gap. ... It's STRUCTURAL + normal-case: the re-pin discipline always pins the cut-version's rc, so the auto-prune would delete the self-pin on every cut once activated.

Fix

Add an optional 4th positional arg PINNED_REF to prune-rc-tags.sh. If supplied AND present in the enumerated family, EXCLUDE it from the delete list. _release.yml passes ${{ inputs.toolkit_ref }} so the workflow's known pin is preserved without re-parsing release.yml.

Self-pin is NOT superseded by the cut that runs at it; deleting it during that cut is the bug. The next re-pin cycle handles the replacement.

Bats coverage

5 new fixtures covering the exclusion semantics:

  • In-family preservation (canonical case)
  • No-op when PINNED_REF is from a different version
  • All-filtered exit (only tag matches the pin)
  • Backward-compat: empty / missing PINNED_REF behaves like pre-fix (no exclusion)
  • Exact-match guard: v0.12.0-rc.1 must not shadow v0.12.0-rc.10 / v0.12.0-rc.11 (mutation-verify: grep -vFx not -vF)

Composition

  • AGENTS.md section 2.4 updated: self-pin exclusion documented as load-bearing
  • The two-tier protection model becomes explicit: prefix guard (cross-version) + PINNED_REF exclusion (same-version self-pin)

What this PR will NOT do

  • Does NOT address the abandoned-by-override / abandoned-by-skip classes (still NOT caught at cut-time; same as the original section 2.4 statement). Those need the re-pin-time evolution (foldable with #163)
  • Does NOT change the cross-version prefix-guard behavior (untouched; still load-bearing per Surveyor 6802 mutation-verify)

Refs

  • PR #164 (the feat being fixed — self-orphan gap missed in approval per Surveyor 2acc self-correction)
  • Surveyor 2acc (the catch + fix-first recommendation + scope clarity on cross-version vs same-version protection)
  • AGENTS.md section 2.4 (rc-tag lifecycle — gets the self-pin paragraph added)
  • #153 (parent tracker for rc-tag lifecycle work)
## The bug `scripts/prune-rc-tags.sh` (#164) enumerates `vCUT_VERSION-rc.*` at cut-time and deletes ALL matched tags. The cross-version prefix guard protects external consumer pins (e.g. cellblock pinned `v0.10.1-rc.1` is safe when the toolkit cuts `v0.12.0` — different version family). But the SAME-version self-pin case is unprotected: - toolkit-self's `release.yml` pins `toolkit_ref: vX.Y.Z-rc.N` - `vX.Y.Z` cut runs `prune-rc-tags.sh vX.Y.Z` - Enumeration includes `vX.Y.Z-rc.N` (the live pin) - The script deletes it - Next `push:main` fires `release.yml` at the now-deleted `uses:@vX.Y.Z-rc.N` → workflow fails to start ## Why it hasn't fired yet Pure luck from the v0.12.0 SemVer surprise: the rolling PR currently in flight cuts `v0.12.0`, and toolkit-self is on `v0.11.1-rc.1`. The auto-prune (if it runs at the v0.12.0 cut) enumerates `v0.12.0-rc.*` → empty → self-pin survives. The version-mismatch is accidentally shielding the bug from firing. The moment any cut's CUT_VERSION matches the toolkit-self pin's version family (the normal case once the re-pin discipline is fully in cycle), the bug activates. ## Surveyor's framing (2acc) > The prefix-guard protects CROSS-version pins (cellblock's v0.10.1-rc.1 ✓) but NOT the SAME-version self-pin. I verified cross-version safety and stopped there — the self-pin case is the gap. ... It's STRUCTURAL + normal-case: the re-pin discipline always pins the cut-version's rc, so the auto-prune would delete the self-pin on every cut once activated. ## Fix Add an optional 4th positional arg `PINNED_REF` to `prune-rc-tags.sh`. If supplied AND present in the enumerated family, EXCLUDE it from the delete list. `_release.yml` passes `${{ inputs.toolkit_ref }}` so the workflow's known pin is preserved without re-parsing release.yml. Self-pin is NOT superseded by the cut that runs at it; deleting it during that cut is the bug. The next re-pin cycle handles the replacement. ## Bats coverage 5 new fixtures covering the exclusion semantics: - In-family preservation (canonical case) - No-op when PINNED_REF is from a different version - All-filtered exit (only tag matches the pin) - Backward-compat: empty / missing PINNED_REF behaves like pre-fix (no exclusion) - Exact-match guard: `v0.12.0-rc.1` must not shadow `v0.12.0-rc.10` / `v0.12.0-rc.11` (mutation-verify: `grep -vFx` not `-vF`) ## Composition - AGENTS.md section 2.4 updated: self-pin exclusion documented as load-bearing - The two-tier protection model becomes explicit: prefix guard (cross-version) + PINNED_REF exclusion (same-version self-pin) ## What this PR will NOT do - Does NOT address the abandoned-by-override / abandoned-by-skip classes (still NOT caught at cut-time; same as the original section 2.4 statement). Those need the re-pin-time evolution (foldable with #163) - Does NOT change the cross-version prefix-guard behavior (untouched; still load-bearing per Surveyor 6802 mutation-verify) ## Refs - PR #164 (the feat being fixed — self-orphan gap missed in approval per Surveyor 2acc self-correction) - Surveyor 2acc (the catch + fix-first recommendation + scope clarity on cross-version vs same-version protection) - AGENTS.md section 2.4 (rc-tag lifecycle — gets the self-pin paragraph added) - #153 (parent tracker for rc-tag lifecycle work)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#167
No description provided.