changelog: seed ## Unreleased with all three section headings, and let the -dev bump write them
#135
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/box#135
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?
## Unreleasedshould always carry### Added,### Changedand### Fixed, even when empty — and the-devbump should be what seeds them.The problem
Every open PR writes its entry under
## Unreleased, and the section starts out with no subsection headings. So the first PR to need### Fixedcreates it, and any other PR that also needs### Fixedcreates it too — at the same position, in the same otherwise-empty region. Git sees two sides both inserting at one anchor and calls it a conflict.That makes the conflict rate a function of how many PRs are open, not of whether they have anything to do with each other. Two changes that touch entirely different files, in different subsystems, still collide in
CHANGELOG.md.It compounds: each merge re-conflicts every PR still open, so a batch of N PRs costs on the order of N² rebases rather than N.
Evidence from a real batch
A recent batch in heavy-duty/rig merged seven entries — 1
Added, 3Changed, 3Fixed— across eight PRs. Nearly every rebase in it hitCHANGELOG.md, and not one of those conflicts was a genuine disagreement about content. Two shapes showed up:### Changed, the other### Fixed. They compose perfectly; the conflict exists only because both had to create their heading. This proposal eliminates these entirely.### Fixedbullets landing at the same anchor. This proposal does not fix these — see Limits.The worst single case was shape 1 with an ordering twist: a PR adding
### Addedconflicted against### Fixed, and resolving it correctly meant hoisting the new section to the top, because this file ordersAdded → Changed → Fixed. Concatenating would have produced out-of-order release notes that nothing would have caught. With the headings pre-seeded, that judgment call does not exist — the entry goes under a heading that is already in the right place.Proposal
1.
## Unreleasedalways carries all three headings, even when empty:A PR then only ever appends a bullet under an existing heading. It never creates a heading, never chooses where a section goes, and never reorders anything.
2. The
-devbump commit is what seeds them. The release flow already re-armsVERSIONtoX.Y.(Z+1)-devautomatically after a release; that same commit should write the fresh## Unreleasedwith the three empty headings.This is the part that makes the whole thing safe. Release notes are extracted verbatim between
## <ver>and the next##(changelog_sectionin.github/scripts/release-lib.sh), so if empty headings lived in a stamped section they would ship into the published release body. Seeding them on the-devside means the template only ever exists in## Unreleased, and a stamped section only ever contains real entries.It also moves the changelog re-arm from a manual ceremony step into the automation that already re-arms
VERSION— which is a bonus, not a side effect: the re-arm exists because a forgotten manual one fails silently and misattributes entries to a shipped release.The guard this breaks, and how
The release flow asserts that the section it is about to publish is non-empty. With seeded headings, a section containing only three empty headings is textually non-empty — so a genuinely empty release would sail past that check.
The guard must test for at least one real entry (a
-bullet), not for bytes. Landing the seeding without this is a net loss: it trades a noisy, self-announcing conflict for a silent hole in a release gate.Limits — stated honestly
### Fixedstill insert at the same anchor. This converts "conflicts between nearly every pair" into "conflicts only within a section", and leaves only the trivial resolution shape (keep both bullets, no ordering decision).changelog.d/directory, one file per PR, assembled at release time), which makes conflicts structurally impossible because no two PRs touch the same file. That is a bigger change and deserves its own issue if the cheap fix proves insufficient. This one is a few lines and no new concepts.Acceptance
## Unreleasedcarries all three headings onmain-devbump commit seeds them, so a stamped section never contains an empty headingCONTRIBUTING.mdsays "append under the right heading" rather than "add an entry under## Unreleased"Filed in all three repos
box, rig and cast share this release flow and this failure. Whichever lands first should be the reference the other two follow, so the three do not drift.
Siblings: heavy-duty/rig#86, heavy-duty/cast#126.
Triage: closing — the deliverable this issue names can no longer be built here, and its substance has landed (and been superseded) upstream.
The spec targets the
-devbump in box's own.github/scripts/release-lib.sh. That file is gone: #164 delegated release governance to the shared ceremony —.github/workflows/release.ymlis now a thinuses: heavy-duty/ceremony/.github/workflows/release.yml@0.1.0caller — so the re-arm this issue wants to extend is ceremony code, not box code. No box PR can implement it.Upstream, both halves already happened:
### Added/### Changed/### Fixed.changelog.dfragments — each PR writes its own file, which eliminates both conflict shapes (disjoint sections and same-section bullets), not just shape 1.At the
0.1.0pin box consumes today neither exists yet (lib/changelog.sh@0.1.0only extracts sections), so the conflict pain described here is real until box bumps its ceremony pin — but that bump is a different deliverable with its own contract (it swaps the whole changelog workflow over to fragments), minted when box takes it. Keeping this spec open would only mislead a builder into patching a file that no longer exists. Siblings rig#86 / cast#126 are the same story in their own repos.