The ceremony disarms CHANGELOG.md: a PR predating a release lands its Unreleased entry inside the shipped section, silently #108
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#108
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Filed as the box-side record of
heavy-duty/rig#66, which documents a confirmed occurrence in rig. box has the same mechanism and has not drifted yet — this is the "decide deliberately" half of that issue's closing note, answered as no, it is not a non-issue here.The mechanism
The release ceremony stamps
## Unreleased→## X.Y.Z — DATEby hand in the ceremony PR —07c0eddfor 0.7.0 shows exactly this. No workflow does it;release.yml:164-186only touchesVERSION. Nothing re-adds the heading afterwards, somainsits with no## Unreleasedbetween the ceremony and the next PR that happens to re-create one.A PR authored before the release and merged after it wrote its entry under
## Unreleased. That heading no longer exists, so git lands the entry — cleanly, with no conflict — under whatever heading now occupies that position: the just-shipped section. The signal an author would rely on ("git told me to look") is absent precisely when the outcome is wrong.box's actual exposure
No drift on either release. But the reason is luck, not design:
77599abadded## 0.6.0without removing## Unreleased, so main was never disarmed. Structurally protected by a slip in the ceremony.cdf4c04(#98), a VERSION-only bump touching one file. The first changelog-touching branch after,b58ce55, has merge-base exactly the release mergefb7fc84, so it was cut from the stamped tree and re-added## Unreleaseditself.Both released sections on main are currently byte-identical to their tags. The exposure is real; the sample size is just small. box merges PRs in flight across releases as a matter of course, so this recurs at every release with the normal state of the repo.
Blast radius is bounded, which is also why nobody notices
The published GitHub release body is safe —
release.yml:128and:208extract notes from the changelog at release time, from the tree at the tag, before any later PR can merge. So the artifact users read stays correct; the repository's ownCHANGELOG.mdis what drifts. The wrong version of the file is the one only maintainers read.box has no guard at all
Worth stating plainly, because rig and cast at least have something: box's
test/release.sh:95-96pins only that the 0.6.0 and 0.5.0 sections still extract. There is no top-section guard. rig (test/release.sh:103-107) and cast (test/release.test.ts:157-165) have one, but it asserts only that whatever the top##section is extracts non-empty — which cannot catch this either, and that relaxation was deliberate (rig#44, cast#108) because the old guard demanded a literal## Unreleased, false by construction on the ceremony PR's own tree.So the naive fix is already closed off: re-introducing an unconditional
## Unreleasedrequirement re-breaks the ceremony PR.Proposed fix
Both halves, matching what rig and cast are getting:
## Unreleasedimmediately after stamping, so a late-merging PR has a correct heading to land in. This belongs in CONTRIBUTING's ceremony (CONTRIBUTING.md:51says "stamp the Unreleased section" and stops), not inrelease.yml, which only touches VERSION. Fixes the cause: the entry goes to the right place with no author action.## X.Y.Ztop section is legal exactly whenVERSIONis bare; whenever VERSION ends in-dev, the top section must be## Unreleased. That distinguishes the two states the current guards collapse, stays green through the ceremony PR, and goes red on a disarmed-devmain.(1) makes the common case correct; (2) catches a hand-edit or a skipped re-arm. The cost is an empty section sitting in the file between releases, which seems cheap next to a changelog that misattributes shipped releases.
Refs
heavy-duty/rig#66(origin, with the confirmed occurrence and the reproduction) · rig#44 /heavy-duty/cast#108(why the existing guards were relaxed)