diff --git a/changelog.d/202.md b/changelog.d/202.md index edd9953..7ff9cc3 100644 --- a/changelog.d/202.md +++ b/changelog.d/202.md @@ -1,11 +1,76 @@ +### Added + +- `docs/RUNNER-PROBES.md` documents the standing runner-probe venue, + `heavy-duty/ceremony-runner-probe` — the place runner-only facts are measured + on demand, ruled as option A by the operator (#202). + +- `drills/README.md` cross-links it beside the disposal rule, so the exception + is visible where the dangerous habit lives (#202). + +- 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). + +- 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 + 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). + +- 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). + +- 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 + 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). + +- 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). + ### Changed -- `docs/RUNNER-PROBES.md` records the venue's first delivered drills: the +- `docs/RUNNER-PROBES.md` records the venue's first delivered drills — the #192 asymmetry re-observed on demand under the workflow token, the dispatch route's 204 under both identities, and #215's boundary finding — each with the probe-issue URL it is recorded in (#202). - Two venue lessons join the runbook where the next probe author will look: findings must be written to issues because the venue's log route 404s for - non-admin reads, and report templates must never interpolate `${{ … }}` - expressions (#202). + non-admin reads, and report content sent to the forge must never contain a + credential expression or value (#202). diff --git a/docs/RUNNER-PROBES.md b/docs/RUNNER-PROBES.md index 6878278..64c7d7d 100644 --- a/docs/RUNNER-PROBES.md +++ b/docs/RUNNER-PROBES.md @@ -345,13 +345,22 @@ memory. afterward — the failure observable in the set, not merely a status — then `PUT` full-set clear → **200**, set actually empty. Record: [probe issue #1](https://forgejo.heavyduty.builders/heavy-duty/ceremony-runner-probe/issues/1) - (runs 1 and 4). + (run 1) and + [probe issue #2](https://forgejo.heavyduty.builders/heavy-duty/ceremony-runner-probe/issues/2) + (run 4 — the clean independent repeat after the redaction incident below). - **ceremony#205** — DELIVERED with a correction to the line above's - premise. The dispatch route answers **204** to a valid - `{"ref":"refs/heads/" | "", "inputs":{…}}` under both a PAT - and the workflow token; the earlier opaque `500` came from a bare - unresolvable ref or an unknown/unparseable workflow — the diagnostic !213 - ships now names this. The `GET /actions/workflows` listing still 404s. + premise. The dispatch route answers **204** to a valid body carrying the + bare resolvable ref `main` — under the workflow token + ([ceremony#205 comment #6263](https://forgejo.heavyduty.builders/heavy-duty/ceremony/issues/205#issuecomment-6263), + run 504, and again as + [probe issue #4](https://forgejo.heavyduty.builders/heavy-duty/ceremony-runner-probe/issues/4), + run 6) and under a PAT + ([probe issue #5](https://forgejo.heavyduty.builders/heavy-duty/ceremony-runner-probe/issues/5), + run 7, and ceremony run 459). The earlier opaque `500` came from a bare + UNRESOLVABLE ref or an unknown/unparseable workflow — the diagnostic !213 + ships now names this; a fully-qualified bad ref gets a clean 404 instead. + The `GET /actions/workflows` listing still 404s. Claims here are limited to + what those runs measured. - **ceremony#215** — the discriminator drill: REST-body `inputs` DO reach a top-level dispatched workflow, both contexts (`inputs.*` and `github.event.inputs.*`), both identities. What loses the @@ -368,7 +377,11 @@ look: - **Rule 4 is load-bearing on this instance**: the probe repo's web log route 404s for non-admin reads, and a log-only observation (run 2) was lost where issue-written ones were not. -- **Never let a report template interpolate `${{ … }}`** — an escaped - `${{ github.token }}` in a comment body was expanded by the runner into the - recorded text (the run's ephemeral token; redacted in place). Build report - strings from plain shell variables only. +- **Report content sent to the forge must never contain a credential + expression OR a credential value** — moving a secret from `${{ … }}` syntax + into a shell variable does not make it safe to emit. Name identities in + literal prose ("the workflow token"), allowlist the fields a report emits, + and treat anything else as unsendable. The incident this teaches from: an + escaped `${{ github.token }}` in a comment template was expanded by the + runner into the recorded text (the run's ephemeral token); it was redacted + in place and the drill repeated clean (probe issue #2, run 4).