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