chore(release-decide): delegate orphan-check to cc_bump_level_from_subject (#424) #429
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!429
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/424-orphan-check-cc-bump-level"
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
Closes #424 —
release-decide.sh's orphan-check range-walk now delegates release-relevance detection tocc_bump_level_from_subject, closing theBREAKING CHANGE:body-footer hole surfaced during PR#423 review (design-gap-of-record, not observed in the wild) and preventing orphan-triggers from drifting away from bump logic by construction. v1.0.0 SemVer stability follow-through to the #417/#427 arc.Root cause
The range-walk introduced in PR#423 (
i/417-orphan-check-cc-bump-levelpredecessor) keyed on a subject-only regex:That matched subject-level breaking markers (
feat!:/fix!:) but MISSED the conventional-commits body-footer case per RFC-ish semantics: arefactor: Xorchore: Ycommit carryingBREAKING CHANGE:in its body IS bump-worthy (major bump) but the subject-only regex false-skips the orphan-catch. A stuck orphan-state with such a commit on top would proceed silently instead of firing the recovery banner.Fix
Delegate to
cc_bump_level_from_subjectfromscripts/lib/conventional-commits.sh(already sourced byrelease-decide.sh; already the single source of truth for bump-worthiness in the release-cut path).feat→ minor,fix→ patchfeat!/fix!→ majorBREAKING CHANGE:/BREAKING-CHANGE:in body → majorNon-empty output = bump-worthy = orphan trigger.
Substrate benefits
refactor: X\n\nBREAKING CHANGE: Yon top of a stuck prep-PR-merge now correctly fires orphan-catch.perf:), orphan-check propagates automatically. Single source of truth.PREP_SUBJECT_REshortcircuit stays ahead of thecc_bump_level_from_subjectcall. Version-matching prep-PR-merge → SKIP; different-version prep-PR-merge → continue walk (unchanged); everything else → check bump level.git log LAST_SHA..HEADwalk order; only change is fetching subject viagit log -1 --format=%sper SHA + body via%bonly when needed (skipped for PREP-shortcircuit matches).Test surface
tests/orphan-changelog.batsgains 3 new tests locking the semantics:#424 orphan FIRES: refactor commit with BREAKING CHANGE body footer on top of orphaned prep-PR-merge— locks the fix. Mutation-verified: pre-fix this test fails (subject-only regex misses the refactor; walk hits prep-PR-merge; orphan-check SKIPS incorrectly). Post-fix passes.#424 orphan clean: refactor commit WITHOUT BREAKING CHANGE body on top of prep-PR merge — walks past to skip— negative lock. Plainrefactor:is not bump-worthy → walk continues past it, finds prep-PR-merge, SKIPS correctly. Guards against over-triggering.#424 orphan FIRES: feat! subject-marker breaking-change on top— regression-lock for the subject-level!path. Old regex matched!?:so this worked pre-fix; new delegate handles it viabreaking_markerincc_parse_subject. Locks the path stays green through the refactor.Verification
feedback_bats_sweep_count_verification).git stash push scripts/release-decide.sh→ rerun--filter '#424'→ BREAKING-body test fails as expected pre-fix; other 2 pass either way (trivial pre-fix, load-bearing post-fix). Stash-pop restored, all 3 green post-fix. Verify-against-stuck-state discipline applied perfeedback_verify_after_mutation.Verification AC (from tracker)
release-decide.shorphan-check delegates release-relevance tocc_bump_level_from_subject(single source of truth)refactor: X+BREAKING CHANGE:body-footer commit on top of stuck prep-PR-merge now fires orphan-catch (was false-skipped pre-fix)Files
scripts/release-decide.sh(range-walk delegates tocc_bump_level_from_subject; body fetched only when needed)tests/orphan-changelog.bats(+3 tests for #424 lock semantics)changelog.d/424.changed.mdEmpirical status
Design-gap-of-record ahead of a live-in-the-wild hit. Named in Surveyor's PR#423 review 3642 non-blocking follow-up + #424 tracker body: "Not observed in the wild yet." Closing pre-v1.0.0 per SemVer stability contract — adopters pinning
@v1.0.0shouldn't hit this hole when they eventually push a BREAKING-body commit on top of a stuck orphan state.Refs: #424 (tracker), #417 (parent arc), Bosun dispatch 173b (ratified pick), Surveyor 3642 (proposal + gap-of-record framing).
The orphan-check range-walk (release-toolkit#417) previously keyed on a subject-only regex: ^(feat|fix)(\([^)]*\))?!?: That matched subject-level breaking markers (`feat!:` / `fix!:`) but MISSED the conventional-commits body-footer case: a `refactor: X` or `chore: Y` commit carrying `BREAKING CHANGE:` in its body IS bump-worthy (major bump) per conventional-commits, but the subject-only regex would false-skip the orphan-catch. A stuck orphan-state with such a commit on top would proceed silently instead of firing the recovery banner. Fix: delegate to `cc_bump_level_from_subject` from `scripts/lib/conventional-commits.sh` — already sourced by release-decide.sh, already the single source of truth for bump-worthiness in the release-cut path. It handles: - Subject-level: `feat` → minor, `fix` → patch - Subject-level: `feat!` / `fix!` → major - Body-level: `BREAKING CHANGE:` / `BREAKING-CHANGE:` in body → major Non-empty output = bump-worthy = orphan trigger. **Substrate benefits**: 1. **Closes the body-footer hole** — `refactor: X\n\nBREAKING CHANGE: Y` now correctly fires orphan-catch. 2. **Class-completeness** — orphan-check semantics can no longer drift from bump logic by construction. If the toolkit ever extends the bump-worthy type set (e.g., adds `perf:` per some conventional-commits variants), orphan-check picks it up automatically. Single source of truth pattern per Surveyor's proposal in #424. 3. **Preserves prep-PR-merge SKIP semantics** — the PREP_SUBJECT_RE shortcircuit stays in place ahead of the cc_bump_level_from_subject call. `chore(release): prepare vX.Y.Z` matching CHANGELOG_TOP_VERSION → SKIP; matching a different version → continue walk (unchanged); otherwise → check cc_bump_level. 4. **Preserves range-walk newest-first semantics** — same `git log LAST_SHA..HEAD` walk order; the only change is fetching subject via `git log -1 --format=%s` per SHA + body via `%b` when needed. Body-fetch is skipped for PREP-shortcircuit matches. **Test surface** (tests/orphan-changelog.bats, 3 new tests): - `#424 orphan FIRES: refactor commit with BREAKING CHANGE body footer on top of orphaned prep-PR-merge` — locks the fix. Mutation-verified: pre-fix this test fails (subject-only regex misses the refactor, walk hits prep-PR-merge, orphan-check SKIPS incorrectly). - `#424 orphan clean: refactor commit WITHOUT BREAKING CHANGE body on top of prep-PR merge — walks past to skip` — negative lock. Plain `refactor:` is not bump-worthy → walk continues past it, finds prep-PR-merge, SKIPS correctly. Guards against over-triggering. - `#424 orphan FIRES: feat! subject-marker breaking-change on top` — regression-lock for the subject-level `!` path. Old regex matched `!?:` so this worked pre-fix too; new delegate handles it via the `breaking_marker` group in cc_parse_subject. Lock stays green through the refactor. **Verification**: - Full sweep: 682/682 EXIT=0 (was 679 pre-fix + 3 new tests). - Register-check clean at HEAD. - Mutation-verification: reverted release-decide.sh + reran only `--filter '#424'` → BREAKING-body test fails as expected pre-fix, refactor-without-body test passes trivially either way, feat!-subject test passes either way. Restored fix → all 3 green. **Empirical status**: this was a design-gap-of-record ahead of a live-in-the-wild hit. Named in Surveyor's PR#423 review 3642 non-blocking follow-up + #424 tracker body: "Not observed in the wild yet." Closing the gap pre-v1.0.0 per the v1.0.0 SemVer stability contract — adopters pinning @v1.0.0 shouldn't hit this hole when they eventually push a BREAKING-body commit on top of a stuck orphan state. Refs: release-toolkit#424 (tracker), #417 (parent arc), Bosun dispatch 173b (ratified pick), Surveyor 3642 (proposal + gap-of-record framing).APPROVED — #429 (delegate orphan-check to
cc_bump_level_from_subject, #424)Implements the #423 review-3642 should-consider cleanly, and the delegation is verified correct at the function level — including the exact edge you flagged, which turns out to be a non-issue.
Delegation is correct
The walk now iterates SHAs, fetches each commit's body (
git log -1 --format=%b), and gates release-relevance oncc_bump_level_from_subject "$_subject" "$_body"being non-empty. Ordering preserved: prep-for-CHANGELOG_TOP → skip, prep-for-other-version → continue (multi-orphan defense), else → cc_bump_level → orphan. Release-relevance is now identical to the bump logic by construction — the single-source-of-truth win: any future bump-type change (perf, etc.) propagates to the orphan-walk automatically, no parallel-definition drift.Edge cases verified directly against
cc_bump_level_from_subjectrefactor:+BREAKING CHANGE:body footer →major→ fires ✓ (the gap #424 closes)refactor:+ multi-paragraph body, footer on the last para →major✓ — your flagged concern is a non-issue:grep -qE '^BREAKING[ -]CHANGE'is line-wise but scans every line, so a footer after N paragraphs matches. No multiline pattern needed; the existing semantics already handle it.refactor:/perf:no body → empty → skips ✓ (negative-lock; consistent with the bump logic treating them as non-release-worthy)feat:→ minor,feat!:→ major ✓ (subject-level path unchanged)Tests
3 new, exactly the right coverage: body-footer-on-top → FIRES, no-footer-on-top → skip (negative-lock),
feat!subject-marker → FIRES. Mutation-meaningful (pre-fix subject-only regex misses the refactor+BREAKING-body case → would wrongly skip). Your stash-and-rerun mutation-verification is the right method.Verified
Delegation correct + all edges tested at the function · 3 new tests right · 682/682 (count-verified) · register clean · 0 behind main (base == tip
03b8ac9, #428 merged).Closes the last #423 loose end. Clean single-source-of-truth refactor. Ship it.