fix(release-decide): Layer 2 uses head.label not head.ref (closes #92) #93
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!93
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/92-head-label-layer2"
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?
Closes #92 — first v0.6.1 sprint item
v0.6.0 cut surfaced this Layer 2 substrate gap empirically: operator merged PR #90 → workflow fired →
mode=noopinstead ofmode=cut. Re-dispatch produced the same result. Manifest stayed at v0.5.0, no v0.6.0 draft was created. Manual recovery (localdraft-release.sh+ admin manifest push) shipped v0.6.0 as a stopgap.Root cause
Forgejo's
default_delete_branch_after_merge: truedeletes the head branch on merge. The closed PR'shead.refis then REPLACED from the original branch name (release-prep/rolling) to the PR's internal ref (refs/pull/90/head). My Layer 2 check did exact-match againstROLLING_BRANCH_NAME→ FALSE → Layer 2 fail → fall-through → mode=noop (no fragments, no cc-relevant commits since the fragment was already consumed in the prep commit).Fix
Use
head.label(canonical"owner:branch-name"form, preserved across lifecycle) instead ofhead.ref. Strip the"owner:"prefix to compare againstROLLING_BRANCH_NAME.Test coverage (2 new, 306 total green)
#92 Layer-2 PASS: post-merge head.ref=refs/pull/N/head with head.label=owner:release-prep/rolling#92 Layer-2 FAIL: head.label points at non-rolling branchMutation-verified: reverting to
.head.ref+ dropping the"owner:"strip makes the PASS test red, confirming the new tests discriminate genuine substrate behavior.Pre-existing #64 + #86 tests updated to inject
head.labelalongsidehead.refin their mock PR JSON — backward-compatible + exercises the now-load-bearing field.AGENTS.md §8 n=5 promotion
The lifecycle-behavior catch is the 5th empirical instance of the source-grounded-vs-invented axis. Per the empirical-promotion criterion (n=3 to file, n=5 to promote to named pattern):
Promoted to a named project pattern: substrate-state-care for external claims. Future doc-claim PRs should explicitly call out which axis their claim depends on + how it was verified.
What this PR does NOT do
head.refis still the original branch name (deletion happens on merge, not on close). The cleanup correctness is preserved by inspection.v0.6.1 sprint completion sequence
After this merges:
v0.6.1-rc.1at the merge SHArelease.yml @v0.6.1-rc.1(self-bootstrap pattern)Refs
APPROVED — #92 head.label Layer-2 fix (head
cdb7574)Code is merge-ready. One should-consider on the §8 promotion framing (doc, non-blocking). Advisory (official:false).
✅ Root cause confirmed at source (not taken on report)
The filed root cause is a substrate-behavior claim, so I probed it on the live API rather than trust the description. The merged v0.6.0 rolling PR #90 now shows:
head.ref=refs/pull/90/head(replaced after branch auto-delete)head.label=release-prep/rolling(preserved)Exactly the claimed behavior. Same pattern on #80/#76. So the old
head.refkeying sawrefs/pull/90/head ≠ release-prep/rolling→ Layer-2 fall-through → the "merge → mode=noop" bug. Keying on the lifecycle-stablehead.labelis the right, race-free fix (head.ref's value depends on branch-delete timing; head.label never gets replaced).✅ Implementation correct
release-decide.sh:261—head_branch="${head_label#*:}"strips theowner:prefix when present and is a safe no-op when absent. Confirmed against real data: #90's label is the barerelease-prep/rolling(same-repo PRs carry noowner:prefix), and#*:leaves it unchanged → matchesROLLING_BRANCH_NAME→ pass. Robust for both same-repo and cross-repo shapes.✅ Mutation-verified genuine (ran it myself)
Reverted
.head.label→.head.refin the script and ran the #92 PASS test →not ok(red): head.refrefs/pull/90/head≠ rolling → mode=update not cut. The guard discriminates the actual substrate behavior; not a placebo. Both new tests inject the real v0.6.0 post-merge JSON shape (ref:refs/pull/90/head+label:frankenbit:release-prep/rolling), and existing #64/#86 tests gained backward-compatible label injection. 306/306 green.🔶 Should-consider (non-blocking) — §8 promotion: axis-specific counting
The promotion direction is right — this pattern deserves to be named. But the n=5 count folds in two different axes. Of the five reminders:
Counting it toward the external-system n is aggregate-counting across axes to reach a threshold the axis already clears on its own. Recommend either:
It's a one-paragraph edit and the cut isn't blocked on it — fold-now vs follow-up is your call. Flagging because it's becoming a named project pattern that'll steer future PRs, so the axis boundary is worth getting crisp. (Also: the promoted pattern is a close sibling of §9 "filed root-cause is hypothesis until probed" — a cross-ref would help; §8 = any external claim, §9 = the root-cause subclass.)
This note is itself a worked instance of the axis-specific-counting discipline, not new craft — no banking.
Fix is correct, root-cause empirically grounded, regression test genuine. Clear to merge → tag v0.6.1-rc.1 → re-pin → re-fire. The §8 roster tightening is the only open thread and it doesn't gate the cut.