8.5 KiB
0.6.0 — drill record
IN PROGRESS — this record is being written as the rehearsal runs. It is committed early so the candidate carries evidence at every head rather than appearing at the end; the release PR is a draft until it is complete, and no probe row below is written before its run exists.
Run 2026-08-05 by cndgrr against the 0.6.0 release PR (Refs #249),
candidate branch build/249-release-0-6-0.
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 state is written below when the rehearsal ends, as this record's author observes it — never as an intention.
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 | — | — |
| 6 | mismatched tag | — | — |
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.