ceremony adopts changelog.d — the flag flip #125
41 changed files with 285 additions and 161 deletions
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
|
|
@ -16,6 +16,7 @@ scope:release-flow:
|
|||
- bin/**
|
||||
- VERSION
|
||||
- CHANGELOG.md
|
||||
- changelog.d/**
|
||||
- drills/**
|
||||
- test/decide.test.sh
|
||||
- test/facts.test.sh
|
||||
|
|
|
|||
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
|
|
@ -11,8 +11,8 @@ to the issue for triage to amend, not silently unshipped. -->
|
|||
|
||||
## Changelog
|
||||
|
||||
- [ ] One line under `## Unreleased` — inserted **above** the heading below
|
||||
it, never over it — or no behavior change, stated here.
|
||||
- [ ] One fragment, `changelog.d/<issue>.md` — the exact prose to publish,
|
||||
never an edit to `CHANGELOG.md` — or no behavior change, stated here.
|
||||
|
||||
## Round log
|
||||
|
||||
|
|
|
|||
38
.github/workflows/release-exercise.yml
vendored
38
.github/workflows/release-exercise.yml
vendored
|
|
@ -87,11 +87,14 @@ jobs:
|
|||
echo "RELEASE_ASSETS_DIR=$RUNNER_TEMP/release-assets" >> "$GITHUB_ENV"
|
||||
- name: construct the fixture consumer tree and the gh stub
|
||||
# The fixture release.yml's steps run against (below): a base at
|
||||
# 0.6.9-dev with an armed changelog, then the ceremony merge —
|
||||
# VERSION bumped bare, Unreleased stamped. Same shape as
|
||||
# test/release-chain.test.sh. The gh stub answers the one API fact
|
||||
# the ceremony path consults (the merged release-labeled PR) so
|
||||
# nothing here talks to GitHub.
|
||||
# 0.6.9-dev armed the fragment way (#112) — changelog.d/ with its
|
||||
# marker and one fragment — then the ceremony merge: VERSION bumped
|
||||
# bare and the section stamped by the REAL assembler, the command
|
||||
# the real ceremony PR runs by hand (#112 D12), so the exercise
|
||||
# consumes the tool end to end instead of hand-writing its output.
|
||||
# Same shape as test/release-chain.test.sh. The gh stub answers the
|
||||
# one API fact the ceremony path consults (the merged
|
||||
# release-labeled PR) so nothing here talks to GitHub.
|
||||
run: |
|
||||
mkdir -p "$RUNNER_TEMP/stub"
|
||||
cat > "$RUNNER_TEMP/stub/gh" <<'EOF'
|
||||
|
|
@ -110,31 +113,18 @@ jobs:
|
|||
cat > CHANGELOG.md <<'EOF'
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- The entry this release ships.
|
||||
|
||||
## 0.6.8 — 2026-07-01
|
||||
|
||||
- An older entry.
|
||||
EOF
|
||||
git add VERSION CHANGELOG.md
|
||||
mkdir changelog.d
|
||||
printf '# changelog.d/ — assembled at release (heavy-duty/ceremony#112); the marker keeps the directory tracked.\n' > changelog.d/README.md
|
||||
printf -- '- The entry this release ships.\n' > changelog.d/42.md
|
||||
git add VERSION CHANGELOG.md changelog.d
|
||||
git commit -qm "base"
|
||||
printf '0.7.0\n' > VERSION
|
||||
cat > CHANGELOG.md <<'EOF'
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.7.0 — 2026-07-21
|
||||
|
||||
- The entry this release ships.
|
||||
|
||||
## 0.6.8 — 2026-07-01
|
||||
|
||||
- An older entry.
|
||||
EOF
|
||||
git add VERSION CHANGELOG.md
|
||||
bash "$CEREMONY_DIR/bin/changelog-assemble" 0.7.0 2026-07-21
|
||||
git add -A
|
||||
git commit -qm "release: 0.7.0"
|
||||
echo "FIXTURE_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
|
||||
- name: gather the facts — version, base version, released, labeled
|
||||
|
|
|
|||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -344,7 +344,7 @@ jobs:
|
|||
# shellcheck source=/dev/null
|
||||
. "$CEREMONY_DIR/lib/changelog.sh"
|
||||
if ! diagnosis="$(changelog_section_problem CHANGELOG.md "$VER")"; then
|
||||
echo "CHANGELOG.md has no '## $VER' section — stamp the Unreleased section in the release PR before tagging; refusing to publish an empty release" >&2
|
||||
echo "CHANGELOG.md has no '## $VER' section — run changelog-assemble in the release PR before tagging; refusing to publish an empty release" >&2
|
||||
printf '%s\n' "$diagnosis" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
12
BUILDER.md
12
BUILDER.md
|
|
@ -105,10 +105,14 @@ triage bug, and the move is to say so on the issue, not to guess.
|
|||
them as a checklist in the PR body and check them honestly as you go. If
|
||||
one turns out to be wrong or unreachable, say so on the issue and get it
|
||||
amended by triage — do not silently ship less than the issue says.
|
||||
- Every behavior change adds one line to `CHANGELOG.md` under
|
||||
`## Unreleased` — in a grouped changelog, append under an existing heading
|
||||
and create one only when the kind is genuinely new; insert **above** the
|
||||
heading below, never over it (the monotonic guard's whole reason to exist).
|
||||
- Every behavior change writes one fragment, `changelog.d/<issue>.md`,
|
||||
named for the authorizing issue (`<repo>-<issue>.md` when the work is
|
||||
cross-repo) — the exact prose that will be published, nothing else: `- `
|
||||
bullets, and in a grouped repo the `### Added` / `### Changed` /
|
||||
`### Fixed` headings inside the fragment, creating a rarer kind only when
|
||||
a change genuinely is one. Never edit `CHANGELOG.md` for an entry — the
|
||||
release PR assembles the section from the fragments (#112); the monotonic
|
||||
guard still refuses anything that deletes a shipped heading.
|
||||
- Follow the repo's conventions file and match the code you touch. Tests are
|
||||
not optional: the issue's test plan is the floor, not the ceiling.
|
||||
- **Scope discipline: the PR does the issue — whole, and nothing else.**
|
||||
|
|
|
|||
35
CHANGELOG.md
35
CHANGELOG.md
|
|
@ -2,37 +2,10 @@
|
|||
|
||||
The curated history of the ceremony itself. Each release's section is
|
||||
published verbatim as that release's body (lib/changelog.sh extracts it),
|
||||
so entries say what changed, cite the issue, and stop.
|
||||
|
||||
## Unreleased
|
||||
|
||||
- `test/changelog-assembled.test.sh` — keep the trio interaction aligned with fragment mode: a dropped entry makes armed red too, while a hand-edited section leaves assembled as the sole red (#126).
|
||||
- `changelog-armed` — treat `changelog.d/` as the arming, validate every development fragment, and require bare releases to consume the directory into their exact publishable section (#115).
|
||||
- `actions/changelog-assembled` — a release PR's stamped section must be byte-for-byte what the fragments it consumed assemble to, replayed from the merge base; inapplicable trees pass with a NOTICE (#116).
|
||||
- `lib/changelog.sh` + `bin/changelog-assemble` — read the `changelog.d/` fragments, assemble one release section (canonical group order, one shape per repo), and consume exactly what was published (#114).
|
||||
- BUILDER.md — the directed hold is the parked claim's fifth shape, its attention demand is acknowledged in the declaration comment, and its board bookkeeping covers in-flight work; TRIAGE.md no longer excludes it (#113).
|
||||
- BUILDER.md — the handed-off PR is the parked claim's fourth shape, its handoff is its declaration, and shape 2 covers the round awaiting its first verdicts (#109).
|
||||
- `labels-reconcile` — a degraded mergeability/checks read now logs gh's actual stderr (collapsed, bounded) beside the byte-identical counted line, and the blind-sweep warning leads with the observed reason instead of asserting the permissions cause (#101).
|
||||
- `LABELS.md` — drop the vendored scope-table enumeration; the per-repo set lives in `.github/labels.conf` and the repo's own CONTRIBUTING (#104).
|
||||
- Changelog publication — count entries instead of bytes, refuse dangling grouped headings, and seed grouped re-arms with Added/Changed/Fixed (#98).
|
||||
- `labels-reconcile` — warn once per sweep when a repository lacks labels declared by the pinned core taxonomy (#105).
|
||||
- `labels-reconcile` — grant callers private-repo check reads and warn when an entire PR sweep is blind (#95).
|
||||
- `labels-reconcile` — the bootstrap now retires the six GitHub defaults `LABELS.md` publishes as deleted, tolerating both an already-absent label and a refused delete (#93).
|
||||
- `issueflow-reconcile` — a triage-authored issue arrival stands down with exit 0 instead of killing the run before the sweep (#91).
|
||||
- FLEET.md — the assignee's `attention` wake: one role-independent trigger ahead of every per-role list, one acked session per demand; a spec on paper until `duty.sh` polls it (#86).
|
||||
- `attention` doctrine — define its assignee-owned pickup, ack, queue and clock semantics across labels, triage, and builder roles (#85).
|
||||
- `attention` — add the issue-only, hand-set assignee-demand flag to the core label taxonomy (#84).
|
||||
- `issueflow-reconcile` — keep cross-repo references out of local dependency decisions and require triage to resolve cross-repo blockers by hand (#61).
|
||||
- `needs-ruling` — the cross-cutting flag for a pending human decision, excluded from `state:needs-human` and from the staleness sweep (#51).
|
||||
- Cross-repo doctrine: the panel is the PR's repo's roster, a review request is authorization but not panel membership, and `Part of <repo>#N` replaces the `Closes #N` that cannot cross repos (#57).
|
||||
- `actions/runner-isolated` — a `pull_request`-triggered job may never run on a self-hosted runner (#58).
|
||||
- The sweep's `needs-ruling` invariants, one implementation for both surfaces: the issue-side staleness exemption, the bare-flag check (comment-only, the label is never removed), and the 7-day nudge to the decider (#52).
|
||||
- `offsite` — protect claimed issues whose PR lives in another repository from the claim-reclaim clock (#68).
|
||||
- `issueflow-reconcile` — nudge once when an `offsite` flag outlives every visible cross-referenced PR (#69).
|
||||
- Ruling doctrine — define every human-owned trigger, the fixed escalation shape, and the 0–24h builder-to-triage ladder (#72).
|
||||
- The sweep observes the escalation contract: a malformed escalation is named field-by-field, and the ladder's 12h/24h rungs each draw one comment to the flag-setter — comment-only, per-episode, both surfaces (#73).
|
||||
- FLEET.md — the operator notifier's `needs-ruling` queue (one tracked message per item, edited in place across the rungs) and triage's past-24h wake condition; a spec on paper until an operator updates the box (#74).
|
||||
- One issue at a time counts build work in flight: the parked claim's three shapes, its declared-never-inferred comment, and triage's duty to name a directed hold as a park (#77).
|
||||
so entries say what changed, cite the issue, and stop. Entries arrive as
|
||||
fragments — one `changelog.d/<issue>.md` per PR, never an edit to this
|
||||
file — and the release PR assembles them into the next section here
|
||||
(`bin/changelog-assemble`, #112).
|
||||
|
||||
## 0.1.0 — 2026-07-22
|
||||
|
||||
|
|
|
|||
|
|
@ -40,11 +40,12 @@ already proven:
|
|||
|
||||
1. **One issue, one PR**, opened as a **draft** while building, with
|
||||
`Closes #N` in the body. Drafts are invisible to the reviewer panel on
|
||||
purpose. Every behavior change adds one line to `CHANGELOG.md` under
|
||||
`## Unreleased` (in a grouped changelog, append under an existing heading
|
||||
and create one only when the kind is genuinely new; insert **above** the
|
||||
heading below — never type over it; the monotonic guard exists because of
|
||||
exactly that edit).
|
||||
purpose. Every behavior change writes one fragment,
|
||||
`changelog.d/<issue>.md` — the exact prose to publish, nothing else
|
||||
(cross-repo work names it `<repo>-<issue>.md`; a grouped repo puts its
|
||||
`### Added` / `### Changed` / `### Fixed` headings inside the fragment).
|
||||
Never edit `CHANGELOG.md` for an entry — the release PR assembles the
|
||||
section from the fragments (#112).
|
||||
2. **When it's ready**: mark ready-for-review and request the whole panel.
|
||||
3. **Rounds are answered whole.** Wait until every reviewer has a verdict in,
|
||||
then answer the entire round in a **single reply**, push the fixes, and
|
||||
|
|
|
|||
171
README.md
171
README.md
|
|
@ -21,36 +21,29 @@ stamps:
|
|||
|
||||
1. **The version goes bare**: `X.Y.Z-dev` → `X.Y.Z`
|
||||
([lib/version.sh](lib/version.sh)).
|
||||
2. **The changelog is stamped *and re-armed* — two edits, not one**
|
||||
(box#108). `## Unreleased` becomes `## X.Y.Z — DATE`, and an **empty
|
||||
`## Unreleased` goes back on top**, immediately above it. When the repo
|
||||
groups entries, the ceremony PR seeds its three standing headings:
|
||||
2. **The changelog section is assembled — one edit, produced by the tool**
|
||||
(#112). Entries never accumulate in `CHANGELOG.md`: each PR wrote one
|
||||
fragment file, `changelog.d/<issue>.md`, and the ceremony PR runs
|
||||
[bin/changelog-assemble](bin/changelog-assemble) — by hand, on purpose,
|
||||
so the section lands in the PR's diff where the panel reads it (#112
|
||||
D12; a consumer's exact invocation is in
|
||||
[docs/CONSUMERS.md](docs/CONSUMERS.md#assembling-a-release-section)).
|
||||
The tool folds every fragment into a new `## X.Y.Z — DATE` section on
|
||||
top and deletes the fragments it consumed; the
|
||||
[assembled guard](#changelog-assembled--the-stamp-is-exactly-the-fragments)
|
||||
replays that run and refuses a stamp that is not byte-for-byte the
|
||||
fragments' assembly.
|
||||
|
||||
```markdown
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
## 0.7.1 — 2026-07-19
|
||||
|
||||
### Fixed
|
||||
...
|
||||
```
|
||||
|
||||
The second edit is not cosmetic and not deferrable. Between the stamp
|
||||
and the next re-creation of that heading, main has no `## Unreleased`. A
|
||||
PR authored *before* the release wrote its entry under that heading;
|
||||
with the heading gone, git lands the entry under whatever now occupies
|
||||
the position — **the section that just shipped** — and it merges
|
||||
cleanly, no conflict, no signal. The changelog then credits a released
|
||||
version with a change it does not contain, and nothing but a human
|
||||
reading the file will ever say so (box#108; confirmed cross-repo as
|
||||
rig#66). The [armed guard](#changelog-armed--main-never-sits-disarmed)
|
||||
exists because of exactly this edit.
|
||||
There is no second edit: the old stamp *re-armed* — put an empty
|
||||
`## Unreleased` back on top — because every PR inserted at that one
|
||||
shared anchor, and between the stamp and the re-arm a PR authored
|
||||
*before* the release landed its entry under whatever now occupied the
|
||||
position — **the section that just shipped** — cleanly, no conflict,
|
||||
no signal (box#108; confirmed cross-repo as rig#66). Fragments make
|
||||
that failure structurally impossible rather than guarded-against: a
|
||||
fragment merged after the release simply sits in the directory and is
|
||||
assembled into the *next* section. There is no anchor left to misplace,
|
||||
and nothing to re-arm — the directory is always armed.
|
||||
|
||||
3. **The drill record is present**: `drills/X.Y.Z.md`, non-blank — the
|
||||
evidence the release rests on
|
||||
|
|
@ -70,7 +63,8 @@ the version's own changelog section as the body — the curated prose, never
|
|||
the generated PR list
|
||||
([lib/changelog.sh](lib/changelog.sh) is the one canonical extractor) —
|
||||
and re-arms main by bumping to `X.Y.(Z+1)-dev`
|
||||
([release.yml](.github/workflows/release.yml#L266-L300)). The machine does
|
||||
([release.yml](.github/workflows/release.yml#L266-L300)) — the version is
|
||||
the only re-arm left; the changelog needs none (#112). The machine does
|
||||
the transcription because humans err silently and machines fail loudly:
|
||||
**everything asserts its way to certainty and fails loudly, creating
|
||||
nothing** — a wrong release is worse than a missing one, so every failed
|
||||
|
|
@ -129,7 +123,7 @@ refuse (#1 constraint 8).
|
|||
|
||||
## The guards
|
||||
|
||||
Three composite actions run in every consumer's CI (and in this repo's
|
||||
Four composite actions run in every consumer's CI (and in this repo's
|
||||
own). Shared shape: version-keyed where the tree's state matters, loud
|
||||
where it fails, and **a file of its own so a test can drive it**. The full
|
||||
war stories are in the scripts' header comments — authoritative and longer
|
||||
|
|
@ -137,8 +131,28 @@ than this; what follows is the operator's cut.
|
|||
|
||||
### changelog-armed — main never sits disarmed
|
||||
|
||||
**The rule** ([actions/changelog-armed/changelog-armed.sh](actions/changelog-armed/changelog-armed.sh#L27-L36)),
|
||||
keyed on the tree's version:
|
||||
**The rule** ([actions/changelog-armed/changelog-armed.sh](actions/changelog-armed/changelog-armed.sh)),
|
||||
keyed on the tree's shape, then its version. In **fragment mode** —
|
||||
`changelog.d/` exists, the arming property moved onto the directory
|
||||
(#112 D7):
|
||||
|
||||
- always → the marker `changelog.d/README.md` must exist (what keeps the
|
||||
directory tracked when it holds no fragments), no `## Unreleased`
|
||||
section may survive in `CHANGELOG.md` (a second anchor with no owner),
|
||||
and every fragment must be publishable on its own — named `<issue>.md`
|
||||
or `<repo>-<issue>.md`, no `## ` heading, at least one bullet, no
|
||||
`### ` heading without an entry. A malformed fragment fails the PR that
|
||||
wrote it, not the release that consumes it (#112 D9).
|
||||
- `-dev` tree → nothing more. The directory **is** the arming: the next
|
||||
PR's entry is a new file, and a new file always has somewhere to land.
|
||||
- bare tree (the ceremony PR and its merge) → every fragment must be
|
||||
consumed, and the top section must be the stamped, publishable section
|
||||
for exactly that version. Fragment mode has no re-armed shape — there
|
||||
is nothing left to re-arm.
|
||||
|
||||
In **legacy mode** — no `changelog.d/` — the version-keyed rules stand
|
||||
verbatim; both shapes stay supported so a consumer adopts fragments on a
|
||||
pin bump, on its own schedule (#112 D8):
|
||||
|
||||
- `-dev` tree → the top section **must** be `## Unreleased`.
|
||||
- bare tree (the ceremony PR and its merge) → the top section may be
|
||||
|
|
@ -152,10 +166,17 @@ keyed on the tree's version:
|
|||
uses, so the two cannot disagree about what a section is).
|
||||
|
||||
**The incident**: box#108 / rig#66 — the silent mislanding described
|
||||
[above](#what-a-release-is). **Red means** a PR entry has nowhere safe to
|
||||
land, or a stamped version would publish no entries or a dangling grouped
|
||||
heading; **the fix** is to re-arm the top or delete/populate the named
|
||||
heading before publishing.
|
||||
[above](#what-a-release-is). Fragment mode retires the incident's
|
||||
mechanism outright; legacy mode guards it. **Red means** a PR entry has
|
||||
nowhere safe to land — a missing marker, a surviving `## Unreleased`, a
|
||||
malformed fragment — or a stamped version would publish no entries, a
|
||||
dangling grouped heading, or a bare tree still carrying fragments the
|
||||
stamp did not consume (`not consumed` — re-run the assembler); the
|
||||
message names the fix in every case. What this guard cannot see is a
|
||||
fragment that *was* consumed but whose entry the stamp omits — the
|
||||
fragment is gone from HEAD, so only
|
||||
[changelog-assembled](#changelog-assembled--the-stamp-is-exactly-the-fragments)'s
|
||||
merge-base replay catches that loss.
|
||||
|
||||
**Do not "simplify" this to "always require `## Unreleased`".** The
|
||||
unconditional form is false by construction on the ceremony PR's own tree
|
||||
|
|
@ -164,12 +185,43 @@ to revert exactly that
|
|||
([the script's header](actions/changelog-armed/changelog-armed.sh#L8-L16)).
|
||||
The version-keyed form is what rig and cast get back by adopting this repo.
|
||||
|
||||
One consequence worth knowing before it happens: a ceremony PR that stamps
|
||||
and forgets to re-arm still passes this guard — a bare tree is allowed to
|
||||
be stamped. It goes red **the moment the automatic `-dev` bump lands on
|
||||
main** ([the script](actions/changelog-armed/changelog-armed.sh#L37-L42)).
|
||||
The guard does not block the release; it refuses to let main *sit*
|
||||
disarmed, which is the window a late PR falls into.
|
||||
One consequence worth knowing before it happens, legacy mode only: a
|
||||
ceremony PR that stamps and forgets to re-arm still passes this guard — a
|
||||
bare tree is allowed to be stamped. It goes red **the moment the automatic
|
||||
`-dev` bump lands on main**. The guard does not block the release; it
|
||||
refuses to let main *sit* disarmed, which is the window a late PR falls
|
||||
into. Fragment mode has no such window: with no re-arm step there is
|
||||
nothing to forget.
|
||||
|
||||
### changelog-assembled — the stamp is exactly the fragments
|
||||
|
||||
**The rule**
|
||||
([actions/changelog-assembled/changelog-assembled.sh](actions/changelog-assembled/changelog-assembled.sh)):
|
||||
on a release PR in fragment mode, the stamped `## X.Y.Z` section must be
|
||||
**byte-for-byte** what the fragments it consumed assemble to. The guard
|
||||
reads the fragments as of the merge base (they are gone from HEAD — that
|
||||
is the point of the ceremony), replays `changelog-assemble --check` over
|
||||
that set, and diffs the result against HEAD's section body. Every tree it
|
||||
does not apply to — a `-dev` tree, legacy mode, no consumed fragments —
|
||||
passes with a green `NOTICE`, so a non-ceremony PR is never red here.
|
||||
|
||||
**The failure it catches** (#116): assembly is a hand-run step by design —
|
||||
the section must land in the PR's diff where the panel reads it (#112 D12)
|
||||
— and a mis-run hand step can leave no trace. The two failure shapes
|
||||
differ, and the guards split them exactly as
|
||||
[test/changelog-assembled.test.sh](test/changelog-assembled.test.sh)'s
|
||||
trio rows record: leave a fragment **out of the deletion** and it survives
|
||||
on HEAD, where
|
||||
[changelog-armed](#changelog-armed--main-never-sits-disarmed) already
|
||||
refuses the bare tree (`not consumed`) — this guard goes red too, naming
|
||||
the entry the section lost. But **delete** a fragment while omitting its
|
||||
entry from the stamp, or hand-edit one word of the assembled prose, and
|
||||
nothing on HEAD is out of place: armed is green, monotonic is green, and
|
||||
the publisher would happily publish history that is not what the authors
|
||||
wrote. Only the merge-base replay catches those. The replay is what
|
||||
makes a hand-run step safe. **This guard needs history** — same stance as
|
||||
the monotonic guard: `fetch-depth: 0`, and in CI an unresolvable base is a
|
||||
hard failure, not a skip.
|
||||
|
||||
### changelog-monotonic — shipped headings are append-only
|
||||
|
||||
|
|
@ -180,9 +232,12 @@ the set at the merge base, and no heading may appear twice on HEAD. The
|
|||
rule needs no tuning because release headings are append-only by doctrine:
|
||||
the ceremony adds one and nothing ever legitimately removes one — so
|
||||
superset has no exception to carve. The ceremony's own stamp passes by
|
||||
construction: rewriting `## Unreleased` into `## X.Y.Z — DATE` adds a
|
||||
heading and removes none (`Unreleased` is not a version heading; it is
|
||||
[changelog-armed](#changelog-armed--main-never-sits-disarmed)'s business).
|
||||
construction: the assembler writes a new `## X.Y.Z — DATE` heading and
|
||||
removes none. Fragment mode changes nothing here (#112 D10): fragments add
|
||||
no `## ` heading, and `Unreleased` was never in the guard's set — it is
|
||||
not a version heading; it is
|
||||
[changelog-armed](#changelog-armed--main-never-sits-disarmed)'s business —
|
||||
which is why a repo's adoption PR can delete it and stay green.
|
||||
|
||||
**The incidents**: box#122 (caught in review of box#118) — an author
|
||||
adding an entry under `## Unreleased` **replaced** the heading below it
|
||||
|
|
@ -357,27 +412,31 @@ open.
|
|||
[L333–L337](.github/workflows/release.yml#L333-L337). The message is the
|
||||
remedy.
|
||||
|
||||
> CHANGELOG.md has no '## $VER' section — stamp the Unreleased section in the release PR before tagging; refusing to publish an empty release
|
||||
> CHANGELOG.md has no '## $VER' section — run changelog-assemble in the release PR before tagging; refusing to publish an empty release
|
||||
|
||||
[L346–L349](.github/workflows/release.yml#L346-L349). The tagged tree was
|
||||
never stamped. Stamp first, then delete and re-push the tag.
|
||||
never stamped. Assemble the section
|
||||
([docs/CONSUMERS.md](docs/CONSUMERS.md#assembling-a-release-section)),
|
||||
then delete and re-push the tag.
|
||||
|
||||
### Red main that is not the release workflow
|
||||
|
||||
Consumer CI runs its guard steps on pushes to main too (this repo's
|
||||
[ci.yml](.github/workflows/ci.yml) does the same). The one guard red an
|
||||
operator will actually meet on main is
|
||||
**changelog-armed after a re-arm was forgotten**: the ceremony stamped
|
||||
without putting `## Unreleased` back, the release's own `-dev` bump
|
||||
landed, and the guard now says (first line):
|
||||
operator will actually meet on main is **changelog-armed after a re-arm
|
||||
was forgotten — legacy mode only**: the ceremony stamped without putting
|
||||
`## Unreleased` back, the release's own `-dev` bump landed, and the guard
|
||||
now says (first line):
|
||||
|
||||
> changelog-armed: the version is '$ver' (a development tree) but the top
|
||||
> section of $changelog is: …
|
||||
|
||||
The fix is a one-line PR: add an empty `## Unreleased` above the stamped
|
||||
section. The full message
|
||||
([the script](actions/changelog-armed/changelog-armed.sh#L87-L101))
|
||||
carries the same instruction.
|
||||
section. The full message carries the same instruction. Fragment mode has
|
||||
no re-arm to forget, so it has no equivalent red on main — its refusals
|
||||
(a missing marker, a surviving `## Unreleased`, a malformed or unconsumed
|
||||
fragment) all fire on the PR that caused them, where the author is still
|
||||
holding it.
|
||||
|
||||
## Design lineage
|
||||
|
||||
|
|
@ -391,6 +450,6 @@ constraints — each bought with an incident, none of them safe to
|
|||
their war stories, in the headers of the scripts they bind:
|
||||
[release.yml](.github/workflows/release.yml#L1-L109),
|
||||
[lib/decide.sh](lib/decide.sh#L1-L74),
|
||||
[lib/facts.sh](lib/facts.sh#L1-L24), and the three
|
||||
[lib/facts.sh](lib/facts.sh#L1-L24), and the four
|
||||
[guard scripts](actions/). The comments are the documentation of record;
|
||||
this README is their operator-facing cut.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ fi
|
|||
}
|
||||
|
||||
if ! diagnosis="$(changelog_section_problem "$changelog" "$ver")"; then
|
||||
echo "changelog-section: $changelog has no section for '$ver' — the release PR stamps the Unreleased section with version + date BEFORE the tag" >&2
|
||||
echo "changelog-section: $changelog has no section for '$ver' — the release PR assembles the section (changelog-assemble) with version + date BEFORE the tag" >&2
|
||||
printf 'changelog-section: %s\n' "$diagnosis" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
1
changelog.d/101.md
Normal file
1
changelog.d/101.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `labels-reconcile` — a degraded mergeability/checks read now logs gh's actual stderr (collapsed, bounded) beside the byte-identical counted line, and the blind-sweep warning leads with the observed reason instead of asserting the permissions cause (#101).
|
||||
1
changelog.d/104.md
Normal file
1
changelog.d/104.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `LABELS.md` — drop the vendored scope-table enumeration; the per-repo set lives in `.github/labels.conf` and the repo's own CONTRIBUTING (#104).
|
||||
1
changelog.d/105.md
Normal file
1
changelog.d/105.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `labels-reconcile` — warn once per sweep when a repository lacks labels declared by the pinned core taxonomy (#105).
|
||||
1
changelog.d/109.md
Normal file
1
changelog.d/109.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- BUILDER.md — the handed-off PR is the parked claim's fourth shape, its handoff is its declaration, and shape 2 covers the round awaiting its first verdicts (#109).
|
||||
1
changelog.d/112.md
Normal file
1
changelog.d/112.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Ceremony adopts `changelog.d/` — a PR writes one fragment per issue instead of editing `CHANGELOG.md`, the release PR assembles the section, and `## Unreleased` is gone (#112).
|
||||
1
changelog.d/113.md
Normal file
1
changelog.d/113.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- BUILDER.md — the directed hold is the parked claim's fifth shape, its attention demand is acknowledged in the declaration comment, and its board bookkeeping covers in-flight work; TRIAGE.md no longer excludes it (#113).
|
||||
1
changelog.d/114.md
Normal file
1
changelog.d/114.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `lib/changelog.sh` + `bin/changelog-assemble` — read the `changelog.d/` fragments, assemble one release section (canonical group order, one shape per repo), and consume exactly what was published (#114).
|
||||
1
changelog.d/115.md
Normal file
1
changelog.d/115.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `changelog-armed` — treat `changelog.d/` as the arming, validate every development fragment, and require bare releases to consume the directory into their exact publishable section (#115).
|
||||
1
changelog.d/116.md
Normal file
1
changelog.d/116.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `actions/changelog-assembled` — a release PR's stamped section must be byte-for-byte what the fragments it consumed assemble to, replayed from the merge base; inapplicable trees pass with a NOTICE (#116).
|
||||
1
changelog.d/126.md
Normal file
1
changelog.d/126.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `test/changelog-assembled.test.sh` — keep the trio interaction aligned with fragment mode: a dropped entry makes armed red too, while a hand-edited section leaves assembled as the sole red (#126).
|
||||
1
changelog.d/51.md
Normal file
1
changelog.d/51.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `needs-ruling` — the cross-cutting flag for a pending human decision, excluded from `state:needs-human` and from the staleness sweep (#51).
|
||||
1
changelog.d/52.md
Normal file
1
changelog.d/52.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- The sweep's `needs-ruling` invariants, one implementation for both surfaces: the issue-side staleness exemption, the bare-flag check (comment-only, the label is never removed), and the 7-day nudge to the decider (#52).
|
||||
1
changelog.d/57.md
Normal file
1
changelog.d/57.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Cross-repo doctrine: the panel is the PR's repo's roster, a review request is authorization but not panel membership, and `Part of <repo>#N` replaces the `Closes #N` that cannot cross repos (#57).
|
||||
1
changelog.d/58.md
Normal file
1
changelog.d/58.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `actions/runner-isolated` — a `pull_request`-triggered job may never run on a self-hosted runner (#58).
|
||||
1
changelog.d/61.md
Normal file
1
changelog.d/61.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `issueflow-reconcile` — keep cross-repo references out of local dependency decisions and require triage to resolve cross-repo blockers by hand (#61).
|
||||
1
changelog.d/68.md
Normal file
1
changelog.d/68.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `offsite` — protect claimed issues whose PR lives in another repository from the claim-reclaim clock (#68).
|
||||
1
changelog.d/69.md
Normal file
1
changelog.d/69.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `issueflow-reconcile` — nudge once when an `offsite` flag outlives every visible cross-referenced PR (#69).
|
||||
1
changelog.d/72.md
Normal file
1
changelog.d/72.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Ruling doctrine — define every human-owned trigger, the fixed escalation shape, and the 0–24h builder-to-triage ladder (#72).
|
||||
1
changelog.d/73.md
Normal file
1
changelog.d/73.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- The sweep observes the escalation contract: a malformed escalation is named field-by-field, and the ladder's 12h/24h rungs each draw one comment to the flag-setter — comment-only, per-episode, both surfaces (#73).
|
||||
1
changelog.d/74.md
Normal file
1
changelog.d/74.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- FLEET.md — the operator notifier's `needs-ruling` queue (one tracked message per item, edited in place across the rungs) and triage's past-24h wake condition; a spec on paper until an operator updates the box (#74).
|
||||
1
changelog.d/77.md
Normal file
1
changelog.d/77.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- One issue at a time counts build work in flight: the parked claim's three shapes, its declared-never-inferred comment, and triage's duty to name a directed hold as a park (#77).
|
||||
1
changelog.d/84.md
Normal file
1
changelog.d/84.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `attention` — add the issue-only, hand-set assignee-demand flag to the core label taxonomy (#84).
|
||||
1
changelog.d/85.md
Normal file
1
changelog.d/85.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `attention` doctrine — define its assignee-owned pickup, ack, queue and clock semantics across labels, triage, and builder roles (#85).
|
||||
1
changelog.d/86.md
Normal file
1
changelog.d/86.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- FLEET.md — the assignee's `attention` wake: one role-independent trigger ahead of every per-role list, one acked session per demand; a spec on paper until `duty.sh` polls it (#86).
|
||||
1
changelog.d/91.md
Normal file
1
changelog.d/91.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `issueflow-reconcile` — a triage-authored issue arrival stands down with exit 0 instead of killing the run before the sweep (#91).
|
||||
1
changelog.d/93.md
Normal file
1
changelog.d/93.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `labels-reconcile` — the bootstrap now retires the six GitHub defaults `LABELS.md` publishes as deleted, tolerating both an already-absent label and a refused delete (#93).
|
||||
1
changelog.d/95.md
Normal file
1
changelog.d/95.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- `labels-reconcile` — grant callers private-repo check reads and warn when an entire PR sweep is blind (#95).
|
||||
1
changelog.d/98.md
Normal file
1
changelog.d/98.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Changelog publication — count entries instead of bytes, refuse dangling grouped headings, and seed grouped re-arms with Added/Changed/Fixed (#98).
|
||||
10
changelog.d/README.md
Normal file
10
changelog.d/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# changelog.d/ — the next release's section, one fragment per issue
|
||||
|
||||
Machine-assembled by `bin/changelog-assemble` (#112): every PR that changes
|
||||
behavior writes one file here — `<issue>.md`, the exact prose that will be
|
||||
published, nothing else — and the release PR folds them all into the next
|
||||
`## X.Y.Z — DATE` section of `CHANGELOG.md`, consuming them. Distinct
|
||||
filenames never conflict, which is this directory's whole reason to exist.
|
||||
This README is the marker that keeps the directory tracked when it holds no
|
||||
fragments (#112 D1) — `changelog-armed` refuses a tree without it; do not
|
||||
delete it.
|
||||
|
|
@ -48,19 +48,23 @@ the machinery at all:
|
|||
[lib/decide.sh](../lib/decide.sh#L70-L74)). Bootstrapping at `-dev`
|
||||
keeps the repo clear of it entirely. (`package-json` backend: the
|
||||
`version` field, same rule.)
|
||||
2. **An armed `CHANGELOG.md`**: a preamble plus an empty `## Unreleased`
|
||||
section for the first entries to land under. If the repo groups entries,
|
||||
seed the shape the ceremony PR will restore at every re-arm:
|
||||
2. **An armed changelog: a `CHANGELOG.md` preamble plus `changelog.d/`.**
|
||||
The changelog file starts as preamble only — no section; the first
|
||||
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
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
```
|
||||
Fragment mode is **unreleased** and not in `0.1.0`. A consumer pinned
|
||||
to `0.1.0` bootstraps the legacy shape instead — the preamble plus an
|
||||
empty `## Unreleased` section for entries to land under — and converts
|
||||
on the pin bump to the first tag carrying fragment mode; never mix
|
||||
refs to adopt it early.
|
||||
3. **`drills/README.md`** defining what a drill *means* in this repo —
|
||||
each repo names its own
|
||||
([the drill doctrine](../README.md#the-drill-doctrine)). Plain
|
||||
|
|
@ -73,13 +77,19 @@ the machinery at all:
|
|||
```yaml
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# changelog-monotonic compares HEAD against the merge base; a
|
||||
# checkout that cannot resolve it is a hard failure in CI, not
|
||||
# a skip (a guard that can quietly stop guarding is the failure
|
||||
# shape these checks exist to refuse).
|
||||
# changelog-monotonic and changelog-assembled compare HEAD
|
||||
# against the merge base; a checkout that cannot resolve it is
|
||||
# a hard failure in CI, not a skip (a guard that can quietly
|
||||
# stop guarding is the failure shape these checks exist to
|
||||
# refuse).
|
||||
fetch-depth: 0
|
||||
- uses: heavy-duty/ceremony/actions/changelog-armed@<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>
|
||||
# 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.
|
||||
|
|
@ -105,8 +115,9 @@ the machinery at all:
|
|||
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
|
||||
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
|
||||
plus `docs-sync`, but not `runner-isolated`.
|
||||
consumer. In particular, `0.1.0` carries `changelog-armed`,
|
||||
`changelog-monotonic` and `drill-recorded` plus `docs-sync`, but not
|
||||
`changelog-assembled` or `runner-isolated`.
|
||||
6. **Labels automation** (optional but recommended): the caller from
|
||||
[Labels automation](#labels-automation), plus `.github/labels.conf`
|
||||
(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
|
||||
tarball is the package.
|
||||
|
||||
From there the flow is the doctrine: ordinary PRs add their changelog
|
||||
line, the ceremony PR makes
|
||||
From there the flow is the doctrine: ordinary PRs write their fragment,
|
||||
the ceremony PR makes
|
||||
[the three stamps](../README.md#what-a-release-is), a human merges, the
|
||||
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 format](#labels-automation)). `.github/labeler.yml` stays as
|
||||
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:
|
||||
`.github/scripts/release-notes.sh` (box, cast) or
|
||||
`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
|
||||
[the portable rule](#the-changelog-rule).
|
||||
- [ ] 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/actions/release-artifact/` — the full kept-vs-moved table
|
||||
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
|
||||
CONTRIBUTING may sharpen it, but this is the floor the guards assume:
|
||||
|
||||
- **Every PR that changes behavior adds one line** under `## Unreleased`.
|
||||
- **Grouped changelogs keep three standing headings:** `### Added`,
|
||||
`### Changed`, and `### Fixed`. The ceremony PR's hand-edited re-arm seeds
|
||||
all three; append under one instead of creating a heading at the top
|
||||
anchor. Create `Deprecated`, `Removed`, or `Security` only when a change
|
||||
genuinely needs that rarer kind.
|
||||
- **Insert above the heading below — never type over it.** Replacing a
|
||||
shipped `## X.Y.Z` heading with your entry deletes that release's
|
||||
section, silently; this exact edit is why the
|
||||
[monotonic guard](../README.md#changelog-monotonic--shipped-headings-are-append-only)
|
||||
exists (box#122).
|
||||
- **Every PR that changes behavior writes one fragment**:
|
||||
`changelog.d/<issue>.md`, named for the authorizing issue —
|
||||
`<repo>-<issue>.md` for cross-repo work carrying `Part of <repo>#N` —
|
||||
so the name is known at claim time and two builders can only collide by
|
||||
working the same issue (#112 D2). Never an edit to `CHANGELOG.md`: the
|
||||
release PR assembles the section
|
||||
([below](#assembling-a-release-section)).
|
||||
- **The fragment is the prose, not a description of it** (#112 D3): the
|
||||
exact lines that will be published — no front-matter, no `## ` heading
|
||||
(that one is the assembler's to write). `changelog-armed` refuses a
|
||||
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
|
||||
the mechanism — "`state:needs-human` is set at handoff" beats "the
|
||||
labels workflow now also wakes on `labeled`". The why and the how
|
||||
belong in the PR body, where anyone chasing the reasoning already goes.
|
||||
- **Cite the issue or PR** — `(#141)`.
|
||||
- **Mark a breaking change** with a leading `BREAKING:`.
|
||||
- Flat changelogs remain flat; the standing-heading rule applies only when a
|
||||
repo already groups its entries.
|
||||
- A repo not yet on fragment mode — no `changelog.d/` — keeps the legacy
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
candidate SHA in every pin carrier, and records that one-line deviation
|
||||
in its record. From the second release on, this paragraph is moot.
|
||||
3. Give it a fixture `VERSION` / `CHANGELOG.md` / `drills/` in the armed
|
||||
state (`X.Y.Z-dev`, `## Unreleased` on top).
|
||||
3. Give it a fixture `VERSION` / `CHANGELOG.md` / `changelog.d/` /
|
||||
`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:
|
||||
|
||||
1. a merge-door ceremony publishes exactly one release and re-arms main
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ WRAPPER="$ROOT/bin/changelog-section"
|
|||
check "wrapper publishes the requested body" 0 "The seven-oh entry" "$WRAPPER" 0.7.0 "$FIXTURE"
|
||||
check "wrapper refuses an empty section" 1 "no section for '0.5.0'" "$WRAPPER" 0.5.0 "$FIXTURE"
|
||||
check "wrapper refuses an absent section" 1 "no section for '9.9.9'" "$WRAPPER" 9.9.9 "$FIXTURE"
|
||||
check "wrapper explains how the release PR fixes refusal" 1 "stamps the Unreleased section" "$WRAPPER" 9.9.9 "$FIXTURE"
|
||||
check "wrapper explains how the release PR fixes refusal" 1 "assembles the section" "$WRAPPER" 9.9.9 "$FIXTURE"
|
||||
check "wrapper refuses a heading-only version section" 1 \
|
||||
"section '1.5.0' has no entries — a heading is not an entry" \
|
||||
"$WRAPPER" 1.5.0 "$PROBLEM_FIXTURE"
|
||||
|
|
|
|||
Loading…
Reference in a new issue