fix: the release ceremony re-arms CHANGELOG.md, and CI keeps main armed (#108) #110

Merged
dan-claude-bot merged 2 commits from fix/changelog-rearm into main 2026-07-19 21:07:11 +00:00
3 changed files with 78 additions and 2 deletions
Showing only changes of commit 67331ebecd - Show all commits

View file

@ -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 <<EOF
changelog-armed: VERSION is '$ver' but $changelog has no non-empty section for
'$ver'. The top section is:
$top
This is a HALF-DONE ceremony: the version was bumped but its section was
never stamped — the stamp is MISSING, not misnumbered. A bare VERSION means
this tree is a release, and the section it is about to publish has to exist
and have prose in it. Left alone, this passes CI, merges, and only then does
release.yml refuse to extract the notes — on main, after the fact, with the
release already half-shipped.
The fix is the ceremony's first edit (CONTRIBUTING.md, "Releases"): stamp
'## Unreleased' into '## $ver — DATE', then put an empty '## Unreleased'
back above it.
EOF
exit 1
fi

View file

@ -63,7 +63,22 @@ which records not just what changed but what each drill run proved.
false by construction on the ceremony PR's own tree, which is why rig#44
and heavy-duty/cast#108 both had to revert it. So a forgotten re-arm
does not block the release; it turns `main` red on the very next push,
the automatic `-dev` bump the release itself makes.
the automatic `-dev` bump the release itself makes. Leaving the bare
branch's top heading unconstrained is what keeps both ceremony shapes
legal, and a review round on the sibling fix (heavy-duty/cast#114) found
the gap that asymmetry leaves: a **half-ceremony** tree — `VERSION`
bumped, `## Unreleased` still populated on top, and the section for that
version never stamped — makes the wrong-number test false on its first
clause, short-circuits, and passes. Nothing then refuses until
`release.yml` extracts the notes, which is *after* the merge, on `main`,
with the release already half-shipped. So the bare branch now also
requires that the section it is about to publish exists and is non-empty,
and it asserts that by running `release-notes.sh` — the very script
`release.yml` runs — so the guard and the publisher cannot drift apart
over what a section is. The message is its own: a missing stamp is not a
misnumbered one, and an operator sent to correct a version number that is
already right will not find the real problem. Matches
heavy-duty/rig#67, so the three repos agree.
- **`box restore` asks before it destroys — and the confirmation prompt is
now the row's, not rm's** (#105) — `restore` and `rm` both irreversibly
discard user state, and only one of them asked. The table gave `restore`

View file

@ -229,6 +229,30 @@ check "armed: ...and so does the RE-ARMED ceremony tree (the shape #108 asks for
T="$(tree rel-wrong 0.7.1 '## 0.7.0 — 2026-07-19' '' '- **Some other release**')"
check "armed: a bare VERSION under someone ELSE's stamped section fails" 1 "wrong number" armed "$T"
# --- the HALF-ceremony: the gap the two bare-VERSION clauses leave ---------
# VERSION bumped to the release, '## Unreleased' still populated on top, and
# the section for that version never stamped at all. The wrong-number test
# above is false on its FIRST clause here and short-circuits, so before
# heavy-duty/rig#67's rule this tree passed the guard and was refused instead
# by release.yml — at publish time, after the merge, on main, with the release
# already half-shipped. Caught here one step earlier, by running the same
# extraction release.yml runs.
T="$(tree rel-half 0.8.0 '## Unreleased' '' '- **A pending entry**' '' '## 0.7.0 — 2026-07-19' '' '- **Shipped**')"
check "armed: a bare VERSION whose section was never stamped fails (half-ceremony)" \
1 "no non-empty section" armed "$T"
check "armed: ...and names the stamp as MISSING, not misnumbered" \
1 "MISSING, not misnumbered" armed "$T"
# The wording is the whole point of the separate branch: an operator sent to
# fix a version number that is already correct will not find the real problem.
not_wrong_number() { ! armed "$1" 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"