diff --git a/changelog.d/202.md b/changelog.d/202.md index ced3047..a7e03c7 100644 --- a/changelog.d/202.md +++ b/changelog.d/202.md @@ -33,8 +33,12 @@ equal the candidate code SHA, and callers match the layer they belong to (#202). -- It counts the `CEREMONY_SELF_REF` carriers as well as comparing them, which - is what catches one that vanished rather than being rewritten (#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). + +- The published snippet parses, lints clean and runs: driven against an unarmed + tree it refuses, naming the carrier (#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 diff --git a/docs/RUNNER-PROBES.md b/docs/RUNNER-PROBES.md index ed25ba4..e03758a 100644 --- a/docs/RUNNER-PROBES.md +++ b/docs/RUNNER-PROBES.md @@ -124,43 +124,76 @@ So: - reusable-workflow callers → `/ceremony/.github/workflows/@`, since that is the only revision whose inner checkout is rewritten. -4. **Gate the arming POSITIVELY, against the armed tree.** Absence of the old - literal is not proof of correct arming: a negative grep stays green if - `CEREMONY_SELF_REF` names a tag, the *armed* SHA or some other commit; if a - carrier was rewritten to the wrong fork; if an executable carrier lives - outside `.github`; or if a carrier simply disappeared - (@codex-reviewer-andresmgsl, #202 review). So assert what each carrier - **is**, not merely what it is not: +4. **Gate the arming POSITIVELY, and against the tree each check is about.** + Absence of the old literal proves nothing: a negative grep stays green if + `CEREMONY_SELF_REF` names a tag or the *armed* SHA, if a carrier went to the + wrong fork, or if a carrier vanished instead of being rewritten. So assert + what each carrier **is** — and enumerate them, because the set is a property + of the current tree rather than a number to remember + (@codex-reviewer-andresmgsl, #202 review). + + **The carriers on `main` today.** Re-derive this list each time rather than + trusting it; workflows come and go: ```sh - fork=/ceremony ; code_sha= ; armed_sha= - fail() { echo "arming incomplete: $*" >&2; exit 1; } - - # every self-checkout points at the fork - for v in $(git grep -hoP '(?<=repository:\s)\S+' -- .github); do - [ "$v" = "$fork" ] || fail "repository: $v" - done - # both CEREMONY_SELF_REF carriers name the CANDIDATE CODE sha - n=0 - for v in $(git grep -hoP '(?<=CEREMONY_SELF_REF:\s)"?\K[^"]+' -- .github); do - [ "$v" = "$code_sha" ] || fail "CEREMONY_SELF_REF: $v"; n=$((n+1)) - done - [ "$n" -eq 2 ] || fail "expected 2 CEREMONY_SELF_REF carriers, found $n" - # and in the PROBE repo: workflows pin the armed sha, actions the code sha - git grep -hoP '(?<=uses:\s)\S+' -- .github | while read -r u; do - case "$u" in - */.github/workflows/*) [ "${u##*@}" = "$armed_sha" ] || fail "workflow caller $u" ;; - */actions/*) [ "${u##*@}" = "$code_sha" ] || fail "action caller $u" ;; - esac - done - # the negative check stays, as a cheap extra rather than as the proof - ! git grep -qE '(uses:|repository:)[[:space:]]*heavy-duty/ceremony' -- .github \ - || fail "canonical coordinate still present" + git grep -n 'CEREMONY_SELF_REF:' -- .github/workflows + git grep -n 'repository:' -- .github/workflows ``` - **A count, not just a comparison** — `n -eq 2` is what catches a carrier - that vanished rather than being rewritten, which a per-value loop alone - cannot see. + | carrier | on current `main` | armed value | + |---|---|---| + | `CEREMONY_SELF_REF` | `labels-sweep.yml`, `labels.yml`, `release.yml` | the **candidate code** SHA | + | internal self-checkout | `labels-sweep.yml`, `labels.yml`, `release.yml` ×2 | `/ceremony` | + | **consumer** checkout | `labels-sweep.yml`, `labels.yml`, `release-exercise.yml` | **unchanged** — `${{ github.repository }}` | + + That third row is the one an over-broad rewrite destroys. Those checkouts + fetch the **caller's** repository; pointing them at the fork changes what + the probe exercises into something else entirely. + + ```sh + #!/usr/bin/env bash + # check-arming + set -euo pipefail + armed="$1"; probe="$2"; fork="$3"; code_sha="$4"; armed_sha="$5" + fail() { echo "arming incomplete: $*" >&2; exit 1; } + + # (a) every self-ref carrier names the CANDIDATE CODE sha — enumerated, so a + # carrier that vanished is a failure rather than one fewer loop pass. + mapfile -t refs < <(git -C "$armed" grep -hoP '(?<=CEREMONY_SELF_REF: ")[^"]+' \ + -- .github/workflows) + [ "${#refs[@]}" -gt 0 ] || fail "no CEREMONY_SELF_REF carriers found" + for v in "${refs[@]}"; do [ "$v" = "$code_sha" ] || fail "CEREMONY_SELF_REF=$v"; done + + # (b) internal checkouts point at the fork; consumer checkouts stay dynamic. + mapfile -t repos < <(git -C "$armed" grep -hoP '(?<=repository: ).*' \ + -- .github/workflows) + for v in "${repos[@]}"; do + case "$v" in + '${{ github.repository }}') ;; # the caller's repo — must NOT change + "$fork") ;; # an internal self-checkout, armed + *) fail "repository: $v" ;; + esac + done + + # (c) in the PROBE repo: workflow callers pin the armed sha, action callers + # the code sha. + mapfile -t uses < <(git -C "$probe" grep -hoP '(?<=uses: ).*' -- .github) + for u in "${uses[@]}"; do + case "$u" in + */.github/workflows/*@*) [ "${u##*@}" = "$armed_sha" ] || fail "workflow caller $u" ;; + */actions/*@*) [ "${u##*@}" = "$code_sha" ] || fail "action caller $u" ;; + esac + done + + # (d) the negative check, as a cheap extra rather than as the proof. + ! git -C "$armed" grep -qE '(uses:|repository:)[[:space:]]*heavy-duty/ceremony' \ + -- .github || fail "canonical coordinate still present" + ``` + + **`mapfile`, not a pipeline.** `git grep | while … fail` runs the loop in a + subshell, so `fail` exits *that* subshell and the gate continues. Collect + first, validate after — and run under `set -euo pipefail`, which the snippet + declares rather than assumes. 5. **Invoke the probe by the event it is about**, and record which: a `workflow_dispatch`, or the real board event under test. A probe that fires