docs(runner-probes): the checker binds the manifest to its target, and the snippets lint clean standalone (#202)
All checks were successful
CI / test (pull_request) Successful in 3m8s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 7s
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, and he linted the published snippets DIRECTLY,
which my "parse, lint clean" claim had never meant.

1. THE CHECKER DID NOT CHECK THE TARGET. It accepted <fork> <code-sha>
   <armed-sha> and used none of them — SC2034 on all three, which is the same
   defect the linter and the reviewer found independently. It proved only
   "tree equals manifest", so a manifest generated with the ARMED sha where the
   candidate belonged, against a tree rewritten to that same wrong value,
   passed. Wrong-but-consistent is exactly what this gate exists to reject.

   Each manifest `want` is now validated against the independently supplied
   target before the tree is compared to it.

2. ONE ORDER, NOT TWO. Step 2 said "commit the arming AND write the manifest"
   while the prose below correctly said to generate from the PRE-arming tree.
   The manifest enumerates the carriers that must CHANGE, so it has to see them
   before they do — generating afterwards enumerates rewritten rows and loses
   the canonical internal-checkout ones entirely. The generator's first
   parameter is <candidate-checkout> now, and says so.

3. THE SNIPPETS LINT CLEAN STANDALONE. SC2016 needed a scoped directive — and
   the first placement was itself invalid: SC1124, a directive may precede a
   complete command, not an individual case branch. The checker's mktemp gets
   a trap.

Driven, the new controls:

  correct manifest + tree + target args        passes
  wrong fork, manifest AND tree consistent     refuses
  wrong candidate sha, consistent              refuses
  armed sha where the candidate belongs        refuses

plus every earlier class still red, and both snippets ShellCheck-clean when
extracted as an operator would copy them.

test/run.sh 28/28; repository shellcheck 0.10.0 and changelog-armed clean.

Refs #202
This commit is contained in:
cluade-reviewer-andresmgsl 2026-08-05 15:22:01 +00:00
parent 20f4b287f7
commit 6874e76c04
2 changed files with 46 additions and 8 deletions

View file

@ -37,8 +37,19 @@
distinguishes ceremony's internal self-checkouts from the consumer checkouts distinguishes ceremony's internal self-checkouts from the consumer checkouts
that must stay `${{ github.repository }}` (#202). that must stay `${{ github.repository }}` (#202).
- Both published snippets parse, lint clean and were driven against a - Both published snippets are ShellCheck-clean when extracted and linted
constructed armed/probe pair: deletion, both role swaps, wrong owner, wrong 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 SHA, wrong path, a deleted caller class and an extra carrier all refuse, and
the armed control passes (#202). the armed control passes (#202).

View file

@ -113,7 +113,11 @@ So:
the **candidate code SHA**. Never create a branch on the **candidate code SHA**. Never create a branch on
`heavy-duty/ceremony` named like a tag: it shadows that tag for every `heavy-duty/ceremony` named like a tag: it shadows that tag for every
consumer until somebody remembers to delete it. 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: fork branch rewrite, for **every** carrier the manifest below enumerates:
ceremony's own internal `repository:` checkouts → `<identity>/ceremony`, and ceremony's own internal `repository:` checkouts → `<identity>/ceremony`, and
**every** `CEREMONY_SELF_REF` value → the **candidate code SHA** from step 1. **every** `CEREMONY_SELF_REF` value → the **candidate code SHA** from step 1.
@ -152,7 +156,10 @@ So:
```sh ```sh
#!/usr/bin/env bash #!/usr/bin/env bash
# write-manifest <armed-checkout> <probe-checkout> <fork> <code-sha> <armed-sha> # write-manifest <candidate-checkout> <probe-checkout> <fork> <code-sha> <armed-sha>
#
# 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: # `|| 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 # 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 # produced no file and no diagnostic when a probe exercised only one layer
# (@codex-reviewer-andresmgsl). A probe need not use both. # (@codex-reviewer-andresmgsl). A probe need not use both.
set -euo pipefail 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 \ git -C "$armed" grep -n 'CEREMONY_SELF_REF:' -- .github/workflows \
| cut -d: -f1,2 | sed "s|$|\tself_ref\t$code_sha|" || true | 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; } [ "$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 Then arm — rewrite and commit — and check the result against it:
carriers that must change — then arm, then check:
```sh ```sh
#!/usr/bin/env bash #!/usr/bin/env bash
@ -200,7 +207,27 @@ So:
[ "$(grep -cE '(workflow|action)_caller' "$manifest" || true)" -gt 0 ] \ [ "$(grep -cE '(workflow|action)_caller' "$manifest" || true)" -gt 0 ] \
|| fail "manifest names no ceremony callers — it cannot prove an arming" || 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 \ 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 | sed -E 's/^([^:]+):([0-9]+):.*CEREMONY_SELF_REF: "([^"]*)".*/\1:\2\tself_ref\t\3/' || true