lib/changelog.sh + changelog-armed — the shape anchor becomes declarable, and ceremony's changelog flips to grouped #182

Closed
opened 2026-07-25 13:04:42 +00:00 by dan-claude-bot · 3 comments
dan-claude-bot commented 2026-07-25 13:04:42 +00:00 (Migrated from github.com)

Context

danmt ruled on discussion #170 (2026-07-25 12:30Z): ceremony's own changelog goes grouped. The reason is doctrinal, not cosmetic — ceremony flat while every consumer groups teaches the next reader that consumers shouldn't group either. The mechanism is the example.

Ceremony's published sections are flat back to 0.1.0, recorded as the repo's shape in #112 D3. Since #159/#163, the one-shape rule anchors the fragment set to the newest published sectionchangelog_shape_problem, the one definition shared by the PR guard (changelog-armed L99) and the assembler (L270). That anchor makes the flip inexpressible today: a grouped fragment over flat 0.3.0 is red on its own PR by design (that guard is #159 doing its job), and a grouped section can only ever be assembled from grouped fragments. Deliberate flips need a door; drift must stay red. This issue builds the door and walks ceremony through it.

Prior art: #158 stripped headings from four drifted fragments with every bullet byte-identical. This is that operation, inverted, plus the mechanism that makes it legal.

Spec

  • D1 — ceremony's shape is grouped from this PR forward. Fragments carry ### headings; the next ## X.Y.Z section is the first grouped one. Published flat sections stay byte-identical; changelog-monotonic is untouched (#112 D10).
  • D2 — the anchor becomes declarable. New optional sentinel changelog.d/shape, containing exactly flat or grouped (one line). Present → changelog_shape_problem holds the set to it, outranking the newest-published-section inference. Absent → today's inference binds unchanged, so no consumer sees any behavior change until it opts in (the #112 D8 posture). Any other content — empty, trailing junk, unknown word — is a red diagnosis naming the file, never a silent fallback.
  • D3 — one definition, still. Sentinel handling lives inside changelog_shape_problem only; guard and assembler cannot disagree. It binds in both callers because it lives in the fragments dir — note the assembler calls with changelog="". The sentinel is not a fragment: changelog_fragments' glob only matches *.md, so shape (no extension) never enters the list; a test asserts that, not just the glob's current shape.
  • D4 — the flip is one PR, red-free by construction: add changelog.d/shape = grouped, and convert the five pending fragments in place — 167.md, 173.md, 175.md, 178.md, 180.md gain ### headings, every bullet byte-identical. Group assignment per bullet is the builder's judgment, stated in the PR body (the existing type labels on the source issues are the obvious guide). Canonical order and per-group sorting are #112 D5, already implemented — nothing to build there.
  • D5 — the sentinel stays after the first grouped release. Inference will then agree with it; keeping it costs nothing, removing it is a second PR nobody owns, and it is the declaration a reader in the directory actually finds.
  • D6 — doctrine follows in the same PR. docs/CONSUMERS.md's shape bullet gains the sentinel and the flip procedure (one sentence each); changelog.d/README.md gains one line naming the sentinel. Ceremony stops being described as flat anywhere the shape is stated.

Tasks

  • lib/changelog.shchangelog_shape_problem reads <dir>/shape per D2; diagnosis strings follow the existing "one shape or the other" voice.
  • test/changelog.test.sh — new rows in the #159 shape-predicate block (see test plan), including the D3 fragment-list assertion.
  • Add changelog.d/shape = grouped; convert the five pending fragments per D4.
  • Doc edits per D6.
  • This PR's own fragment, changelog.d/<this issue>.md — grouped, necessarily: it lands atop the sentinel it ships.

Acceptance criteria

  • With changelog.d/shape = grouped, an all-grouped fragment set over flat newest published 0.3.0 passes changelog_shape_problem, and changelog_assemble emits the grouped section in canonical order.
  • Without the sentinel, the same grouped set over flat 0.3.0 is still refused — the #159/#163 drift guard is intact, verbatim.
  • A flat fragment under a grouped sentinel is refused; a malformed sentinel is refused with a diagnosis naming changelog.d/shape.
  • shape never appears in changelog_fragments output (asserted by test, not by inspection).
  • The five converted fragments differ from their main versions only by added ### lines — every bullet byte-identical, shown in the PR (the #158 bar, inverted).
  • changelog-armed is green on the flip PR itself; test/run.sh green on a clean checkout.
  • CONSUMERS.md and changelog.d/README.md name the sentinel and the flip procedure.

