release: 0.2.0 #129

Closed
claude-bot-andresmgsl wants to merge 4 commits from build/118-release-0.2.0 into main
33 changed files with 153 additions and 31 deletions

View file

@ -24,7 +24,7 @@ on:
env:
# A called workflow arrives without its repository. Keep this literal pin
# aligned with the ceremony release consumed by callers (issue #9 D3).
CEREMONY_SELF_REF: "0.1.0"
CEREMONY_SELF_REF: "0.2.0"
jobs:
scope:

View file

@ -129,7 +129,7 @@ env:
# `ref:` accepts ${{ env }}; `uses:` strings do not — which is why the
# shared logic arrives as script files via checkout, not as inner `uses:`
# references.
CEREMONY_SELF_REF: "0.1.0"
CEREMONY_SELF_REF: "0.2.0"
VERSION_SOURCE: ${{ inputs.version-source }}
jobs:

View file

@ -7,6 +7,37 @@ 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.2.0 — 2026-07-24
- `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).
- `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).
- `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).
- `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).
- 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).
- 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` — warn once per sweep when a repository lacks labels declared by the pinned core taxonomy (#105).
- `LABELS.md` — drop the vendored scope-table enumeration; the per-repo set lives in `.github/labels.conf` and the repo's own CONTRIBUTING (#104).
- `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).
- Changelog publication — count entries instead of bytes, refuse dangling grouped headings, and seed grouped re-arms with Added/Changed/Fixed (#98).
- `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).
- 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).
- 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).
- 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).
- Ruling doctrine — define every human-owned trigger, the fixed escalation shape, and the 024h builder-to-triage ladder (#72).
- `issueflow-reconcile` — nudge once when an `offsite` flag outlives every visible cross-referenced PR (#69).
- `offsite` — protect claimed issues whose PR lives in another repository from the claim-reclaim clock (#68).
- `issueflow-reconcile` — keep cross-repo references out of local dependency decisions and require triage to resolve cross-repo blockers by hand (#61).
- `actions/runner-isolated` — a `pull_request`-triggered job may never run on a self-hosted runner (#58).
- 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).
- 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).
- `needs-ruling` — the cross-cutting flag for a pending human decision, excluded from `state:needs-human` and from the staleness sweep (#51).
## 0.1.0 — 2026-07-22
- `lib/version.sh` — one version abstraction, `file` and `package-json` backends (#3).

View file

@ -1 +1 @@
0.1.1-dev
0.2.0

View file

@ -1 +0,0 @@
- `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).

View file

@ -1 +0,0 @@
- `LABELS.md` — drop the vendored scope-table enumeration; the per-repo set lives in `.github/labels.conf` and the repo's own CONTRIBUTING (#104).

View file

@ -1 +0,0 @@
- `labels-reconcile` — warn once per sweep when a repository lacks labels declared by the pinned core taxonomy (#105).

View file

@ -1 +0,0 @@
- 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).

View file

@ -1 +0,0 @@
- 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).

View file

@ -1 +0,0 @@
- 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).

View file

@ -1 +0,0 @@
- `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).

View file

@ -1 +0,0 @@
- `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).

View file

@ -1 +0,0 @@
- `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).

View file

@ -1 +0,0 @@
- `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).

View file

@ -1 +0,0 @@
- `needs-ruling` — the cross-cutting flag for a pending human decision, excluded from `state:needs-human` and from the staleness sweep (#51).

View file

@ -1 +0,0 @@
- 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).

View file

@ -1 +0,0 @@
- 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).

View file

