docs: CONSUMERS and drills describe fragment-mode adoption

Bootstrap arms with a preamble plus changelog.d/, the guard list gains
changelog-assembled, the conversion checklist gains the fragment
conversion, the changelog rule is fragment-first with the legacy floor
kept for unconverted pins, and the assembly command block gives a reader
everything needed to produce a section (#112 D12, #117).
This commit is contained in:
claude-bot-andresmgsl 2026-07-24 10:24:31 +00:00
parent a3e59241b5
commit 566712c690
2 changed files with 94 additions and 36 deletions

View file

@ -48,19 +48,23 @@ the machinery at all:
[lib/decide.sh](../lib/decide.sh#L70-L74)). Bootstrapping at `-dev` [lib/decide.sh](../lib/decide.sh#L70-L74)). Bootstrapping at `-dev`
keeps the repo clear of it entirely. (`package-json` backend: the keeps the repo clear of it entirely. (`package-json` backend: the
`version` field, same rule.) `version` field, same rule.)
2. **An armed `CHANGELOG.md`**: a preamble plus an empty `## Unreleased` 2. **An armed changelog: a `CHANGELOG.md` preamble plus `changelog.d/`.**
section for the first entries to land under. If the repo groups entries, The changelog file starts as preamble only — no section; the first
seed the shape the ceremony PR will restore at every re-arm: release writes the first one. The fragments directory beside it is the
arming (#112): it carries a `README.md` marker naming the assembler and
the doctrine — take ceremony's own
[changelog.d/README.md](../changelog.d/README.md) at the pin — which is
what keeps the directory tracked while it holds no fragments and what
`changelog-armed` asserts. Every behavior-change PR then writes
`changelog.d/<issue>.md` ([The changelog rule](#the-changelog-rule));
the release PR assembles the section
([Assembling a release section](#assembling-a-release-section)).
```markdown Fragment mode is **unreleased** and not in `0.1.0`. A consumer pinned
## Unreleased to `0.1.0` bootstraps the legacy shape instead — the preamble plus an
empty `## Unreleased` section for entries to land under — and converts
### Added on the pin bump to the first tag carrying fragment mode; never mix
refs to adopt it early.
### Changed
### Fixed
```
3. **`drills/README.md`** defining what a drill *means* in this repo — 3. **`drills/README.md`** defining what a drill *means* in this repo —
each repo names its own each repo names its own
([the drill doctrine](../README.md#the-drill-doctrine)). Plain ([the drill doctrine](../README.md#the-drill-doctrine)). Plain
@ -73,13 +77,19 @@ the machinery at all:
```yaml ```yaml
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
# changelog-monotonic compares HEAD against the merge base; a # changelog-monotonic and changelog-assembled compare HEAD
# checkout that cannot resolve it is a hard failure in CI, not # against the merge base; a checkout that cannot resolve it is
# a skip (a guard that can quietly stop guarding is the failure # a hard failure in CI, not a skip (a guard that can quietly
# shape these checks exist to refuse). # stop guarding is the failure shape these checks exist to
# refuse).
fetch-depth: 0 fetch-depth: 0
- uses: heavy-duty/ceremony/actions/changelog-armed@<pinned-tag> - uses: heavy-duty/ceremony/actions/changelog-armed@<pinned-tag>
- uses: heavy-duty/ceremony/actions/changelog-monotonic@<pinned-tag> - uses: heavy-duty/ceremony/actions/changelog-monotonic@<pinned-tag>
# Unreleased: changelog-assembled is not in 0.1.0. Adopt this step
# with the pin bump to the first tag that carries it; never mix
# refs. Green NOTICE on every non-release PR; on a release PR it
# asserts the stamped section is exactly the fragments it consumed.
- uses: heavy-duty/ceremony/actions/changelog-assembled@<pinned-tag>
- uses: heavy-duty/ceremony/actions/drill-recorded@<pinned-tag> - uses: heavy-duty/ceremony/actions/drill-recorded@<pinned-tag>
# Unreleased: runner-isolated is not in 0.1.0. Adopt this step with # Unreleased: runner-isolated is not in 0.1.0. Adopt this step with
# the pin bump to the first tag that carries it; never mix refs. # the pin bump to the first tag that carries it; never mix refs.
@ -105,8 +115,9 @@ the machinery at all:
here until a release tag ships it. If an action does not exist at the here until a release tag ships it. If an action does not exist at the
consumer's pinned tag, adopt it with the pin bump to the first tag that consumer's pinned tag, adopt it with the pin bump to the first tag that
carries it; never mix a moving or newer ref into an otherwise exact-pin carries it; never mix a moving or newer ref into an otherwise exact-pin
consumer. In particular, `0.1.0` carries the three release guards above consumer. In particular, `0.1.0` carries `changelog-armed`,
plus `docs-sync`, but not `runner-isolated`. `changelog-monotonic` and `drill-recorded` plus `docs-sync`, but not
`changelog-assembled` or `runner-isolated`.
6. **Labels automation** (optional but recommended): the caller from 6. **Labels automation** (optional but recommended): the caller from
[Labels automation](#labels-automation), plus `.github/labels.conf` [Labels automation](#labels-automation), plus `.github/labels.conf`
(panel + the repo's `scope:*` rows) and `.github/labeler.yml` (the (panel + the repo's `scope:*` rows) and `.github/labeler.yml` (the
@ -116,8 +127,8 @@ the machinery at all:
per [The artifact hook](#the-artifact-hook). No hook → the source per [The artifact hook](#the-artifact-hook). No hook → the source
tarball is the package. tarball is the package.
From there the flow is the doctrine: ordinary PRs add their changelog From there the flow is the doctrine: ordinary PRs write their fragment,
line, the ceremony PR makes the ceremony PR makes
[the three stamps](../README.md#what-a-release-is), a human merges, the [the three stamps](../README.md#what-a-release-is), a human merges, the
machine transcribes. machine transcribes.
@ -143,6 +154,18 @@ precisely so the machinery is safe to work on
the `panel=` roster line and the repo's `scope:*` rows the `panel=` roster line and the repo's `scope:*` rows
([the format](#labels-automation)). `.github/labeler.yml` stays as ([the format](#labels-automation)). `.github/labeler.yml` stays as
it is (path globs are inherently repo-specific). it is (path globs are inherently repo-specific).
- [ ] Convert the changelog to fragments (requires a pin at the first tag
carrying fragment mode — not `0.1.0`): move every entry under
`## Unreleased` to `changelog.d/<issue>.md`, verbatim — the filename
is derivable from the entry's own `(#N)`; an entry citing several
issues goes to the file for the first cited — delete the
`## Unreleased` heading, and add the `changelog.d/README.md` marker
([bootstrap step 2](#bootstrap-a-new-repo)). Published sections stay
byte-identical; `changelog-monotonic` proves that on the conversion
PR, and `changelog-armed` refuses a surviving `## Unreleased` the
moment the directory exists. Rewrite the repo's own contributor
docs that say "add a line under `## Unreleased`" in the same PR —
split either way, main lies for as long as the split lasts.
- [ ] Delete the now-shadowed copies — zero shared scripts remain: - [ ] Delete the now-shadowed copies — zero shared scripts remain:
`.github/scripts/release-notes.sh` (box, cast) or `.github/scripts/release-notes.sh` (box, cast) or
`release-lib.sh` (rig), `changelog-armed.sh` (box), `release-lib.sh` (rig), `changelog-armed.sh` (box),
@ -170,7 +193,7 @@ precisely so the machinery is safe to work on
changelog house style if it differs from changelog house style if it differs from
[the portable rule](#the-changelog-rule). [the portable rule](#the-changelog-rule).
- [ ] What stays, per repo, forever: `VERSION` (or the `package.json` - [ ] What stays, per repo, forever: `VERSION` (or the `package.json`
version), `CHANGELOG.md`, `drills/`, `.github/labeler.yml`, version), `CHANGELOG.md`, `changelog.d/`, `drills/`, `.github/labeler.yml`,
`.github/labels.conf`, the optional `.github/labels.conf`, the optional
`.github/actions/release-artifact/` — the full kept-vs-moved table `.github/actions/release-artifact/` — the full kept-vs-moved table
is in [#1](https://github.com/heavy-duty/ceremony/issues/1). is in [#1](https://github.com/heavy-duty/ceremony/issues/1).
@ -395,25 +418,58 @@ Bumping the pin re-syncs the mirror in the same PR —
The portable version of the family's contributor rule — the repo's own The portable version of the family's contributor rule — the repo's own
CONTRIBUTING may sharpen it, but this is the floor the guards assume: CONTRIBUTING may sharpen it, but this is the floor the guards assume:
- **Every PR that changes behavior adds one line** under `## Unreleased`. - **Every PR that changes behavior writes one fragment**:
- **Grouped changelogs keep three standing headings:** `### Added`, `changelog.d/<issue>.md`, named for the authorizing issue —
`### Changed`, and `### Fixed`. The ceremony PR's hand-edited re-arm seeds `<repo>-<issue>.md` for cross-repo work carrying `Part of <repo>#N`
all three; append under one instead of creating a heading at the top so the name is known at claim time and two builders can only collide by
anchor. Create `Deprecated`, `Removed`, or `Security` only when a change working the same issue (#112 D2). Never an edit to `CHANGELOG.md`: the
genuinely needs that rarer kind. release PR assembles the section
- **Insert above the heading below — never type over it.** Replacing a ([below](#assembling-a-release-section)).
shipped `## X.Y.Z` heading with your entry deletes that release's - **The fragment is the prose, not a description of it** (#112 D3): the
section, silently; this exact edit is why the exact lines that will be published — no front-matter, no `## ` heading
[monotonic guard](../README.md#changelog-monotonic--shipped-headings-are-append-only) (that one is the assembler's to write). `changelog-armed` refuses a
exists (box#122). malformed fragment on the PR that wrote it.
- **Grouped repos group inside the fragment**: `### Added`, `### Changed`,
`### Fixed` headings with bullets under them; create `Deprecated`,
`Removed`, or `Security` only when a change genuinely needs that rarer
kind. A repo is grouped or flat, never both (#112 D4). The assembler
merges groups in canonical order — Added, Changed, Fixed, Removed,
Deprecated, Security, then anything else first-seen — and inside a
group entries read newest issue first (#112 D5).
- **One line: say what changed, and stop.** Lead with the surface, not - **One line: say what changed, and stop.** Lead with the surface, not
the mechanism — "`state:needs-human` is set at handoff" beats "the the mechanism — "`state:needs-human` is set at handoff" beats "the
labels workflow now also wakes on `labeled`". The why and the how labels workflow now also wakes on `labeled`". The why and the how
belong in the PR body, where anyone chasing the reasoning already goes. belong in the PR body, where anyone chasing the reasoning already goes.
- **Cite the issue or PR**`(#141)`. - **Cite the issue or PR**`(#141)`.
- **Mark a breaking change** with a leading `BREAKING:`. - **Mark a breaking change** with a leading `BREAKING:`.
- Flat changelogs remain flat; the standing-heading rule applies only when a - A repo not yet on fragment mode — no `changelog.d/` — keeps the legacy
repo already groups its entries. floor until its conversion: one line under `## Unreleased`, inserted
**above** the heading below it, never over it (replacing a shipped
heading deletes that release's section silently — box#122, why the
[monotonic guard](../README.md#changelog-monotonic--shipped-headings-are-append-only)
exists), appended under a standing `### ` heading where the repo groups.
## Assembling a release section
The ceremony PR's changelog stamp is one command, run **by hand, never in
CI** — the assembled section must land in the release PR's diff, where
the panel reads it (#112 D12). A consumer runs the tool from a ceremony
checkout at its own pin:
```sh
git clone --depth 1 --branch <pinned-tag> https://github.com/heavy-duty/ceremony /tmp/ceremony
/tmp/ceremony/bin/changelog-assemble <X.Y.Z>
```
Run it at the repo root. It folds every `changelog.d/` fragment into a
new `## X.Y.Z — DATE` section on top of `CHANGELOG.md` (DATE is today's
UTC date; pass one as a second argument to choose it) and deletes the
fragments it consumed — commit both halves together. `--check` prints the
would-be section body without touching anything; read it before running
the real thing. In CI, `changelog-assembled` replays the run from the
merge base and refuses a stamp that is not byte-for-byte what the
fragments assemble to — a mis-run hand step fails the PR, not the
published release.
## Adopting the agent team flow ## Adopting the agent team flow

View file

@ -26,8 +26,10 @@ drill is where they run live *before* a version rests on them.
carrying the candidate tree with `CEREMONY_SELF_REF` rewritten to the carrying the candidate tree with `CEREMONY_SELF_REF` rewritten to the
candidate SHA in every pin carrier, and records that one-line deviation candidate SHA in every pin carrier, and records that one-line deviation
in its record. From the second release on, this paragraph is moot. in its record. From the second release on, this paragraph is moot.
3. Give it a fixture `VERSION` / `CHANGELOG.md` / `drills/` in the armed 3. Give it a fixture `VERSION` / `CHANGELOG.md` / `changelog.d/` /
state (`X.Y.Z-dev`, `## Unreleased` on top). `drills/` in the armed state (`X.Y.Z-dev`, the fragments directory with
its `README.md` marker plus at least one fragment for the ceremony to
consume).
4. Exercise both doors, one probe at a time: 4. Exercise both doors, one probe at a time:
1. a merge-door ceremony publishes exactly one release and re-arms main 1. a merge-door ceremony publishes exactly one release and re-arms main