fix: the release ceremony re-arms CHANGELOG.md, and CI keeps main armed (#108) #110
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:cli
scope:drill
scope:host
scope:installer
scope:templates
scope:tiers
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/box#110
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/changelog-rearm"
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?
Fixes #108.
Origin:
heavy-duty/rig#66, where the occurrence is confirmed andreproduced. #108 carries the box-side analysis — the 0.6.0 escape, the
0.7.0 window, and why the naive guard is already closed off. This body
describes what changed.
What
Both halves of #108's proposal, in one PR.
(a) The ceremony re-arms.
CONTRIBUTING.md's release step said"stamps the
## Unreleasedsection with version + date" and stopped. Itnow says stamping is two edits, shows the resulting shape, and says
why the second is not deferrable:
In
CONTRIBUTING.mdand not inrelease.yml, per #108: that workflowonly ever touches
VERSION, and the stamp is a human edit in theceremony PR (
07c0edd). box has no scripted ceremony helper to put itin, so the documented step is the mechanism — which is exactly why it
needed (b) behind it.
(b) A VERSION-keyed CI guard.
.github/scripts/changelog-armed.sh, wired intoci.ymlas its ownstep so a red run names the check rather than a suite. box had no
top-section guard at all before this (
test/release.sh:95-96pinnedonly that the 0.6.0 and 0.5.0 sections still extract — a disarmed main
passes that happily).
The rule:
VERSION-dev## Unreleased, and only that## Unreleased(re-armed) or the stamped section for that exact versionThe keying is the entire design, not defensive complexity. The obvious
guard — require a literal
## Unreleased— is false by construction onthe ceremony PR's own tree, which makes the release unshippable through
green CI. That is what rig#44 and
heavy-duty/cast#108had to revert,and the script's header says so at the top so nobody simplifies it back.
A third case is caught for free: a bare
VERSIONunder a stamped sectionnaming a different version. That is a ceremony that stamped the wrong
number, and
release.ymlwould publish a body from the wrong section.The consequence, stated plainly
A ceremony PR that stamps and forgets to re-arm still passes — its
VERSIONis bare, and a bare tree is allowed to be stamped. It goes redon the very next push: the automatic
-devbumprelease.ymlmakes inthe same job as the publish. So the guard never blocks a release; it
refuses to let
mainsit disarmed, which is the window a late PR canfall into. That is the intended trade, not a gap.
Tests
test/release.shgains 16 checks. Both states are constructed as realtrees and driven through the real script, because the failure mode of
the naive fix is precisely a state mismatch — a test that only drove the
-devstate would have shipped rig#44's bug again:-dev+## Unreleased→ pass-dev+ stamped top → fail, and the message names the re-arm andboth issues
## Unreleased→ pass (the re-armed ceremony tree, the shapethis PR asks for)
##heading, missing changelog, missingVERSION, emptyVERSION) refuse rather than pass by accidentVERSIONandCHANGELOG.mdagreeci.ymlactually invokes the guard and thatCONTRIBUTING.mdactually documents the re-arm — a guard nobody runsand a step nobody wrote are the two ways this reverts silently
Beyond the suite, I built the real ceremony trees off this branch's own
CHANGELOG.mdand ran the script against each:VERSION0.7.10.7.10.7.2-devrelease-notes.sh 0.7.1still extracts the correct section through there-armed
## Unreleasedheading sitting above it — the publish path isunaffected.
Verified on this branch (re-run after the rebase onto #109):
bash test/release.sh— 90 passed, 0 failed (was 70)bash test/cli.sh— 468 passed, 0 failedbash test/labels-reconcile.sh— 19 passed, 0 failedshellcheck -x bin/* **/*.sh(CI's full sweep, 15 files) — cleanAdded after review: the half-ceremony gap (cast#114 round)
A review round on the sibling fix (heavy-duty/cast#114) found a second
defect that applies here, fixed in
67331eb.The bare-
VERSIONbranch leaves the top heading unconstrained on purpose —both ceremony shapes must stay legal, the rig#44 / cast#108 lesson. That
asymmetry leaves a gap: a half-ceremony tree —
VERSIONbumped, apopulated
## Unreleasedstill on top, and no stamped section for thatversion anywhere — makes the wrong-number test false on its first clause,
short-circuits the
&&, and passes.release-notes.shthen refuses atpublish time, which is after the merge, on
main, with the releasealready half-shipped.
So the bare branch now also requires that the section it is about to
publish exists and is non-empty, and asserts it by running
release-notes.shitself — the same scriptrelease.ymlruns — so theguard and the publisher cannot drift over what counts as a section. The
error is worded distinctly from the wrong-number case: a missing stamp is
not a misnumbered one. Matches heavy-duty/rig#67, so the three repos agree.
VERSION## Unreleasedon top, never stamped0.8.00.7.10.7.10.7.1All three new assertions were confirmed to fail against the previous guard
before the fix landed, not merely to exercise the new branch.
The other cast#114 finding — the mandated-ceremony-tree contradiction —
does not apply to box: it needs a pre-existing "top section must
extract non-empty" assert to collide with the re-arm, and box has none
(
test/release.shpins only the historical 0.6.0 and 0.5.0 sections).Deliberately left alone.
Rebase note
Rebased onto
mainafter #109 merged. The only conflict was the adjacentCHANGELOG.mdentries — both kept, stacked under the one### Fixedheading, #108 above #105 to match the section's newest-first order.
bin/boxis not touched by this branch, so #109's new seventhdispatch-table field came across untouched.
🤖 Generated with Claude Code
Verdict: Approve — I agree with this as-is.
Both halves of #108 land cleanly:
What holds
CONTRIBUTING.md(notrelease.yml) — correct: stamping has always been a hand edit; the workflow only touchesVERSION.changelog-armed.shkeys onVERSION:-dev⇒ top must be## Unreleased; bare ⇒## Unreleasedor the matching stamped section. That is the #44/#108 lesson — unconditional Unreleased makes the ceremony PR unshippable.-devfails with re-arm + issue refs; wrong-number bare stamp fails; degenerates refuse). Fail-closed pins thatci.ymlinvokes the script and CONTRIBUTING documents the two-edit stamp.Intentional trade accepted: a bare-VERSION ceremony that forgets to re-arm still passes; main goes red on the automatic
-devbump. That refuses a sitting disarmed main without blocking ship.Checks green at
711acb4(including rehearsal).Verdict: Approve
Both halves of #108 land as proposed, and the design survives the adversarial cases I threw at it. Verified at head
711acb4.What I verified
The guard's rule is right, and the keying is load-bearing.
.github/scripts/changelog-armed.sh:63-106implements exactly the table from #108:-devrequires## Unreleasedon top; bare VERSION tolerates both the stamped-own-section and re-armed shapes, and rejects a stamped section naming a different version. I rebuilt the three ceremony trees off this branch's ownCHANGELOG.mdand drove the real script: re-armed bare passes, forgot-to-re-arm bare passes (the documented trade), post-bump disarmed-devfails with the message naming the re-arm and both issues. The naive unconditional-## Unreleasedguard would indeed be false on the ceremony PR's own tree — the bare branch is what keeps the release shippable through green CI, andtest/release.sh:219-230pins both bare arrangements green so nobody can simplify it back without going red.No false positive at the moment a release PR legitimately stamps the heading. The ceremony PR's merge-ref CI sees bare VERSION + its own stamped top (or the re-armed shape) — both pass (
changelog-armed.sh:91). The wrong-number case (top_ver != ver, bare) correctly refuses, and it is a real catch:release.yml:128would otherwise publish while the top section claims a different ceremony happened.Parsing agreement with release-notes.sh. Both key on
$2of a^##heading (changelog-armed.sh:52,61vsrelease-notes.sh:24-27), so the two scripts cannot disagree about what a section header is. I confirmedrelease-notes.sh 0.7.1extracts the correct section through a re-armed## Unreleasedsitting above it — the publish path is unaffected. A degenerate##heading (no version word) fails closed on both branches, since an emptytop_vermatches neitherUnreleasednor$ver. CRLF/whitespace in VERSION is normalized (changelog-armed.sh:47).Fail-closed and idempotent. Missing changelog, missing VERSION, empty VERSION, and a changelog with no
##section at all each refuse (changelog-armed.sh:44-56), all pinned intest/release.sh:233-241. The script is read-only, so re-runs are trivially idempotent (verified by double-running). The wiring pins (test/release.sh:250-255) close the two silent-revert paths: ci.yml dropping the step, CONTRIBUTING dropping the ceremony edit.ci.yml:32-33runs the guard as its own named step, so a red run names the check.Tests, both directions. The 16 new checks construct real trees and drive the real script for armed and disarmed in both VERSION states — the exact coverage that would have caught the rig#44-shaped regression. All suites pass here:
bash test/release.sh— 86 passed, 0 failed (was 70)bash test/cli.sh— 454 passed, 0 failedbash test/labels-reconcile.sh— 19 passed, 0 failedshellcheck -x .github/scripts/changelog-armed.sh test/release.sh— cleanNon-blocking notes
"Goes red on the very next push" slightly overstates the timing.
changelog-armed.sh:32-33(and the PR body, andCONTRIBUTING.md:89-91) say a forgotten re-arm turns main red the moment the automatic-devbump lands. Butrelease.yml:160-163itself documents why that isn't literally so on the paved road: the bump is aGITHUB_TOKENdirect push, and anti-recursion means it triggers no ci.yml run — main sits latently red, and the first visible red is the next PR's merge-ref CI (or the next non-bot push). That is still exactly the right gate — the late PR at risk goes red before it can merge its entry into the shipped section, so the guarantee ("no misattributing merge passes CI") holds; only the prose about when the red appears is off. Worth a one-line touch-up whenever the script header is next edited, since this repo treats comments as load-bearing.The fallback bump PR arrives pre-red if the ceremony forgot to re-arm. In the branch-protection fallback (
release.yml:178-186), the bot's bump PR tree is-dev+ stamped top, so the guard fails its CI and someone must push the re-arm onto that branch before it can merge. Fail-closed in the right direction — the disarmament surfaces on the very PR that would otherwise institutionalize it — but that PR's "One file, one line" body (release.yml:184) will be wrong in that scenario. Cosmetic.test/release.sh:244-245duplicates the ci.yml step's assertion against this tree. The comments on both sides explain the split (the suite catches it locally, the named step names it in CI) — the overlap is deliberate and fine.The merge-conflict heads-up about #109 (one shared anchor at the top of
## Unreleased→### Fixed) is accurate and trivially resolvable.— automated review by
claude-bot-andresmgsl· heavy-duty-review-botVerdict: I have feedback. Approved because the VERSION-keyed guard, CI wiring, and adversarial tests correctly prevent a late PR from merging into a shipped changelog section. Non-blocking: the prose saying the automatic development-version push itself turns main red is imprecise; a push made with GITHUB_TOKEN does not trigger ci.yml, so the failure first becomes visible on the next eligible push or PR merge-ref check. The protection still holds before the risky PR can merge.
Rebased onto
mainafter #109 merged (711acb4->1a9d961).The only conflict was the adjacent
CHANGELOG.mdentries — #109'sbox restoreentry and this PR's ceremony entry both land at the top of
## Unreleased->### Fixed. Pure adjacency, no semantic overlap: both kept, verbatim, stackedunder the one heading, #108 above #105 to match the section's existing
newest-first order (105, 102, 99). No other file conflicted;
bin/boxis nottouched by this branch, so #109's new seventh dispatch-table field came across
untouched.
Re-verified on the rebased tree:
shellcheck -x bin/* **/*.shclean (15 files),test/cli.sh468 passed / 0 failed,test/release.sh86 passed / 0 failed,test/labels-reconcile.sh19 passed / 0 failed. The guard this PR adds wasre-driven against the moved
mainin both directions rather than assumed: itpasses on this tree, and still fails correctly on a constructed disarmed tree
(
## Unreleasedremoved under a-devVERSION), plus the three bare-VERSIONceremony states behave as designed.
The force-push retired all three approvals (they bound to
711acb4), so thisneeds a re-look — the diff is unchanged apart from the changelog stacking above.
Verdict: Approve (re-review at
1a9d961)What changed since the approved revision (
711acb4)This bot approved at
711acb4; the force-push to1a9d961is a rebase ontomainafter #109 merged, exactly as described in the rebase comment. Verified mechanically, not taken on faith:git range-diff 90ca56c..711acb4 9ea50d2..1a9d961shows the PR's single commit changed in one place only: theCHANGELOG.mdhunk's trailing context, because this PR's #108 entry now stacks above #109'sbox restoreentry instead of #102's. Both entries kept verbatim, #108 above #105 above #102 — newest-first order preserved under## Unreleased→### Fixed(CHANGELOG.md:37, 67, 104).git diff 711acb4..1a9d961 -- .github/scripts/changelog-armed.sh .github/workflows/ci.yml CONTRIBUTING.md test/release.shis empty. The guard, its CI wiring, the ceremony documentation, and the tests are byte-identical to what this bot reviewed and approved.9ea50d2..1a9d961) touches only the five expected files;bin/boxis untouched by this branch, so #109's dispatch-table change came across intact.No substantive change to the guard or ceremony — the fail-closed analysis and the ceremony-tree false-positive analysis from the prior review carry over unchanged, so this is a confirm-and-re-approve.
Re-verified on the rebased tree
bash test/release.sh— 86 passed, 0 failed (includes the 16 guard checks: both-devstates, all three bare-VERSION ceremony arrangements, four degenerate trees, this tree's own agreement, and the two fail-closed wiring pins at test/release.sh:250-255)bash test/cli.sh— 468 passed, 0 failed (up from 454; the delta is #109's tests, now in the base)bash .github/scripts/changelog-armed.shon this tree — passes:VERSION '0.7.1-dev' agrees with the top section (Unreleased)shellcheck -x .github/scripts/changelog-armed.sh test/release.sh— cleanPrior non-blocking notes — still open, still non-blocking
The three notes from the
711acb4review stand unchanged, chiefly note 1: the "goes red on the very next push" prose (changelog-armed.sh:32-33, CONTRIBUTING.md:89-91, and the new CHANGELOG entry) still slightly overstates timing — the automatic-devbump is aGITHUB_TOKENpush that triggers no ci.yml run, so the first visible red is the next PR's merge-ref check. The guarantee itself (no misattributing merge passes CI) is unaffected; worth a one-line touch-up whenever the header is next edited. codex-bot's review flagged the same thing independently.— automated review by
claude-bot-andresmgsl· heavy-duty-review-botVerdict: Approve — I agree with this as-is at head
1a9d961.Re-review after rebase
Prior approve was at
711acb4. Force-push to1a9d961is rebase onto main after #109: only CHANGELOG adjacency restack (#108 entry above #109’s restore entry); guard/CI/CONTRIBUTING/tests byte-identical per peer range-diff. CI green including rehearsal.No remaining blockers.
Follow-up in
67331eb, folded into this PR rather than a separate one: thehalf-ceremony gap, from the cast#114 review round.
The bare-
VERSIONbranch leaves the top heading unconstrained on purpose (bothceremony shapes must stay legal — rig#44 / cast#108, not negotiable). That
asymmetry left a gap:
VERSIONbumped, a populated## Unreleasedstill ontop, no stamped section for that version — the wrong-number test is false on
its first clause, short-circuits, and passes.
release-notes.shthen refusesat publish time, after the merge, on
main.The bare branch now also requires the section it is about to publish to exist
and be non-empty, asserted by running
release-notes.shitself so the guardand the publisher cannot drift over what a section is. The error names the
stamp as MISSING, distinct from the wrong-number case. Matches rig#67.
The three new assertions were verified to fail against the previous guard
before the fix landed — half-ceremony and stamped-but-empty both went from
exit 0 to exit 1 — while the re-armed and un-re-armed ceremony trees stay
green, constructed and driven as real trees.
The other cast#114 finding (the mandated-ceremony-tree contradiction) does not
apply to box — it needs a pre-existing non-empty top-section assert to collide
with, and box has none. Deliberately left alone.
test/release.sh90 passed / 0 failed (was 86),test/cli.sh468 / 0,test/labels-reconcile.sh19 / 0,shellcheck -x bin/* **/*.shclean.CHANGELOG entry and PR body updated to tell the final story. Re-requested all
three reviews.
Verdict: I have feedback.
Approved at
67331eb: the new bare-version check correctly rejects both a missing stamp and an empty stamped section by invoking the same extractor used by the publisher, while preserving both legal ceremony shapes. The targeted release-flow check is green; the unrelated full rehearsal is still running.Non-blocking: the new script comment says release.yml “publishes a release with an empty body,” but
release-notes.shexits nonzero first, so release.yml refuses publication after merge. The behavior and guard are correct; only that explanatory sentence is inaccurate.Verdict: Approve (re-review at
67331eb)What changed since the approved revision (
1a9d961)Unlike the last re-review, this is not a rebase —
67331ebis a child commit of the approved1a9d961(same base,9ea50d2), and it is a substantive guard change: the half-ceremony fix from the cast#114 review round, exactly as the follow-up comment describes. Scope verified mechanically:git diff --stat 1a9d9616..67331ebetouches only.github/scripts/changelog-armed.sh(+39),test/release.sh(+24), andCHANGELOG.md(+17).ci.yml,CONTRIBUTING.md,release.yml, andrelease-notes.share byte-identical to what this bot approved, so the prior wiring and ceremony-doc analysis carries over; the new logic got full re-scrutiny.The new rule, re-scrutinized
The bare-VERSION branch gains one clause (changelog-armed.sh:122): after the wrong-number check, it requires
release-notes.sh "$ver" "$changelog"to succeed — the section about to ship must exist and be non-empty.The gap was real, and I reproduced it independently. I extracted the guard at
1a9d961and drove it against a constructed half-ceremony tree (bare0.8.0, populated## Unreleasedon top, no0.8.0section) and a stamped-but-empty tree (bare0.7.1, empty## 0.7.1section): both passed the old guard — the wrong-number test is false on its first clause and short-circuits, exactly as the PR body says. Both fail the new guard with exit 1.No false positive on either legal ceremony shape. Driven, not assumed: re-armed (
## Unreleasedabove the stamped0.7.1section) and un-re-armed (stamped section on top) both still pass — the rig#44/cast#108 invariant holds. The wrong-number tree still fails with its own message. This PR's own tree is-dev, untouched by the new clause; the guard passes on it (VERSION '0.7.1-dev' agrees with the top section (Unreleased)).Delegation is the right mechanism and the seams are sound.
release-notes.shaccepts<version> [<changelog>](release-notes.sh:15-16) and exits 1 on a missing or whitespace-only section (release-notes.sh:29), so guard and publisher cannot drift over what counts as a section — the exit-2 usage path is unreachable because the guard validates$vernon-empty first (changelog-armed.sh:55).$hereresolves viaBASH_SOURCE(changelog-armed.sh:49), so the guard finds the extractor from any cwd while the changelog path stays the caller's — which is exactly what letstest/release.shdrive it against fixture trees.Error taxonomy is pinned, not just worded. The MISSING-stamp message is distinct from wrong-number, and test/release.sh:247-249 asserts the absence of the wrong-number wording on the half-ceremony tree — the negative pin that keeps an operator from being sent to fix a version number that is already correct.
Verified on this head
bash test/release.sh— 90 passed, 0 failed (was 86; the 4 new checks: half-ceremony fails, names MISSING, not wrong-number, stamped-but-empty fails)bash test/cli.sh— 468 passed, 0 failedshellcheck -x .github/scripts/changelog-armed.sh test/release.sh— cleanrelease-notes.sh 0.7.1still extracts the correct section through a re-armed## Unreleasedabove it — publish path unaffectedNon-blocking notes
67331ebreview, confirmed independently): changelog-armed.sh:118-119 says that without this clause release.yml "publishes a release with an empty body" — it doesn't;release-notes.shexits nonzero and release.yml refuses after the merge. The guard's own error text (changelog-armed.sh:132-134) and the CHANGELOG entry describe it correctly ("refuse to extract the notes"); only that one header sentence overstates. Worth folding into the same touch-up as prior note 1.711acb4stand unchanged and non-blocking, chiefly the "goes red on the very next push" timing prose (theGITHUB_TOKENbump push triggers no ci.yml run; first visible red is the next merge-ref check).test/release.shpins only the historical 0.6.0/0.5.0 extractions, so there is no non-empty-top-section assert to collide with the re-arm.— automated review by
claude-bot-andresmgsl· heavy-duty-review-botVerdict: Approve — I agree with this as-is at head
67331eb.Re-review after
1a9d961→67331ebPrior approve bound to
1a9d961(rebase only). Tip is a substantive child: half-ceremony gap from the cast#114 round. Scope is onlychangelog-armed.sh,test/release.sh, and the CHANGELOG entry for that fix — CI wiring / CONTRIBUTING /release.ymlunchanged.What holds at tip
release-notes.sh "$ver" "$changelog"so the section about to ship must exist and carry prose. Same extractor as publish — no drift over what counts as a section.## Unreleased(first clause false), so a bumped bare VERSION with no stamped section used to pass and only fail post-merge inrelease.yml. Caught one step earlier now.wrong numberwording on half-ceremony).release-notes.shpath.Checks
CI green at
67331eb(check, scope, reconcile, rehearsal).Nit (optional, non-blocking)
Script header comment (~L118) says release.yml would "publish a release with an empty body"; the error body and CHANGELOG are accurate (
release-notes.shrefuses after merge). Peer notes same — prose only, not a blocker.No remaining blockers. Matches the rig#67 half-ceremony rule family.
@danmt — handoff summary. Three head-current approvals at
67331eb, all four checks green including the real-Incus rehearsal.What it fixes
#108: the release ceremony stamps
CHANGELOG.md's## Unreleasedto## X.Y.Z — DATEand nothing re-arms it. A PR authored before the release and merged after files its entry — cleanly, with no conflict — into the section that already shipped.box has never actually drifted, but only by luck, and the luck has already run out once:
77599abadded## 0.6.0without removing## Unreleased, so main was never disarmed — protected by a slip.heavy-duty/rig#66documents the same mechanism firing for real: an entry landed inside shipped## 0.1.0and was caught two minutes later by chance.What it does
Re-arm in
CONTRIBUTING.md's ceremony step, now stated as explicitly two edits — stamp, then put an empty## Unreleasedback. Not inrelease.yml, which only touchesVERSION; no workflow ever wrote the changelog heading, and box has no scripted ceremony helper, so the prose step is the whole surface.A VERSION-keyed guard,
.github/scripts/changelog-armed.sh, with its ownci.ymlstep:-dev⟹ top section must be## Unreleased## Unreleasedor the stamped section for that exact versionNote this could not simply re-add an unconditional
## Unreleasedrequirement — that is what rig#44 andheavy-duty/cast#108had to revert, because it is false by construction on the ceremony PR's own tree and makes the release PR unshippable.Review round
R1 — three approvals, then I held the handoff. A round on the cast sibling (
heavy-duty/cast#114) surfaced two defects in that repo's version, and I checked both against box rather than assuming they transferred:test/release.sh:95-96pins only historical 0.6.0/0.5.0), so box's ceremony tree was green either way. Deliberately left alone.if [ "$top_ver" != "Unreleased" ] && [ "$top_ver" != "$ver" ]. A bare0.8.0with a populated## Unreleasedon top and no## 0.8.0section anywhere makes the first clause false, short-circuits, and passes. Nothing then refuses untilrelease.ymlextracts the notes — after the merge, on main — publishing a release with an empty body.R2 — fix pushed as
67331eb, three re-approvals.The fix is worth a look because it is stronger than a reimplementation: rather than duplicating the extraction logic, the bare branch now invokes
release-notes.shitself — the same scriptrelease.ymlruns — so the guard and the publisher cannot drift over what counts as a valid section. The top heading stays unconstrained, both ceremony shapes stay legal, and the new error names the stamp as MISSING rather than misnumbered (with a test asserting the output does not say "wrong number").Verification
test/release.sh— 90 passed, 0 failed (86 before, +4)test/cli.sh— 468 passed, 0 failedtest/labels-reconcile.sh— 19 passed, 0 failedshellcheck -x bin/* **/*.sh— clean across all 15 filesThe half-ceremony fixture genuinely fails without the fix, proven two ways rather than asserted: manually, the half-ceremony tree made the old guard print "agrees" and exit 0 while
release-notes.shon the same tree exited 1; and reverting only the script made 3 new assertions fail.Guard behavior constructed and actually run in both directions:
0.7.1-dev, armed)-dev, no## Unreleased)Rebase note
This was rebased after #109 merged.
CHANGELOG.mdwas the only conflicted file — this branch does not touchbin/box, so #109's restructured dispatch table came across untouched. Both entries kept under the one### Fixedheading, #108 above #105, matching the section's existing newest-first ordering (105, 102, 99). Both entries were diffed against their sources afterward and are byte-identical toorigin/mainand711acb4respectively.Flagged for your judgment
One detection window stays open by design.
release.ymlbumpsVERSIONto-devby direct push withGITHUB_TOKEN, and such pushes fire no workflows. So a ceremony that skips the re-arm leaves main disarmed-and-unflagged until the next PR's CI run — which then goes red on an innocent PR that did not touch the changelog.That is a consequence of the #44 lesson rather than an oversight: an un-re-armed ceremony must stay shippable, so the guard cannot demand the re-arm at ceremony time. The trade is sound — the red arrives before any misfiled merge, which is the outcome that matters — but the first person to hit it will reasonably ask why their unrelated PR is failing. Worth a line in CONTRIBUTING if it bites.
Merge note
box#112 conflicts with this on one anchor — both insert at the top of
## Unreleased→### Fixed. Pure adjacency; keep both bullets. #112 does not touch any other file this PR owns. Whoever merges second needs a rebase, and that push retires its approvals.Siblings:
heavy-duty/rig#67(3/3, awaiting you) andheavy-duty/cast#114(3/3, awaiting you). Origin analysis onheavy-duty/rig#66.🤖 Generated with Claude Code