lib/changelog.sh — a publishable section is counted in entries, not bytes, and the re-arm seeds Added/Changed/Fixed #98
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:docs
scope:guards
scope:labels
scope:release-flow
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/ceremony#98
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?
Context
From discussion #97, which carried
heavy-duty/rig#86 here after it was closed as
wrong-repo. All line references pinned at
6b127f1.Two halves, minted as one issue because landing either alone is a net loss — the discussion's own
conclusion, and this issue's ordering constraint:
## Unreleasedshould carry### Added,### Changedand### Fixedfromthe moment it is re-armed, so a PR only ever appends a bullet under an existing heading —
never creates a heading at the same anchor another PR is creating. The conflict rate today is a
function of how many PRs are open, not of whether they are related, and it compounds: each merge
re-conflicts every PR still open. Evidence is rig#86's real batch (seven entries across eight
PRs; nearly every rebase hit
CHANGELOG.md, not one conflict a disagreement about content) andbox's
## Unreleasedtoday, whichcarries
### Changedabove### Added— the ordering hazard, live, in the family's oldestconsumer.
test in this repo is a byte test. Seeding without fixing them trades a noisy, self-announcing
conflict for a silent hole in a release gate.
One premise from the discussion is wrong, and correcting it is what makes this issue small. #97
says the seeding belongs to the
-devre-arm, and that a product repo can therefore no longerimplement it because that commit is authored here. The
-devre-arm(release.yml L266–L291)
writes only the version file —
git add VERSION/git add package.json package-lock.json,under a comment that forbids
git add -Aoutright — and has never touchedCHANGELOG.md. Thechangelog's re-arm, putting an empty
## Unreleasedback above the stamped section, is and alwayswas the second hand edit of the ceremony PR
(README, "two edits, not one").
So the seeding half is doctrine here (README,
docs/CONSUMERS.md, the vendoredBUILDER.md),not a workflow change, and it reaches each consumer with its next pin bump. The guard half is the
only machinery in this issue.
The four byte-based emptiness tests, all of which answer "empty" with
wc -c:[ -z "$(changelog_section …)" ]— the half-ceremony refusal (rig#67)[ ! -s "$RUNNER_TEMP/notes.md" ][ -n "$notes" ]— the hand-run wrapperlib/changelog.shis where the definition belongs and already says so: "Empty output represents either an absent or
an empty section, which callers deliberately treat as the same refusal." Four callers, one
definition — the same reason
changelog_sectionitself exists (#4).Spec
D1 — the seeding is a hand edit; doctrine states it, and no workflow changes. Per the premise
correction above.
release.ymlis not touched by this issue.D2 — the seeded set is three:
### Added,### Changed,### Fixed. Those are the threeevery grouped consumer uses (box, rig, cast, incubator). Keep-a-Changelog's
Deprecated,RemovedandSecurityare typed by hand when a PR needs one: a rare heading-creating edit is anacceptable conflict; six standing empty headings are noise on every read of the file.
docs/CONSUMERS.md's changelog ruletoday lists four kinds (
Added/Changed/Fixed/Removed) and says nothing about seeding; it gains thesplit — three seeded, the rest by hand.
D3 — a flat changelog is unaffected, including this repo's own. The rule is conditional: if a
repo groups its entries under
###headings, its re-arm seeds the three. This repo'sCHANGELOG.mdis a flat bullet list and stays one; there is nothing to seed and the guard'sheading rule below is vacuous on it. No retroactive edit to any existing
## Unreleasedis askedfor — the template arrives at a repo's next re-arm, on a section that is empty anyway. That is
also #97's question 3 (migration) answered: there is no window to manage, because nothing in the
machinery depends on the template being present.
D4 — "non-empty" becomes "carries entries, and has no dangling heading", on version sections
only. A section is publishable iff:
^[[:space:]]*[-*][[:space:]];###heading inside it carries at least one entry before the next###or thesection's end.
Unreleasedis exempt from (b) and (c) by construction: it is the work-in-progress section,and the seeded template is exactly what it is supposed to look like when empty. Only (a) applies
there.
Rule (c) is what stops the template from becoming a fresh box#108. Without it, the stamp carries
the unused headings into the published release body and nothing ever says so; with it, the ceremony
PR's own CI refuses before the merge —
changelog-armedruns on everypull_request(ci.yml L74) —
where the fix is deleting a line. The two release doors keep their copy of the assert as
belt-and-braces on main, exactly as they do today.
D5 — one definition, four call sites.
lib/changelog.shgains one function beside theextractor:
Diagnoses, in this order, first match wins:
no section for '<ver>'section '<ver>' has no entries — a heading is not an entrysection '<ver>' has an empty heading: '### Added'(name the first one)changelog_sectionitself is unchanged: it is the extractor the publisher pipes into therelease body, and the new predicate reads the same output, so the guard and the publisher still
cannot disagree about what a section is (rig#67). Each caller keeps its own remedy prose — the four
remedies genuinely differ (PR-time re-arm, merge-door stamp, tag-door stamp, hand run) — and
appends the diagnosis line to it. mawk-compatible awk, no
\xescapes (CONTRIBUTING).D6 — no consumer changes and no pin bump in this issue. box, rig, cast and incubator all pin
0.1.0; they take the guard and the doctrine at their next bump, which is #1's standing pin-bumpitem. Nothing here is a cross-repo change.
Tasks
lib/changelog.sh— addchangelog_section_problemper D4/D5; leavechangelog_sectionbyte-identical.
actions/changelog-armed/changelog-armed.sh— replace the L137 byte test with the predicate,keeping the half-ceremony remedy text and appending the diagnosis.
.github/workflows/release.yml— both doors (L199–L206, L343–L350) call the predicate insteadof
[ ! -s ], keeping each door's existing message.bin/changelog-section— same swap at L19–L23.README.md— the re-arm bullet (L24–L45) shows the seeded template as the shape the ceremony PRputs back; the
changelog-armedsection (L131–L142) states the entry rule and the dangling-headingrefusal.
docs/CONSUMERS.md— the changelog rule (L372–L392) gains D2's split and the "append, nevercreate at the top anchor" sentence; the bootstrap's armed-
CHANGELOG.mditem (L51–L52) shows theseeded shape.
BUILDER.md(vendored) L86–L88 andCONTRIBUTING.mdL43–L45 — the insert rule gains "appendunder an existing heading; create one only when the kind is genuinely new".
test/changelog.test.shandtest/changelog-armed.test.sh— the cases in the test plan.CHANGELOG.md— one line under## Unreleased, citing this issue.Acceptance criteria
changelog_section_problemexists inlib/changelog.sh, is the only place rules (a)–(c)are expressed, and
changelog_sectionis unchanged.changelog-armed, both release doors,bin/changelog-section.grep -n '\-s "\$RUNNER_TEMP/notes.md"' .github/workflows/release.ymlreturns nothing.### Addedwith no bullets is refused bychangelog-armed, bybin/changelog-section, and by both doors' notes step.### Added(empty) plus### Fixed(one bullet) is refused,naming
### Added.## Unreleasedholding the three empty headings passeschangelog-armedon a-devtree, and
bin/changelog-section Unreleasedprints it without refusing.###headings) still passes — this repo's ownCHANGELOG.mdisthe fixture.
docs/CONSUMERS.md,BUILDER.mdandCONTRIBUTING.mdstate the template, thethree-heading set, and that the ceremony PR's re-arm is what seeds it.
test/run.shgreen; shellcheck- and actionlint-clean.Test plan
test/changelog.test.sh— the predicate, driven against constructed fixtures:### Fixedwith one bullet; three headings where allthree carry bullets;
*bullets as well as-.### Added/### Changed/### Fixedwith no bullets (the seeded-and-shipped case thiswhole issue exists to catch); section with one populated and one empty heading, with the empty
one named in the message.
Unreleasedwith three empty headings returns clean;Unreleasedabsent still refuses."no entries" first.
test/changelog-armed.test.sh— the guard rows, in-tree:-devtree +## Unreleasedcarrying the three empty headings → passes (the post-re-arm treeevery consumer will sit on).
### Added→ fails, message names the headingand keeps the half-ceremony remedy.
Not covered, stated honestly: the doors' notes steps are exercised by
release-exercise.yml's step-replay against its fixture, so the fixture gains an entry-lessvariant; a live release is still the only end-to-end proof, as it is for every other line in that
job.
Dependencies
wrong-repo. rig#86's analysis and its real-batch evidence are its author's.
changelog.d/,assembled at release time), the structural fix that would make changelog conflicts impossible.
Same-section collisions still conflict after this issue, and that is the honest limit of it.
Claimed by @codex-bot-andresmgsl. Starting implementation in
build/98-publishable-changelog-sections; I will open a draft PR after the first tested commit.