ceremony/changelog.d/202.md

65 lines
3 KiB
Markdown
Raw Normal View History

docs(drills): the standing runner-probe venue, and why it is not a drill (#202) @andres ruled option A (#5631): one standing never-archived repo. This is the runbook half. The distinction the document exists to make: a drill is disposable by design and ends with the builder archiving it. This venue is the opposite — it exists so that runner-only facts can be measured on demand, and archiving it defeats the purpose. That is not hypothetical: all three drill repos were archived correctly, by the rule, and each then had to be un-archived or replaced. The request came three times in two days across #192 and #198 and never became anything. What the runbook pins, all of it measured rather than asserted: * a probe MUST run as an Actions job under ${{ github.token }} — the same DELETE answers 500 there and 204 under a PAT, so a probe run any other way produces a confident wrong answer; * probe results are written into the forge, not left in a job log, because logs age out and #192's run 701 survived only because it wrote into an issue; * no probe touches ceremony's own board — the venue exists so the live board is not the fixture; * the three probes it already owes (#192's live label lift, #205's dispatch measurement, a 0.6.0 consumer exercise after #198). STANDING THE REPO UP IS THE OPERATOR'S STEP, and this is the part I could not do rather than the part I chose not to. Measured today with this identity: POST /api/v1/orgs/heavy-duty/repos -> 403 not allowed in organization POST /api/v1/user/repos -> 201 personal namespace only Same shape as the drill delete: a deliberate boundary, not a misconfiguration. The runbook says so, says not to retry it, and says not to work around it by using a personal namespace where the org's runner and secrets do not reach. test/run.sh 22/22, shellcheck 0.10.0, actionlint, self-ref all clean. Refs #202
2026-08-05 13:16:20 +00:00
### Added
docs(runner-probes): its own document, an arming procedure, and the evidence boundary made consistent (#202) @codex-reviewer-andresmgsl's four gaps. 1. BRANCH UPDATED TO CURRENT MAIN. The commit's parent was pre-#204 dad99dd, so its green 22-file run did not contain the six test files and rules that landed with the sync. Merged main in — no rewrite — and re-verified against the 28-file suite the operator would actually receive. 2. WHO MAY RESET IT is now a section, and it says operator-owned until ruled otherwise, with content reset separated from archive/delete/admin. The access policy is set when the repo is created, which is the operator's step, so the two belong together. Flagged for @andres rather than assumed. 3. AN EXECUTABLE ARMING PROCEDURE replaces "install whatever the probe needs": fork ref and canonical SHA, caller stubs pinned to it, BOTH CEREMONY_SELF_REF carriers rewritten, the event invoked recorded by name, and what reset removes afterwards. It reuses the drill rehearsal's fork-ref pattern rather than inventing a floating pin, including its rule against ever creating a tag-shaped branch on heavy-duty/ceremony. 4. THE EVIDENCE CONTRADICTION IS RESOLVED. "Write results into an issue in this repo" and "no probe touches ceremony's board" could not both be followed in a file where "this repo" reads as ceremony. The job now writes raw results into the PROBE repo, and a human carries the issue URL and run number to the ceremony issue. The probe workflow holds no credential and no code path that can write to ceremony, which is what makes the two rules compatible. Placement: the operational contract moves to docs/RUNNER-PROBES.md, with a short cross-link in drills/README.md beside the disposal rule it excepts — the exception stays visible where the dangerous habit lives, and neither document grows a second top-level heading. test/run.sh 28/28 on the updated branch; shellcheck 0.10.0, actionlint, self-ref, marker, vendored and changelog-armed clean. Refs #202
2026-08-05 13:40:28 +00:00
- `docs/RUNNER-PROBES.md` documents the standing runner-probe venue,
docs(drills): the standing runner-probe venue, and why it is not a drill (#202) @andres ruled option A (#5631): one standing never-archived repo. This is the runbook half. The distinction the document exists to make: a drill is disposable by design and ends with the builder archiving it. This venue is the opposite — it exists so that runner-only facts can be measured on demand, and archiving it defeats the purpose. That is not hypothetical: all three drill repos were archived correctly, by the rule, and each then had to be un-archived or replaced. The request came three times in two days across #192 and #198 and never became anything. What the runbook pins, all of it measured rather than asserted: * a probe MUST run as an Actions job under ${{ github.token }} — the same DELETE answers 500 there and 204 under a PAT, so a probe run any other way produces a confident wrong answer; * probe results are written into the forge, not left in a job log, because logs age out and #192's run 701 survived only because it wrote into an issue; * no probe touches ceremony's own board — the venue exists so the live board is not the fixture; * the three probes it already owes (#192's live label lift, #205's dispatch measurement, a 0.6.0 consumer exercise after #198). STANDING THE REPO UP IS THE OPERATOR'S STEP, and this is the part I could not do rather than the part I chose not to. Measured today with this identity: POST /api/v1/orgs/heavy-duty/repos -> 403 not allowed in organization POST /api/v1/user/repos -> 201 personal namespace only Same shape as the drill delete: a deliberate boundary, not a misconfiguration. The runbook says so, says not to retry it, and says not to work around it by using a personal namespace where the org's runner and secrets do not reach. test/run.sh 22/22, shellcheck 0.10.0, actionlint, self-ref all clean. Refs #202
2026-08-05 13:16:20 +00:00
`heavy-duty/ceremony-runner-probe` — the place runner-only facts are measured
on demand, ruled as option A by the operator (#202).
docs(runner-probes): its own document, an arming procedure, and the evidence boundary made consistent (#202) @codex-reviewer-andresmgsl's four gaps. 1. BRANCH UPDATED TO CURRENT MAIN. The commit's parent was pre-#204 dad99dd, so its green 22-file run did not contain the six test files and rules that landed with the sync. Merged main in — no rewrite — and re-verified against the 28-file suite the operator would actually receive. 2. WHO MAY RESET IT is now a section, and it says operator-owned until ruled otherwise, with content reset separated from archive/delete/admin. The access policy is set when the repo is created, which is the operator's step, so the two belong together. Flagged for @andres rather than assumed. 3. AN EXECUTABLE ARMING PROCEDURE replaces "install whatever the probe needs": fork ref and canonical SHA, caller stubs pinned to it, BOTH CEREMONY_SELF_REF carriers rewritten, the event invoked recorded by name, and what reset removes afterwards. It reuses the drill rehearsal's fork-ref pattern rather than inventing a floating pin, including its rule against ever creating a tag-shaped branch on heavy-duty/ceremony. 4. THE EVIDENCE CONTRADICTION IS RESOLVED. "Write results into an issue in this repo" and "no probe touches ceremony's board" could not both be followed in a file where "this repo" reads as ceremony. The job now writes raw results into the PROBE repo, and a human carries the issue URL and run number to the ceremony issue. The probe workflow holds no credential and no code path that can write to ceremony, which is what makes the two rules compatible. Placement: the operational contract moves to docs/RUNNER-PROBES.md, with a short cross-link in drills/README.md beside the disposal rule it excepts — the exception stays visible where the dangerous habit lives, and neither document grows a second top-level heading. test/run.sh 28/28 on the updated branch; shellcheck 0.10.0, actionlint, self-ref, marker, vendored and changelog-armed clean. Refs #202
2026-08-05 13:40:28 +00:00
- `drills/README.md` cross-links it beside the disposal rule, so the exception
is visible where the dangerous habit lives (#202).
docs(drills): the standing runner-probe venue, and why it is not a drill (#202) @andres ruled option A (#5631): one standing never-archived repo. This is the runbook half. The distinction the document exists to make: a drill is disposable by design and ends with the builder archiving it. This venue is the opposite — it exists so that runner-only facts can be measured on demand, and archiving it defeats the purpose. That is not hypothetical: all three drill repos were archived correctly, by the rule, and each then had to be un-archived or replaced. The request came three times in two days across #192 and #198 and never became anything. What the runbook pins, all of it measured rather than asserted: * a probe MUST run as an Actions job under ${{ github.token }} — the same DELETE answers 500 there and 204 under a PAT, so a probe run any other way produces a confident wrong answer; * probe results are written into the forge, not left in a job log, because logs age out and #192's run 701 survived only because it wrote into an issue; * no probe touches ceremony's own board — the venue exists so the live board is not the fixture; * the three probes it already owes (#192's live label lift, #205's dispatch measurement, a 0.6.0 consumer exercise after #198). STANDING THE REPO UP IS THE OPERATOR'S STEP, and this is the part I could not do rather than the part I chose not to. Measured today with this identity: POST /api/v1/orgs/heavy-duty/repos -> 403 not allowed in organization POST /api/v1/user/repos -> 201 personal namespace only Same shape as the drill delete: a deliberate boundary, not a misconfiguration. The runbook says so, says not to retry it, and says not to work around it by using a personal namespace where the org's runner and secrets do not reach. test/run.sh 22/22, shellcheck 0.10.0, actionlint, self-ref all clean. Refs #202
2026-08-05 13:16:20 +00:00
- The runbook states that the drill disposal rule does **not** apply to it.
Archiving it defeats its purpose, and that is exactly how the three existing
drill repos each became unavailable (#202).
- It records that a probe must run as an Actions job under the workflow token:
the same call answers 500 there and 204 under a PAT, so a probe run any other
way produces a confident wrong answer (#202).
- Creating the repo is recorded as the operator's step, measured rather than
assumed: a fleet identity gets 403 on org repo creation and 201 in its own
namespace (#202).
docs(runner-probes): its own document, an arming procedure, and the evidence boundary made consistent (#202) @codex-reviewer-andresmgsl's four gaps. 1. BRANCH UPDATED TO CURRENT MAIN. The commit's parent was pre-#204 dad99dd, so its green 22-file run did not contain the six test files and rules that landed with the sync. Merged main in — no rewrite — and re-verified against the 28-file suite the operator would actually receive. 2. WHO MAY RESET IT is now a section, and it says operator-owned until ruled otherwise, with content reset separated from archive/delete/admin. The access policy is set when the repo is created, which is the operator's step, so the two belong together. Flagged for @andres rather than assumed. 3. AN EXECUTABLE ARMING PROCEDURE replaces "install whatever the probe needs": fork ref and canonical SHA, caller stubs pinned to it, BOTH CEREMONY_SELF_REF carriers rewritten, the event invoked recorded by name, and what reset removes afterwards. It reuses the drill rehearsal's fork-ref pattern rather than inventing a floating pin, including its rule against ever creating a tag-shaped branch on heavy-duty/ceremony. 4. THE EVIDENCE CONTRADICTION IS RESOLVED. "Write results into an issue in this repo" and "no probe touches ceremony's board" could not both be followed in a file where "this repo" reads as ceremony. The job now writes raw results into the PROBE repo, and a human carries the issue URL and run number to the ceremony issue. The probe workflow holds no credential and no code path that can write to ceremony, which is what makes the two rules compatible. Placement: the operational contract moves to docs/RUNNER-PROBES.md, with a short cross-link in drills/README.md beside the disposal rule it excepts — the exception stays visible where the dangerous habit lives, and neither document grows a second top-level heading. test/run.sh 28/28 on the updated branch; shellcheck 0.10.0, actionlint, self-ref, marker, vendored and changelog-armed clean. Refs #202
2026-08-05 13:40:28 +00:00
docs(runner-probes): arming is two layers, because a commit cannot contain its own SHA (#202) @codex-reviewer-andresmgsl found that the procedure was not executable as written, and the reason is structural rather than a wording slip. The candidate's workflows carry `repository: heavy-duty/ceremony` beside `ref: ${{ env.CEREMONY_SELF_REF }}`, so arming must rewrite them. But rewriting CREATES A NEW COMMIT, and a commit cannot embed its own object ID. So a single-layer arming is self-referential: pin the callers to the pre-rewrite SHA and they load the UNARMED workflows; pin them to the post-rewrite SHA and you are asking that commit to contain itself. My step 3 asked for exactly that. Two layers, stated as a table because the distinction is the whole thing: candidate code SHA the immutable tree under test — actions/, lib/ armed workflow SHA a child commit whose workflows point at the fork and whose CEREMONY_SELF_REF is the candidate code SHA And callers pin by layer, because they are not the same thing: composite actions to the candidate code SHA, reusable workflows to the armed SHA, which is the only revision whose inner checkout is rewritten. The completeness check becomes a mechanical non-zero gate — `git grep` for executable `uses:`/`repository:` carriers over the ARMED tree, exiting non-zero on any hit — rather than "every remaining hit must be prose". A partial rewrite does not announce itself: it silently tests canonical main. The result issue records both SHAs, not one, or a later reader cannot tell which tree answered. test/run.sh 28/28; shellcheck 0.10.0 and changelog-armed clean. Refs #202
2026-08-05 13:54:44 +00:00
- It carries an executable two-layer arming procedure: an immutable candidate
code SHA and an armed workflow commit on top of it. A single layer is
self-referential — rewriting a workflow makes a new commit, and a commit
cannot contain its own object ID (#202).
- Callers are pinned by layer: composite actions to the candidate code SHA,
reusable workflows to the armed SHA, which is the only revision whose inner
checkout points at the fork (#202).
- The arming gate asserts what each carrier IS, not only that the old literal
docs(runner-probes): the generator survives a one-layer probe, callers carry full coordinates, one domain (#202) @codex-reviewer-andresmgsl drove the published commands again and found three. 1. THE GENERATOR ABORTED ON AN ABSENT CALLER CLASS — the same `set -e` + `git grep` no-match bug I had just fixed in the CHECKER, in the generator I wrote in the same commit and did not apply the lesson to. A probe that exercises one layer produced no manifest and no diagnostic. `|| true` on every extraction, plus an explicit count so ZERO ceremony callers refuses by name while workflow-only and action-only probes generate valid manifests. That count check was itself broken on its first write: `grep -E '\t…'` reads a literal `t`, not a tab, so it counted zero on a perfectly good manifest and refused it. Found by running it. 2. CALLERS CARRY THE COMPLETE COORDINATE. The manifest stored only the sha and the checker compared owner and suffix separately, so `<fork>/actions/WRONG-ONE@<right-sha>` passed. The manifest now records `<fork>/<path>@<sha>` and every kind is one exact comparison — which also removes the per-kind branch that made the omission possible. 3. GENERATOR AND CHECKER SHARE ONE DOMAIN. `actual` extracted every `uses:` while the generator manifested only ceremony patterns, so a legitimate `actions/checkout` was always an unrecognised carrier. Both are restricted to ceremony callers; a wrong OWNER is still caught because `wrong-owner/ceremony/...` is still a ceremony caller. And the stale fragment wording, which glm flagged and codex re-flagged: "both CEREMONY_SELF_REF values" -> "every". DRIVEN, all of it: generator: both / workflow-only / action-only -> valid manifests generator: zero ceremony callers -> refuses by name deletion, role swap x2, wrong owner, wrong sha, wrong path, deleted caller class, extra carrier -> all refuse armed control, third-party actions/checkout present -> passes test/run.sh 28/28; shellcheck 0.10.0 and changelog-armed clean. Refs #202
2026-08-05 15:07:11 +00:00
is gone: every `repository:` equals the fork, every `CEREMONY_SELF_REF` value
equal the candidate code SHA, and callers match the layer they belong to
(#202).
docs(runner-probes): enumerate the real carriers, spare the consumer checkouts, and make the snippet run (#202) @codex-reviewer-andresmgsl's three, all verified against current main before fixing. 1. THERE ARE THREE SELF-REF CARRIERS, NOT TWO — labels-sweep.yml:52, labels.yml:51, release.yml:132. My `[ "$n" -eq 2 ]` came from the pre-upstream tree, so it would have REJECTED a correctly armed candidate and told the operator to rewrite two of three, leaving one workflow pinned to the tag. The gate enumerates from the tree now, with the derivation commands beside the table so the list is re-checked rather than trusted. 2. NOT EVERY `repository:` BELONGS TO THE FORK. Three are `${{ github.repository }}` — labels-sweep.yml:69, labels.yml:92, release-exercise.yml:72 — and they fetch the CALLER's repository. My loop required every one to equal the fork, which would have rewritten the consumer checkouts and quietly changed what the probe exercises. Internal self-checkouts (four) are asserted to be the fork; consumer checkouts are asserted to stay dynamic. 3. EACH CHECK IS BOUND TO THE TREE IT IS ABOUT — `git -C "$armed"` for the carriers, `git -C "$probe"` for the callers, instead of depending on the operator's current directory. And `mapfile` rather than `git grep | while … fail`: the loop ran in a pipeline subshell, so `fail` exited the subshell and the gate carried on. Collect first, validate after, under a declared `set -euo pipefail`. And the snippet is now executable rather than illustrative: placeholders became positional parameters, so it parses, is shellcheck-clean, and runs. Driven against the unarmed tree it refuses with `CEREMONY_SELF_REF=0.6.0` — a tag rather than the candidate SHA, which is exactly the case it exists to catch. Publishing a gate that could not run would have been the same defect one level up. Branch updated from merged main (e236318). test/run.sh 28/28; shellcheck 0.10.0 and changelog-armed clean. Refs #202
2026-08-05 14:13:40 +00:00
- It enumerates the carriers from the tree rather than encoding a count, and
distinguishes ceremony's internal self-checkouts from the consumer checkouts
that must stay `${{ github.repository }}` (#202).
docs(runner-probes): the checker binds the manifest to its target, and the snippets lint clean standalone (#202) @codex-reviewer-andresmgsl, and he linted the published snippets DIRECTLY, which my "parse, lint clean" claim had never meant. 1. THE CHECKER DID NOT CHECK THE TARGET. It accepted <fork> <code-sha> <armed-sha> and used none of them — SC2034 on all three, which is the same defect the linter and the reviewer found independently. It proved only "tree equals manifest", so a manifest generated with the ARMED sha where the candidate belonged, against a tree rewritten to that same wrong value, passed. Wrong-but-consistent is exactly what this gate exists to reject. Each manifest `want` is now validated against the independently supplied target before the tree is compared to it. 2. ONE ORDER, NOT TWO. Step 2 said "commit the arming AND write the manifest" while the prose below correctly said to generate from the PRE-arming tree. The manifest enumerates the carriers that must CHANGE, so it has to see them before they do — generating afterwards enumerates rewritten rows and loses the canonical internal-checkout ones entirely. The generator's first parameter is <candidate-checkout> now, and says so. 3. THE SNIPPETS LINT CLEAN STANDALONE. SC2016 needed a scoped directive — and the first placement was itself invalid: SC1124, a directive may precede a complete command, not an individual case branch. The checker's mktemp gets a trap. Driven, the new controls: correct manifest + tree + target args passes wrong fork, manifest AND tree consistent refuses wrong candidate sha, consistent refuses armed sha where the candidate belongs refuses plus every earlier class still red, and both snippets ShellCheck-clean when extracted as an operator would copy them. test/run.sh 28/28; repository shellcheck 0.10.0 and changelog-armed clean. Refs #202
2026-08-05 15:22:01 +00:00
- Both published snippets are ShellCheck-clean when extracted and linted
directly, not merely as part of the repository sweep (#202).
- The checker validates the MANIFEST against the target it was given, so a
manifest that describes a wrong arming consistently — wrong fork, or the
armed SHA where the candidate belongs — refuses instead of matching a tree
rewritten to the same wrong value (#202).
- The manifest is generated from the PRE-arming tree, which is the only order
that enumerates the carriers that must change (#202).
- Both published snippets were driven against a constructed candidate/probe
pair: deletion, both role swaps, wrong owner, wrong
docs(runner-probes): the generator survives a one-layer probe, callers carry full coordinates, one domain (#202) @codex-reviewer-andresmgsl drove the published commands again and found three. 1. THE GENERATOR ABORTED ON AN ABSENT CALLER CLASS — the same `set -e` + `git grep` no-match bug I had just fixed in the CHECKER, in the generator I wrote in the same commit and did not apply the lesson to. A probe that exercises one layer produced no manifest and no diagnostic. `|| true` on every extraction, plus an explicit count so ZERO ceremony callers refuses by name while workflow-only and action-only probes generate valid manifests. That count check was itself broken on its first write: `grep -E '\t…'` reads a literal `t`, not a tab, so it counted zero on a perfectly good manifest and refused it. Found by running it. 2. CALLERS CARRY THE COMPLETE COORDINATE. The manifest stored only the sha and the checker compared owner and suffix separately, so `<fork>/actions/WRONG-ONE@<right-sha>` passed. The manifest now records `<fork>/<path>@<sha>` and every kind is one exact comparison — which also removes the per-kind branch that made the omission possible. 3. GENERATOR AND CHECKER SHARE ONE DOMAIN. `actual` extracted every `uses:` while the generator manifested only ceremony patterns, so a legitimate `actions/checkout` was always an unrecognised carrier. Both are restricted to ceremony callers; a wrong OWNER is still caught because `wrong-owner/ceremony/...` is still a ceremony caller. And the stale fragment wording, which glm flagged and codex re-flagged: "both CEREMONY_SELF_REF values" -> "every". DRIVEN, all of it: generator: both / workflow-only / action-only -> valid manifests generator: zero ceremony callers -> refuses by name deletion, role swap x2, wrong owner, wrong sha, wrong path, deleted caller class, extra carrier -> all refuse armed control, third-party actions/checkout present -> passes test/run.sh 28/28; shellcheck 0.10.0 and changelog-armed clean. Refs #202
2026-08-05 15:07:11 +00:00
SHA, wrong path, a deleted caller class and an extra carrier all refuse, and
the armed control passes (#202).
- The manifest records complete caller coordinates, so a path swapped under the
right owner and SHA is caught (#202).
- Generator and checker share one domain — ceremony callers — so a third-party
`actions/checkout` is neither manifested nor reported as unrecognised (#202).
docs(runner-probes): its own document, an arming procedure, and the evidence boundary made consistent (#202) @codex-reviewer-andresmgsl's four gaps. 1. BRANCH UPDATED TO CURRENT MAIN. The commit's parent was pre-#204 dad99dd, so its green 22-file run did not contain the six test files and rules that landed with the sync. Merged main in — no rewrite — and re-verified against the 28-file suite the operator would actually receive. 2. WHO MAY RESET IT is now a section, and it says operator-owned until ruled otherwise, with content reset separated from archive/delete/admin. The access policy is set when the repo is created, which is the operator's step, so the two belong together. Flagged for @andres rather than assumed. 3. AN EXECUTABLE ARMING PROCEDURE replaces "install whatever the probe needs": fork ref and canonical SHA, caller stubs pinned to it, BOTH CEREMONY_SELF_REF carriers rewritten, the event invoked recorded by name, and what reset removes afterwards. It reuses the drill rehearsal's fork-ref pattern rather than inventing a floating pin, including its rule against ever creating a tag-shaped branch on heavy-duty/ceremony. 4. THE EVIDENCE CONTRADICTION IS RESOLVED. "Write results into an issue in this repo" and "no probe touches ceremony's board" could not both be followed in a file where "this repo" reads as ceremony. The job now writes raw results into the PROBE repo, and a human carries the issue URL and run number to the ceremony issue. The probe workflow holds no credential and no code path that can write to ceremony, which is what makes the two rules compatible. Placement: the operational contract moves to docs/RUNNER-PROBES.md, with a short cross-link in drills/README.md beside the disposal rule it excepts — the exception stays visible where the dangerous habit lives, and neither document grows a second top-level heading. test/run.sh 28/28 on the updated branch; shellcheck 0.10.0, actionlint, self-ref, marker, vendored and changelog-armed clean. Refs #202
2026-08-05 13:40:28 +00:00
- Probe results are written to an issue in the probe repo and carried to the
ceremony issue by a human, so the probe holds no path that can write to the
live board (#202).