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:
|
||||
# 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.5.0"
|
||||
CEREMONY_SELF_REF: "0.6.0"
|
||||
|
||||
jobs:
|
||||
reconcile:
|
||||
|
|
|
|||
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
|
|
@ -48,7 +48,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.5.0"
|
||||
CEREMONY_SELF_REF: "0.6.0"
|
||||
|
||||
jobs:
|
||||
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
|
||||
# shared logic arrives as script files via checkout, not as inner `uses:`
|
||||
# references.
|
||||
CEREMONY_SELF_REF: "0.5.0"
|
||||
CEREMONY_SELF_REF: "0.6.0"
|
||||
VERSION_SOURCE: ${{ inputs.version-source }}
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
### 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>
|
||||
```
|
||||
|
||||
`refs-not-closing` is **unreleased** (#218) until the first tag carrying it.
|
||||
Adopt this caller with that ordinary pin bump; never point only this file
|
||||
at a moving or newer ref.
|
||||
`refs-not-closing` is available at `0.6.0` and later (#218). Adopt this
|
||||
caller with that ordinary pin bump; never point only this file at a
|
||||
moving or newer ref.
|
||||
7. **Labels automation** (optional but recommended): the two callers from
|
||||
[Labels automation](#labels-automation) — the event-facing labels
|
||||
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
|
||||
pin goes red instead of quietly governing.
|
||||
|
||||
`RELEASES.md` joins that mirror with the first tag carrying ceremony#248.
|
||||
It is **unreleased** (#248) until that tag exists: consumers add
|
||||
`.ceremony/RELEASES.md` only with the ordinary pin bump and re-sync, never by
|
||||
copying it ahead of their pinned doctrine set.
|
||||
`RELEASES.md` joins that mirror with the first tag carrying ceremony#248,
|
||||
and is available at `0.6.0` and later: consumers add `.ceremony/RELEASES.md`
|
||||
only with the ordinary pin bump and re-sync, never by copying it ahead of
|
||||
their pinned doctrine set.
|
||||
|
||||
### 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
|
||||
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
|
||||
complete set as of that tag. That guarantee is **unreleased** (#251) until
|
||||
the first tag carrying it exists; the manifest is worth reading at every
|
||||
earlier pin regardless, since it is what `actions/docs-sync` has always
|
||||
mirrored.
|
||||
complete set as of that tag. That guarantee holds at `0.6.0` and later
|
||||
(#251); the manifest is worth reading at every earlier pin regardless, since
|
||||
it is what `actions/docs-sync` has always mirrored.
|
||||
|
||||
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