From 20f4b287f79336a7124654e4febf7e92a978517e Mon Sep 17 00:00:00 2001 From: cluade-reviewer-andresmgsl Date: Wed, 5 Aug 2026 15:07:11 +0000 Subject: [PATCH] docs(runner-probes): the generator survives a one-layer probe, callers carry full coordinates, one domain (#202) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @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 `/actions/WRONG-ONE@` passed. The manifest now records `/@` 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 --- changelog.d/202.md | 13 ++++++++--- docs/RUNNER-PROBES.md | 53 +++++++++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/changelog.d/202.md b/changelog.d/202.md index 3768949..b60a343 100644 --- a/changelog.d/202.md +++ b/changelog.d/202.md @@ -29,7 +29,7 @@ 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, both `CEREMONY_SELF_REF` values + 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). @@ -38,8 +38,15 @@ that must stay `${{ github.repository }}` (#202). - Both published snippets parse, lint clean and were driven against a - constructed armed/probe pair: all six failure classes refuse and the armed - control passes (#202). + constructed armed/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 diff --git a/docs/RUNNER-PROBES.md b/docs/RUNNER-PROBES.md index 476b271..32b4392 100644 --- a/docs/RUNNER-PROBES.md +++ b/docs/RUNNER-PROBES.md @@ -153,20 +153,33 @@ So: ```sh #!/usr/bin/env bash # write-manifest + # + # `|| true` on every extraction, for the same reason the checker needs it: + # git grep exits 1 on no-match and `set -e` would abort BEFORE the manifest + # is written — silently, which is how the first version of this generator + # produced no file and no diagnostic when a probe exercised only one layer + # (@codex-reviewer-andresmgsl). A probe need not use both. set -euo pipefail armed="$1"; probe="$2"; fork="$3"; code_sha="$4"; armed_sha="$5" { git -C "$armed" grep -n 'CEREMONY_SELF_REF:' -- .github/workflows \ - | cut -d: -f1,2 | sed "s|\$|\tself_ref\t$code_sha|" + | cut -d: -f1,2 | sed "s|$|\tself_ref\t$code_sha|" || true git -C "$armed" grep -n 'repository: heavy-duty/ceremony' -- .github/workflows \ - | cut -d: -f1,2 | sed "s|\$|\tinternal_repo\t$fork|" + | cut -d: -f1,2 | sed "s|$|\tinternal_repo\t$fork|" || true git -C "$armed" grep -n 'repository: ${{ github.repository }}' -- .github/workflows \ - | cut -d: -f1,2 | sed 's|$|\tconsumer_repo\t${{ github.repository }}|' - git -C "$probe" grep -n 'uses: .*/.github/workflows/' -- .github \ - | cut -d: -f1,2 | sed "s|\$|\tworkflow_caller\t$armed_sha|" - git -C "$probe" grep -n 'uses: .*/actions/' -- .github \ - | cut -d: -f1,2 | sed "s|\$|\taction_caller\t$code_sha|" + | cut -d: -f1,2 | sed 's|$|\tconsumer_repo\t${{ github.repository }}|' || true + # Callers record the COMPLETE expected coordinate, not just the sha: the + # path is as rewritable as the owner, and a manifest that stores only the + # suffix cannot notice `…/actions/wrong-one@`. + git -C "$probe" grep -nE 'uses:[[:space:]]*[^[:space:]]*/ceremony/\.github/workflows/' -- .github \ + | sed -E "s|^([^:]+):([0-9]+):.*/ceremony/(\.github/workflows/[^@[:space:]]+)@.*|\\1:\\2\\tworkflow_caller\\t$fork/\\3@$armed_sha|" || true + git -C "$probe" grep -nE 'uses:[[:space:]]*[^[:space:]]*/ceremony/actions/' -- .github \ + | sed -E "s|^([^:]+):([0-9]+):.*/ceremony/(actions/[^@[:space:]]+)@.*|\\1:\\2\\taction_caller\\t$fork/\\3@$code_sha|" || true } | sort >manifest.tsv + + # Zero ceremony callers is a refusal by name; one layer only is fine. + callers="$(grep -cE '(workflow|action)_caller' manifest.tsv || true)" + [ "$callers" -gt 0 ] || { echo "manifest: no ceremony callers found in $probe" >&2; exit 1; } ``` Run it against the **pre-arming** tree — that is what enumerates the @@ -184,13 +197,21 @@ So: # carrier class that vanished ENTIRELY produced silence instead of a # refusal. Silence is the worst of the three outcomes; the comparison below # is what must report it. + [ "$(grep -cE '(workflow|action)_caller' "$manifest" || true)" -gt 0 ] \ + || fail "manifest names no ceremony callers — it cannot prove an arming" + actual="$(mktemp)" { git -C "$armed" grep -nP '(?<=CEREMONY_SELF_REF: ")[^"]+' -- .github/workflows \ | sed -E 's/^([^:]+):([0-9]+):.*CEREMONY_SELF_REF: "([^"]*)".*/\1:\2\tself_ref\t\3/' || true git -C "$armed" grep -nE 'repository: .+' -- .github/workflows \ | sed -E 's|^([^:]+):([0-9]+):[[:space:]]*repository:[[:space:]]*(.*)$|\1:\2\t__repo__\t\3|' || true - git -C "$probe" grep -nE 'uses: .+' -- .github \ + # Only CEREMONY callers, matching the generator's domain exactly — a + # third-party `actions/checkout` is not this gate's business, and + # extracting it here while the generator ignores it made every probe fail + # as an "unrecognised carrier" (@codex-reviewer-andresmgsl). A wrong OWNER + # is still caught: `wrong-owner/ceremony/...` matches this pattern. + git -C "$probe" grep -nE 'uses:[[:space:]]*[^[:space:]]*/ceremony/' -- .github \ | sed -E 's|^([^:]+):([0-9]+):[[:space:]]*-?[[:space:]]*uses:[[:space:]]*(.*)$|\1:\2\t__uses__\t\3|' || true } | sort >"$actual" @@ -205,17 +226,11 @@ So: have="$(awk -F'\t' -v l="$loc" '$1==l && $2=="__uses__"{print $3}' "$actual")" ;; esac [ -n "$have" ] || fail "carrier vanished: $loc ($kind)" - case "$kind" in - self_ref|internal_repo|consumer_repo) - [ "$have" = "$want" ] || fail "$loc ($kind): expected '$want', found '$have'" ;; - workflow_caller) - [ "$have" = "$fork/.github/workflows/${have##*/.github/workflows/}" ] \ - || fail "$loc: caller owner is not $fork — '$have'" - [ "${have##*@}" = "$armed_sha" ] || fail "$loc: workflow caller not armed sha" ;; - action_caller) - case "$have" in "$fork/actions/"*) ;; *) fail "$loc: caller owner is not $fork — '$have'" ;; esac - [ "${have##*@}" = "$code_sha" ] || fail "$loc: action caller not code sha" ;; - esac + # ONE comparison for every kind: the manifest already carries the complete + # expected value, so owner, path AND sha are checked at once. Checking the + # owner and the sha separately let `…/actions/wrong-one@` + # through (@codex-reviewer-andresmgsl). + [ "$have" = "$want" ] || fail "$loc ($kind): expected '$want', found '$have'" done <"$manifest" # and nothing UNRECOGNISED: every uses:/repository: in the trees must appear