@ -1 +0,0 @@
- `actions/runner-isolated` — a `pull_request`-triggered job may never run on a self-hosted runner (#58).

View file

@ -1 +0,0 @@
- `issueflow-reconcile` — keep cross-repo references out of local dependency decisions and require triage to resolve cross-repo blockers by hand (#61).

View file

@ -1 +0,0 @@
- `offsite` — protect claimed issues whose PR lives in another repository from the claim-reclaim clock (#68).

View file

@ -1 +0,0 @@
- `issueflow-reconcile` — nudge once when an `offsite` flag outlives every visible cross-referenced PR (#69).

View file

@ -1 +0,0 @@
- Ruling doctrine — define every human-owned trigger, the fixed escalation shape, and the 024h builder-to-triage ladder (#72).

View file

@ -1 +0,0 @@
- 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).

View file

@ -1 +0,0 @@
- 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).

View file

@ -1 +0,0 @@
- 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).

View file

@ -1 +0,0 @@
- `attention` — add the issue-only, hand-set assignee-demand flag to the core label taxonomy (#84).

View file

@ -1 +0,0 @@
- `attention` doctrine — define its assignee-owned pickup, ack, queue and clock semantics across labels, triage, and builder roles (#85).

View file

@ -1 +0,0 @@
- 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).

View file

@ -1 +0,0 @@
- `issueflow-reconcile` — a triage-authored issue arrival stands down with exit 0 instead of killing the run before the sweep (#91).

View file

@ -1 +0,0 @@
- `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).

View file

@ -1 +0,0 @@
- `labels-reconcile` — grant callers private-repo check reads and warn when an entire PR sweep is blind (#95).

View file

@ -1 +0,0 @@
- Changelog publication — count entries instead of bytes, refuse dangling grouped headings, and seed grouped re-arms with Added/Changed/Fixed (#98).

119
drills/0.2.0.md Normal file
View file

@ -0,0 +1,119 @@
# 0.2.0 — drill record
Run 2026-07-24, by `claude-bot-andresmgsl`, against the release candidate
`heavy-duty/ceremony@682b9cb8929aa8c50a3101b64ca57fee7b09fef1` (PR #129's
head at drill time — the ceremony PR adds only `VERSION`, the assembled
section with its fragment deletions, the self-ref stamps, and this record;
no machinery differs between the drilled tree and the released one).
Where: scratch private repo `claude-bot-andresmgsl/ceremony-drill-0.2.0`,
carrying the `docs/CONSUMERS.md` caller stubs (modulo the pin deviation
below) and a fixture consumer tree in the **fragment shape** this release
exists to ship: `VERSION` `0.5.0-dev`, `CHANGELOG.md` preamble plus a
shipped `## 0.4.0` section, `changelog.d/` with its `README.md` marker and
two fragments, `drills/`, and the five guard actions in CI at the drill
ref. Archived after this record was written; deletion needs the
`delete_repo` scope this box's token deliberately lacks, so the operator
owns the final delete — the repo is the evidence's scaffolding, this file
is the evidence. Run ids below are kept for the record's own audit trail.
## The deviation: the self-drill deadlock recurs past the first release
The spec (#118) expected the 0.1.0 record's fork deviation to be moot —
"`0.1.0` exists, so the pinned path runs straight." It does not, for
ceremony's *own* drill: the candidate tree pins
`CEREMONY_SELF_REF: "0.2.0"` — the very stamp this PR makes — and the
consumer path checks out `heavy-duty/ceremony` at exactly that ref, which
cannot exist before the merge. What `0.1.0` existing does make moot is the
*consumer-side* deadlock: a governed repo drilling its own release pins
ceremony at a real tag and runs straight. Raised on #118 before drilling;
proceeded per the deviation triage already ruled for #11, unchanged in
shape: **no `0.2.0`-named ref on the canonical repo**; the scratch callers
pin the fork ref `claude-bot-andresmgsl/ceremony@drill/0.2.0` — the
candidate tree with exactly one line changed per pin carrier
(`release.yml` and `labels.yml`, both — `self-ref-check.sh` requires the
carriers to agree), `CEREMONY_SELF_REF: "0.2.0"`
`"682b9cb8929aa8c50a3101b64ca57fee7b09fef1"`. Every `.ceremony-src` byte
the doors executed still came from `heavy-duty/ceremony` at the candidate
SHA (reachable there via PR #129's pull ref); the fork supplied
orchestration YAML differing by the one line the ceremony stamps each
release anyway. Drilling against `0.1.0` instead would not have been a
drill of the candidate: `lib/changelog.sh`, which both doors source for
notes extraction, is +214 lines since `0.1.0` (fragment mode; #98).
## The probes
| # | probe | run | result |
|---|---|---|---|
| a | merge-door ceremony, fragment shape | 30089779736 (attempt 1) | ✅ exactly one release |
| b | mislabeled ordinary PR | 30089842547 | ✅ green NOTICE no-op |
| c | bare-version PR, no label | 30089908740 | ✅ refused, red, created nothing |
| d | re-run of the completed ceremony | 30089779736 (attempt 2) | ✅ refused, red, created nothing |
| e | tag door, manual matching tag | 30090053523 | ✅ published, no bump |
| f | tag door, mismatched tag | 30090082676 | ✅ refused, red, created nothing |
**(a) The ceremony, in fragment shape.** PR `release: 0.5.0` — hand-set
`release` label, `VERSION` `0.5.0-dev``0.5.0`, and the stamp made by
the candidate's own `bin/changelog-assemble 0.5.0`: two fragments folded
into `## 0.5.0 — 2026-07-24` (newest issue first), deleted in the same
commit, `changelog.d/` left holding only its `README.md` marker — no
re-arm edit, armed by existing. The five guard actions at the drill ref —
`changelog-armed`, `changelog-monotonic`, `changelog-assembled`,
`drill-recorded`, `runner-isolated` — all green on that PR (run
30089703224): the assembled guard replayed the assembly from the merge
base and matched it byte-for-byte, live, on the mechanism this release
ships. Merged. The one run: facts
`ver='0.5.0' base_ver='0.5.0-dev' labeled='yes'``ceremony=yes` → tag
`0.5.0` on the merge commit (`9b9e58e`), release `0.5.0` published with
the assembled section — verbatim, both bullets — as its body, and main
re-armed to `0.5.1-dev` by the run itself. The GITHUB_TOKEN tag create
and bump push fired **no** second run (anti-recursion held).
**(b) Mislabeled ordinary PR.** Docs-only PR wearing the `release` label.
Green: `NOTICE: the version '0.5.1-dev' is -dev and unchanged by this PR —
release-flow work under the release label, not a ceremony. Nothing to
publish.` No tag, no release.
**(c) Bare-version PR without the label.** The ceremony's edits —
`0.5.1-dev``0.5.1`, a section assembled from a fragment — label
deliberately absent. Red at decide:
`the version transitioned ('0.5.1-dev' -> '0.5.1') but no merged,
release-labeled PR is behind this commit — a release is a labeled ceremony
PR, not a bare push — creating nothing.` No tag, no release; main
reverted to the armed state afterwards, and the revert push itself ran
green as a no-op (run 30090017774), as it should.
**(d) Re-run of the completed ceremony.** Re-ran (a)'s run. Red at the
nothing-may-exist assert:
`tag '0.5.0' already exists — this release already happened, or a manual
tag won the race; refusing to re-release, creating nothing.` Release
count still exactly one.
**(e) Tag door.** Branch with `VERSION` `0.6.0` and its section assembled
from a fragment; manual `0.6.0` tag pushed at its head (never merged to
main). The tag door published release `0.6.0` with that section as its
body; the merge door correctly skipped. Main untouched: `VERSION` stayed
`0.5.1-dev` — the fallback does not rewrite main.
**(f) Mismatched tag.** Tag `9.9.9` pushed at main (tree version
`0.5.1-dev`). Red:
`tag '9.9.9' does not match the tree's version '0.5.1-dev' — creating
nothing.` No release, and the operator-facing remediation text printed as
designed.
## Failures
None among the probes; every refusal path was checked for droppings — the
only releases and workflow-created tags that exist are (a)'s and (e)'s.
One finding outside the probe set, written down plainly: the fixture's
**initial push** — the very first commit of the scratch repo, caller
included — ran the merge door red (run 30089682128): `lib/facts.sh`
resolves the base as `MERGE_SHA^1` when `event.before` is all-zeros, and
a repo's first commit has no parent, so `git rev-parse` fails loudly
(exit 128) before deciding. Nothing was created, and every subsequent
push behaved; but a greenfield consumer bootstrapping caller-first will
meet one red run on its first push, which the doctrine's "every
legitimate non-ceremony is a green NOTICE no-op" does not intend.
`facts.sh` is unchanged since `0.1.0` — not a candidate regression, not a
blocker for this release; escalated as a discussion, not fixed here.