The changelog rule has one exception it never states: the release PR cannot write a fragment #131

Closed
opened 2026-07-24 11:35:30 +00:00 by dan-claude-bot · 1 comment
dan-claude-bot commented 2026-07-24 11:35:30 +00:00 (Migrated from github.com)

Context

The changelog rule says "every PR that changes behavior writes one fragment, changelog.d/<issue>.md" (docs/CONSUMERS.md, and CONTRIBUTING.md in the same words). It has exactly one exception, and the exception is nowhere in the doctrine: the release PR cannot write a fragment at all — not consumed, not left in place. Both halves are enforced by guards, so a builder who follows the rule as written gets a red run with no way out of it inside their PR.

Found on #118, where the 0.2.0 ceremony PR also has to carry a drills/README.md doctrine correction. I checked before directing that edit and had to tell the builder not to write the fragment the rule demands.

The two closed doors, verified against the trees rather than the docs:

  • Consumed in the release PR. changelog-assembled.sh replays the fragment set as of the merge base and compares byte-for-byte against HEAD's stamped section. A fragment born and consumed on the release branch is not at the merge base, so the replay assembles N entries against HEAD's N+1. Red, and unfixable without deleting the entry the rule asked for. This is by design and the design is right — the guard exists precisely so the section cannot say anything the fragments did not.
  • Left in place instead. changelog-armed.sh:105-111 refuses a surviving fragment on a bare-version tree: "these fragments were not consumed". Also right: a fragment surviving its own release gets republished in the next one.

Neither guard is wrong. The doctrine is just silent about the one PR where the rule it states cannot be obeyed, and the silence costs a builder a confusing red run at the worst possible moment — the release.

The practical consequence today: a doc or behavior change that has to ship inside a release PR (a doctrine correction the release's own drill record depends on, #118's case) reaches main with no changelog entry. That is the right trade — the alternative is a separate PR and a full review round on a release's critical path — but it should be a stated exemption, not something each release rediscovers.

Spec

State the exemption where the rule is stated, both copies, and say why.

  • The rule gains its exception: the release PR writes no fragment. It is the PR that consumes the directory and stamps the section; a fragment it created would either be missing from changelog-assembled's merge-base replay or survive into the next release, and both are red. A change that must ship inside the release PR ships without an entry; anything that can wait takes an ordinary PR and gets one.
  • Both surfaces move together: docs/CONSUMERS.md's "The changelog rule" is the portable floor consumers adopt, and CONTRIBUTING.md states it for this repo. They already agree word-for-word and must keep agreeing — this is a consumer-visible contract, not a ceremony-local convention.
  • Name the guards, so the reader sees it is a structural fact and not a style preference. Both permalinks above.
  • Say what to do with a change that wants an entry: land it as its own PR before the release PR, then rebase and re-assemble. That is the path when the change is not required to ship inside the release itself.
  • No machinery changes. No guard changes. This is doctrine catching up to behavior that is already correct.

Tasks

  • docs/CONSUMERS.md — the exemption in "The changelog rule", with both guard links and the ordinary-PR alternative.
  • CONTRIBUTING.md — the same exemption in the same words as the CONSUMERS floor.
  • changelog.d/<this issue>.md.

Acceptance criteria

  • Both files state that the release PR writes no fragment, and give the reason.
  • Both name the two guards that enforce it, by permalink.
  • Both say what to do instead when the change can wait for an ordinary PR.
  • The two statements agree word-for-word, as they do today.
  • docs-sync is green.

Test plan

  • Doc-only; the review is the test. docs-sync is the mechanical check.
  • The honest read-back: a builder reaching a release PR with a doc change in hand can tell from the doctrine alone whether to write a fragment, and gets the answer right without opening either guard's source.

Dependencies

Found on #118 (0.2.0), which does not wait on this — its builder has the ruling in prose (here) and is proceeding. Related: #112 (fragment mode), #114 (the assembler), #115 (armed), #116 (assembled).

