actions/changelog-armed — fragment mode: the directory is the arming, and a bad entry fails on its own PR #115

Closed
opened 2026-07-24 08:13:46 +00:00 by dan-claude-bot · 2 comments
dan-claude-bot commented 2026-07-24 08:13:46 +00:00 (Migrated from github.com)

Context

Part of #112. actions/changelog-armed asks one question — is there a heading for the next PR's entry to land under, and is it the right one for the state this tree is in — keyed on the tree's version. Under fragments the answer moves: the place a PR's entry lands is changelog.d/, and a directory is always armed. The guard keeps its name and its question, and gains a second mode (D7).

Two things it must not lose. Its header comment's warning — rig#44 and cast#108 both had to revert the naive "always require ## Unreleased" form — stays true and stays in the file, because legacy mode still is that guard. And the box#108 failure it was built for (an entry landing cleanly under a heading that shipped while the PR was open) is structurally impossible under fragments; the file should say so rather than leave a reader wondering what happened to the guard's reason for existing.

The consumers are pinned at 0.1.0 and adopt on a pin bump. Dual mode (D8) is what lets that happen per repo instead of in one flag day, and it is what lets ceremony's own flip (#117) be a single reviewable PR.

Spec

Mode detection: changelog.d/ exists in the tree → fragment mode. Otherwise → legacy mode, which is today's script, behavior-identical, tests unchanged. The directory is the switch because it is the thing the adoption PR creates, so a repo's mode is a property of its tree and never of a flag someone forgets to pass. The action gains an optional fragments-dir input defaulting to changelog.d; the script takes it as a third positional argument, matching the existing <changelog> <version-source> shape.

Fragment mode, -dev tree (the everyday case, every PR):

  1. changelog.d/ must exist and contain its generated README.md — the directory is the arming, and the README is what makes an otherwise-empty directory trackable by git (the .ceremony/README.md precedent).
  2. CHANGELOG.md must have no ## Unreleased section (D6). Nothing accumulates there any more; one left behind is a second anchor with no owner and no assembler, and prose written into it would never ship.
  3. Every fragment must be well-formedchangelog_fragment_problem from #114, run over each file, diagnosis reproduced verbatim (D9). This is the real prize: the rules the publisher used to enforce at release time, on main, after the merge, now fail on the PR that wrote the bad entry, where the author is still holding it.

Fragment mode, bare tree (the ceremony PR and its merge commit):

  1. changelog.d/ must contain no fragments — they were consumed by the assembly. A fragment surviving the ceremony is an entry that silently misses its release.
  2. The top section must be ## <ver> — not Unreleased, which no longer exists in this mode — and it must be publishable: the existing changelog_section_problem assert, unchanged, through the very extractor the publisher uses.
  3. Rule 2 applies here too.

The bare-tree asymmetry that legacy mode needs — both re-armed and stamped shapes legal — has no fragment-mode counterpart, because there is no re-arm. Say that in the file, next to the paragraph that explains why the asymmetry exists in legacy mode.

Diagnostics: every refusal prints the fix, in the register the file already uses. The two new ones a reader will not have seen before are "a ## Unreleased section survived the adoption — move its entries into changelog.d/<issue>.md and delete the heading" and "these fragments were not consumed: … — re-run changelog-assemble <ver>".

Tasks

  • actions/changelog-armed/changelog-armed.sh — mode detection, the six rules, diagnostics.
  • actions/changelog-armed/action.yml — the fragments-dir input, defaulted, documented.
  • Header comment — what fragment mode asserts, why box#108 cannot happen in it, and why legacy mode's asymmetry has no counterpart.
  • test/changelog-armed.test.sh — fragment-mode rows beside the existing ones.
  • shellcheck clean; test/run.sh green.

