changelog: nothing notices a DELETED release heading — and release-notes.sh re-arms on duplicates #133
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:apply
scope:capture
scope:coolify-api
scope:fleet
scope:manifest
scope:secrets
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/cast#133
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?
Nothing here notices a deleted release heading.
boxgrew a guard forthis (heavy-duty/box#122,
caught in review of box#118); cast never got it — and cast's
release-notes.shcarries the exact awk shape that made box#118 dangerous.What cast already has
To be clear about what is not missing: the arming rule is covered
thoroughly, in
test/release.test.tsunder "the changelog is armed for thenext entry (rig#66)" — the real tree, the ceremony trees, the disarmed-
-devred case, and a double-re-arm case. That is the rig#66 fix and it works.
Arming is just narrow by design. It asks one question — does the top section
agree with
package.json's version? — about one heading, the one a PR isabout to write under. It says nothing about the rest of the file.
The gap
Release headings are append-only: the ceremony adds one and nothing in
CONTRIBUTING's release flow ever removes one. Nothing asserts that.
The failure is an author adding an entry under
## Unreleasedwho replacesthe heading below it instead of inserting above it:
git merges that cleanly — a one-line edit in a file nobody touched
concurrently, so no conflict and no signal. The arming test stays green and is
not wrong to: the top section is still the right one for the version.
0.1.1'sbody is now sitting under
## Unreleased, and0.1.1has no section at all.Why cast is the more exposed of the two
box's guard also asserts version headings are unique on HEAD, because
containment cannot catch a duplicate — a duplicate is head-side surplus, and
base-minus-head is blind to it.
cast's .github/scripts/release-notes.sh is
the vulnerable shape, byte for byte:
There is no
exit.grabre-arms on every matching##line, so two## 0.1.1headings make the published body absorb whatever sits between thecopies — and an entry stranded there is dropped from the next release's
notes as well. That is precisely the box#118 outcome, and box's guard is the
thing that now stops it. (rig's extractor has
if (found) exit, so ittruncates instead of absorbing — same class, milder symptom. cast has the
absorbing one.)
Note the existing
"goes RED on a double re-arm — two Unreleased headings"test does not cover this: it asserts on duplicate
## Unreleased, not onduplicate version headings, which is the case that reaches
release-notes.sh.Proposal
Port
.github/scripts/changelog-monotonic.shfrom box, and wire it in CI theway box does:
Three details from box's version that should survive the port:
history, not two files, and its degradation is different (no base ref is a
skip, not a failure). cast's arming assertions run against constructed
in-memory changelog strings that are not git repos at all, so a
git-dependent assert folded in there would either skip or lie. A shell
script here keeps it identical to box and rig rather than reimplementing the
invariant a third time in TypeScript — though driving it from
release.test.ts(as cast already drivesrelease-notes.sh) is the naturalway to test it.
assert is vacuous.
STRICT=1in CI. A checkout that cannot reach the base ref must failrather than skip quietly forever — needs
fetch-depth: 0on the PRcheckout.
## Unreleasedis deliberately outside the guarded set — the arming check ownsthat heading, and the ceremony legitimately consumes it.
Sibling issue for the same gap in rig: heavy-duty/rig#98.