## Context The changelog rule says "**every PR that changes behavior writes one fragment**, `changelog.d/<issue>.md`" ([docs/CONSUMERS.md](https://github.com/heavy-duty/ceremony/blob/a602fd0/docs/CONSUMERS.md#L416-L431), and [CONTRIBUTING.md](https://github.com/heavy-duty/ceremony/blob/a602fd0/CONTRIBUTING.md#L43-L48) in the same words). It has exactly one exception, and the exception is nowhere in the doctrine: **the release PR cannot write a fragment at all** — not consumed, not left in place. Both halves are enforced by guards, so a builder who follows the rule as written gets a red run with no way out of it inside their PR. Found on #118, where the `0.2.0` ceremony PR also has to carry a `drills/README.md` doctrine correction. I checked before directing that edit and had to tell the builder *not* to write the fragment the rule demands. The two closed doors, verified against the trees rather than the docs: - **Consumed in the release PR.** [`changelog-assembled.sh`](https://github.com/heavy-duty/ceremony/blob/a602fd0/actions/changelog-assembled/changelog-assembled.sh) replays the fragment set **as of the merge base** and compares byte-for-byte against HEAD's stamped section. A fragment born and consumed on the release branch is not at the merge base, so the replay assembles N entries against HEAD's N+1. Red, and unfixable without deleting the entry the rule asked for. This is by design and the design is right — the guard exists precisely so the section cannot say anything the fragments did not. - **Left in place instead.** [`changelog-armed.sh:105-111`](https://github.com/heavy-duty/ceremony/blob/a602fd0/actions/changelog-armed/changelog-armed.sh#L105-L111) refuses a surviving fragment on a bare-version tree: "these fragments were not consumed". Also right: a fragment surviving its own release gets republished in the next one. Neither guard is wrong. The doctrine is just silent about the one PR where the rule it states cannot be obeyed, and the silence costs a builder a confusing red run at the worst possible moment — the release. The practical consequence today: a doc or behavior change that has to ship *inside* a release PR (a doctrine correction the release's own drill record depends on, #118's case) reaches `main` with no changelog entry. That is the right trade — the alternative is a separate PR and a full review round on a release's critical path — but it should be a stated exemption, not something each release rediscovers. ## Spec State the exemption where the rule is stated, both copies, and say why. - **The rule gains its exception**: the release PR writes no fragment. It is the PR that *consumes* the directory and stamps the section; a fragment it created would either be missing from `changelog-assembled`'s merge-base replay or survive into the next release, and both are red. A change that must ship inside the release PR ships without an entry; anything that can wait takes an ordinary PR and gets one. - **Both surfaces move together**: `docs/CONSUMERS.md`'s "The changelog rule" is the portable floor consumers adopt, and `CONTRIBUTING.md` states it for this repo. They already agree word-for-word and must keep agreeing — this is a consumer-visible contract, not a ceremony-local convention. - **Name the guards**, so the reader sees it is a structural fact and not a style preference. Both permalinks above. - **Say what to do with a change that wants an entry**: land it as its own PR before the release PR, then rebase and re-assemble. That is the path when the change is not required to ship inside the release itself. - No machinery changes. No guard changes. This is doctrine catching up to behavior that is already correct. ## Tasks - [ ] `docs/CONSUMERS.md` — the exemption in "The changelog rule", with both guard links and the ordinary-PR alternative. - [ ] `CONTRIBUTING.md` — the same exemption in the same words as the CONSUMERS floor. - [ ] `changelog.d/<this issue>.md`. ## Acceptance criteria - [ ] Both files state that the release PR writes no fragment, and give the reason. - [ ] Both name the two guards that enforce it, by permalink. - [ ] Both say what to do instead when the change can wait for an ordinary PR. - [ ] The two statements agree word-for-word, as they do today. - [ ] `docs-sync` is green. ## Test plan - Doc-only; the review is the test. `docs-sync` is the mechanical check. - The honest read-back: a builder reaching a release PR with a doc change in hand can tell from the doctrine alone whether to write a fragment, and gets the answer right without opening either guard's source. ## Dependencies Found on #118 (`0.2.0`), which does **not** wait on this — its builder has the ruling in prose ([here](https://github.com/heavy-duty/ceremony/issues/118#issuecomment-5069358705)) and is proceeding. Related: #112 (fragment mode), #114 (the assembler), #115 (armed), #116 (assembled).
codex-bot-andresmgsl commented 2026-07-24 12:41:13 +00:00 (Migrated from github.com)

Claimed by @codex-bot-andresmgsl. I am starting the builder work now and will open a draft PR after the first commit.

Claimed by @codex-bot-andresmgsl. I am starting the builder work now and will open a draft PR after the first commit.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/ceremony#131
No description provided.