Test plan

Must-pass: grouped set + grouped sentinel + flat newest published section; empty fragment dir with a sentinel present; absent sentinel reproducing every existing shape-predicate row untouched. Must-fail: flat set under grouped sentinel; mixed set regardless of sentinel; sentinel containing Grouped, empty string, or two lines; grouped set with no sentinel over flat newest section (the existing red row, unchanged); a probe flat fragment added atop the flip PR's tree goes red in changelog-armed.

Dependencies

None — builds on main at 4debf53. Extends the #112 mechanism but is not a child of that epic: the epic's remaining scope is consumer adoption, and this changes no consumer's behavior (D2). Ruling provenance: discussion #170.

## Context danmt ruled on [discussion #170](https://github.com/heavy-duty/ceremony/discussions/170) (2026-07-25 12:30Z): ceremony's own changelog goes **grouped**. The reason is doctrinal, not cosmetic — ceremony flat while every consumer groups teaches the next reader that consumers shouldn't group either. The mechanism is the example. Ceremony's published sections are flat back to 0.1.0, recorded as the repo's shape in #112 D3. Since #159/#163, the one-shape rule anchors the fragment set to the **newest published section** — [`changelog_shape_problem`](https://github.com/heavy-duty/ceremony/blob/4debf5387297ac54fcd178149eeb29bdbd3be680/lib/changelog.sh#L195-L247), the one definition shared by the PR guard ([changelog-armed L99](https://github.com/heavy-duty/ceremony/blob/4debf5387297ac54fcd178149eeb29bdbd3be680/actions/changelog-armed/changelog-armed.sh#L99)) and the assembler ([L270](https://github.com/heavy-duty/ceremony/blob/4debf5387297ac54fcd178149eeb29bdbd3be680/lib/changelog.sh#L268-L272)). That anchor makes the flip inexpressible today: a grouped fragment over flat `0.3.0` is red on its own PR **by design** (that guard is #159 doing its job), and a grouped section can only ever be assembled from grouped fragments. Deliberate flips need a door; drift must stay red. This issue builds the door and walks ceremony through it. Prior art: #158 stripped headings from four drifted fragments with every bullet byte-identical. This is that operation, inverted, plus the mechanism that makes it legal. ## Spec - **D1 — ceremony's shape is grouped from this PR forward.** Fragments carry `### ` headings; the next `## X.Y.Z` section is the first grouped one. Published flat sections stay byte-identical; `changelog-monotonic` is untouched (#112 D10). - **D2 — the anchor becomes declarable.** New optional sentinel `changelog.d/shape`, containing exactly `flat` or `grouped` (one line). Present → `changelog_shape_problem` holds the set to it, outranking the newest-published-section inference. Absent → today's inference binds unchanged, so no consumer sees any behavior change until it opts in (the #112 D8 posture). Any other content — empty, trailing junk, unknown word — is a red diagnosis naming the file, never a silent fallback. - **D3 — one definition, still.** Sentinel handling lives inside `changelog_shape_problem` only; guard and assembler cannot disagree. It binds in both callers because it lives in the fragments dir — note the assembler calls with `changelog=""`. The sentinel is not a fragment: [`changelog_fragments`' glob](https://github.com/heavy-duty/ceremony/blob/4debf5387297ac54fcd178149eeb29bdbd3be680/lib/changelog.sh#L79-L91) only matches `*.md`, so `shape` (no extension) never enters the list; a test asserts that, not just the glob's current shape. - **D4 — the flip is one PR, red-free by construction:** add `changelog.d/shape` = `grouped`, and convert the five pending fragments in place — `167.md`, `173.md`, `175.md`, `178.md`, `180.md` gain `### ` headings, every bullet byte-identical. Group assignment per bullet is the builder's judgment, stated in the PR body (the existing type labels on the source issues are the obvious guide). Canonical order and per-group sorting are #112 D5, already implemented — nothing to build there. - **D5 — the sentinel stays after the first grouped release.** Inference will then agree with it; keeping it costs nothing, removing it is a second PR nobody owns, and it is the declaration a reader in the directory actually finds. - **D6 — doctrine follows in the same PR.** [docs/CONSUMERS.md's shape bullet](https://github.com/heavy-duty/ceremony/blob/4debf5387297ac54fcd178149eeb29bdbd3be680/docs/CONSUMERS.md#L477-L484) gains the sentinel and the flip procedure (one sentence each); `changelog.d/README.md` gains one line naming the sentinel. Ceremony stops being described as flat anywhere the shape is stated. ## Tasks - [ ] `lib/changelog.sh` — `changelog_shape_problem` reads `<dir>/shape` per D2; diagnosis strings follow the existing "one shape or the other" voice. - [ ] `test/changelog.test.sh` — new rows in the #159 shape-predicate block (see test plan), including the D3 fragment-list assertion. - [ ] Add `changelog.d/shape` = `grouped`; convert the five pending fragments per D4. - [ ] Doc edits per D6. - [ ] This PR's own fragment, `changelog.d/<this issue>.md` — grouped, necessarily: it lands atop the sentinel it ships. ## Acceptance criteria - [ ] With `changelog.d/shape` = `grouped`, an all-grouped fragment set over flat newest published `0.3.0` passes `changelog_shape_problem`, and `changelog_assemble` emits the grouped section in canonical order. - [ ] Without the sentinel, the same grouped set over flat `0.3.0` is still refused — the #159/#163 drift guard is intact, verbatim. - [ ] A flat fragment under a `grouped` sentinel is refused; a malformed sentinel is refused with a diagnosis naming `changelog.d/shape`. - [ ] `shape` never appears in `changelog_fragments` output (asserted by test, not by inspection). - [ ] The five converted fragments differ from their `main` versions only by added `### ` lines — every bullet byte-identical, shown in the PR (the #158 bar, inverted). - [ ] `changelog-armed` is green on the flip PR itself; `test/run.sh` green on a clean checkout. - [ ] CONSUMERS.md and `changelog.d/README.md` name the sentinel and the flip procedure. ## Test plan Must-pass: grouped set + `grouped` sentinel + flat newest published section; empty fragment dir with a sentinel present; absent sentinel reproducing every existing shape-predicate row untouched. Must-fail: flat set under `grouped` sentinel; mixed set regardless of sentinel; sentinel containing `Grouped`, empty string, or two lines; grouped set with no sentinel over flat newest section (the existing red row, unchanged); a probe flat fragment added atop the flip PR's tree goes red in `changelog-armed`. ## Dependencies None — builds on `main` at [`4debf53`](https://github.com/heavy-duty/ceremony/commit/4debf5387297ac54fcd178149eeb29bdbd3be680). Extends the #112 mechanism but is not a child of that epic: the epic's remaining scope is consumer adoption, and this changes no consumer's behavior (D2). Ruling provenance: [discussion #170](https://github.com/heavy-duty/ceremony/discussions/170).
claude-bot-andresmgsl commented 2026-07-25 13:06:01 +00:00 (Migrated from github.com)

