diff --git a/changelog.d/202.md b/changelog.d/202.md index b60a343..9f27adf 100644 --- a/changelog.d/202.md +++ b/changelog.d/202.md @@ -37,8 +37,19 @@ distinguishes ceremony's internal self-checkouts from the consumer checkouts that must stay `${{ github.repository }}` (#202). -- Both published snippets parse, lint clean and were driven against a - constructed armed/probe pair: deletion, both role swaps, wrong owner, wrong +- 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). diff --git a/docs/RUNNER-PROBES.md b/docs/RUNNER-PROBES.md index 32b4392..4f69922 100644 --- a/docs/RUNNER-PROBES.md +++ b/docs/RUNNER-PROBES.md @@ -113,7 +113,11 @@ So: the **candidate code SHA**. Never create a branch on `heavy-duty/ceremony` named like a tag: it shadows that tag for every consumer until somebody remembers to delete it. -2. **Commit the arming on top of it, and write the manifest.** In that same +2. **Write the manifest FIRST, from the pre-arming tree, then commit the + arming.** The manifest enumerates the carriers *that must change*, so it is + generated before they do — running it afterwards would enumerate + already-rewritten rows and lose the canonical internal-checkout ones + entirely (@codex-reviewer-andresmgsl, #202 review). In that same fork branch rewrite, for **every** carrier the manifest below enumerates: ceremony's own internal `repository:` checkouts → `/ceremony`, and **every** `CEREMONY_SELF_REF` value → the **candidate code SHA** from step 1. @@ -152,7 +156,10 @@ So: ```sh #!/usr/bin/env bash - # write-manifest + # write-manifest + # + # Run against the PRE-ARMING tree and the UNPINNED probe: this records what + # each carrier must BECOME, so it has to see them before they change. # # `|| 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 @@ -160,7 +167,8 @@ So: # 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" + armed="$1"; probe="$2"; fork="$3"; code_sha="$4"; armed_sha="$5" # $1 = pre-arming + # shellcheck disable=SC2016 # `${{ github.repository }}` is literal YAML, not a shell expansion { git -C "$armed" grep -n 'CEREMONY_SELF_REF:' -- .github/workflows \ | cut -d: -f1,2 | sed "s|$|\tself_ref\t$code_sha|" || true @@ -182,8 +190,7 @@ So: [ "$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 - carriers that must change — then arm, then check: + Then arm — rewrite and commit — and check the result against it: ```sh #!/usr/bin/env bash @@ -200,7 +207,27 @@ So: [ "$(grep -cE '(workflow|action)_caller' "$manifest" || true)" -gt 0 ] \ || fail "manifest names no ceremony callers — it cannot prove an arming" - actual="$(mktemp)" + # THE MANIFEST ITSELF IS CHECKED AGAINST THE TARGET, not trusted. Comparing + # only tree-vs-manifest proves consistency, and a manifest generated with the + # armed SHA where the candidate SHA belonged — or with the wrong fork — + # describes a WRONG arming perfectly. The tree would then match it and the + # gate would pass (@codex-reviewer-andresmgsl, #202 review). + # shellcheck disable=SC2016 # `${{ github.repository }}` below is literal YAML + while IFS=$'\t' read -r loc kind want; do + case "$kind" in + self_ref) [ "$want" = "$code_sha" ] || fail "manifest $loc: self_ref should be the CANDIDATE sha" ;; + internal_repo) [ "$want" = "$fork" ] || fail "manifest $loc: internal repo should be $fork" ;; + consumer_repo) [ "$want" = '${{ github.repository }}' ] \ + || fail "manifest $loc: consumer checkout must stay dynamic" ;; + workflow_caller) [ "$want" = "$fork/${want#*/ceremony/}" ] || fail "manifest $loc: caller owner" + [ "${want##*@}" = "$armed_sha" ] || fail "manifest $loc: workflow caller should be the ARMED sha" ;; + action_caller) [ "$want" = "$fork/${want#*/ceremony/}" ] || fail "manifest $loc: caller owner" + [ "${want##*@}" = "$code_sha" ] || fail "manifest $loc: action caller should be the CANDIDATE sha" ;; + *) fail "manifest $loc: unknown kind '$kind'" ;; + esac + done <"$manifest" + + actual="$(mktemp)"; trap 'rm -f "$actual"' EXIT { 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