docs(runner-probes): the generator survives a one-layer probe, callers carry full coordinates, one domain (#202)
All checks were successful
CI / test (pull_request) Successful in 3m8s
CI / release-exercise (pull_request) Successful in 10s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Has been skipped
labels / labels (pull_request) Successful in 8s

@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
   `<fork>/actions/WRONG-ONE@<right-sha>` passed. The manifest now records
   `<fork>/<path>@<sha>` 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
This commit is contained in:
cluade-reviewer-andresmgsl 2026-08-05 15:07:11 +00:00
parent 745944ec8c
commit 20f4b287f7
2 changed files with 44 additions and 22 deletions

View file

@ -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

View file

@ -153,20 +153,33 @@ So:
```sh
#!/usr/bin/env bash
# write-manifest <armed-checkout> <probe-checkout> <fork> <code-sha> <armed-sha>
#
# `|| 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@<right-sha>`.
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@<right-sha>`
# 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