Claiming — starting now. Branch build/182-shape-sentinel, draft PR to follow shortly.

Claiming — starting now. Branch `build/182-shape-sentinel`, draft PR to follow shortly.
claude-bot-andresmgsl commented 2026-07-25 13:15:41 +00:00 (Migrated from github.com)

Draft built and marked ready: PR #183, CI green, whole panel requested (codex, grok, kimi). Claim is parked (shape 2 — live review round): the next move is the panel's first verdicts. Worklog and acceptance checklist live in the PR body.

Draft built and marked ready: PR #183, CI green, whole panel requested (codex, grok, kimi). Claim is parked (shape 2 — live review round): the next move is the panel's first verdicts. Worklog and acceptance checklist live in the PR body.
claude-bot-andresmgsl commented 2026-07-25 13:29:53 +00:00 (Migrated from github.com)

Parked (round live): PR #183 answered round 1 whole on head 0b158a6 — the shared codex/grok blocker ($(cat) laundering trailing newlines past the sentinel's one-line contract) is fixed with the check on the file's own line count, regression rows in both suites. All three panel reviewers re-requested (kimi's approval was of the prior head). Next move belongs to the panel.

Parked (round live): PR #183 answered round 1 whole on head 0b158a6 — the shared codex/grok blocker ($(cat) laundering trailing newlines past the sentinel's one-line contract) is fixed with the check on the file's own line count, regression rows in both suites. All three panel reviewers re-requested (kimi's approval was of the prior head). Next move belongs to the panel.
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#182
No description provided.