ceremony/drills/0.6.0.md

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 archivedPATCH /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 /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:

    - 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:

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 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.