Acceptance criteria

  • A tree with no changelog.d/ behaves exactly as at 2f58d9b — every existing test row passes unmodified.
  • -dev + changelog.d/ present + no ## Unreleased + well-formed fragments → green.
  • -dev + a surviving ## Unreleased → red, naming the section.
  • -dev + changelog.d/ missing its README.md → red.
  • -dev + one malformed fragment → red, quoting changelog_fragment_problem's diagnosis and the file.
  • bare + fragments left in the directory → red, listing them.
  • bare + top section is the stamped ## <ver> with entries + directory consumed → green.
  • bare + top section for a different version → red (the mis-stamp rule survives the mode change).
  • bare + version bumped but no section stamped → red via changelog_section_problem (the half-ceremony row survives the mode change).
  • The guard reads the fragment rules from lib/changelog.sh — no second copy of them in this file.

Test plan

Extend test/changelog-armed.test.sh, whose constructed two-file trees grow a directory. Every existing row stays and must stay green — that is the D8 proof.

Fragment rows that must pass: -dev, directory with README and no fragments (a freshly released main); -dev with several well-formed fragments, flat; the same grouped; bare with the stamped section and an emptied directory; bare where the changelog also still carries every older section.

Fragment rows that must fail: each of the six rules violated in isolation; a ## Unreleased that is empty (still refused — the heading is the problem, not its contents); a fragment named notes.md; a fragment with a ### heading and nothing under it; bare with the stamp missing entirely.

Cross-mode: the same tree with and without changelog.d/ produces different verdicts on the same changelog, and both are correct — the row that proves mode detection is doing the work.

Dependencies

Part of #112. Blocked by #114. Blocks #117.

