forked from heavy-duty/ceremony
Merge pull request #323 from cndgrr/build/249-release-0-6-0
release: cut 0.6.0
This commit is contained in:
commit
0ce6cb961a
42 changed files with 481 additions and 278 deletions
2
.github/workflows/labels-sweep.yml
vendored
2
.github/workflows/labels-sweep.yml
vendored
|
|
@ -49,7 +49,7 @@ on:
|
||||||
env:
|
env:
|
||||||
# A called workflow arrives without its repository. Keep this literal pin
|
# A called workflow arrives without its repository. Keep this literal pin
|
||||||
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
||||||
CEREMONY_SELF_REF: "0.5.0"
|
CEREMONY_SELF_REF: "0.6.0"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
reconcile:
|
reconcile:
|
||||||
|
|
|
||||||
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
|
|
@ -48,7 +48,7 @@ on:
|
||||||
env:
|
env:
|
||||||
# A called workflow arrives without its repository. Keep this literal pin
|
# A called workflow arrives without its repository. Keep this literal pin
|
||||||
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
||||||
CEREMONY_SELF_REF: "0.5.0"
|
CEREMONY_SELF_REF: "0.6.0"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
scope:
|
scope:
|
||||||
|
|
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -129,7 +129,7 @@ env:
|
||||||
# `ref:` accepts ${{ env }}; `uses:` strings do not — which is why the
|
# `ref:` accepts ${{ env }}; `uses:` strings do not — which is why the
|
||||||
# shared logic arrives as script files via checkout, not as inner `uses:`
|
# shared logic arrives as script files via checkout, not as inner `uses:`
|
||||||
# references.
|
# references.
|
||||||
CEREMONY_SELF_REF: "0.5.0"
|
CEREMONY_SELF_REF: "0.6.0"
|
||||||
VERSION_SOURCE: ${{ inputs.version-source }}
|
VERSION_SOURCE: ${{ inputs.version-source }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
195
CHANGELOG.md
195
CHANGELOG.md
|
|
@ -12,6 +12,201 @@ 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
|
an edit to this file — and the release PR assembles them into the next
|
||||||
section here (`bin/changelog-assemble`, #112).
|
section here (`bin/changelog-assemble`, #112).
|
||||||
|
|
||||||
|
## 0.6.0 — 2026-08-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The issue-flow sweep's `claimed`-branch ruling pre-read is pinned: an
|
||||||
|
unassigned claim under `needs-ruling` must draw its board diagnostic and
|
||||||
|
its ruling nudge in one sweep, so a read that drifts below the diagnostic
|
||||||
|
reds instead of silently costing the escalation 7 days (#284, #307).
|
||||||
|
- The issue-flow sweep now flags a collision the board never declared: two
|
||||||
|
open, unblocked issues whose titles name one deliverable draw a comment
|
||||||
|
naming the newer's owed `Blocked by` edge. Keys normalize, so
|
||||||
|
`actions/x` and `x` are one deliverable (#288).
|
||||||
|
- The sweep now flags an unblocked non-member during a standing release
|
||||||
|
window, naming the window's invariant. `claimed` counts, PR in flight or
|
||||||
|
not. The gate is read from the release issue's own `Blocked by`
|
||||||
|
declarations, and an emptied gate leaves it dormant (#292).
|
||||||
|
- Both flags are advisory: comments only, no label write and no state
|
||||||
|
change, deduped against each family's last word on the thread so a
|
||||||
|
standing state re-sweeps silently (#293).
|
||||||
|
- The fragment guard now requires each entry to end with its issue
|
||||||
|
citation: one `(#N)` group — local, `repo#N` or `owner/repo#N`
|
||||||
|
references separated by `, ` — then the final `.` and nothing after it
|
||||||
|
(#262).
|
||||||
|
- The refusal distinguishes an entry carrying no reference at all from one
|
||||||
|
whose reference is present but not terminal, and names the shape to
|
||||||
|
write in both (#262).
|
||||||
|
- The 300-character bound still outranks the citation across the whole
|
||||||
|
fragment, and the outranked problem stays out of the message it lost
|
||||||
|
to: one fragment, one diagnosis, wherever in the file it sits (#262).
|
||||||
|
- BUILDER.md now describes a fix round that rides a draft: the draft phase
|
||||||
|
stays the builder's, ready-for-review is the builder's own act, and where a
|
||||||
|
draft suppressed the checks green is proven at the flip (#258).
|
||||||
|
- REVIEWER.md now reads a draft carrying `state:addressing` as a fix round in
|
||||||
|
progress rather than abandonment (#258).
|
||||||
|
- A `post-merge` item with no comment for 7 days now draws one nudge from the
|
||||||
|
issue sweep: the wake evidence is owed. A starving criterion used to be
|
||||||
|
found only when someone happened to run the right read (#254).
|
||||||
|
- Label churn does not reset that clock, and neither does an assignment: on
|
||||||
|
`post-merge` an assignee is an invalid composition, not activity, and it
|
||||||
|
must not buy the item another 7 days of silence (#254).
|
||||||
|
- The nudge names the triage actor from `triage-actors=`, not the human
|
||||||
|
reviewer: `post-merge` is triage's completion queue, so the starved wake
|
||||||
|
condition is triage's to answer (#254).
|
||||||
|
- It links the item and parses nothing from the body — which criterion
|
||||||
|
starved is prose, and the machine never judges prose (#254).
|
||||||
|
- Like the ruling nudge it carries no idempotency marker on purpose: the
|
||||||
|
comment is itself activity, so the rule self-rate-limits to one nudge per 7
|
||||||
|
quiet days. Comment-only — no path here writes a label (#254).
|
||||||
|
- Release epics now announce release initialization when their declared dependency gates clear (#253).
|
||||||
|
- The issue sweep now echoes an issue's parsed `Blocked by` set as a comment
|
||||||
|
whenever that set changes, so a readable-but-wrong declaration is visible in
|
||||||
|
one sweep instead of days later, when a human happens to run the parser by
|
||||||
|
hand (#252).
|
||||||
|
- The echo's marker carries the parsed set itself: an unchanged parse never
|
||||||
|
re-posts on a 15-minute cron, and a changed one always speaks. Comment-only
|
||||||
|
— no path here writes a label (#252).
|
||||||
|
- CI now refuses a root `*.md` declared in neither `docs/VENDORED.txt` nor the
|
||||||
|
guard's short exemption list, so a new doctrine file can no longer reach a
|
||||||
|
tag undeclared and stay invisible to every consumer's `docs-sync` (#251).
|
||||||
|
- The same guard reads the manifest the other way: every entry must resolve to
|
||||||
|
a regular, non-empty, tracked file — no symlink, no directory, no `../`
|
||||||
|
escape (#251).
|
||||||
|
- Document the optional, operator-ruled release-epic flow for governed repositories. (#248).
|
||||||
|
- Guard documentation availability markers against missing issue citations
|
||||||
|
and release candidates that already ship the cited work (#238).
|
||||||
|
- The label and issue-flow sweeps now comment once per episode when
|
||||||
|
`attention` targets a pull request or an unassigned issue, without
|
||||||
|
retargeting the demand or changing labels or assignees (#232).
|
||||||
|
- Pull requests that promise `Refs #N` now fail a read-only, body-edit-aware
|
||||||
|
guard if GitHub would close N through a keyword or sidebar link (#218).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `README.md` is rewritten whole from the current tree: the front page names
|
||||||
|
the governance repo ceremony now is, routes to `docs/CONSUMERS.md`,
|
||||||
|
`AGENTS.md`, `LABELS.md` and `RELEASES.md` rather than restating them, and
|
||||||
|
keeps the operator's release runbook as its core, re-measured (#311).
|
||||||
|
- Standing release windows are dependency DAGs: every mint is placed in the window or behind it, and only current sources are `ready` (#292).
|
||||||
|
- TRIAGE.md now requires unconditional collision-edge chains when open issues
|
||||||
|
carry the same deliverable, keeping the ready queue concurrently claimable
|
||||||
|
(#288).
|
||||||
|
- TRIAGE.md now states its rules with bare record cites: the label-race and
|
||||||
|
lifted-hold incident narratives leave the normative text while their
|
||||||
|
operational rules remain complete (#282).
|
||||||
|
- `BUILDER.md` states its rules and cites their record bare: the incident
|
||||||
|
narratives, the links into issue comments and the cross-repo issue cites
|
||||||
|
leave the normative text, which no rule leaves with them (#281).
|
||||||
|
- CONTRIBUTING.md now keeps vendored doctrine self-contained: state the rule,
|
||||||
|
retain at most one sentence of why, cite the local record bare, and leave the
|
||||||
|
incident narrative in that record (#280).
|
||||||
|
- BUILDER.md's green ruled term now says which entry to read before it says
|
||||||
|
what an entry means: a check's word at a head is its newest entry by start
|
||||||
|
time, and a cancelled entry is not that word while the same check carries a
|
||||||
|
non-cancelled one at that head (#276).
|
||||||
|
- A check whose every entry at the head is cancelled is unchanged — nothing
|
||||||
|
survived to be its word, so it never reported and is not green — and the
|
||||||
|
collapse mirrors `checks_state`'s carve-out rather than adding a class
|
||||||
|
(#276).
|
||||||
|
- BUILDER.md's step 1 now rules the checkless head: no checks configured is
|
||||||
|
nothing to wait for, and the request goes out straight away — stated once,
|
||||||
|
in the ruled-term paragraph, with the draft-round restatement removed
|
||||||
|
(#272).
|
||||||
|
- `README.md` and `RELEASES.md` derive `scope:docs`, and the
|
||||||
|
`changelog-assembled`, `docs-sync` and `runner-isolated` actions and tests
|
||||||
|
derive `scope:guards`; all five were mapped nowhere. The docs block matched
|
||||||
|
a literal `README`, which this tree does not carry (#267).
|
||||||
|
- `lib/read.sh` and `lib/ruling.sh` derive `scope:labels` beside
|
||||||
|
`scope:release-flow`. Both reconcilers share them, and a mixed file wears
|
||||||
|
both labels rather than `lib/**` being re-carved into a row per file (#267).
|
||||||
|
- TRIAGE.md now tells every epic author to put its progress checklist under
|
||||||
|
the literal `## Task list` heading, because any other heading is silently
|
||||||
|
invisible to the completion sweep (#266).
|
||||||
|
- TRIAGE.md now scopes the no-assignee board bug to flagging an unassigned
|
||||||
|
issue, while still directing triage to repair ownership instead (#264).
|
||||||
|
- `BUILDER.md` and `CHANGELOG.md` state the citation as guard-enforced
|
||||||
|
rather than as house style, beside the 300-character bound it now sits
|
||||||
|
next to (#262).
|
||||||
|
- Four fragments in flight gained a terminal citation; published sections
|
||||||
|
are untouched, so no shipped prose is re-opened (#262).
|
||||||
|
- BUILDER.md's green ruled term now names its field: greenness is read from
|
||||||
|
each check's `conclusion`, never its `status`, and *stale* means a check
|
||||||
|
of a superseded head — not a same-head node whose `status` lags its own
|
||||||
|
conclusion (#260).
|
||||||
|
- Consumer guidance: re-vendor tooling reads the pin's `docs/VENDORED.txt`,
|
||||||
|
never a hardcoded list, so a new doctrine file propagates at the next
|
||||||
|
ordinary pin bump with zero list edits (#251).
|
||||||
|
- Define the doors-unchanged drill record and an executable release-path list,
|
||||||
|
so a release may reuse live evidence only when its door bytes are unchanged
|
||||||
|
since the last rehearsed tag (#237).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- A roster edit no longer reds the whole suite: the labels-reconcile
|
||||||
|
state-machine fixtures name their own panel instead of binding
|
||||||
|
`.github/labels.conf` by slot (#304).
|
||||||
|
- Shrinking `panel=` to three had left that binding's third slot unbound, and
|
||||||
|
`set -u` aborted the file before its first assertion — 217 assertions
|
||||||
|
became 0, on `main` and on every branch cut from it (#304).
|
||||||
|
- The one case still reading the shipped roster asserts a property, not a
|
||||||
|
size: it parses, and each member is recused from its own panel. Any
|
||||||
|
`panel=` of one or more members leaves `test/run.sh` green (#304).
|
||||||
|
- `lib/attention.sh` locates as label machinery beside its two shelf-mates —
|
||||||
|
`[scope:release-flow]` alone was a wrong answer of the class #267 measured
|
||||||
|
— and the map learns the sweep workflow pair, the shared-lib tests, and
|
||||||
|
seven enumerated test/guard surfaces (#302).
|
||||||
|
- Claiming a `needs-ruling` issue no longer buys its escalation another 7
|
||||||
|
quiet days: the issue-side ruling clock reads comments alone — an
|
||||||
|
assignment is the claim clock's fact — and LABELS.md now names what each
|
||||||
|
surface's clock reads (#284).
|
||||||
|
- `scope:release-flow` no longer rides every pull request: `changelog.d/**`
|
||||||
|
is out of its path map. Doctrine makes every behavior change write a
|
||||||
|
fragment, so the glob labelled 20 of the last 20 PRs while 3 touched a
|
||||||
|
release surface. `CHANGELOG.md` stays, as only the release PR edits it
|
||||||
|
(#267).
|
||||||
|
- The issue-flow reconciler and its test now derive `scope:labels`, the scope
|
||||||
|
that already names the taxonomy they reconcile (#267).
|
||||||
|
- Abort issue-flow reconciliation when the board read fails instead of reporting a complete pass over an empty or partial result (#257).
|
||||||
|
- The issue sweep no longer derives label writes from a read that failed. An
|
||||||
|
HTTP 504 whose body is GitHub's JSON error object passed every guard and
|
||||||
|
emptied the label set, so a healthy epic was written `needs-triage` and the
|
||||||
|
pass reported success (#247).
|
||||||
|
- A failed comments read no longer reclaims a live claim. Swallowed, it dated
|
||||||
|
the issue by `created_at` and unassigned the builder under a comment
|
||||||
|
asserting 48 hours of silence about an issue commented on seconds earlier
|
||||||
|
(#247).
|
||||||
|
- A failed comments read no longer reads as "no marker", which re-posted the
|
||||||
|
comment the marker exists to suppress (#247).
|
||||||
|
- Every read inside the per-issue subshell is checked explicitly, on its
|
||||||
|
status and on its payload shape; the issue is left exactly as it is and the
|
||||||
|
sweep continues. A partial pass names its skipped issues after
|
||||||
|
`reconciled.` (#247).
|
||||||
|
- A per-issue pass is now atomic: its writes and its log lines commit only
|
||||||
|
once the pass completes. A skip could previously land after an earlier
|
||||||
|
mutation, reporting an issue as untouched when a label had already been
|
||||||
|
written or removed (#247).
|
||||||
|
- The issue-flow sweep now reads an issue's deliverable as the `Refs` PR that
|
||||||
|
merged last, not the one numbered highest — merge order is not number order,
|
||||||
|
and the old rule spent the transition marker on the wrong PR (#242).
|
||||||
|
- Preserve active claims when an open local pull request links them with `Refs #N`. (#241).
|
||||||
|
- `blocker:unrequested` no longer fires while a head's checks are pending or
|
||||||
|
red: the review round forbids requesting there, so the one blocker that
|
||||||
|
demanded an act flagged builders for complying. Pending is CI's move, red is
|
||||||
|
`blocker:ci-red`'s (#236).
|
||||||
|
- `blocker:unrequested` now waits for the round to settle — the head and the
|
||||||
|
newest verdict must have stood for `RECONCILE_UNREQUESTED_GRACE` (default
|
||||||
|
300s) — so a sweep landing between a push and its re-request no longer flags
|
||||||
|
a round in motion (#236).
|
||||||
|
- LABELS.md no longer claims nothing in `actions/` clears or reads
|
||||||
|
`attention`: the reconciler has done both since the derived `claimed` →
|
||||||
|
`post-merge` transition shipped. The amended text keeps the hand-set rule
|
||||||
|
and admits the one clear and the diagnostic read (#231).
|
||||||
|
- Triage now puts `attention` on the assigned issue that owns a claim, never
|
||||||
|
on its pull request, and treats an unassigned issue as a board bug rather
|
||||||
|
than a demand (#230).
|
||||||
|
|
||||||
## 0.5.0 — 2026-08-03
|
## 0.5.0 — 2026-08-03
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.5.1-dev
|
0.6.0
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- Pull requests that promise `Refs #N` now fail a read-only, body-edit-aware
|
|
||||||
guard if GitHub would close N through a keyword or sidebar link (#218).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Triage now puts `attention` on the assigned issue that owns a claim, never
|
|
||||||
on its pull request, and treats an unassigned issue as a board bug rather
|
|
||||||
than a demand (#230).
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- LABELS.md no longer claims nothing in `actions/` clears or reads
|
|
||||||
`attention`: the reconciler has done both since the derived `claimed` →
|
|
||||||
`post-merge` transition shipped. The amended text keeps the hand-set rule
|
|
||||||
and admits the one clear and the diagnostic read (#231).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- The label and issue-flow sweeps now comment once per episode when
|
|
||||||
`attention` targets a pull request or an unassigned issue, without
|
|
||||||
retargeting the demand or changing labels or assignees (#232).
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `blocker:unrequested` no longer fires while a head's checks are pending or
|
|
||||||
red: the review round forbids requesting there, so the one blocker that
|
|
||||||
demanded an act flagged builders for complying. Pending is CI's move, red is
|
|
||||||
`blocker:ci-red`'s (#236).
|
|
||||||
- `blocker:unrequested` now waits for the round to settle — the head and the
|
|
||||||
newest verdict must have stood for `RECONCILE_UNREQUESTED_GRACE` (default
|
|
||||||
300s) — so a sweep landing between a push and its re-request no longer flags
|
|
||||||
a round in motion (#236).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Define the doors-unchanged drill record and an executable release-path list,
|
|
||||||
so a release may reuse live evidence only when its door bytes are unchanged
|
|
||||||
since the last rehearsed tag (#237).
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- Guard documentation availability markers against missing issue citations
|
|
||||||
and release candidates that already ship the cited work (#238).
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Preserve active claims when an open local pull request links them with `Refs #N`. (#241).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- The issue-flow sweep now reads an issue's deliverable as the `Refs` PR that
|
|
||||||
merged last, not the one numbered highest — merge order is not number order,
|
|
||||||
and the old rule spent the transition marker on the wrong PR (#242).
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- The issue sweep no longer derives label writes from a read that failed. An
|
|
||||||
HTTP 504 whose body is GitHub's JSON error object passed every guard and
|
|
||||||
emptied the label set, so a healthy epic was written `needs-triage` and the
|
|
||||||
pass reported success (#247).
|
|
||||||
- A failed comments read no longer reclaims a live claim. Swallowed, it dated
|
|
||||||
the issue by `created_at` and unassigned the builder under a comment
|
|
||||||
asserting 48 hours of silence about an issue commented on seconds earlier
|
|
||||||
(#247).
|
|
||||||
- A failed comments read no longer reads as "no marker", which re-posted the
|
|
||||||
comment the marker exists to suppress (#247).
|
|
||||||
- Every read inside the per-issue subshell is checked explicitly, on its
|
|
||||||
status and on its payload shape; the issue is left exactly as it is and the
|
|
||||||
sweep continues. A partial pass names its skipped issues after
|
|
||||||
`reconciled.` (#247).
|
|
||||||
- A per-issue pass is now atomic: its writes and its log lines commit only
|
|
||||||
once the pass completes. A skip could previously land after an earlier
|
|
||||||
mutation, reporting an issue as untouched when a label had already been
|
|
||||||
written or removed (#247).
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- Document the optional, operator-ruled release-epic flow for governed repositories. (#248).
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- CI now refuses a root `*.md` declared in neither `docs/VENDORED.txt` nor the
|
|
||||||
guard's short exemption list, so a new doctrine file can no longer reach a
|
|
||||||
tag undeclared and stay invisible to every consumer's `docs-sync` (#251).
|
|
||||||
- The same guard reads the manifest the other way: every entry must resolve to
|
|
||||||
a regular, non-empty, tracked file — no symlink, no directory, no `../`
|
|
||||||
escape (#251).
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Consumer guidance: re-vendor tooling reads the pin's `docs/VENDORED.txt`,
|
|
||||||
never a hardcoded list, so a new doctrine file propagates at the next
|
|
||||||
ordinary pin bump with zero list edits (#251).
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- The issue sweep now echoes an issue's parsed `Blocked by` set as a comment
|
|
||||||
whenever that set changes, so a readable-but-wrong declaration is visible in
|
|
||||||
one sweep instead of days later, when a human happens to run the parser by
|
|
||||||
hand (#252).
|
|
||||||
- The echo's marker carries the parsed set itself: an unchanged parse never
|
|
||||||
re-posts on a 15-minute cron, and a changed one always speaks. Comment-only
|
|
||||||
— no path here writes a label (#252).
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- Release epics now announce release initialization when their declared dependency gates clear (#253).
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- A `post-merge` item with no comment for 7 days now draws one nudge from the
|
|
||||||
issue sweep: the wake evidence is owed. A starving criterion used to be
|
|
||||||
found only when someone happened to run the right read (#254).
|
|
||||||
- Label churn does not reset that clock, and neither does an assignment: on
|
|
||||||
`post-merge` an assignee is an invalid composition, not activity, and it
|
|
||||||
must not buy the item another 7 days of silence (#254).
|
|
||||||
- The nudge names the triage actor from `triage-actors=`, not the human
|
|
||||||
reviewer: `post-merge` is triage's completion queue, so the starved wake
|
|
||||||
condition is triage's to answer (#254).
|
|
||||||
- It links the item and parses nothing from the body — which criterion
|
|
||||||
starved is prose, and the machine never judges prose (#254).
|
|
||||||
- Like the ruling nudge it carries no idempotency marker on purpose: the
|
|
||||||
comment is itself activity, so the rule self-rate-limits to one nudge per 7
|
|
||||||
quiet days. Comment-only — no path here writes a label (#254).
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Abort issue-flow reconciliation when the board read fails instead of reporting a complete pass over an empty or partial result (#257).
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- BUILDER.md now describes a fix round that rides a draft: the draft phase
|
|
||||||
stays the builder's, ready-for-review is the builder's own act, and where a
|
|
||||||
draft suppressed the checks green is proven at the flip (#258).
|
|
||||||
- REVIEWER.md now reads a draft carrying `state:addressing` as a fix round in
|
|
||||||
progress rather than abandonment (#258).
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- BUILDER.md's green ruled term now names its field: greenness is read from
|
|
||||||
each check's `conclusion`, never its `status`, and *stale* means a check
|
|
||||||
of a superseded head — not a same-head node whose `status` lags its own
|
|
||||||
conclusion (#260).
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- The fragment guard now requires each entry to end with its issue
|
|
||||||
citation: one `(#N)` group — local, `repo#N` or `owner/repo#N`
|
|
||||||
references separated by `, ` — then the final `.` and nothing after it
|
|
||||||
(#262).
|
|
||||||
- The refusal distinguishes an entry carrying no reference at all from one
|
|
||||||
whose reference is present but not terminal, and names the shape to
|
|
||||||
write in both (#262).
|
|
||||||
- The 300-character bound still outranks the citation across the whole
|
|
||||||
fragment, and the outranked problem stays out of the message it lost
|
|
||||||
to: one fragment, one diagnosis, wherever in the file it sits (#262).
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- `BUILDER.md` and `CHANGELOG.md` state the citation as guard-enforced
|
|
||||||
rather than as house style, beside the 300-character bound it now sits
|
|
||||||
next to (#262).
|
|
||||||
- Four fragments in flight gained a terminal citation; published sections
|
|
||||||
are untouched, so no shipped prose is re-opened (#262).
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- TRIAGE.md now scopes the no-assignee board bug to flagging an unassigned
|
|
||||||
issue, while still directing triage to repair ownership instead (#264).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- TRIAGE.md now tells every epic author to put its progress checklist under
|
|
||||||
the literal `## Task list` heading, because any other heading is silently
|
|
||||||
invisible to the completion sweep (#266).
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `scope:release-flow` no longer rides every pull request: `changelog.d/**`
|
|
||||||
is out of its path map. Doctrine makes every behavior change write a
|
|
||||||
fragment, so the glob labelled 20 of the last 20 PRs while 3 touched a
|
|
||||||
release surface. `CHANGELOG.md` stays, as only the release PR edits it
|
|
||||||
(#267).
|
|
||||||
- The issue-flow reconciler and its test now derive `scope:labels`, the scope
|
|
||||||
that already names the taxonomy they reconcile (#267).
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- `README.md` and `RELEASES.md` derive `scope:docs`, and the
|
|
||||||
`changelog-assembled`, `docs-sync` and `runner-isolated` actions and tests
|
|
||||||
derive `scope:guards`; all five were mapped nowhere. The docs block matched
|
|
||||||
a literal `README`, which this tree does not carry (#267).
|
|
||||||
- `lib/read.sh` and `lib/ruling.sh` derive `scope:labels` beside
|
|
||||||
`scope:release-flow`. Both reconcilers share them, and a mixed file wears
|
|
||||||
both labels rather than `lib/**` being re-carved into a row per file (#267).
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- BUILDER.md's step 1 now rules the checkless head: no checks configured is
|
|
||||||
nothing to wait for, and the request goes out straight away — stated once,
|
|
||||||
in the ruled-term paragraph, with the draft-round restatement removed
|
|
||||||
(#272).
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- BUILDER.md's green ruled term now says which entry to read before it says
|
|
||||||
what an entry means: a check's word at a head is its newest entry by start
|
|
||||||
time, and a cancelled entry is not that word while the same check carries a
|
|
||||||
non-cancelled one at that head (#276).
|
|
||||||
- A check whose every entry at the head is cancelled is unchanged — nothing
|
|
||||||
survived to be its word, so it never reported and is not green — and the
|
|
||||||
collapse mirrors `checks_state`'s carve-out rather than adding a class
|
|
||||||
(#276).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- CONTRIBUTING.md now keeps vendored doctrine self-contained: state the rule,
|
|
||||||
retain at most one sentence of why, cite the local record bare, and leave the
|
|
||||||
incident narrative in that record (#280).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- `BUILDER.md` states its rules and cites their record bare: the incident
|
|
||||||
narratives, the links into issue comments and the cross-repo issue cites
|
|
||||||
leave the normative text, which no rule leaves with them (#281).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- TRIAGE.md now states its rules with bare record cites: the label-race and
|
|
||||||
lifted-hold incident narratives leave the normative text while their
|
|
||||||
operational rules remain complete (#282).
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Claiming a `needs-ruling` issue no longer buys its escalation another 7
|
|
||||||
quiet days: the issue-side ruling clock reads comments alone — an
|
|
||||||
assignment is the claim clock's fact — and LABELS.md now names what each
|
|
||||||
surface's clock reads (#284).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- TRIAGE.md now requires unconditional collision-edge chains when open issues
|
|
||||||
carry the same deliverable, keeping the ready queue concurrently claimable
|
|
||||||
(#288).
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Standing release windows are dependency DAGs: every mint is placed in the window or behind it, and only current sources are `ready` (#292).
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- The issue-flow sweep now flags a collision the board never declared: two
|
|
||||||
open, unblocked issues whose titles name one deliverable draw a comment
|
|
||||||
naming the newer's owed `Blocked by` edge. Keys normalize, so
|
|
||||||
`actions/x` and `x` are one deliverable (#288).
|
|
||||||
- The sweep now flags an unblocked non-member during a standing release
|
|
||||||
window, naming the window's invariant. `claimed` counts, PR in flight or
|
|
||||||
not. The gate is read from the release issue's own `Blocked by`
|
|
||||||
declarations, and an emptied gate leaves it dormant (#292).
|
|
||||||
- Both flags are advisory: comments only, no label write and no state
|
|
||||||
change, deduped against each family's last word on the thread so a
|
|
||||||
standing state re-sweeps silently (#293).
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `lib/attention.sh` locates as label machinery beside its two shelf-mates —
|
|
||||||
`[scope:release-flow]` alone was a wrong answer of the class #267 measured
|
|
||||||
— and the map learns the sweep workflow pair, the shared-lib tests, and
|
|
||||||
seven enumerated test/guard surfaces (#302).
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- A roster edit no longer reds the whole suite: the labels-reconcile
|
|
||||||
state-machine fixtures name their own panel instead of binding
|
|
||||||
`.github/labels.conf` by slot (#304).
|
|
||||||
- Shrinking `panel=` to three had left that binding's third slot unbound, and
|
|
||||||
`set -u` aborted the file before its first assertion — 217 assertions
|
|
||||||
became 0, on `main` and on every branch cut from it (#304).
|
|
||||||
- The one case still reading the shipped roster asserts a property, not a
|
|
||||||
size: it parses, and each member is recused from its own panel. Any
|
|
||||||
`panel=` of one or more members leaves `test/run.sh` green (#304).
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- The issue-flow sweep's `claimed`-branch ruling pre-read is pinned: an
|
|
||||||
unassigned claim under `needs-ruling` must draw its board diagnostic and
|
|
||||||
its ruling nudge in one sweep, so a read that drifts below the diagnostic
|
|
||||||
reds instead of silently costing the escalation 7 days (#284, #307).
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- `README.md` is rewritten whole from the current tree: the front page names
|
|
||||||
the governance repo ceremony now is, routes to `docs/CONSUMERS.md`,
|
|
||||||
`AGENTS.md`, `LABELS.md` and `RELEASES.md` rather than restating them, and
|
|
||||||
keeps the operator's release runbook as its core, re-measured (#311).
|
|
||||||
|
|
@ -155,9 +155,9 @@ the machinery at all:
|
||||||
- uses: heavy-duty/ceremony/actions/refs-not-closing@<pinned-tag>
|
- uses: heavy-duty/ceremony/actions/refs-not-closing@<pinned-tag>
|
||||||
```
|
```
|
||||||
|
|
||||||
`refs-not-closing` is **unreleased** (#218) until the first tag carrying it.
|
`refs-not-closing` is available at `0.6.0` and later (#218). Adopt this
|
||||||
Adopt this caller with that ordinary pin bump; never point only this file
|
caller with that ordinary pin bump; never point only this file at a
|
||||||
at a moving or newer ref.
|
moving or newer ref.
|
||||||
7. **Labels automation** (optional but recommended): the two callers from
|
7. **Labels automation** (optional but recommended): the two callers from
|
||||||
[Labels automation](#labels-automation) — the event-facing labels
|
[Labels automation](#labels-automation) — the event-facing labels
|
||||||
caller and the sweep caller (#209) — plus `.github/labels.conf`
|
caller and the sweep caller (#209) — plus `.github/labels.conf`
|
||||||
|
|
@ -593,10 +593,10 @@ marking the directory machine-managed. `actions/docs-sync` owns the copy:
|
||||||
mirror), `--check` re-diffs it in CI on every PR, so a hand edit or a stale
|
mirror), `--check` re-diffs it in CI on every PR, so a hand edit or a stale
|
||||||
pin goes red instead of quietly governing.
|
pin goes red instead of quietly governing.
|
||||||
|
|
||||||
`RELEASES.md` joins that mirror with the first tag carrying ceremony#248.
|
`RELEASES.md` joins that mirror with the first tag carrying ceremony#248,
|
||||||
It is **unreleased** (#248) until that tag exists: consumers add
|
and is available at `0.6.0` and later: consumers add `.ceremony/RELEASES.md`
|
||||||
`.ceremony/RELEASES.md` only with the ordinary pin bump and re-sync, never by
|
only with the ordinary pin bump and re-sync, never by copying it ahead of
|
||||||
copying it ahead of their pinned doctrine set.
|
their pinned doctrine set.
|
||||||
|
|
||||||
### Read the manifest, never a copy of it
|
### Read the manifest, never a copy of it
|
||||||
|
|
||||||
|
|
@ -625,10 +625,9 @@ What makes reading the manifest *sufficient* — rather than merely better
|
||||||
than a copy — is that ceremony's CI now refuses a root doctrine file that is
|
than a copy — is that ceremony's CI now refuses a root doctrine file that is
|
||||||
declared in neither the manifest nor a short in-script exemption list
|
declared in neither the manifest nor a short in-script exemption list
|
||||||
(`.github/scripts/vendored-check.sh`), so the manifest at a tag is the
|
(`.github/scripts/vendored-check.sh`), so the manifest at a tag is the
|
||||||
complete set as of that tag. That guarantee is **unreleased** (#251) until
|
complete set as of that tag. That guarantee holds at `0.6.0` and later
|
||||||
the first tag carrying it exists; the manifest is worth reading at every
|
(#251); the manifest is worth reading at every earlier pin regardless, since
|
||||||
earlier pin regardless, since it is what `actions/docs-sync` has always
|
it is what `actions/docs-sync` has always mirrored.
|
||||||
mirrored.
|
|
||||||
|
|
||||||
The consumer's ci.yml gains the guard alongside the others:
|
The consumer's ci.yml gains the guard alongside the others:
|
||||||
|
|
||||||
|
|
|
||||||
272
drills/0.6.0.md
Normal file
272
drills/0.6.0.md
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
# 0.6.0 — drill record
|
||||||
|
|
||||||
|
Run 2026-08-05 by `cndgrr` against the 0.6.0 release PR (Refs #249),
|
||||||
|
candidate branch `build/249-release-0-6-0`, canonical candidate SHA
|
||||||
|
`fb8f8282a9e7b317d4d028f8e8da50501a882d14`. All six probes ran; every row in
|
||||||
|
the table below was written from its own run.
|
||||||
|
|
||||||
|
## Scope ruling — a full rehearsal is owed, and doors-unchanged is refused
|
||||||
|
|
||||||
|
This record's shape was measured, not chosen. `drills/README.md` allows the
|
||||||
|
doors-unchanged shape only when all three of its conditions hold at the
|
||||||
|
candidate head; the first one does not.
|
||||||
|
|
||||||
|
The baseline is the last **rehearsed** tag, never the previous tag:
|
||||||
|
`drills/0.4.1.md` and `drills/0.5.0.md` are both doors-unchanged records, so
|
||||||
|
the anchor is **`0.4.0`**, whose record is a full disposable-repo rehearsal,
|
||||||
|
whose release is published, and after which `main` was re-armed to
|
||||||
|
`0.4.1-dev` (`84bb1a4`). Condition 3 holds.
|
||||||
|
|
||||||
|
The release path is exactly the output of `.github/scripts/release-path.sh`
|
||||||
|
at this head — `.github/workflows/release.yml`, `bin/`, `lib/version.sh`,
|
||||||
|
`lib/decide.sh`, `lib/facts.sh`, `lib/changelog.sh`. Condition 2 holds.
|
||||||
|
|
||||||
|
Condition 1 fails. Measured at this candidate:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ git diff 0.4.0..HEAD -- $(.github/scripts/release-path.sh)
|
||||||
|
.github/workflows/release.yml | 2 +-
|
||||||
|
lib/changelog.sh | 83 ++++++++++++++++++++++++++++++++++++++---
|
||||||
|
```
|
||||||
|
|
||||||
|
`release.yml`'s two lines are the `CEREMONY_SELF_REF` pin, which the
|
||||||
|
condition exempts. **`lib/changelog.sh` is not exempt and is not empty**: it
|
||||||
|
carries `72fa3e0` (the terminal issue-citation rule joining the fragment
|
||||||
|
guard, #262) and `75a5b68` (one fragment, one diagnosis, #262). That file is
|
||||||
|
on the release path because the merge door sources it to assemble and read
|
||||||
|
the release section — this is a door byte, not a neighbouring library, and
|
||||||
|
the last-rehearsed anchor exists precisely so an accumulated change like
|
||||||
|
this forces a new rehearsal rather than chaining a third doors-unchanged
|
||||||
|
assertion off the second.
|
||||||
|
|
||||||
|
So this release owes the disposable-repo rehearsal, and this record is it.
|
||||||
|
|
||||||
|
## Where
|
||||||
|
|
||||||
|
Disposable **private** repo `cndgrr/ceremony-drill-0.6.0`, created
|
||||||
|
2026-08-05T00:02:58Z. It carries the `docs/CONSUMERS.md` release caller
|
||||||
|
verbatim (`version-source: file`) over a fragment-mode fixture armed at
|
||||||
|
`0.6.0-dev`: a preamble-only `CHANGELOG.md`, `changelog.d/README.md` plus
|
||||||
|
one fragment, and a non-blank `drills/0.6.0.md`. The `release` label was
|
||||||
|
created there before the first ceremony PR, per the guide's prerequisite.
|
||||||
|
|
||||||
|
**Disposal, as this record's author observed it**: the repository is
|
||||||
|
**archived** — `PATCH /repos/cndgrr/ceremony-drill-0.6.0` with
|
||||||
|
`archived: true` returned `true`, and a fresh read afterwards reported
|
||||||
|
`archived=true private=true`. It is **pending the operator's delete**, which
|
||||||
|
this builder cannot perform: `delete_repo` is absent from fleet tokens by
|
||||||
|
doctrine (#135). No delete was attempted and none is claimed. Cleanup gates
|
||||||
|
nothing — not this PR's ready-for-review, not the panel, not the merge.
|
||||||
|
|
||||||
|
## Candidate-ref deviation
|
||||||
|
|
||||||
|
The pure consumer path cannot resolve this candidate's
|
||||||
|
`CEREMONY_SELF_REF: "0.6.0"`: that tag is the one this release has not
|
||||||
|
created yet. No `0.6.0` branch was created on `heavy-duty/ceremony`.
|
||||||
|
|
||||||
|
The scratch caller instead pins `cndgrr/ceremony/.github/workflows/release.yml@drill/0.6.0`.
|
||||||
|
That fork ref's parent is the canonical candidate SHA
|
||||||
|
`fb8f8282a9e7b317d4d028f8e8da50501a882d14`, and its one additional commit
|
||||||
|
(`775b4d1f6485ebdde924979ac2dce536643c6071`) rewrites all three
|
||||||
|
`CEREMONY_SELF_REF` carriers — `release.yml`, `labels.yml`,
|
||||||
|
`labels-sweep.yml` — to that same SHA. All runtime machinery in every probe
|
||||||
|
below was therefore fetched from the 0.6.0 candidate tree.
|
||||||
|
|
||||||
|
Commits pushed to the candidate after `fb8f828` are this record only; the
|
||||||
|
release path (`.github/scripts/release-path.sh`) is byte-identical at the
|
||||||
|
canonical SHA and at the final head.
|
||||||
|
|
||||||
|
## Probes
|
||||||
|
|
||||||
|
One row per probe, written from its run. Runs are in
|
||||||
|
`cndgrr/ceremony-drill-0.6.0`.
|
||||||
|
|
||||||
|
| # | probe | run | result |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | merge-door ceremony | 30992108742 (attempt 1) | ✅ exactly one `0.6.0` release; tag equals the merge commit; main re-armed to `0.6.1-dev` |
|
||||||
|
| 2 | mislabeled ordinary PR | 30991634654 | ✅ green NOTICE no-op; no tag, no release |
|
||||||
|
| 3 | bare-version PR without `release` | 30991832001 | ✅ refused at decide; no tag, no release |
|
||||||
|
| 4 | re-run completed ceremony | 30992108742 (attempt 2) | ✅ refused at the nothing-exists assert; the release count stayed one |
|
||||||
|
| 5 | manual matching tag | 30992258952 | ✅ `0.6.1` published from its own changelog section; main untouched |
|
||||||
|
| 6 | mismatched tag | 30992310031 | ✅ refused before publication; no `9.9.9` release, and the probe tag was removed afterwards |
|
||||||
|
|
||||||
|
### Probe 5 — a manual tag matching its tree
|
||||||
|
|
||||||
|
Branch `probe5-tag` carried `VERSION` at `0.6.1` and a
|
||||||
|
`## 0.6.1 — 2026-08-05` section; tag `0.6.1` was pushed at that commit
|
||||||
|
(`dfd0cfeaca772cf45bcb63a1a639829185510c60`) with a personal token, so it
|
||||||
|
fired the door — the anti-recursion property probe 1 relies on is exactly
|
||||||
|
what makes a hand-pushed tag the only way to reach this door. The
|
||||||
|
`release-on-merge` job skipped and `release-on-tag` ran: the version assert
|
||||||
|
passed, notes were extracted, the release published.
|
||||||
|
|
||||||
|
The branch, not main, carried the tagged tree on purpose — the tag door
|
||||||
|
takes no bump step, and pointing it at a side branch proves that without a
|
||||||
|
bare version ever sitting on main. Observed afterwards: `0.6.1` published
|
||||||
|
with exactly its own section's bullet, and main still reading `0.6.1-dev`,
|
||||||
|
untouched by the publish. Two releases now exist, `0.6.0` and `0.6.1`,
|
||||||
|
neither a draft, neither carrying assets.
|
||||||
|
|
||||||
|
### Probe 6 — a mismatched tag
|
||||||
|
|
||||||
|
Tag `9.9.9` was pushed at the same `0.6.1` commit. The door refused at its
|
||||||
|
first assert, before notes and before publication:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tag '9.9.9' does not match the tree's version '0.6.1' — creating nothing.
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes, the artifact hook and publish all skipped. `GET /releases` still
|
||||||
|
returned exactly `0.6.1` and `0.6.0`. The `9.9.9` ref was deleted afterwards
|
||||||
|
(`DELETE /git/refs/tags/9.9.9`); `GET /git/refs/tags` then listed `0.6.0`
|
||||||
|
and `0.6.1` only. The probe tag was the operator's artefact, never the
|
||||||
|
workflow's — the door created nothing, which is the whole assertion.
|
||||||
|
|
||||||
|
### Probe 1 — the merge-door ceremony
|
||||||
|
|
||||||
|
PR #4 (`probe1-ceremony`) bumped `0.6.0-dev` to bare `0.6.0` and stamped
|
||||||
|
`## 0.6.0 — 2026-08-05`, assembled from the three fixture fragments by the
|
||||||
|
candidate's own `bin/changelog-assemble` and committed with the deletions
|
||||||
|
in one commit. The `release` label was applied and confirmed before the
|
||||||
|
merge. Facts and verdict:
|
||||||
|
|
||||||
|
```text
|
||||||
|
VER: 0.6.0
|
||||||
|
BASE_VER: 0.6.0-dev
|
||||||
|
RELEASED:
|
||||||
|
LABELED: yes
|
||||||
|
ceremony=yes
|
||||||
|
```
|
||||||
|
|
||||||
|
Observed after the run:
|
||||||
|
|
||||||
|
- **Exactly one** release: `GET /releases` returned `0.6.0` alone, not a
|
||||||
|
draft, not a pre-release, zero assets (no artifact hook in the fixture —
|
||||||
|
the hook step skipped).
|
||||||
|
- `GET /tags` returned `0.6.0` alone, pointing at
|
||||||
|
`64d02539f4a20286afc08b9997f0f8a7d1dbfccd`, which is PR #4's merge commit
|
||||||
|
— the tag names the tree that was reviewed.
|
||||||
|
- The release body was byte-for-byte the assembled section's bullets:
|
||||||
|
|
||||||
|
```text
|
||||||
|
- A second ordinary fragment, written by probe 2 of the 0.6.0 drill (#249).
|
||||||
|
- An ordinary behavior change, landing under the release label (#249).
|
||||||
|
- Fragment mode is exercised by the ceremony 0.6.0 drill (#249).
|
||||||
|
```
|
||||||
|
|
||||||
|
- Main re-armed itself: commit `2d0e19a` ("bump main to 0.6.1-dev — a dev
|
||||||
|
install must not impersonate 0.6.0"), pushed by the job's own token. Main
|
||||||
|
reads `0.6.1-dev` and `changelog.d/` holds only `README.md`.
|
||||||
|
- **The anti-recursion property held.** Neither the tag create nor the bump
|
||||||
|
push started a workflow run — the run list after the ceremony ends at
|
||||||
|
30992108742. That is what makes the merge door the release's only chance
|
||||||
|
to publish, and it is the reason probe 4 below is the door's own guard
|
||||||
|
rather than a second run's.
|
||||||
|
|
||||||
|
### Probe 4 — a re-run of the completed ceremony
|
||||||
|
|
||||||
|
Re-running 30992108742 as attempt 2 re-decided `ceremony=yes` — the facts
|
||||||
|
at that merge commit have not changed — and then died at the assert:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tag '0.6.0' already exists — this release already happened, or a manual tag won the race; refusing to re-release, creating nothing.
|
||||||
|
```
|
||||||
|
|
||||||
|
Tag, publish and bump all skipped. `GET /releases` still returned exactly
|
||||||
|
one `0.6.0`. The refusal is loud (the job is red) and creates nothing, which
|
||||||
|
is the required shape: the assert is what covers a manual tag racing the
|
||||||
|
merge, not only an operator's stray re-run.
|
||||||
|
|
||||||
|
### Probe 3 — a bare-version PR without the `release` label
|
||||||
|
|
||||||
|
PR #3 (`probe3-bare`) bumped `VERSION` to bare `0.6.0` and carried no label;
|
||||||
|
the label list was read as empty before merging. The merge run refused at
|
||||||
|
decide, row 5 of the table:
|
||||||
|
|
||||||
|
```text
|
||||||
|
VER: 0.6.0
|
||||||
|
BASE_VER: 0.6.0-dev
|
||||||
|
RELEASED:
|
||||||
|
LABELED: no
|
||||||
|
the version transitioned ('0.6.0-dev' -> '0.6.0') but no merged, release-labeled PR is behind this commit — a release is a labeled ceremony PR, not a bare push — creating nothing.
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes, the assert, tag, hook, publish and bump all skipped; tags and
|
||||||
|
releases were both still empty afterwards. The merge was then undone and
|
||||||
|
main re-armed to `0.6.0-dev` before the ceremony probe ran (see Setup).
|
||||||
|
|
||||||
|
### Probe 2 — a mislabeled ordinary PR
|
||||||
|
|
||||||
|
PR #2 (`probe2b-mislabeled`) added one changelog fragment and touched no
|
||||||
|
version. The `release` label was applied through
|
||||||
|
`POST /repos/{owner}/{repo}/issues/2/labels` and confirmed present before
|
||||||
|
the merge. The merge run decided row 1 of the table and published nothing:
|
||||||
|
|
||||||
|
```text
|
||||||
|
VER: 0.6.0-dev
|
||||||
|
BASE_VER: 0.6.0-dev
|
||||||
|
RELEASED:
|
||||||
|
LABELED:
|
||||||
|
NOTICE: the version '0.6.0-dev' is -dev and unchanged by this PR — release-flow work under the release label, not a ceremony. Nothing to publish.
|
||||||
|
ceremony=no
|
||||||
|
```
|
||||||
|
|
||||||
|
`RELEASED` and `LABELED` are empty on purpose — the `-dev` rows never
|
||||||
|
consult them, which is precisely why the label alone cannot ship anything.
|
||||||
|
Notes, the nothing-exists assert, tag, artifact hook, publish and bump all
|
||||||
|
skipped; `GET /tags` and `GET /releases` were both empty afterwards.
|
||||||
|
|
||||||
|
An earlier merge (PR #1, run 30991571096) was intended as this probe but
|
||||||
|
landed **unlabeled**: `gh pr edit --add-label` failed against this repo's
|
||||||
|
projects-classic GraphQL surface, and the merge went ahead before the
|
||||||
|
failure was read. That run is a green no-op too, but it is not evidence for
|
||||||
|
this probe — an unlabeled ordinary merge proves less than a labeled one —
|
||||||
|
so the probe was re-run as PR #2 with the label applied through the REST
|
||||||
|
endpoint and verified before merging. Recorded here because the run exists
|
||||||
|
in the repo's history and a reader will find it.
|
||||||
|
|
||||||
|
## Setup, and the runs that are not probes
|
||||||
|
|
||||||
|
The armed fixture was committed before the caller, so the first door run had
|
||||||
|
a real parent version to inspect: run **30962040469** is that green baseline
|
||||||
|
no-op. The probes then ran in the order 2, 3, 1, 4, 5, 6 — the refusals
|
||||||
|
first, against an armed tree, so the ceremony itself ran last against a
|
||||||
|
fixture the refusals had already proven intact.
|
||||||
|
|
||||||
|
Three non-probe runs are on the board and are accounted for here rather than
|
||||||
|
left for a reader to guess at:
|
||||||
|
|
||||||
|
- **30991571096** (green) — PR #1, the unlabeled first attempt at probe 2,
|
||||||
|
described above.
|
||||||
|
- **30991892212** (green) — restoring `VERSION` to `0.6.0-dev` after probe
|
||||||
|
3's refusal, so the ceremony probe met an armed tree. Row 2 of the table:
|
||||||
|
the version changed and still ends `-dev`.
|
||||||
|
- **30991958967** (red) — **a builder error, not a door finding.** An
|
||||||
|
uncommitted `VERSION` bump left over from staging the ceremony branch rode
|
||||||
|
along into a setup commit that was meant to touch only the fragments, and
|
||||||
|
pushed bare `0.6.0` straight to main. The door refused it exactly as it
|
||||||
|
refused probe 3, by the same row-5 path, and created nothing: tags and
|
||||||
|
releases were both still empty when the failure was read. Main was re-armed
|
||||||
|
to `0.6.0-dev` (green run **30992046247**) before the ceremony probe. It is
|
||||||
|
written down because a red run on a drill repo that the record does not
|
||||||
|
explain is indistinguishable from a door that failed.
|
||||||
|
|
||||||
|
The fixture's three fragments were also rewritten mid-setup to carry
|
||||||
|
terminal issue citations. The candidate's own `bin/changelog-assemble`
|
||||||
|
refused them without one — `fragment 'changelog.d/1.md' has an entry with no
|
||||||
|
issue citation` — which is #262's rule, one of the two commits on
|
||||||
|
`lib/changelog.sh` that make this release owe a rehearsal at all. The
|
||||||
|
fixture had been written before that rule existed. The refusal is the guard
|
||||||
|
working; the correction is recorded because the fragments the ceremony
|
||||||
|
consumed are not the fragments the repo was created with.
|
||||||
|
|
||||||
|
## What the rehearsal establishes
|
||||||
|
|
||||||
|
Both doors ran live against the 0.6.0 candidate's own machinery. The merge
|
||||||
|
door published exactly one release from a labeled ceremony PR, tagged the
|
||||||
|
reviewed merge commit, and re-armed main itself; it refused a bare push
|
||||||
|
without a label, refused a re-run of its own completed ceremony, and stayed
|
||||||
|
a green no-op under a label carried by ordinary work. The tag door published
|
||||||
|
from a matching manual tag without touching main, and refused a mismatched
|
||||||
|
one before creating anything. Every refusal created nothing — no tag, no
|
||||||
|
release, on any of the four refusal paths.
|
||||||
Loading…
Reference in a new issue