From 67331ebecd0cb1400c018c8830b0a50e5e90837b Mon Sep 17 00:00:00 2001 From: dan-claude-bot Date: Sun, 19 Jul 2026 20:43:52 +0000 Subject: [PATCH] fix: the armed guard also refuses a half-done ceremony (#108, cast#114 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bare-VERSION branch left the top heading unconstrained on purpose — both ceremony shapes have to stay legal, which is the rig#44 / cast#108 lesson. A review round on the sibling fix found the gap that asymmetry leaves: a tree with VERSION bumped, '## Unreleased' still populated on top, and no stamped section for that version makes the wrong-number test false on its first clause, short-circuits, and passes. release.yml then refuses at publish time — after the merge, on main, with the release already half-shipped. So the bare branch now also requires the section it is about to publish to exist and be non-empty, asserted by running release-notes.sh itself so the guard and the publisher cannot drift over what a section is. The message is distinct from the wrong-number case: a missing stamp is not a misnumbered one. Matches heavy-duty/rig#67. test/release.sh constructs the half-ceremony tree and the stamped-but-empty tree and drives the real script at both — all three new assertions fail against the previous guard — while the re-armed and un-re-armed ceremony trees stay green. Co-Authored-By: Claude Opus 4.8 --- .github/scripts/changelog-armed.sh | 39 +++++++++++++++++++++++++++++- CHANGELOG.md | 17 ++++++++++++- test/release.sh | 24 ++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/.github/scripts/changelog-armed.sh b/.github/scripts/changelog-armed.sh index c2c37df..a5bf4c7 100755 --- a/.github/scripts/changelog-armed.sh +++ b/.github/scripts/changelog-armed.sh @@ -19,7 +19,9 @@ set -euo pipefail # VERSION ends in -dev -> the top section MUST be '## Unreleased' # VERSION is bare -> the top section may be '## Unreleased' (armed, # the ceremony's own re-arm) or the stamped -# section for exactly that VERSION +# section for exactly that VERSION — AND the +# section for that VERSION must exist and carry +# prose, because it is the one about to ship # # Keying on VERSION is the whole design, and the reason this is not simply # "require '## Unreleased'". That unconditional form is what rig#44 and @@ -41,6 +43,11 @@ set -euo pipefail changelog="${1:-CHANGELOG.md}" version_file="${2:-VERSION}" +# release-notes.sh lives beside this script; the bare-VERSION branch runs it +# rather than re-implementing the extraction, so the guard and the publisher +# cannot disagree about what a section is or when one counts as empty. +here="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + [ -f "$changelog" ] || { echo "changelog-armed: no such file: $changelog" >&2; exit 1; } [ -f "$version_file" ] || { echo "changelog-armed: no such file: $version_file" >&2; exit 1; } @@ -99,6 +106,36 @@ changelog-armed: VERSION is '$ver' but the top section of $changelog is: itself. A stamped section naming a different version means the ceremony stamped the wrong number, and the published release body would come from the wrong section. +EOF + exit 1 + fi + # The top heading is deliberately left UNCONSTRAINED above — both ceremony + # shapes must stay legal, which is the #44 / cast#108 lesson and is not + # negotiable. That asymmetry leaves a gap of its own, the HALF-ceremony + # tree: VERSION bumped to the release, a populated '## Unreleased' still on + # top, and no stamped section for the version anywhere. The test above is + # false on its first clause, short-circuits, and passes. Nothing else + # refuses until release.yml extracts the notes — which happens AFTER the + # merge, on main, and publishes a release with an empty body, the worst + # place for this to land. So make the same assert one step earlier by + # running the very script release.yml runs (heavy-duty/rig#67). + if ! bash "$here/release-notes.sh" "$ver" "$changelog" >/dev/null 2>&1; then + cat >&2 <&1 | grep -qF 'wrong number'; } +check "armed: ...and not as the wrong-number case, which has a different fix" \ + 0 "" not_wrong_number "$T" +# A section that exists but carries no prose is the same failure: release.yml +# would publish an empty body, which is what release-notes.sh already refuses. +T="$(tree rel-empty 0.7.1 '## 0.7.1 — 2026-07-19' '' '## 0.7.0 — 2026-07-19' '' '- **Shipped**')" +check "armed: a bare VERSION whose section is stamped but EMPTY fails" \ + 1 "no non-empty section" armed "$T" + # --- degenerate trees refuse rather than pass by accident ------------------ T="$(tree no-sections 0.7.1-dev 'Prose and no headings at all.')" check "armed: a changelog with no '## ' section at all fails" 1 "no '## ' section at all" armed "$T"