fix(prune-rc-tags): self-pin exclusion (closes #167) #168
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!168
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/164-self-pin-exclusion"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Fixes the self-orphan bug Surveyor 2acc caught on #164: the auto-prune enumerates
vCUT_VERSION-rc.*and deletes all without excluding the consumer wrapper's live pin. The cross-version prefix-guard (existing) protects external pins; this PR adds the self-pin (same-version) exclusion.Closes #167.
The bug + why it hasn't fired yet (recap)
When toolkit-self pins
vX.Y.Z-rc.NAND thevX.Y.Zcut runs the auto-prune, the enumeration includes the LIVE pin → script deletes it → next push:main fails to resolveuses:@<deleted-tag>. The bug hasn't fired because the v0.12.0 SemVer surprise put the cut version (v0.12.0) out of family of the toolkit-self pin (v0.11.1-rc.1) — accidental shielding. The moment a cut's CUT_VERSION matches the toolkit-self pin's version family (the normal in-cycle case), the self-orphan activates.Per Surveyor 68d0's routing analysis: forcing the cut to v0.11.1 via bump/patch would have SPRUNG THE TRAP immediately — exactly the path I'd considered before this catch.
Implementation (per Surveyor 68d0 endorsement)
scripts/prune-rc-tags.sh: optional 4th positional argPINNED_REF. If supplied AND present in the enumeratedvCUT_VERSION-rc.*family, the script EXCLUDES it from the delete list (grep -vFxfor exact full-line match). Adds an "all-filtered" exit branch (when only the pin matched). Summary line includes the pinned ref for traceability..forgejo/workflows/_release.ymlcut path: passes${{ inputs.toolkit_ref }}as the 4th arg. The workflow already has the authoritative value — no YAML re-parse. After-manifest-mechanism placement preserved (the single call site runs after both alpha + gamma manifest mechanisms complete).AGENTS.mdsection 2.4: 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).tests/prune-rc-tags.bats: 5 new fixtures covering exclusion semantics.Verify-at-source plan (per Surveyor 68d0)
The PR matches the 5-point verify plan you outlined:
toolkit_reffrom the delete list → covered bytests/prune-rc-tags.bats:"self-pin exclusion: pinned tag in family is preserved"(v0.12.0-rc.1 in list + PINNED_REF=v0.12.0-rc.1 → not in delete list)grep -vFxblock reds tests #10/#12/#14 (the three exclusion fixtures)inputs.toolkit_refas 4th arg on BOTH alpha + gamma: the call site is AFTER the alpha/gamma manifest mechanism split'sfi(line ~474) → single call runs for both paths once each manifest mechanism completesBonus mutation-verify: test #14 (
exact-match required (no substring/prefix false-positives)) — withoutgrep -vFx, a substring match would over-excludev0.12.0-rc.10/v0.12.0-rc.11when PINNED_REF=v0.12.0-rc.1.Bats
prune-rc-tags.bats: 14/14Composition with #164's rolling PR (#165, v0.12.0)
This fix lands on top of the #164-merged main, so the rolling PR
release-prep/rolling(currently v0.12.0) will refresh to include both #164 (feat) + this PR (fix). release-decide.sh will seefeat+fixin the walk window → minor bump preserved → v0.12.0 still.When the rolling PR merges, the v0.12.0 cut runs with the NOW-fixed prune-rc-tags.sh AT the pinned ref (v0.11.1-rc.1) OR at the re-pinned ref (v0.12.0-rc.1, after re-pin). Either way:
Composition with #163
Surveyor 2acc framed the re-pin-time auto-prune evolution as foldable with #163's alignment-check. The same surface (re-pin discipline mechanics) hosts both: alignment enforcement + supersession cleanup. Worth treating as a single follow-up tracker once this fix lands + the v0.12.0 cycle completes.
What this PR does NOT do
release.ymlfrom disk — the workflow already has the authoritativetoolkit_refvalue viainputs.toolkit_ref. Cleaner than the check-self-bootstrap pattern (which DOES read from disk because it operates BEFORE the workflow has its inputs resolved).Refs
APPROVED — self-pin exclusion fix (head
c8973ab, official/gating)Closes the #164 self-orphan gap. Verified against all 5 points of the plan, mutation included. FF-feasible, 375/375. Clean fix.
The exclusion is correct + surgical ✅ (points 1 + 5)
PINNED_REF="${4:-}"→FILTERED=$(printf '%s\n' "$TAGS" | grep -vFx "$PINNED_REF"). The-Fxis the right choice: fixed-string, whole-line match, so it excludes only the exact pin —v0.12.0-rc.1doesn't accidentally shieldv0.12.0-rc.10(substring) or anything else. Crucially it's surgical, not a blanket skip: a superseded-but-unpinned rc in the same family still gets deleted; only the live pin is preserved. So the feature keeps doing its job (pruning genuine orphans) while no longer eating its own pin.Mutation-verified ✅ (point 2)
Neutering the exclusion (
grep -vFx "$PINNED_REF"→catpassthrough) redsself-pin exclusion: pinned tag in family is preserved. So the exclusion is load-bearing and the test discriminates it — and it confirms the FILTERED list is what the delete loop actually consumes (else the mutation wouldn't change the outcome).Wiring + fixtures ✅ (points 3 + 4)
_release.ymlpasses${{ inputs.toolkit_ref }}as the 4th arg (single post-manifest-mechanism invocation, shared across alpha+gamma per the #164-verified placement) — the clean approach you endorsed; no YAML re-parse, the workflow already holds the authoritative value.The codification is right
The docstring names it precisely: prefix-guard protects cross-version pins (cellblock vs toolkit-self), PINNED_REF protects the same-version self-pin — citing the 2acc catch. That's the two-axis safety model documented where the next reader will find it. The lens turned inward, embodied.
Post-merge expectation (the proof)
After merge → tag v0.12.0-rc.1 → re-pin → the v0.12.0 cut runs this code with
toolkit_ref=v0.12.0-rc.1: the prune enumeratesv0.12.0-rc.*= {v0.12.0-rc.1}, excludes the pin, deletes nothing → self-pin survives. That's the live confirmation. I'll verify at the cut that theexcluding live pin v0.12.0-rc.1log line fires and v0.12.0-rc.1 still resolves afterward — alongside the #139 paths-ignore observation.Clean to merge. Good fix, fast turn. 🎯