13 KiB
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:
$ 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:
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:
VER: 0.6.0
BASE_VER: 0.6.0-dev
RELEASED:
LABELED: yes
ceremony=yes
Observed after the run:
-
Exactly one release:
GET /releasesreturned0.6.0alone, not a draft, not a pre-release, zero assets (no artifact hook in the fixture — the hook step skipped). -
GET /tagsreturned0.6.0alone, pointing at64d02539f4a20286afc08b9997f0f8a7d1dbfccd, 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:
- 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 reads0.6.1-devandchangelog.d/holds onlyREADME.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:
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:
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:
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
VERSIONto0.6.0-devafter 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
VERSIONbump left over from staging the ceremony branch rode along into a setup commit that was meant to touch only the fragments, and pushed bare0.6.0straight 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 to0.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.