## Context Part of #112. [`actions/changelog-armed`](https://github.com/heavy-duty/ceremony/blob/2f58d9b/actions/changelog-armed/changelog-armed.sh) asks one question — *is there a heading for the next PR's entry to land under, and is it the right one for the state this tree is in* — keyed on the tree's version. Under fragments the answer moves: the place a PR's entry lands is `changelog.d/`, and a directory is always armed. The guard keeps its name and its question, and gains a second mode (D7). Two things it must not lose. Its [header comment's warning](https://github.com/heavy-duty/ceremony/blob/2f58d9b/actions/changelog-armed/changelog-armed.sh#L9-L16) — rig#44 and cast#108 both had to revert the naive "always require `## Unreleased`" form — stays true and stays in the file, because legacy mode still is that guard. And the box#108 failure it was built for (an entry landing cleanly under a heading that shipped while the PR was open) is *structurally* impossible under fragments; the file should say so rather than leave a reader wondering what happened to the guard's reason for existing. The consumers are pinned at `0.1.0` and adopt on a pin bump. Dual mode (D8) is what lets that happen per repo instead of in one flag day, and it is what lets ceremony's own flip (#117) be a single reviewable PR. ## Spec **Mode detection**: `changelog.d/` exists in the tree → fragment mode. Otherwise → legacy mode, which is today's script, behavior-identical, tests unchanged. The directory is the switch because it is the thing the adoption PR creates, so a repo's mode is a property of its tree and never of a flag someone forgets to pass. The action gains an optional `fragments-dir` input defaulting to `changelog.d`; the script takes it as a third positional argument, matching the existing `<changelog> <version-source>` shape. **Fragment mode, `-dev` tree** (the everyday case, every PR): 1. `changelog.d/` must exist and contain its generated `README.md` — the directory is the arming, and the README is what makes an otherwise-empty directory trackable by git (the `.ceremony/README.md` precedent). 2. `CHANGELOG.md` must have **no `## Unreleased` section** (D6). Nothing accumulates there any more; one left behind is a second anchor with no owner and no assembler, and prose written into it would never ship. 3. **Every fragment must be well-formed** — `changelog_fragment_problem` from #114, run over each file, diagnosis reproduced verbatim (D9). This is the real prize: the rules the publisher used to enforce at release time, on main, after the merge, now fail on the PR that wrote the bad entry, where the author is still holding it. **Fragment mode, bare tree** (the ceremony PR and its merge commit): 4. `changelog.d/` must contain **no fragments** — they were consumed by the assembly. A fragment surviving the ceremony is an entry that silently misses its release. 5. The top section must be `## <ver>` — not `Unreleased`, which no longer exists in this mode — and it must be publishable: the existing [`changelog_section_problem`](https://github.com/heavy-duty/ceremony/blob/2f58d9b/lib/changelog.sh#L26-L69) assert, unchanged, through the very extractor the publisher uses. 6. Rule 2 applies here too. The bare-tree asymmetry that legacy mode needs — both re-armed and stamped shapes legal — has no fragment-mode counterpart, because there is no re-arm. Say that in the file, next to the paragraph that explains why the asymmetry exists in legacy mode. **Diagnostics**: every refusal prints the fix, in the register the file already uses. The two new ones a reader will not have seen before are "a `## Unreleased` section survived the adoption — move its entries into `changelog.d/<issue>.md` and delete the heading" and "these fragments were not consumed: … — re-run `changelog-assemble <ver>`". ## Tasks - [ ] `actions/changelog-armed/changelog-armed.sh` — mode detection, the six rules, diagnostics. - [ ] `actions/changelog-armed/action.yml` — the `fragments-dir` input, defaulted, documented. - [ ] Header comment — what fragment mode asserts, why box#108 cannot happen in it, and why legacy mode's asymmetry has no counterpart. - [ ] `test/changelog-armed.test.sh` — fragment-mode rows beside the existing ones. - [ ] shellcheck clean; `test/run.sh` green. ## Acceptance criteria - [ ] A tree with no `changelog.d/` behaves exactly as at `2f58d9b` — every existing test row passes unmodified. - [ ] `-dev` + `changelog.d/` present + no `## Unreleased` + well-formed fragments → green. - [ ] `-dev` + a surviving `## Unreleased` → red, naming the section. - [ ] `-dev` + `changelog.d/` missing its `README.md` → red. - [ ] `-dev` + one malformed fragment → red, quoting `changelog_fragment_problem`'s diagnosis and the file. - [ ] bare + fragments left in the directory → red, listing them. - [ ] bare + top section is the stamped `## <ver>` with entries + directory consumed → green. - [ ] bare + top section for a different version → red (the mis-stamp rule survives the mode change). - [ ] bare + version bumped but no section stamped → red via `changelog_section_problem` (the half-ceremony row survives the mode change). - [ ] The guard reads the fragment rules from `lib/changelog.sh` — no second copy of them in this file. ## Test plan Extend `test/changelog-armed.test.sh`, whose constructed two-file trees grow a directory. Every existing row stays and must stay green — that is the D8 proof. **Fragment rows that must pass**: `-dev`, directory with README and no fragments (a freshly released main); `-dev` with several well-formed fragments, flat; the same grouped; bare with the stamped section and an emptied directory; bare where the changelog also still carries every older section. **Fragment rows that must fail**: each of the six rules violated in isolation; a `## Unreleased` that is *empty* (still refused — the heading is the problem, not its contents); a fragment named `notes.md`; a fragment with a `### ` heading and nothing under it; bare with the stamp missing entirely. **Cross-mode**: the same tree with and without `changelog.d/` produces different verdicts on the same changelog, and both are correct — the row that proves mode detection is doing the work. ## Dependencies Part of #112. Blocked by #114. Blocks #117.
github-actions[bot] commented 2026-07-24 09:10:54 +00:00 (Migrated from github.com)

Every issue named by Blocked by is closed. The sweep is moving this issue to ready.

<!-- issueflow:blockers-cleared --> Every issue named by `Blocked by` is closed. The sweep is moving this issue to `ready`.
codex-bot-andresmgsl commented 2026-07-24 09:17:59 +00:00 (Migrated from github.com)

Starting this build. I’ll implement fragment-mode detection and guard behavior in changelog-armed, preserve the legacy contract unchanged, add the specified constructed-tree coverage, and open a draft PR as soon as the first red-test checkpoint is committed.

Starting this build. I’ll implement fragment-mode detection and guard behavior in `changelog-armed`, preserve the legacy contract unchanged, add the specified constructed-tree coverage, and open a draft PR as soon as the first red-test checkpoint is committed.
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#115
No description provided.