From 76ad6c8622c4e5e6dd4cd9263fc77bf2eeacf432 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 13:37:05 +0200 Subject: [PATCH 01/26] feat(ci): tier=reuse for a PR with a verified signed receipt (Refs #5797) Squashed from #5798 for the REBASE merge queue. Co-Authored-By: Claude Sonnet 5.5 --- .flow009/batcher.pub | 1 + .github/workflows/ci.yml | 56 +++++ .github/workflows/guards-nightly.yml | 2 + ci/sections.yml | 13 +- docs/roadmaps/entries/PMAT-5797.yaml | 18 ++ docs/roadmaps/roadmap.yaml | 18 ++ scripts/check_receipt_gate.sh | 308 +++++++++++++++++++++++++++ scripts/check_runner_labels.sh | 2 +- scripts/ci/receipt_ok_arg.sh | 39 ++++ scripts/ci_receipt_gate.sh | 87 ++++++++ scripts/ci_test_tier.sh | 23 +- scripts/receipt_toolchain_id.sh | 25 +++ 12 files changed, 588 insertions(+), 4 deletions(-) create mode 100644 .flow009/batcher.pub create mode 100644 docs/roadmaps/entries/PMAT-5797.yaml create mode 100755 scripts/check_receipt_gate.sh create mode 100755 scripts/ci/receipt_ok_arg.sh create mode 100755 scripts/ci_receipt_gate.sh create mode 100755 scripts/receipt_toolchain_id.sh diff --git a/.flow009/batcher.pub b/.flow009/batcher.pub new file mode 100644 index 0000000000..22337055f9 --- /dev/null +++ b/.flow009/batcher.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINna9SBgvdA0Te1LhyTa6jXbDu8SgJmiDDPhHkLmljw7 flow009-batcher diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bf828a5b4..312406d4e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -439,10 +439,65 @@ jobs: # The workspace tests, one shard per job (nextest hash partition N/12 plus the # explicit runner's round-robin N/12), in parallel on the X64 clean-room pool. # The section body is ci/sections.yml `workspace-test-shard`, unchanged. + # #5798: the receipt judge. A PR cannot forge a receipt: this job checks out the BASE only + # and fetches the PR merge commit as data (a --no-checkout worktree that is read, never + # executed), on a dedicated self-hosted ephemeral runner (label receipt-judge) that no PR-code job ever shares, and runs the + # verifier under `env -i` with absolute tools. What it does NOT cover: a PR that edits the CI + # files or the judge itself. merge_group ignores --receipt-ok but runs only the QUICK tier + # (full only on tag/release, scripts/ci_test_tier.sh merge_group case), from the PR's own + # CI files, so it does not re-judge such an edit with a trusted copy; CI-edit review is the + # only backstop. scripts/check_receipt_gate.sh (W16-W32) turns RED if + # this job gains a step that runs PR-tree code or checks the PR out as code. + # Fail closed: the job runs only once the repo variable RECEIPT_JUDGE_RUNNER_READY is 'true' (set + # after the label has a runner). Until then it is skipped, the shard gets ok=0 and the PR runs the + # tier its diff selects, so a judge with no runner never queues and never blocks a PR. + # It always exits 0; the shard job reads `ok` as 1 only on a literal 1. + receipt-judge: + if: github.event_name == 'pull_request' && vars.RECEIPT_JUDGE_RUNNER_READY == 'true' + runs-on: [self-hosted, Linux, X64, receipt-judge] + timeout-minutes: 10 + permissions: + contents: read + outputs: + ok: ${{ steps.judge.outputs.ok }} + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.base_ref }} + persist-credentials: false + - name: Judge the receipt in the PR body + id: judge + env: + GH_TOKEN: ${{ github.token }} + PR_BODY: ${{ github.event.pull_request.body }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + export GIT_NO_REPLACE_OBJECTS=1 + ok=0 + printf '%s' "${PR_BODY:-}" > "$RUNNER_TEMP/pr-body.md" + export GIT_TERMINAL_PROMPT=0 GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=http.https://github.com/.extraheader + GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')" + export GIT_CONFIG_VALUE_0 + if git fetch -q --no-tags --depth=1 origin "refs/pull/${PR_NUMBER}/merge" && + [ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] && + unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0 GH_TOKEN && + git -c core.hooksPath=/dev/null worktree add -q --no-checkout --detach "$RUNNER_TEMP/prtree" FETCH_HEAD && + git show HEAD:scripts/ci/receipt_ok_arg.sh > "$RUNNER_TEMP/rok.sh"; then + # --no-checkout: the PR tree has no files, so no PR executable can sit in the verifier's + # cwd, and no filter or hook runs. The wrapper is read from the base object database. + ok="$(cd "$RUNNER_TEMP/prtree" && env -i HOME="$HOME" PATH=/usr/local/bin:/usr/bin:/bin /usr/bin/bash "$RUNNER_TEMP/rok.sh" "$RUNNER_TEMP/pr-body.md" "$GITHUB_WORKSPACE" "${GITHUB_SHA}")" || ok=0 + fi + [ "$ok" = 1 ] || ok=0 + printf 'ok=%s\n' "$ok" >> "$GITHUB_OUTPUT" + workspace-test-shard: # same lanes as x86-main: clean-room for pull_request/push, mq for a merge_group entry runs-on: ${{ github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","X64","mq"]') || fromJSON('["self-hosted","Linux","X64","clean-room"]') }} timeout-minutes: 180 + # The judge is skipped off a pull_request and fails closed: !cancelled() runs the shard + # whatever the judge did, and RECEIPT_OK is 1 only on the judge's literal 1. + needs: [receipt-judge] + if: ${{ !cancelled() }} strategy: fail-fast: false matrix: @@ -451,6 +506,7 @@ jobs: outputs: results: ${{ steps.run.outputs.results }} env: + RECEIPT_OK: ${{ needs.receipt-judge.outputs.ok || '0' }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} FAT_VARS_JSON: ${{ toJSON(vars) }} steps: diff --git a/.github/workflows/guards-nightly.yml b/.github/workflows/guards-nightly.yml index af5fe6b1d5..c0c5cb802d 100644 --- a/.github/workflows/guards-nightly.yml +++ b/.github/workflows/guards-nightly.yml @@ -174,6 +174,8 @@ jobs: # ───────────────────────────────────────────────────────────────────── - name: "Test-tier decision case table (BSE-17): quick/full/reuse, drift is ENV" run: bash scripts/ci_test_tier.sh --self-test + - name: Receipt gate case table and mutants (#5797, LAB) + run: setsid --wait bash scripts/check_receipt_gate.sh - name: Fleet-cells gate case table and mutants (#5064, LAB) run: setsid --wait bash scripts/release/fleet_cells_gate.sh --self-test - name: compute row zero-tests case table and mutants (#5048, LAB) diff --git a/ci/sections.yml b/ci/sections.yml index 4cc17744ef..24e543c9db 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -377,9 +377,20 @@ jobs: env: GH_TOKEN: ${{ github.token }} MG_BASE_SHA: ${{ github.event.merge_group.base_sha }} + PR_BODY: ${{ github.event.pull_request.body }} run: | set -euo pipefail args=(--event "${GITHUB_EVENT_NAME}") + # #5797: a signed batch receipt in the PR body (signature, tree hash == this + # merge tree, toolchain hash) lets the PR run check/fmt/clippy instead of the + # suite. The verdict is NOT computed here: the `receipt-judge` job in ci.yml + # computes it in a job that never checks out or runs the PR tree (it checks out + # the base, reads the PR merge commit as data), and the shard job hands it in as + # RECEIPT_OK. Anything but a literal 1 -- unset, 0, a skipped or failed judge -- is + # 0, and 0 leaves the tier exactly as it was. Only a pull_request can carry one. + rok=0 + if [ "${GITHUB_EVENT_NAME}" = pull_request ] && [ "${RECEIPT_OK:-0}" = 1 ]; then rok=1; fi + args+=(--receipt-ok "${rok:-0}") # PMAT-1098 67-E2: the queue and a push to main re-derive their own # diff from HEAD's FIRST PARENT (HEAD^1..HEAD is the PR's diff on the # new base). actions/checkout is shallow, so that parent is not in the @@ -1073,7 +1084,7 @@ jobs: python3 scripts/ci/sigma_executed.py nextest --kind "quick tree readers" --junit "$sig/quick-tree.junit.xml" --list-json "$sig/quick-tree.list.json" || rc=1 exit "$rc" - name: "Quick tier: cargo check --workspace (over-the-cap integration, BSE-17 rule (i))" - if: steps.tier.outputs.tier == 'quick' && steps.tier.outputs.check_workspace == '1' && matrix.shard == matrix.shards + if: ((steps.tier.outputs.tier == 'quick' && steps.tier.outputs.check_workspace == '1') || (steps.tier.outputs.tier == 'reuse' && steps.tier.outputs.cite == 'receipt')) && matrix.shard == matrix.shards timeout-minutes: 20 env: REASON: ${{ steps.tier.outputs.reason }} diff --git a/docs/roadmaps/entries/PMAT-5797.yaml b/docs/roadmaps/entries/PMAT-5797.yaml new file mode 100644 index 0000000000..64ece10939 --- /dev/null +++ b/docs/roadmaps/entries/PMAT-5797.yaml @@ -0,0 +1,18 @@ +- id: PMAT-5797 + github_issue: 5797 + item_type: task + title: 'PR gate: signed batch receipt verify (sig, tree, toolchain) as tier=reuse extension' + status: planned + priority: high + assigned_to: null + created: 2026-10-20T14:55:28Z + updated: 2026-10-20T14:55:28Z + spec: null + acceptance_criteria: [] + phases: [] + subtasks: [] + estimated_effort: null + labels: + - kind:code + notes: null + epic: 5510 diff --git a/docs/roadmaps/roadmap.yaml b/docs/roadmaps/roadmap.yaml index ef47935f92..1c242dc8bc 100644 --- a/docs/roadmaps/roadmap.yaml +++ b/docs/roadmaps/roadmap.yaml @@ -25340,6 +25340,24 @@ Not done, pending the ruling: moving the 31 fixture files to an obviously synthe - kind:code notes: null epic: 5510 +- id: PMAT-5797 + github_issue: 5797 + item_type: task + title: 'PR gate: signed batch receipt verify (sig, tree, toolchain) as tier=reuse extension' + status: planned + priority: high + assigned_to: null + created: 2026-10-20T14:55:28Z + updated: 2026-10-20T14:55:28Z + spec: null + acceptance_criteria: [] + phases: [] + subtasks: [] + estimated_effort: null + labels: + - kind:code + notes: null + epic: 5510 - id: PMAT-5817 github_issue: 5817 item_type: task diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh new file mode 100755 index 0000000000..4dc7f54c1c --- /dev/null +++ b/scripts/check_receipt_gate.sh @@ -0,0 +1,308 @@ +#!/usr/bin/env bash +# check_receipt_gate.sh — case table for scripts/ci_receipt_gate.sh (PMAT-5797, #5797). +# The PR gate may skip the full test suite only for a batch whose signed receipt verifies: +# S the signature is valid for the pinned batcher public key, +# T the receipt's tree hash is the PR merge tree, +# C the receipt's toolchain hash is this checkout's pinned toolchain. +# Every row builds a throwaway repo and a throwaway key, so the table is hermetic. +# A row that expects a reason must see exactly that reason: a verifier that fails for +# any other cause does not satisfy it. `bash scripts/check_receipt_gate.sh` exits 0 +# only when every row holds. +set -u +HERE=$(cd "$(dirname "$0")" && pwd) +V="$HERE/ci_receipt_gate.sh" +TID="$HERE/receipt_toolchain_id.sh" +NS="flow009-receipt" +td=$(mktemp -d) || exit 2 +trap 'rm -rf "${td:?}"' EXIT +fail=0 +pass=0 + +command -v ssh-keygen >/dev/null 2>&1 || { echo "ENV: ssh-keygen missing"; exit 2; } +command -v jq >/dev/null 2>&1 || { echo "ENV: jq missing"; exit 2; } + +ssh-keygen -q -t ed25519 -N '' -C batcher -f "$td/key" >/dev/null +ssh-keygen -q -t ed25519 -N '' -C other -f "$td/other" >/dev/null +printf '# batcher public key (test)\n%s\n' "$(cut -d' ' -f1,2 "$td/key.pub")" >"$td/pub" + +repo="$td/repo" +git init -q "$repo" +git -C "$repo" config user.email t@example.invalid +git -C "$repo" config user.name t +printf '[toolchain]\nchannel = "stable"\n' >"$repo/rust-toolchain.toml" +printf 'x\n' >"$repo/a.txt" +git -C "$repo" add -A +git -C "$repo" commit -q -m "base" >/dev/null 2>&1 +printf 'y\n' >>"$repo/a.txt" +git -C "$repo" commit -q -am "batch (PMAT-1)" >/dev/null 2>&1 +tree=$(git -C "$repo" rev-parse 'HEAD^{tree}') +head=$(git -C "$repo" rev-parse HEAD) +tc=$(cd "$repo" && bash "$TID") + +# receipt TREE TOOLCHAIN -> unsigned receipt JSON on stdout +receipt() { + jq -n -S -c --arg tree "$1" --arg tc "$2" --arg sha "$head" ' + {schema:"flow009.receipt/1",repo:"r",batch:"b",base_sha:$sha,tree:$tree, + commits:[{ticket:"PMAT-1",patch_id:"p",sha:$sha}],gate_results:{tests:"pass",coverage:"pass"}, + toolchain:$tc,manifest:"m",steps:"s",host:"h",started:"t0",finished:"t1",sig:""}' +} +# sign KEY RECEIPT_JSON_FILE -> signed JSON (canonical bytes: jq -S -c del(.sig) plus LF) +sign() { + jq -S -c 'del(.sig)' "$2" >"$td/canon" + rm -f "$td/canon.sig" + ssh-keygen -q -Y sign -f "$1" -n "$NS" "$td/canon" >/dev/null 2>&1 + jq -S -c --rawfile sig "$td/canon.sig" '.sig = $sig' "$2" +} +# body FILE JSON -> a PR body holding the one fenced receipt block +body() { printf 'Batch.\n\n```flow009.receipt/1\n%s\n```\n' "$2" >"$1"; } + +# row NAME EXPECT BODY_FILE [PUB] — EXPECT is "ok" or the exact reason +row() { + local name=$1 want=$2 bf=$3 pub=${4:-$td/pub} out + out=$(cd "$repo" && bash "$V" verify "$bf" "$pub" "$head" 2>&1 | head -n1) + if [ "$want" = ok ]; then + [ "$out" = "RECEIPT_OK=1" ] && { pass=$((pass + 1)); echo "ok $name"; return; } + else + [ "$out" = "RECEIPT_OK=0 reason=$want" ] && { pass=$((pass + 1)); echo "ok $name"; return; } + fi + fail=$((fail + 1)) + echo "FAIL $name: want '$want' got '$out'" +} + +good=$(receipt "$tree" "$tc"); printf '%s\n' "$good" >"$td/good.json" +signed=$(sign "$td/key" "$td/good.json") + +body "$td/b-good" "$signed" +row "G1 valid receipt: signature, tree and toolchain all hold" ok "$td/b-good" + +# S: signature +body "$td/b-s1" "$(printf '%s' "$signed" | jq -S -c '.batch = "tampered"')" +row "S1 a field changed after signing -> bad-signature" bad-signature "$td/b-s1" +printf '%s\n' "$good" >"$td/g2.json" +body "$td/b-s2" "$(sign "$td/other" "$td/g2.json")" +row "S2 signed by a key that is not the pinned batcher key -> bad-signature" bad-signature "$td/b-s2" +body "$td/b-s3" "$good" +row "S3 unsigned receipt (empty sig) -> bad-signature" bad-signature "$td/b-s3" +printf '# placeholder, no key yet\n' >"$td/pub-empty" +row "S4 placeholder public key fails closed -> placeholder-pubkey" placeholder-pubkey "$td/b-good" "$td/pub-empty" +printf 'no receipt here\n' >"$td/b-none" +row "S5 no fenced receipt block -> fenced-block" fenced-block "$td/b-none" + +# T: tree hash == merge tree +receipt "0000000000000000000000000000000000000000" "$tc" >"$td/t1.json" +body "$td/b-t1" "$(sign "$td/key" "$td/t1.json")" +row "T1 correctly signed receipt for another tree -> tree-mismatch" tree-mismatch "$td/b-t1" + +# C: toolchain hash +receipt "$tree" "$(printf 'not-this-toolchain' | sha256sum | cut -d' ' -f1)" >"$td/c1.json" +body "$td/b-c1" "$(sign "$td/key" "$td/c1.json")" +row "C1 correctly signed receipt for another toolchain -> toolchain-mismatch" toolchain-mismatch "$td/b-c1" +printf '[toolchain]\nchannel = "nightly"\n' >"$repo/rust-toolchain.toml" +row "C2 the checkout's pinned toolchain changed after signing -> toolchain-mismatch" toolchain-mismatch "$td/b-good" +printf '[toolchain]\nchannel = "stable"\n' >"$repo/rust-toolchain.toml" + +# V: the signed verdict. A validly signed receipt must also say the batch passed. +receipt "$tree" "$tc" | jq -S -c '.gate_results.tests = "fail"' >"$td/v1.json" +body "$td/b-v1" "$(sign "$td/key" "$td/v1.json")" +row "V1 correctly signed receipt whose tests failed -> not-pass" not-pass "$td/b-v1" +receipt "$tree" "$tc" | jq -S -c 'del(.gate_results)' >"$td/v2.json" +body "$td/b-v2" "$(sign "$td/key" "$td/v2.json")" +row "V2 correctly signed receipt with no gate_results -> not-pass" not-pass "$td/b-v2" +receipt "$tree" "$tc" | jq -S -c '.gate_results = {coverage:"pass"}' >"$td/v3.json" +body "$td/b-v3" "$(sign "$td/key" "$td/v3.json")" +row "V3 correctly signed receipt with no tests verdict -> not-pass" not-pass "$td/b-v3" + +# W: the workflow wiring. scripts/ci/receipt_ok_arg.sh prints the ONE word the tier step +# hands to ci_test_tier.sh as --receipt-ok. Anything but a verified receipt must print 0, +# and 0 must leave the PR on the quick/full tier, never on reuse. The judge (verifier, +# toolchain recipe, pinned key) comes from the trusted clone of the base, never from the PR's working tree. +W="$HERE/ci/receipt_ok_arg.sh" +T="$HERE/ci_test_tier.sh" +printf 'crates/aprender-core/src/lib.rs\n' >"$td/touched.txt" +# mkbase NAME PUBFILE|- -> a fresh repo $td/NAME whose HEAD holds the judge files (and the key, +# unless "-"): the shape of the trusted clone the workflow makes before any PR code runs. +mkbase() { + local d="$td/$1" pubf=$2 files="scripts/ci_receipt_gate.sh scripts/receipt_toolchain_id.sh" f + rm -rf "${d:?}"; git init -q "$d" + mkdir -p "$d/scripts" "$d/.flow009" + cp "$V" "$d/scripts/ci_receipt_gate.sh"; cp "$TID" "$d/scripts/receipt_toolchain_id.sh" + cp "$repo/rust-toolchain.toml" "$d/rust-toolchain.toml"; files="$files rust-toolchain.toml" + if [ "$pubf" != - ]; then cp "$pubf" "$d/.flow009/batcher.pub"; files="$files .flow009/batcher.pub"; fi + for f in $files; do git -C "$d" add "$f"; done + git -C "$d" -c user.email=t@example.invalid -c user.name=t commit -q -m judge +} +BASE="$td/trust-main" +mkbase trust-main "$td/pub" +mkbase trust-placeholder "$td/pub-empty" +git init -q "$td/trust-empty" +git -C "$td/trust-empty" -c user.email=t@example.invalid -c user.name=t commit -q --allow-empty -m none + # bashrs disable-next-line=SEC010 +arg() { (cd "$repo" && bash "${3:-$W}" "$1" "${2:-$BASE}" "$head" 2>/dev/null | head -n1); } +wrow() { + local name=$1 want=$2 got=$3 + if [ "$got" = "$want" ]; then pass=$((pass + 1)); echo "ok $name"; else fail=$((fail + 1)); echo "FAIL $name: want '$want' got '$got'"; fi +} +wrow "W1 verified receipt -> --receipt-ok 1" 1 "$(arg "$td/b-good")" +wrow "W2 no receipt in the PR body -> 0 (full gate)" 0 "$(arg "$td/b-none")" +wrow "W3 tampered receipt -> 0" 0 "$(arg "$td/b-s1")" +wrow "W4 receipt for another tree -> 0" 0 "$(arg "$td/b-t1")" +wrow "W5 placeholder pubkey at the base -> 0" 0 "$(arg "$td/b-good" "$td/trust-placeholder")" +wrow "W6 PR body file missing -> 0" 0 "$(arg "$td/no-such-body")" +wrow "W7 base has no judge files (bootstrap) -> 0" 0 "$(arg "$td/b-good" "$td/trust-empty")" +wrow "W11 base ref does not exist -> 0" 0 "$(arg "$td/b-good" "$td/nope")" +wrow "W12 signed receipt whose tests failed -> 0" 0 "$(arg "$td/b-v1")" +wrow "W13 signed receipt with no gate_results -> 0" 0 "$(arg "$td/b-v2")" +# W14: the forged-receipt repro (wsweep, #5798). The PR swaps in its own key and signs its +# own receipt. The base's key is the only trust root, so this must print 0. +mkdir -p "$repo/.flow009"; cp "$td/other.pub" "$repo/.flow009/batcher.pub" +printf '%s\n' "$good" >"$td/f.json" +body "$td/b-forged" "$(sign "$td/other" "$td/f.json")" +wrow "W14 FORGED: receipt signed by a key the PR planted in its own tree -> 0" 0 "$(arg "$td/b-forged")" +# W15/W10 mutants: a wrapper that trusts the PR tree's key, or prints 1 regardless, is caught. +# mutate FILE FROM TO OUT -> OUT is FILE with the literal FROM replaced by TO; non-zero if FROM is absent +mutate() { + local c; c=$(cat "$1"; printf x); c=${c%x} + case "$c" in *"$2"*) ;; *) return 1 ;; esac + printf '%s' "${c//"$2"/$3}" >"$4" +} +if mutate "$W" '"$j/.flow009/batcher.pub"' '.flow009/batcher.pub' "$td/mut-k.sh"; then + wrow "W15 MUTANT: wrapper reading the key from the PR tree is caught by the forged row" 1 "$(arg "$td/b-forged" "$BASE" "$td/mut-k.sh")" +else fail=$((fail + 1)); echo "FAIL W15: mutant did not apply"; fi +if mutate "$W" 'if [ "$first" = "RECEIPT_OK=1" ]; then' 'if true; then' "$td/mut-w.sh"; then + wrow "W10 MUTANT: a wrapper that always prints 1 is caught by the no-receipt row" 1 "$(arg "$td/b-none" "$BASE" "$td/mut-w.sh")" +else fail=$((fail + 1)); echo "FAIL W10: mutant did not apply to $W"; fi +# W16-W20: the judge is its own job and no PR-tree code can run before it reads. ci.yml's +# `receipt-judge` job checks out the BASE (ref: github.base_ref), fetches the PR merge commit as +# a data worktree, and runs no script from anywhere but the base checkout; the shard job needs it +# and takes its verdict as RECEIPT_OK; the tier step in ci/sections.yml only reads that env. Each +# defect below must turn judge_ok RED: a PR checkout as code, a PR-tree script step, a step that +# plants PATH or the env, a shard job that does not wait for the judge, a tier step that runs code. +CI="$HERE/../.github/workflows/ci.yml" +SEC="$HERE/../ci/sections.yml" +judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy + local job code shard tier + job=$(awk '/^ receipt-judge:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$1") + [ -n "$job" ] || return 1 + code=$(printf '%s\n' "$job" | grep -v -E '^[[:space:]]*#') + case "$code" in *'ref: ${{ github.base_ref }}'*) ;; *) return 1 ;; esac + [ "$(printf '%s\n' "$code" | grep -c 'uses:')" -eq 1 ] || return 1 + printf '%s\n' "$code" | grep -q 'uses: actions/checkout@' || return 1 + printf '%s\n' "$code" | grep -q -E 'GITHUB_PATH|GITHUB_ENV|head_ref|pull_request\.head|github\.sha *$' && return 1 + [ "$(printf '%s\n' "$code" | grep -c '^ - ')" -eq 2 ] || return 1 + printf '%s\n' "$code" | grep -E 'scripts/' | grep -v -E 'git show HEAD:scripts/ci/receipt_ok_arg\.sh' | grep -q . && return 1 + case "$code" in *'runs-on: [self-hosted, Linux, X64, receipt-judge]'*) ;; *) return 1 ;; esac + # a judge with no runner must be skipped, never queued: the job is gated on the readiness variable + printf '%s\n' "$job" | grep -q -F "vars.RECEIPT_JUDGE_RUNNER_READY == 'true'" || return 1 + case "$code" in *'env -i HOME='*'/usr/bin/bash "$RUNNER_TEMP/rok.sh"'*) ;; *) return 1 ;; esac + case "$code" in *'worktree add -q --no-checkout'*) ;; *) return 1 ;; esac + case "$code" in *'core.hooksPath=/dev/null'*) ;; *) return 1 ;; esac + case "$code" in *'unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0'*) ;; *) return 1 ;; esac + shard=$(awk '/^ workspace-test-shard:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$1") + case "$shard" in *'needs: [receipt-judge]'*) ;; *) return 1 ;; esac + case "$shard" in *'RECEIPT_OK: ${{ needs.receipt-judge.outputs.ok'*) ;; *) return 1 ;; esac + tier=$(awk '/id: tier$/{on=1} on{print} /--receipt-ok/{if(on)exit}' "$2" | grep -v -E '^[[:space:]]*#') + case "$tier" in *'"${RECEIPT_OK:-0}" = 1'*) ;; *) return 1 ;; esac + printf '%s\n' "$tier" | grep -E 'scripts/|git (fetch|clone)' | grep -q . && return 1 + return 0 +} +if judge_ok "$CI" "$SEC"; then wrow "W16 receipt judge is its own job: base checkout only, no PR-tree code, shard waits for it" ok ok +else wrow "W16 receipt judge is its own job: base checkout only, no PR-tree code, shard waits for it" ok bad; fi +CHK=' - uses: actions/checkout@v7 + with: + ref: ${{ github.base_ref }}' +wmut() { # LABEL FILE(ci|sec) FROM TO + local src=$CI; [ "$2" = sec ] && src=$SEC + if mutate "$src" "$3" "$4" "$td/mut.yml"; then + if [ "$2" = sec ]; then judge_ok "$CI" "$td/mut.yml" && r=missed || r=caught + else judge_ok "$td/mut.yml" "$SEC" && r=missed || r=caught; fi + wrow "$1" caught "$r" + else fail=$((fail + 1)); echo "FAIL ${1%% *}: mutant did not apply"; fi +} +wmut "W17 MUTANT: the judge job checking out the PR merge as code is caught" ci 'ref: ${{ github.base_ref }}' 'ref: ${{ github.sha }}' +wmut "W18 MUTANT: a PR-tree script step before the judge is caught" ci "$CHK" ' - run: bash scripts/ci/pre.sh +'"$CHK" +wmut "W19 MUTANT: an earlier step planting PATH is caught" ci "$CHK" ' - run: echo "$PWD/bin" >> "$GITHUB_PATH" +'"$CHK" +wmut "W27 MUTANT: a judge whose PR worktree is a full checkout is caught" ci 'worktree add -q --no-checkout' 'worktree add -q' +wmut "W28 MUTANT: a judge that leaves the git token env set is caught" ci 'unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0 GH_TOKEN' 'true' +wmut "W33 MUTANT: a judge not gated on the runner-readiness variable (it would queue forever with no runner) is caught" ci " && vars.RECEIPT_JUDGE_RUNNER_READY == 'true'" '' +wmut "W34 MUTANT: the judge back on a GitHub-hosted image is caught" ci 'runs-on: [self-hosted, Linux, X64, receipt-judge]' 'runs-on: ubuntu-latest' +wmut "W20 MUTANT: a shard job that does not wait for the judge is caught" ci 'needs: [receipt-judge]' 'needs: []' +wmut "W20b MUTANT: a tier step that runs PR-tree code is caught" sec 'rok=0 + if [ "${GITHUB_EVENT_NAME}" = pull_request ]' 'rok=0 + bash scripts/ci/pre.sh + if [ "${GITHUB_EVENT_NAME}" = pull_request ]' +# W21/W22: a refs/replace entry in the PR repo must not let a receipt for another tree pass. The +# receipt is signed for the empty tree; the replace ref makes HEAD read as a commit with that tree. +git -C "$repo" -c user.email=t@example.invalid -c user.name=t checkout -q -- . 2>/dev/null +rep=$(git -C "$repo" commit-tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 -m fake) +git -C "$repo" update-ref "refs/replace/$head" "$rep" +# the faked HEAD has an empty tree, so its toolchain id has no toml part +tcE=$({ (cd "$BASE" && rustc -vV); printf 'image=none\n'; } | sha256sum | cut -d' ' -f1) +receipt 4b825dc642cb6eb9a060e54bf8d69288fbee4904 "$tcE" >"$td/r.json" +body "$td/b-repl" "$(sign "$td/key" "$td/r.json")" +wrow "W21 refs/replace on the PR repo cannot make another tree pass -> 0" 0 "$(arg "$td/b-repl")" +if mutate "$W" 'export GIT_NO_REPLACE_OBJECTS=1' ':' "$td/mut-r.sh" && sed -i 's/ GIT_NO_REPLACE_OBJECTS=1 RECEIPT/ RECEIPT/' "$td/mut-r.sh"; then + wrow "W22 MUTANT: a wrapper that honours replace refs is caught by W21's row" 1 "$(arg "$td/b-repl" "$BASE" "$td/mut-r.sh")" +else fail=$((fail + 1)); echo "FAIL W22: mutant did not apply"; fi +git -C "$repo" update-ref -d "refs/replace/$head" +rm -rf "${repo:?}/.flow009" +# W23-W26: the rustc/credential hardening (Opus, #5798). A PR toolchain file with an absolute +# `path =` must not reach rustc; the verifier gets no GH_TOKEN/GITHUB_OUTPUT; rustc runs in the +# base's dir. repo2's receipt is signed over a toolchain id computed the honest way, so only the +# path refusal stands between it and ok=1. +rm -rf "${td:?}/repo2"; cp -r "$repo" "$td/repo2" +printf '[toolchain]\npath = "/nonexistent/fake-toolchain"\n' >"$td/repo2/rust-toolchain.toml" +git -C "$td/repo2" -c user.email=t@example.invalid -c user.name=t commit -q -am "path override" +tree2=$(git -C "$td/repo2" rev-parse 'HEAD^{tree}'); head2=$(git -C "$td/repo2" rev-parse HEAD) +tc2=$({ cat "$td/repo2/rust-toolchain.toml"; (cd "$BASE" && rustc -vV); printf 'image=none\n'; } | sha256sum | cut -d' ' -f1) +receipt "$tree2" "$tc2" >"$td/p.json"; body "$td/b-path" "$(sign "$td/key" "$td/p.json")" +arg2() { (cd "$td/repo2" && bash "${3:-$W}" "$1" "${2:-$BASE}" "$head2" 2>/dev/null | head -n1); } +wrow "W23 a PR toolchain file with path= is refused -> 0" 0 "$(arg2 "$td/b-path")" +if mutate "$TID" 'if printf' 'if false && printf' "$td/mut-tid.sh"; then + mkbase trust-mut "$td/pub"; cp "$td/mut-tid.sh" "$td/trust-mut/scripts/receipt_toolchain_id.sh" + git -C "$td/trust-mut" -c user.email=t@example.invalid -c user.name=t commit -q -am mut + wrow "W24 MUTANT: a toolchain id that accepts path= is caught by W23's row" 1 "$(arg2 "$td/b-path" "$td/trust-mut")" +else fail=$((fail + 1)); echo "FAIL W24: mutant did not apply"; fi +# probe judge: records what the verifier can see (token/output vars, and the rustc dir's listing) +mkbase trust-probe "$td/pub" +cat >"$td/trust-probe/scripts/ci_receipt_gate.sh" </dev/null | tr -d '\\n')" >"$td/probe.out" +printf 'RECEIPT_OK=0\\n' +PROBE +git -C "$td/trust-probe" -c user.email=t@example.invalid -c user.name=t commit -q -am probe + # bashrs disable-next-line=SEC010 +probe() { rm -f "${td:?}/probe.out"; (cd "$repo" && GH_TOKEN=secret GITHUB_OUTPUT=/tmp/x PROBEFILE="$td/probe.out" bash "${1:-$W}" "$td/b-good" "$td/trust-probe" "$head" >/dev/null 2>&1); sed -E 's#\|[^|]*/rc\|#|RC|#' "$td/probe.out" 2>/dev/null; } +wrow "W25 the verifier sees no token or output file, and runs rustc in the base's dir" "|RC|rust-toolchain.toml" "$(probe)" +if mutate "$W" 'first=$(env -i ' 'first=$(env ' "$td/mut-e.sh"; then + wrow "W26 MUTANT: a wrapper without env -i leaves GH_TOKEN in the verifier's env is caught by W25" "secret/tmp/x|RC|rust-toolchain.toml" "$(probe "$td/mut-e.sh")" +else fail=$((fail + 1)); echo "FAIL W26: mutant did not apply"; fi +# W29-W32: forgery by the environment (Opus, #5798). An always-0 ssh-keygen early in PATH, or a +# BASH_ENV file defining ssh-keygen(), would turn a tampered receipt (b-s1: valid in every other +# field) into ok=1. The real wrapper + gate must print 0; a mutant with the defences removed prints 1. +mkdir -p "$td/evil"; printf '#!/bin/sh\nexit 0\n' >"$td/evil/ssh-keygen"; chmod +x "$td/evil/ssh-keygen" +printf 'ssh-keygen() { return 0; }\nexport -f ssh-keygen\n' >"$td/evil.env" +forge() { # WRAPPER BASEDIR -> wrapper output under both forgeries (PATH first, then BASH_ENV) + local a b + # bashrs disable-next-line=SEC010 + a=$(cd "$repo" && PATH="$td/evil:$PATH" bash "$1" "$td/b-s1" "$2" "$head" 2>/dev/null | head -n1) + b=$(cd "$repo" && BASH_ENV="$td/evil.env" bash "$1" "$td/b-s1" "$2" "$head" 2>/dev/null | head -n1) + printf '%s%s' "$a" "$b" +} +wrow "W29 a planted ssh-keygen in PATH or a BASH_ENV function cannot forge ok -> 00" 00 "$(forge "$W" "$BASE")" +wrow "W30 sanity: the tampered receipt really is bad without the forgery -> 0" 0 "$(arg "$td/b-s1")" +if mutate "$W" 'first=$(env -i ' 'first=$(env ' "$td/mut-f.sh" && sed -i 's# PATH=/usr/local/bin:/usr/bin:/bin GIT_NO# GIT_NO#' "$td/mut-f.sh"; then + mkbase trust-forge "$td/pub" + sed -i -e 's#^PATH=/usr/local/bin.*#:#' -e 's#^unset BASH_ENV ENV#:#' -e 's#^SSHKEYGEN=.*#SSHKEYGEN=ssh-keygen#' -e 's#\[ -x "\$SSHKEYGEN" \]#command -v "$SSHKEYGEN" >/dev/null#' "$td/trust-forge/scripts/ci_receipt_gate.sh" + git -C "$td/trust-forge" -c user.email=t@example.invalid -c user.name=t commit -q -am forge + wrow "W31 MUTANT: no env -i, no fixed PATH, bare ssh-keygen -> the PATH forgery gets through" 1 "$(forge "$td/mut-f.sh" "$td/trust-forge" | cut -c1)" + wrow "W32 MUTANT: same, the BASH_ENV forgery gets through" 1 "$(forge "$td/mut-f.sh" "$td/trust-forge" | cut -c2)" +else fail=$((fail + 1)); echo "FAIL W31/W32: mutant did not apply"; fi +tier_of() { (cd "$HERE/.." && bash "$T" --event pull_request --diff-from "$td/touched.txt" --receipt-ok "$1" 2>/dev/null | head -n1); } +t0=$(tier_of 0); case "$t0" in tier=quick|tier=full) t0=notreuse ;; esac +wrow "W8 --receipt-ok 0 on a core diff runs quick or full, never reuse" notreuse "$t0" +wrow "W9 --receipt-ok 1 on the same diff is reuse" tier=reuse "$(tier_of 1)" + +echo "receipt gate rows: $pass passed, $fail failed" +[ "$fail" -eq 0 ] diff --git a/scripts/check_runner_labels.sh b/scripts/check_runner_labels.sh index f765cfd06d..603585bed5 100755 --- a/scripts/check_runner_labels.sh +++ b/scripts/check_runner_labels.sh @@ -24,7 +24,7 @@ cd "$(dirname "$0")/.." # review-light (#4729): one runner on intel-clean-room-15, same unit and isolation as the # clean-room pool, deliberately WITHOUT clean-room so build jobs cannot queue in front of # pr-review-quorum `present` (a seconds-long check). A one-box label, like perf-solo. -DISCRIM='clean-room|cuda|gpu|rtx4090|ada|blackwell|gb10|apple-silicon|m4|perf-solo|mini-builder|review-light' +DISCRIM='clean-room|cuda|gpu|rtx4090|ada|blackwell|gb10|apple-silicon|m4|perf-solo|mini-builder|review-light|receipt-judge' fail=0 while IFS=: read -r file line sel; do diff --git a/scripts/ci/receipt_ok_arg.sh b/scripts/ci/receipt_ok_arg.sh new file mode 100755 index 0000000000..1f57494d43 --- /dev/null +++ b/scripts/ci/receipt_ok_arg.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# receipt_ok_arg.sh BODY_FILE TRUST_DIR PR_HEAD_SHA (PMAT-5797, #5797) +# Prints ONE word for the tier step's `--receipt-ok`: 1 only when the receipt in the PR body +# verified, 0 in every other case (no receipt, a bad one, a missing file, a crashed verifier, +# a base that has no judge yet). Always exits 0: 0 sends the PR to the full gate. +# +# This does not cover a PR that edits the CI files or this judge: merge_group ignores +# --receipt-ok but runs only the quick tier (full on tag/release), from the PR's own CI files, +# so it does not re-judge such an edit with a trusted copy. CI-edit review is the only backstop. +# +# A PR cannot edit its own judge (the #5793 pattern): the verifier, the toolchain-id recipe +# and the pinned batcher public key are read from HEAD of TRUST_DIR, the base-branch checkout +# of a job that never runs PR-tree code, never from the working +# tree. The working tree supplies only what is being judged: HEAD^{tree} and +# rust-toolchain.toml. The caller must itself run THIS script from TRUST_DIR too +# (`git -C TRUST_DIR show HEAD:scripts/ci/receipt_ok_arg.sh`). Replace refs are off for every +# git call here, so a PR cannot make its own HEAD^{tree} read as another tree. +# Cases: scripts/check_receipt_gate.sh (rows W1-W34). +set -u +export GIT_NO_REPLACE_OBJECTS=1 +unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES +body=${1:-}; trust=${2:-}; sha=${3:-} +[ -n "$trust" ] && [ -d "$trust" ] && [ -n "$sha" ] || { printf '0\n'; exit 0; } +j=$(mktemp -d) || { printf '0\n'; exit 0; } +trap 'rm -rf "${j:?}"' EXIT +tg() { GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null git -C "$trust" "$@"; } +tg rev-parse --verify -q 'HEAD^{commit}' >/dev/null 2>&1 || { printf '0\n'; exit 0; } +tg archive HEAD scripts/ci_receipt_gate.sh scripts/receipt_toolchain_id.sh .flow009/batcher.pub 2>/dev/null \ + | tar -x -C "$j" 2>/dev/null || { printf '0\n'; exit 0; } +# rustc runs in a dir holding the BASE's rust-toolchain.toml, never in the PR cwd (a `path=` +# override there would make `rustc -vV` run PR-chosen code). No toml at the base: an empty dir. +mkdir -p "$j/rc" && { tg show HEAD:rust-toolchain.toml >"$j/rc/rust-toolchain.toml" 2>/dev/null || rm -f "${j:?}/rc/rust-toolchain.toml"; } +export RECEIPT_RUSTC_CWD="$j/rc" +# The verifier never sees the job's credentials or its output/env/path files. +# env -i: no PATH entry, BASH_ENV function, RUSTUP_*/GIT_* variable or credential survives. +first=$(env -i HOME="${HOME:-/nonexistent}" ${CARGO_HOME:+CARGO_HOME="$CARGO_HOME"} ${RUSTUP_HOME:+RUSTUP_HOME="$RUSTUP_HOME"} \ + PATH=/usr/local/bin:/usr/bin:/bin GIT_NO_REPLACE_OBJECTS=1 RECEIPT_RUSTC_CWD="$RECEIPT_RUSTC_CWD" \ + /usr/bin/bash "$j/scripts/ci_receipt_gate.sh" verify "$body" "$j/.flow009/batcher.pub" "$sha" 2>/dev/null | head -n1) || first="" +if [ "$first" = "RECEIPT_OK=1" ]; then printf '1\n'; else printf '0\n'; fi diff --git a/scripts/ci_receipt_gate.sh b/scripts/ci_receipt_gate.sh new file mode 100755 index 0000000000..ac5d48f1e1 --- /dev/null +++ b/scripts/ci_receipt_gate.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# ci_receipt_gate.sh verify BODY_FILE PUB_FILE PR_HEAD_SHA (PMAT-5797, #5797) +# +# The PR gate may run check/fmt/clippy instead of the full suite only for a batch whose +# signed receipt verifies. Three checks decide it, in this order: +# signature the armored ssh-keygen signature over the canonical bytes +# (`"$JQ" -S -c 'del(.sig)'` plus LF, namespace below) verifies against the +# single pinned batcher public key in PUB_FILE +# tree the receipt's tree == HEAD^{tree}, the PR merge tree (run inside the +# checked-out merge commit) +# toolchain the receipt's toolchain == scripts/receipt_toolchain_id.sh here +# BODY_FILE is the PR body; the receipt is its one fenced block whose info string is +# exactly the schema id. Prints `RECEIPT_OK=1` or `RECEIPT_OK=0 reason=` as the +# first line and exits 0 either way: the caller falls through to the full gate on 0. +# Anything missing or malformed fails closed. Never on a tag ref or a push event: that +# belongs to the caller (the full gate owns those). +# Cases: scripts/check_receipt_gate.sh. +set -u +# Absolute tools and a fixed PATH: a PATH entry or a BASH_ENV function must never stand in for +# ssh-keygen or jq (#5798). Callers also run this under `env -i`. +PATH=/usr/local/bin:/usr/bin:/bin; export PATH +unset BASH_ENV ENV +SSHKEYGEN=/usr/bin/ssh-keygen +JQ=/usr/bin/jq +NS="flow009-receipt" +SIGNER_ID="flow009-batcher" +SCHEMA="flow009.receipt/1" +HERE=$(cd "$(dirname "$0")" && pwd) + +fail() { printf 'RECEIPT_OK=0 reason=%s\n' "$1"; exit 0; } + +canonical() { "$JQ" -S -c 'del(.sig)' "$1"; } + +# The fenced block's contents on stdout; non-zero unless there is exactly one. +extract_block() { + tr -d '\r' <"$1" | awk -v info="\`\`\`$SCHEMA" ' + state == 0 && $0 == info { state = 1; n++; next } + state == 1 && $0 == "```" { state = 0; next } + state == 1 { print } + END { if (n != 1 || state != 0) exit 1 } + ' +} + +cmd_verify() { + [ $# -eq 3 ] || fail usage + body=$1; pub=$2 + [ -x "$SSHKEYGEN" ] || fail no-ssh-keygen + [ -x "$JQ" ] || fail no-jq + [ -f "$body" ] || fail no-body + [ -f "$pub" ] || fail no-pubkey + work=$(mktemp -d) || fail no-tmpdir + trap 'rm -rf "${work:?}"' EXIT + # The key: first non-comment line. A comment-only file is the placeholder. + keyline=$(grep -v -E '^[[:space:]]*(#|$)' "$pub" | head -n1 | cut -d' ' -f1,2) + [ -n "$keyline" ] || fail placeholder-pubkey + case "$keyline" in ssh-ed25519\ *) ;; *) fail bad-pubkey ;; esac + printf '%s namespaces="%s" %s\n' "$SIGNER_ID" "$NS" "$keyline" >"$work/allowed" + extract_block "$body" >"$work/receipt.json" || fail fenced-block + "$JQ" -e --arg s "$SCHEMA" ' + type == "object" and .schema == $s + and (.sig | type == "string") and (.tree | type == "string") and (.toolchain | type == "string") + ' "$work/receipt.json" >/dev/null 2>&1 || fail shape + # signature + canonical "$work/receipt.json" >"$work/canon" || fail canon + "$JQ" -r '.sig' "$work/receipt.json" >"$work/sig" + "$SSHKEYGEN" -Y verify -f "$work/allowed" -I "$SIGNER_ID" -n "$NS" -s "$work/sig" \ + <"$work/canon" >/dev/null 2>&1 || fail bad-signature + # verdict: the signed bytes must say the batch's suite passed. A validly signed receipt for a + # red batch must not reduce the gate. gate_results is an object, `tests` is present, and every + # value is "pass". + "$JQ" -e '.gate_results | type == "object" and has("tests") and (to_entries | all(.value == "pass"))' \ + "$work/receipt.json" >/dev/null 2>&1 || fail not-pass + # tree + want_tree=$(git rev-parse 'HEAD^{tree}' 2>/dev/null) + [ -n "$want_tree" ] || fail no-head-tree + [ "$("$JQ" -r .tree "$work/receipt.json")" = "$want_tree" ] || fail tree-mismatch + # toolchain + want_tc=$(bash "$HERE/receipt_toolchain_id.sh" 2>/dev/null) + [ -n "$want_tc" ] || fail no-toolchain-id + [ "$("$JQ" -r .toolchain "$work/receipt.json")" = "$want_tc" ] || fail toolchain-mismatch + printf 'RECEIPT_OK=1\n' +} + +case "${1:-}" in + verify) shift; cmd_verify "$@" ;; + *) fail usage ;; +esac diff --git a/scripts/ci_test_tier.sh b/scripts/ci_test_tier.sh index d52c6e4cd2..f692eb9f2b 100755 --- a/scripts/ci_test_tier.sh +++ b/scripts/ci_test_tier.sh @@ -112,7 +112,7 @@ set -euo pipefail # `unset` also drops the export attribute, so no child can inherit them. unset SELF_TEST FILTERSET FS_TARGETS SELF_TEST=0; FILTERSET=0 -EVENT=""; COMPARAND=""; DIFF_FROM=""; PR_HEAD=""; PR_CONCLUSION=""; MG_RUN=""; MG_SHA=""; MG_CONCLUSION=""; MG_GIVEN=0; REGISTRY="scripts/tree_reader_tests.txt"; ROOT="." +EVENT=""; COMPARAND=""; DIFF_FROM=""; PR_HEAD=""; PR_CONCLUSION=""; MG_RUN=""; MG_SHA=""; MG_CONCLUSION=""; RECEIPT_OK=0; MG_GIVEN=0; REGISTRY="scripts/tree_reader_tests.txt"; ROOT="." # The sibling scripts and the registry always come from the checkout this script # runs in (cwd = repo root, in ci.yml and in the case table alike); --repo-root # re-points only the GIT queries — HEAD, its parents, their trees — which is how @@ -129,6 +129,7 @@ while [ $# -gt 0 ]; do --mg-run) MG_RUN=$2; MG_GIVEN=1; shift 2 ;; --mg-sha) MG_SHA=$2; MG_GIVEN=1; shift 2 ;; --mg-conclusion) MG_CONCLUSION=$2; MG_GIVEN=1; shift 2 ;; + --receipt-ok) RECEIPT_OK=$2; shift 2 ;; --registry) REGISTRY=$2; shift 2 ;; --repo-root) ROOT=$2; shift 2 ;; --tsv) TSV=$2; shift 2 ;; @@ -140,7 +141,7 @@ while [ $# -gt 0 ]; do --filterset) FILTERSET=1; shift; if [ $# -gt 0 ]; then FS_TARGETS=$1; shift; fi ;; # same operand rules as --filterset; prints the cargo BUILD flags for the same tokens --build-flags) BUILDFLAGS=1; shift; if [ $# -gt 0 ]; then FS_TARGETS=$1; shift; fi ;; - *) printf 'usage: %s --event EVENT [--comparand REF] [--diff-from FILE] [--pr-head SHA --pr-head-conclusion C] [--mg-run ID --mg-sha SHA --mg-conclusion C] [--registry FILE] [--repo-root DIR] | --filterset [TARGETS] | --tier-of-record [--tsv FILE] [--union-touched --event pull_request --comparand REF] | --self-test\n' "$0" >&2; exit 2 ;; + *) printf 'usage: %s --event EVENT [--comparand REF] [--diff-from FILE] [--pr-head SHA --pr-head-conclusion C] [--mg-run ID --mg-sha SHA --mg-conclusion C] [--receipt-ok 1] [--registry FILE] [--repo-root DIR] | --filterset [TARGETS] | --tier-of-record [--tsv FILE] [--union-touched --event pull_request --comparand REF] | --self-test\n' "$0" >&2; exit 2 ;; esac done @@ -369,6 +370,13 @@ decide() { selection "merge_group: group diff $mgb..HEAD (quick tier; full only on tag/release)" "$df" || rc=$? rm -f "${df:?}"; return ${rc:-0} ;; pull_request) + # Signed batch receipt (#5797): scripts/ci_receipt_gate.sh verified signature, tree and + # toolchain for THIS merge tree, so the PR owes check/fmt/clippy and no test suite. The + # flag is honoured on pull_request only: push, merge_group and tags never read it. + if [ "$RECEIPT_OK" = 1 ]; then + printf 'tier=reuse\ncite=receipt\ncheck_workspace=1\nreceipt_gates=check,fmt,clippy\nreason=pull_request: a signed batch receipt verified for this merge tree and toolchain -- cargo check --workspace, fmt and clippy only; the batcher already ran the suite\n' + return 0 + fi selection "pull_request" "$DIFF_FROM" || return $? ;; *) printf 'ENV: unknown event "%s" — refusing to guess a tier\n' "$EVENT" >&2; return 2 ;; esac @@ -497,6 +505,17 @@ self_test() { leaf=$(cargo metadata --no-deps --format-version 1 2>/dev/null | jq -r '[.packages[]|select(.manifest_path|test("/crates/"))] | (map(.name) - (map(.dependencies[]?.name)|unique)) | sort | .[0]') printf 'crates/%s/src/lib.rs\n' "$leaf" > "$td/d-leaf.txt" row 0 "pull_request, a leaf crate ($leaf) touched -> quick with that crate" "^crates=.*$leaf" bash "$T" --event pull_request --diff-from "$td/d-leaf.txt" + # Signed batch receipt (#5797): the verdict of scripts/ci_receipt_gate.sh arrives as --receipt-ok 1. + row 0 "pull_request with a verified receipt -> tier=reuse, cite=receipt" '^tier=reuse$' bash -c "bash '$T' --event pull_request --diff-from '$td/d-core.txt' --receipt-ok 1 | head -n1" + row 0 " ...owing check_workspace=1 and exactly check,fmt,clippy (no test suite)" '^check_workspace=1 receipt_gates=check,fmt,clippy$' bash -c "o=\$(bash '$T' --event pull_request --diff-from '$td/d-core.txt' --receipt-ok 1); printf '%s %s\\n' \"\$(grep '^check_workspace=' <<< \"\$o\")\" \"\$(grep '^receipt_gates=' <<< \"\$o\")\"" + row 0 " ...even when the diff touches a root manifest (the receipt vouches for the whole tree)" '^tier=reuse$' bash -c "bash '$T' --event pull_request --diff-from '$td/d-root.txt' --receipt-ok 1 | head -n1" + row 0 "pull_request with --receipt-ok 0 is unchanged: still the quick selection" '^tier=quick$' bash -c "bash '$T' --event pull_request --diff-from '$td/d-leaf.txt' --receipt-ok 0 | head -n1" + row 0 "schedule ignores --receipt-ok: tier=full" '^tier=full$' bash -c "bash '$T' --event schedule --receipt-ok 1 | head -n1" + row 0 "push ignores --receipt-ok: never reuse without a merge_group result" '^NOT-RECEIPT$' bash -c "o=\$(bash '$T' --event push --repo-root '$td/q-leaf' --receipt-ok 1); if grep -q '^cite=receipt' <<< \"\$o\"; then echo RECEIPT; else echo NOT-RECEIPT; fi" + row 0 "merge_group ignores --receipt-ok: the queue still owes its own tier" '^NOT-RECEIPT$' bash -c "o=\$(env -u MG_BASE_SHA bash '$T' --event merge_group --repo-root '$td/q-leaf' --receipt-ok 1); if grep -q '^cite=receipt' <<< \"\$o\"; then echo RECEIPT; else echo NOT-RECEIPT; fi" + sed 's|if \[ "\$RECEIPT_OK" = 1 \]; then|if [ "$RECEIPT_OK" != 7 ]; then|' "$T" > "$td/mutant-receipt.sh" + row 0 " (mutant file really differs from the script)" '^DIFFERS$' bash -c "if cmp -s '$T' '$td/mutant-receipt.sh'; then echo SAME; else echo DIFFERS; fi" + row 0 "MUTANT: a pull_request arm that reuses without a verified receipt -> the --receipt-ok 0 row goes RED" '^MUTANT-REUSE$' bash -c "o=\$(bash '$td/mutant-receipt.sh' --event pull_request --diff-from '$td/d-leaf.txt' --receipt-ok 0 | head -n1); if [ \"\$o\" = tier=reuse ]; then echo MUTANT-REUSE; else echo SURVIVED; fi" # registry drift is ENV for the quick tier cp scripts/tree_reader_tests.txt "$td/reg.bak"; printf 'zeta\t--lib\n' >> scripts/tree_reader_tests.txt row 2 "pull_request with a drifted registry -> ENV (exit 2): no quick tier over a stale list" 'drifted' bash "$T" --event pull_request --diff-from "$td/d-scripts.txt" diff --git a/scripts/receipt_toolchain_id.sh b/scripts/receipt_toolchain_id.sh new file mode 100755 index 0000000000..0be67272b1 --- /dev/null +++ b/scripts/receipt_toolchain_id.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Print one sha256 hex identifying the build toolchain: rust-toolchain.toml (nothing when +# absent), the full `rustc -vV`, then the literal line "image=none". Same bytes, same order +# as the batcher's signing side, so a receipt's toolchain field compares equal here. +# A missing rustc exits 1 (the verifier fails closed). +# Hardened (#5798): the toml is read from HEAD's tree (git show), not the working tree; a +# `path =` key (an override that makes rustc run an arbitrary binary) is refused; and rustc runs +# in $RECEIPT_RUSTC_CWD when the judge sets it (a dir holding the BASE's toml), never in the PR cwd. +set -u +root=$(git rev-parse --show-toplevel 2>/dev/null || pwd) +toml="" +if git rev-parse --verify -q 'HEAD^{tree}' >/dev/null 2>&1; then + toml=$(git show HEAD:rust-toolchain.toml 2>/dev/null; printf x); toml=${toml%x} +elif [ -f "$root/rust-toolchain.toml" ]; then + toml=$(cat "$root/rust-toolchain.toml"; printf x); toml=${toml%x} +fi +if printf '%s\n' "$toml" | grep -q -E '^[[:space:]]*path[[:space:]]*='; then exit 1; fi +rustc=rustc; [ -x "${CARGO_HOME:-$HOME/.cargo}/bin/rustc" ] && rustc="${CARGO_HOME:-$HOME/.cargo}/bin/rustc" +rustc_v=$(cd "${RECEIPT_RUSTC_CWD:-.}" && "$rustc" -vV 2>/dev/null) || exit 1 +[ -n "$rustc_v" ] || exit 1 +{ + printf '%s' "$toml" + printf '%s\n' "$rustc_v" + printf 'image=none\n' +} | sha256sum | cut -d' ' -f1 From 589a09d2b0864efbeb56fd119b5e4e3d07a807c9 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 18:56:08 +0200 Subject: [PATCH 02/26] ci(mg-reuse): merge_group skips workspace-test-shard on an identical-tree green PR run (PMAT-5884) Noah 16:28Z #3: skip the MQ re-test only when the merge-group tree sha equals the tree sha of a green PR run; any difference runs the full shards. - ci_mg_reuse.sh: decide() computes wt (K1-K5 + the head run's workspace-test succeeded); the queue override carries wt only when exactly 1 (fail closed). x86-main/determinism/mac stay un-reused on the queue. - ci.yml: new mg-reuse-wt job (merge_group only); workspace-test-shard skips on wt=1; the workspace-test verdict requires shard==skipped under the key, else success, and skips the Sigma step only under the key. - --self-test: RED->GREEN falsifier rows (changed tree / non-ancestor base / failed or missing head verdict -> wt=0; identical tree -> wt=1). Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 55 ++++++++++++++++++++++++++-- docs/roadmaps/entries/PMAT-5884.yaml | 20 ++++++++++ docs/roadmaps/roadmap.yaml | 20 ++++++++++ scripts/ci_mg_reuse.sh | 50 +++++++++++++++++++++++-- 4 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 docs/roadmaps/entries/PMAT-5884.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bf828a5b4..d8910a9d0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,6 +137,41 @@ jobs: printf '%s\n' "$out" printf '%s\n' "$out" | grep -E '^(x86|det|cite|pr_head)=' >> "$GITHUB_OUTPUT" + # T43b (#5884, Noah 16:28Z #3, extends mg-reuse): on merge_group, workspace-test-shard is skipped ONLY when the queue + # entry's tree sha == the tree sha of a green PR run's head (K1-K5: queue head has one parent, the base; the base + # has one parent; the base is an ancestor of the head; M^{tree} == H^{tree}; the head's newest ci.yml pull_request + # run is complete and every job is that run's) AND that run's `workspace-test` verdict succeeded. Any difference + # (changed main, another tree, a lookup that failed) leaves wt=0 and the full shards run. x86-main and determinism + # are NOT read here: they stay un-reused on the queue (20X r2), so x86-main still never waits for this job. + mg-reuse-wt: + if: github.event_name == 'merge_group' + runs-on: [self-hosted, Linux, X64, clean-room] + timeout-minutes: 10 + permissions: + contents: read + actions: read + pull-requests: read + outputs: + wt: ${{ steps.key.outputs.wt }} + wt_cite: ${{ steps.key.outputs.wt_cite }} + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Read the workspace-test reuse key for this queue entry (3 API calls) + id: key + env: + GH_TOKEN: ${{ github.token }} + QREF: ${{ github.event.merge_group.head_ref }} + QHEAD: ${{ github.event.merge_group.head_sha }} + QBASE: ${{ github.event.merge_group.base_sha }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + out=$(bash scripts/ci_mg_reuse.sh resolve --event merge_group --ref "$QREF" --queue-head "$QHEAD" --base "$QBASE" --repo "$REPO") + printf '%s\n' "$out" + printf '%s\n' "$out" | grep -E '^(wt|wt_cite)=' >> "$GITHUB_OUTPUT" + # The same case table on every event, so a change that breaks the key is RED on # its own PR, not first in the queue. gate requires it. mg-reuse-self-test: @@ -440,6 +475,9 @@ jobs: # explicit runner's round-robin N/12), in parallel on the X64 clean-room pool. # The section body is ci/sections.yml `workspace-test-shard`, unchanged. workspace-test-shard: + # T43b: a merge_group entry whose tree equals a green PR run's skips the shards (mg-reuse-wt); anything else runs them + needs: [mg-reuse-wt] + if: ${{ !cancelled() && needs.mg-reuse-wt.outputs.wt != '1' }} # same lanes as x86-main: clean-room for pull_request/push, mq for a merge_group entry runs-on: ${{ github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","X64","mq"]') || fromJSON('["self-hosted","Linux","X64","clean-room"]') }} timeout-minutes: 180 @@ -491,18 +529,28 @@ jobs: workspace-test: runs-on: [self-hosted, Linux, clean-room] timeout-minutes: 10 - needs: [workspace-test-shard] + needs: [workspace-test-shard, mg-reuse-wt] if: always() steps: - - name: Every shard passed + - name: Every shard passed (or the whole set was reused from an identical-tree green PR run) env: SHARD_RESULT: ${{ needs.workspace-test-shard.result }} + WT_REUSE: ${{ needs.mg-reuse-wt.outputs.wt }} + WT_CITE: ${{ needs.mg-reuse-wt.outputs.wt_cite }} run: | set -euo pipefail - echo "workspace-test-shard matrix result: $SHARD_RESULT" + echo "workspace-test-shard matrix result: $SHARD_RESULT (reuse key wt='${WT_REUSE:-}')" + if [ "${WT_REUSE:-}" = 1 ]; then + # T43b: the shards are skipped ONLY under the key; a shard that ran anyway is a contradiction, never a pass + [ "$SHARD_RESULT" = skipped ] || { echo "::error::workspace-test: reuse key wt=1 but the shard matrix result is '$SHARD_RESULT'"; exit 1; } + [ -n "${WT_CITE:-}" ] || { echo "::error::workspace-test: reuse key wt=1 without a citation"; exit 1; } + echo "workspace-test reused: merge-group tree == tree of a green PR run (${WT_CITE})" + exit 0 + fi [ "$SHARD_RESULT" = success ] || { echo "::error::workspace-test: shard matrix result is '$SHARD_RESULT' (a cancelled or skipped shard is not a pass)"; exit 1; } - uses: actions/checkout@v7 - uses: actions/download-artifact@v4 + if: ${{ needs.mg-reuse-wt.outputs.wt != '1' }} with: pattern: sigma-shard-* path: ${{ runner.temp }}/sigma @@ -511,6 +559,7 @@ jobs: # ids is compared with the universe shard 1 listed. A shard whose ids did # not arrive is RED, never a smaller universe. - name: "Σ-executed: the union of the shards is exactly the owed set (#4433)" + if: ${{ needs.mg-reuse-wt.outputs.wt != '1' }} run: | set -euo pipefail d="$RUNNER_TEMP/sigma" diff --git a/docs/roadmaps/entries/PMAT-5884.yaml b/docs/roadmaps/entries/PMAT-5884.yaml new file mode 100644 index 0000000000..100b0c6db1 --- /dev/null +++ b/docs/roadmaps/entries/PMAT-5884.yaml @@ -0,0 +1,20 @@ +- id: PMAT-5884 + github_issue: 5884 + item_type: task + title: 'mg-reuse: merge_group skips workspace-test-shard when its tree == a green PR run''s tree (Noah 16:28Z #3)' + status: planned + priority: high + assigned_to: null + created: 2026-10-21T17:05:00Z + updated: 2026-10-21T17:05:00Z + spec: null + acceptance_criteria: + - 'merge_group skips workspace-test-shard only when K1-K5 hold and the PR head run workspace-test succeeded; any difference runs the full shards' + - 'ci_mg_reuse.sh --self-test falsifier: changed main/tree -> wt=0, identical tree -> wt=1, override carries wt only when exactly 1' + phases: [] + subtasks: [] + estimated_effort: null + labels: + - kind:code + notes: null + epic: 5510 diff --git a/docs/roadmaps/roadmap.yaml b/docs/roadmaps/roadmap.yaml index ef47935f92..d070423a7b 100644 --- a/docs/roadmaps/roadmap.yaml +++ b/docs/roadmaps/roadmap.yaml @@ -25359,3 +25359,23 @@ Not done, pending the ruling: moving the 31 fixture files to an obviously synthe - kind:code notes: null epic: 5510 +- id: PMAT-5884 + github_issue: 5884 + item_type: task + title: 'mg-reuse: merge_group skips workspace-test-shard when its tree == a green PR run''s tree (Noah 16:28Z #3)' + status: planned + priority: high + assigned_to: null + created: 2026-10-21T17:05:00Z + updated: 2026-10-21T17:05:00Z + spec: null + acceptance_criteria: + - 'merge_group skips workspace-test-shard only when K1-K5 hold and the PR head run workspace-test succeeded; any difference runs the full shards' + - 'ci_mg_reuse.sh --self-test falsifier: changed main/tree -> wt=0, identical tree -> wt=1, override carries wt only when exactly 1' + phases: [] + subtasks: [] + estimated_effort: null + labels: + - kind:code + notes: null + epic: 5510 diff --git a/scripts/ci_mg_reuse.sh b/scripts/ci_mg_reuse.sh index 86573f5aff..3ce82032b0 100755 --- a/scripts/ci_mg_reuse.sh +++ b/scripts/ci_mg_reuse.sh @@ -49,7 +49,7 @@ set -uo pipefail CI_WORKFLOW_PATH=".github/workflows/ci.yml" PRH="" -emit() { printf 'x86=%s\ndet=%s\nmac=%s\ncite=%s\npr_head=%s\nreason=%s\n' "$1" "$2" "$3" "$4" "$5" "$6"; } +emit() { printf 'x86=%s\ndet=%s\nmac=%s\ncite=%s\npr_head=%s\nreason=%s\nwt=%s\nwt_cite=%s\n' "$1" "$2" "$3" "$4" "$5" "$6" "${7:-0}" "${8:-}"; } refuse() { emit 0 0 0 "" "$PRH" "$1"; } usage() { echo "usage: ci_mg_reuse.sh decide|resolve|decide-push|resolve-push [opts] | --self-test [--workflow FILE]" >&2; exit 2; } @@ -112,17 +112,30 @@ decide() { [ "$(concl "$jobs" 'ci / gate')" = success ] || why="${why:+$why; }ci / gate is $(concl "$jobs" 'ci / gate')" # R:k6-cigate x=1 [ -z "$why" ] || x=0 + # T43b (#5884, operator 16:28Z #3): workspace-test is its own unit. K1-K5 held, so the queue entry's tree IS the tree + # the head's run tested; reuse that run's `workspace-test` verdict (it is the Σ-executed fan-in over every shard). + # Not tied to x86-main/determinism: a red x86-main on the head does not make a green workspace-test stale. + wt=0 + [ "$(concl "$jobs" workspace-test)" = success ] && wt=1 # R:k6-wt mc=1 [ "$(concl "$jobs" mac-check)" = success ] || mc=0 # R:k6-mac emit "$x" "$x" "$mc" "run $rid attempt $att on ${h:0:10}" "$h" \ - "K1-K5 hold; x86-main+determinism: $([ "$x" = 1 ] && echo reuse || echo "run ($why)"); mac-check: $([ "$mc" = 1 ] && echo reuse || echo "run ($(concl "$jobs" mac-check))")" + "K1-K5 hold; x86-main+determinism: $([ "$x" = 1 ] && echo reuse || echo "run ($why)"); mac-check: $([ "$mc" = 1 ] && echo reuse || echo "run ($(concl "$jobs" mac-check))"); workspace-test: $([ "$wt" = 1 ] && echo reuse || echo "run ($(concl "$jobs" workspace-test))")" \ + "$wt" "$([ "$wt" = 1 ] && echo "run $rid attempt $att on ${h:0:10}")" } # The queue-entry override, FORCED: drop whatever decide said for these keys (or omitted) and write # the values explicitly, so a decide that leaves a key out cannot fail open (r6). force_no_reuse() { - { grep -v -E '^(x86|det|mac|cite|pr_head)=' || true; } - printf 'x86=0\ndet=0\nmac=0\ncite=\npr_head=\n' + local in wt=0 wc="" + in=$(cat) + # T43b (#5884): workspace-test reuse crosses the override ONLY as exactly wt=1 with its citation; anything else is 0. + if [ "$(printf '%s\n' "$in" | sed -n 's/^wt=//p' | tail -n 1)" = 1 ]; then + wt=1 + wc=$(printf '%s\n' "$in" | sed -n 's/^wt_cite=//p' | tail -n 1) + fi + { printf '%s\n' "$in" | grep -v -E '^(x86|det|mac|cite|pr_head|wt|wt_cite)=' || true; } + printf 'x86=0\ndet=0\nmac=0\ncite=\npr_head=\nwt=%s\nwt_cite=%s\n' "$wt" "$wc" } resolve() { @@ -382,6 +395,35 @@ self_test() { dr k6-x86-main-twice "0 0 1" merge_group "$M" "$B" "$H" runs.ok jobs.dup-x86 dr k6-mac-failed "1 1 0" merge_group "$M" "$B" "$H" runs.ok jobs.fail-mac-check + # T43b (#5884, Noah 16:28Z #3): workspace-test reuse. RED->GREEN falsifier: a changed main must run, an identical tree may skip. + sed '/^workspace-test /d' <<< "$GREEN_JOBS" | jobs_json "$H" 1001 0 > "$F/jobs.no-wt" + sed 's|^workspace-test success$|workspace-test failure|' <<< "$GREEN_JOBS" | jobs_json "$H" 1001 0 > "$F/jobs.fail-wt" + wrow() { # wrow NAME WANT decide-args... + local n="$1" want="$2" out; shift 2 + out=$(decide "$@" --repo-root "$R") + if [ "$(kv wt "$out")" = "$want" ]; then pass=$((pass + 1)); else + fail=$((fail + 1)); echo "FAIL wt-$n: want wt=$want got '$(kv wt "$out")' -- $(kv reason "$out")"; fi + } + wd() { local n="$1" want="$2" m="$3" b="$4" h="$5" r="$6" j="$7" + wrow "$n" "$want" --event merge_group --queue-head "$m" --base "$b" --pr-head "$h" --runs "$F/$r" --jobs "$F/$j"; } + wd identical-tree-skips 1 "$M" "$B" "$H" runs.ok jobs.ok + wd changed-tree-runs 0 "$M_TREE" "$B" "$H" runs.ok jobs.ok + wd changed-main-not-ancestor 0 "$M_K3" "$B2" "$H" runs.ok jobs.ok + wd base-is-a-merge-runs 0 "$M_K2" "$BM" "$H2" runs.h2 jobs.h2 + wd head-wt-failed-runs 0 "$M" "$B" "$H" runs.ok jobs.fail-wt + wd head-wt-missing-runs 0 "$M" "$B" "$H" runs.ok jobs.no-wt + wd run-in-progress-runs 0 "$M" "$B" "$H" runs.newer-running jobs.ok + wd head-x86-failed-still-skips 1 "$M" "$B" "$H" runs.ok jobs.fail-determinism + wrow push-never-skips 0 --event push --queue-head "$M" --base "$B" --pr-head "$H" --runs "$F/runs.ok" --jobs "$F/jobs.ok" + # the override passes wt only as exactly 1, with its citation + local wo + wo=$(force_no_reuse <<< $'wt=1\nwt_cite=run 7\nx86=1') + [ "$(kv wt "$wo")|$(kv wt_cite "$wo")|$(kv x86 "$wo")" = "1|run 7|0" ] && pass=$((pass + 1)) || { fail=$((fail + 1)); echo "FAIL wt-override-pass: $wo"; } + for wo in $'wt=2' $'wt=' $'wt=0\nwt_cite=run 7' $'reason=x'; do + wo=$(force_no_reuse <<< "$wo") + [ "$(kv wt "$wo")|$(kv wt_cite "$wo")" = "0|" ] && pass=$((pass + 1)) || { fail=$((fail + 1)); echo "FAIL wt-override-closed: $wo"; } + done + # resolve, end to end, through a stub gh serving canned JSON. A failed lookup never reuses. local SB="$T/bin"; mkdir -p "$SB" cat > "$SB/gh" <<'STUB' From 5247a32c293c21dbedc1e99e7a493ad8b4dae9d5 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 22:31:38 +0200 Subject: [PATCH 03/26] fix(mg-reuse): wt=1 needs >=1 workspace-test-shard job, all success; a draft run's green verdict ran no tests (PMAT-5884) Opus lane-b on fe1d367ed8: a draft PR run reports workspace-test green with the shard matrix skipped (ci.yml draft guard), so reuse of it skipped the queue's tests with none ever run. shards() counts the `workspace-test-shard (N)` jobs; wt=1 requires count>=1 and zero non-success. Self-test rows: draft run (no shards), green verdict with zero shards, one failed shard -> wt=0. Co-Authored-By: Claude Sonnet 5.5 --- scripts/ci_mg_reuse.sh | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/ci_mg_reuse.sh b/scripts/ci_mg_reuse.sh index 45381faba1..54a018b1de 100755 --- a/scripts/ci_mg_reuse.sh +++ b/scripts/ci_mg_reuse.sh @@ -67,6 +67,13 @@ concl() { | if length == 1 then (.[0].conclusion // "none") else "count=\(length)" end' "$1" 2> /dev/null } +# The matrix jobs `workspace-test-shard (N)` in jobs file $1: prints " ". A draft PR run (or a +# reused one) has green `workspace-test` and NO such jobs; that green says no test ran, so it is never reusable (#5885 lane-b). +shards() { + jq -r '[.jobs[]? | select(.name | test("^workspace-test-shard \\([0-9]+\\)$"))] + | "\(length) \([.[] | select(.conclusion != "success")] | length)"' "$1" 2> /dev/null || echo "0 1" +} + decide() { local ev="" m="" b="" h="" runs="" jobs="" root="." while [ $# -gt 0 ]; do @@ -116,7 +123,9 @@ decide() { # the head's run tested; reuse that run's `workspace-test` verdict (it is the Σ-executed fan-in over every shard). # Not tied to x86-main/determinism: a red x86-main on the head does not make a green workspace-test stale. wt=0 - [ "$(concl "$jobs" workspace-test)" = success ] && wt=1 # R:k6-wt + # wt=1 needs the verdict green AND >=1 shard job, all success: a draft run is green with zero shards (#5885 lane-b). + read -r nsh nbad <<< "$(shards "$jobs")" + [ "$(concl "$jobs" workspace-test)" = success ] && [ "${nsh:-0}" -ge 1 ] 2> /dev/null && [ "${nbad:-1}" = 0 ] && wt=1 # R:k6-wt mc=1 [ "$(concl "$jobs" mac-check)" = success ] || mc=0 # R:k6-mac emit "$x" "$x" "$mc" "run $rid attempt $att on ${h:0:10}" "$h" \ @@ -397,6 +406,10 @@ self_test() { # T43b (#5884, Noah 16:28Z #3): workspace-test reuse. RED->GREEN falsifier: a changed main must run, an identical tree may skip. sed '/^workspace-test /d' <<< "$GREEN_JOBS" | jobs_json "$H" 1001 0 > "$F/jobs.no-wt" + { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard (1)\tsuccess\nworkspace-test-shard (2)\tsuccess\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-ok" + sed "s|^determinism\tsuccess\$|determinism\tfailure|" <<< "$GREEN_JOBS" | { cat; printf 'workspace-test-shard (1)\tsuccess\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-x86fail" + { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard (1)\tsuccess\nworkspace-test-shard (2)\tfailure\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-shard-failed" + { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard\tskipped\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-draft" sed 's|^workspace-test success$|workspace-test failure|' <<< "$GREEN_JOBS" | jobs_json "$H" 1001 0 > "$F/jobs.fail-wt" wrow() { # wrow NAME WANT decide-args... local n="$1" want="$2" out; shift 2 @@ -406,14 +419,17 @@ self_test() { } wd() { local n="$1" want="$2" m="$3" b="$4" h="$5" r="$6" j="$7" wrow "$n" "$want" --event merge_group --queue-head "$m" --base "$b" --pr-head "$h" --runs "$F/$r" --jobs "$F/$j"; } - wd identical-tree-skips 1 "$M" "$B" "$H" runs.ok jobs.ok + wd identical-tree-skips 1 "$M" "$B" "$H" runs.ok jobs.wt-ok + wd draft-run-no-shards-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-draft + wd greenverdict-zero-shards 0 "$M" "$B" "$H" runs.ok jobs.ok + wd one-shard-failed-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-shard-failed wd changed-tree-runs 0 "$M_TREE" "$B" "$H" runs.ok jobs.ok wd changed-main-not-ancestor 0 "$M_K3" "$B2" "$H" runs.ok jobs.ok wd base-is-a-merge-runs 0 "$M_K2" "$BM" "$H2" runs.h2 jobs.h2 wd head-wt-failed-runs 0 "$M" "$B" "$H" runs.ok jobs.fail-wt wd head-wt-missing-runs 0 "$M" "$B" "$H" runs.ok jobs.no-wt wd run-in-progress-runs 0 "$M" "$B" "$H" runs.newer-running jobs.ok - wd head-x86-failed-still-skips 1 "$M" "$B" "$H" runs.ok jobs.fail-determinism + wd head-x86-failed-still-skips 1 "$M" "$B" "$H" runs.ok jobs.wt-x86fail wrow push-never-skips 0 --event push --queue-head "$M" --base "$B" --pr-head "$H" --runs "$F/runs.ok" --jobs "$F/jobs.ok" # the override passes wt only as exactly 1, with its citation local wo From 1b7f02cded01b4d84341bd20e7855255e0db182a Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 22:34:09 +0200 Subject: [PATCH 04/26] fix(mg-reuse): shard job names are 'workspace-test-shard (N, M)'; the (N) regex never matched, so wt was never 1 (PMAT-5884) Fixtures now use real names from MG run 39666213701. The old regex fails 2 rows (120/2). Co-Authored-By: Claude Sonnet 5.5 --- scripts/ci_mg_reuse.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/ci_mg_reuse.sh b/scripts/ci_mg_reuse.sh index 54a018b1de..14b3ed290e 100755 --- a/scripts/ci_mg_reuse.sh +++ b/scripts/ci_mg_reuse.sh @@ -67,10 +67,10 @@ concl() { | if length == 1 then (.[0].conclusion // "none") else "count=\(length)" end' "$1" 2> /dev/null } -# The matrix jobs `workspace-test-shard (N)` in jobs file $1: prints " ". A draft PR run (or a +# The matrix jobs `workspace-test-shard (N, M)` in jobs file $1: prints " ". A draft PR run (or a # reused one) has green `workspace-test` and NO such jobs; that green says no test ran, so it is never reusable (#5885 lane-b). shards() { - jq -r '[.jobs[]? | select(.name | test("^workspace-test-shard \\([0-9]+\\)$"))] + jq -r '[.jobs[]? | select(.name | test("^workspace-test-shard \\([0-9]+, [0-9]+\\)$"))] | "\(length) \([.[] | select(.conclusion != "success")] | length)"' "$1" 2> /dev/null || echo "0 1" } @@ -293,7 +293,7 @@ resolve_push() { # The jobs of run 37040341824, #4655's only PR-head run (db86acf021), verbatim # from the API (two GPU-host leg job names replaced; the key never reads them): # x86-main failed while gate and ci / gate passed. -PLANTED_4655_JOBS=$'gpu-touched\tsuccess\nmutants-table-scope\tsuccess\ndeterminism\tsuccess\nworkspace-test-shard (1)\tsuccess\nmac-check\tsuccess\nworkspace-test-shard (3)\tsuccess\nworkspace-test-shard (2)\tsuccess\nx86-main\tfailure\nmutants-table\tskipped\nmutants-shard\tskipped\ngpu-host-leg-a\tsuccess\ngpu-host-leg-b\tsuccess\nworkspace-test\tsuccess\nci / gate\tsuccess\ngate\tsuccess' +PLANTED_4655_JOBS=$'gpu-touched\tsuccess\nmutants-table-scope\tsuccess\ndeterminism\tsuccess\nworkspace-test-shard (1, 4)\tsuccess\nmac-check\tsuccess\nworkspace-test-shard (3, 4)\tsuccess\nworkspace-test-shard (2, 4)\tsuccess\nx86-main\tfailure\nmutants-table\tskipped\nmutants-shard\tskipped\ngpu-host-leg-a\tsuccess\ngpu-host-leg-b\tsuccess\nworkspace-test\tsuccess\nci / gate\tsuccess\ngate\tsuccess' GREEN_JOBS=$'x86-main\tsuccess\ndeterminism\tsuccess\nmac-check\tsuccess\nworkspace-test\tsuccess\nci / gate\tsuccess\ngate\tsuccess' # jobs_json HEAD RUN_ID EXTRA_TOTAL < "nameconclusion" lines @@ -406,9 +406,9 @@ self_test() { # T43b (#5884, Noah 16:28Z #3): workspace-test reuse. RED->GREEN falsifier: a changed main must run, an identical tree may skip. sed '/^workspace-test /d' <<< "$GREEN_JOBS" | jobs_json "$H" 1001 0 > "$F/jobs.no-wt" - { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard (1)\tsuccess\nworkspace-test-shard (2)\tsuccess\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-ok" - sed "s|^determinism\tsuccess\$|determinism\tfailure|" <<< "$GREEN_JOBS" | { cat; printf 'workspace-test-shard (1)\tsuccess\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-x86fail" - { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard (1)\tsuccess\nworkspace-test-shard (2)\tfailure\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-shard-failed" + { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard (1, 4)\tsuccess\nworkspace-test-shard (2, 4)\tsuccess\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-ok" + sed "s|^determinism\tsuccess\$|determinism\tfailure|" <<< "$GREEN_JOBS" | { cat; printf 'workspace-test-shard (1, 4)\tsuccess\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-x86fail" + { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard (1, 4)\tsuccess\nworkspace-test-shard (2, 4)\tfailure\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-shard-failed" { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard\tskipped\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-draft" sed 's|^workspace-test success$|workspace-test failure|' <<< "$GREEN_JOBS" | jobs_json "$H" 1001 0 > "$F/jobs.fail-wt" wrow() { # wrow NAME WANT decide-args... From 197ee71bbb04e92bfcb40ed929640a0703d17f22 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 22:44:45 +0200 Subject: [PATCH 05/26] fix(mg-reuse): wt=1 needs every shard of the matrix present (nsh==M) and a non-empty citation (PMAT-5884) Real-name fixtures; one RED mutant row each (partial-shards 124/1, empty-attempt cite 124/1). Self-test 125/0. Co-Authored-By: Claude Sonnet 5.5 --- scripts/ci_mg_reuse.sh | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/scripts/ci_mg_reuse.sh b/scripts/ci_mg_reuse.sh index 14b3ed290e..fef1db2644 100755 --- a/scripts/ci_mg_reuse.sh +++ b/scripts/ci_mg_reuse.sh @@ -67,11 +67,12 @@ concl() { | if length == 1 then (.[0].conclusion // "none") else "count=\(length)" end' "$1" 2> /dev/null } -# The matrix jobs `workspace-test-shard (N, M)` in jobs file $1: prints " ". A draft PR run (or a -# reused one) has green `workspace-test` and NO such jobs; that green says no test ran, so it is never reusable (#5885 lane-b). +# The matrix jobs `workspace-test-shard (N, M)` in jobs file $1: prints " ", M = the shard total every +# name agrees on (0 if they disagree). A draft PR run (or a reused one) has green `workspace-test` and NO such jobs (#5885 lane-b). shards() { - jq -r '[.jobs[]? | select(.name | test("^workspace-test-shard \\([0-9]+, [0-9]+\\)$"))] - | "\(length) \([.[] | select(.conclusion != "success")] | length)"' "$1" 2> /dev/null || echo "0 1" + jq -r '[.jobs[]? | select(.name | test("^workspace-test-shard \\([0-9]+, [0-9]+\\)$"))] as $s + | ([$s[].name | capture(", (?[0-9]+)\\)$").m | tonumber] | unique) as $m + | "\($s | map(.name) | unique | length) \([$s[] | select(.conclusion != "success")] | length) \(if ($m | length) == 1 then $m[0] else 0 end)"' "$1" 2> /dev/null || echo "0 1 0" } decide() { @@ -124,13 +125,17 @@ decide() { # Not tied to x86-main/determinism: a red x86-main on the head does not make a green workspace-test stale. wt=0 # wt=1 needs the verdict green AND >=1 shard job, all success: a draft run is green with zero shards (#5885 lane-b). - read -r nsh nbad <<< "$(shards "$jobs")" - [ "$(concl "$jobs" workspace-test)" = success ] && [ "${nsh:-0}" -ge 1 ] 2> /dev/null && [ "${nbad:-1}" = 0 ] && wt=1 # R:k6-wt + read -r nsh nbad msh <<< "$(shards "$jobs")" + wcite="run $rid attempt $att on ${h:0:10}" + # ...and EVERY shard of the matrix is present: (N, M) names must agree on M and number M (a partial list is not a run). + [ "$(concl "$jobs" workspace-test)" = success ] && [ "${msh:-0}" -ge 1 ] 2> /dev/null && [ "${nsh:-0}" = "${msh:-0}" ] && [ "${nbad:-1}" = 0 ] && wt=1 # R:k6-wt-all-shards + # a reuse with nothing to cite is not a reuse + [[ "$wcite" =~ ^run\ [0-9]+\ attempt\ [0-9]+\ on\ [0-9a-f]{10}$ ]] || wt=0 # R:k6-wt-cite mc=1 [ "$(concl "$jobs" mac-check)" = success ] || mc=0 # R:k6-mac emit "$x" "$x" "$mc" "run $rid attempt $att on ${h:0:10}" "$h" \ "K1-K5 hold; x86-main+determinism: $([ "$x" = 1 ] && echo reuse || echo "run ($why)"); mac-check: $([ "$mc" = 1 ] && echo reuse || echo "run ($(concl "$jobs" mac-check))"); workspace-test: $([ "$wt" = 1 ] && echo reuse || echo "run ($(concl "$jobs" workspace-test))")" \ - "$wt" "$([ "$wt" = 1 ] && echo "run $rid attempt $att on ${h:0:10}")" + "$wt" "$([ "$wt" = 1 ] && echo "$wcite")" } # The queue-entry override, FORCED: drop whatever decide said for these keys (or omitted) and write @@ -406,9 +411,14 @@ self_test() { # T43b (#5884, Noah 16:28Z #3): workspace-test reuse. RED->GREEN falsifier: a changed main must run, an identical tree may skip. sed '/^workspace-test /d' <<< "$GREEN_JOBS" | jobs_json "$H" 1001 0 > "$F/jobs.no-wt" - { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard (1, 4)\tsuccess\nworkspace-test-shard (2, 4)\tsuccess\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-ok" - sed "s|^determinism\tsuccess\$|determinism\tfailure|" <<< "$GREEN_JOBS" | { cat; printf 'workspace-test-shard (1, 4)\tsuccess\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-x86fail" - { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard (1, 4)\tsuccess\nworkspace-test-shard (2, 4)\tfailure\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-shard-failed" + # shard job names are the real matrix names, "workspace-test-shard (N, M)" (MG run 39666213701); sh4 N... = those N of 4, success + sh4() { local n; for n in "$@"; do printf 'workspace-test-shard (%s, 4)\tsuccess\n' "$n"; done; } + { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-ok" + sed "s|^determinism\tsuccess\$|determinism\tfailure|" <<< "$GREEN_JOBS" | { cat; sh4 1 2 3 4; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-x86fail" + { cat <<< "$GREEN_JOBS"; sh4 1 3 4; printf 'workspace-test-shard (2, 4)\tfailure\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-shard-failed" + { cat <<< "$GREEN_JOBS"; sh4 1 2 3; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-partial" + { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; printf 'workspace-test-shard (5, 12)\tsuccess\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-mixed-m" + run_obj 1001 "$H" completed | jq '.run_attempt = ""' | runs_json 0 > "$F/runs.no-attempt" { cat <<< "$GREEN_JOBS"; printf 'workspace-test-shard\tskipped\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-draft" sed 's|^workspace-test success$|workspace-test failure|' <<< "$GREEN_JOBS" | jobs_json "$H" 1001 0 > "$F/jobs.fail-wt" wrow() { # wrow NAME WANT decide-args... @@ -423,6 +433,9 @@ self_test() { wd draft-run-no-shards-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-draft wd greenverdict-zero-shards 0 "$M" "$B" "$H" runs.ok jobs.ok wd one-shard-failed-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-shard-failed + wd partial-shards-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-partial + wd mixed-shard-totals-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-mixed-m + wd empty-attempt-no-cite-runs 0 "$M" "$B" "$H" runs.no-attempt jobs.wt-ok wd changed-tree-runs 0 "$M_TREE" "$B" "$H" runs.ok jobs.ok wd changed-main-not-ancestor 0 "$M_K3" "$B2" "$H" runs.ok jobs.ok wd base-is-a-merge-runs 0 "$M_K2" "$BM" "$H2" runs.h2 jobs.h2 From fd4b55a9483f3b11badf860ea5790d99f6b4789f Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 02:25:43 +0200 Subject: [PATCH 06/26] ci(#5797): shard if keeps success() on workspace-test-arch; drop unused PR_BODY from the tier step Quorum at cb92a523c2 (2 FAIL, cited): !cancelled() had dropped the implicit success() on workspace-test-arch, and PR_BODY reached a PR-tree step unread. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 2 +- ci/sections.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3aeb87a87f..8591c34ae8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -518,7 +518,7 @@ jobs: needs: [workspace-test-arch, receipt-judge] # draft PRs skip; the judge is skipped off a pull_request and fails closed: !cancelled() runs the shard # whatever the judge did, and RECEIPT_OK is 1 only on the judge's literal 1. - if: ${{ !cancelled() && github.event.pull_request.draft != true }} + if: ${{ !cancelled() && needs.workspace-test-arch.result == 'success' && github.event.pull_request.draft != true }} # arch-neutral (operator 2026-10-21 14:18Z) EXCEPT the full tier, which needs one arch for the Σ (#5882, workspace-test-arch) # clean-room for pull_request/push, mq for a merge_group entry runs-on: ${{ needs.workspace-test-arch.outputs.tier == 'full' && (github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","X64","mq"]') || fromJSON('["self-hosted","Linux","X64","clean-room"]')) || (github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","mq"]') || fromJSON('["self-hosted","Linux","clean-room"]')) }} diff --git a/ci/sections.yml b/ci/sections.yml index cc7cd07201..5a7b93ec1c 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -377,7 +377,6 @@ jobs: env: GH_TOKEN: ${{ github.token }} MG_BASE_SHA: ${{ github.event.merge_group.base_sha }} - PR_BODY: ${{ github.event.pull_request.body }} run: | bash scripts/ci/decide_tier.sh # #4502 row 2: lint_passes_on_real_contracts (aprender-contracts) requires the From 5ce1079ffaebbab735a2fe2718d2c45f0c5e95d2 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 03:09:20 +0200 Subject: [PATCH 07/26] Revert "ci: drop X64 from arch-neutral runs-on, pin the FULL tier's shards to X64 ... (#5891)" REVERT RULE (operator, 2026-10-18): merged/h below 0.46 for 2 consecutive hours after the change went live. 0 merges from 23:29:35Z (#5891 merged) to 01:30Z. Plain revert of the squash b410374203. Restores ci/sections.yml and removes scripts/ci/decide_tier.sh and scripts/ci/sigma_arch_uniform.sh. #5906 (ci_test_tier row 67 retargeted to decide_tier.sh) must be DROPPED if this lands: row 67 asserts the old ci/sections.yml text again. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 36 +------- ci/sections.yml | 62 ++++++++++++- .../src/kernel_registry_parity.rs | 22 +---- evidence/kreg/parity/cpu.matvec.apr_q4.json | 9 -- evidence/kreg/parity/cpu.matvec.apr_q8.json | 9 -- evidence/kreg/parity/cpu.matvec.bf16.json | 9 -- evidence/kreg/parity/cpu.matvec.f16.json | 9 -- evidence/kreg/parity/cpu.matvec.f32.json | 9 -- evidence/kreg/parity/cpu.matvec.q4_0.json | 9 -- evidence/kreg/parity/cpu.matvec.q4_k.json | 9 -- evidence/kreg/parity/cpu.matvec.q5_k.json | 9 -- evidence/kreg/parity/cpu.matvec.q6_k.json | 9 -- evidence/kreg/parity/cpu.matvec.q8_0.json | 9 -- scripts/check_ci_push_reuse.sh | 7 +- scripts/ci/decide_tier.sh | 66 -------------- scripts/ci/sigma_arch_uniform.sh | 91 ------------------- scripts/ci_mg_reuse.sh | 13 +-- 17 files changed, 72 insertions(+), 315 deletions(-) delete mode 100755 scripts/ci/decide_tier.sh delete mode 100755 scripts/ci/sigma_arch_uniform.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d3fa6f001..905ae731c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,7 +102,7 @@ jobs: # its own. It runs on push only; on merge_group it is skipped at t=0, which the verdict blocks # below read as `skipped:::skipped` = "x86-main + determinism ran". if: github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: [self-hosted, Linux, clean-room] + runs-on: [self-hosted, Linux, X64, clean-room] timeout-minutes: 10 permissions: contents: read @@ -316,7 +316,7 @@ jobs: x86-main-advisories: needs: [mg-reuse] if: ${{ !cancelled() && needs.mg-reuse.outputs.x86 == '1' }} - runs-on: [self-hosted, Linux, clean-room] + runs-on: [self-hosted, Linux, X64, clean-room] timeout-minutes: 30 permissions: contents: read @@ -439,35 +439,12 @@ jobs: # failure, never a pass. # ========================================================================== # The workspace tests, one shard per job (nextest hash partition N/12 plus the - # explicit runner's round-robin N/12), in parallel on the clean-room pool (arch-neutral, PMAT-3138). + # explicit runner's round-robin N/12), in parallel on the X64 clean-room pool. # The section body is ci/sections.yml `workspace-test-shard`, unchanged. - # #5882: which tier this run owes decides WHERE the shards may run. The full tier's Σ-executed fan-in - # compares every shard with ONE universe list, and arch-gated tests (target_arch cfgs) differ per - # arch, so a full tier needs one architecture: X64. The quick/PR tier owes no Σ and stays - # arch-neutral. The tier is only knowable from the diff, so it is decided HERE, by the same script - # the shards run (scripts/ci/decide_tier.sh), and workspace-test-shard reads it in its runs-on. - # scripts/ci/sigma_arch_uniform.sh in the fan-in is the backstop: a mixed full tier is RED. - workspace-test-arch: - runs-on: [self-hosted, Linux, clean-room] - timeout-minutes: 15 - outputs: - tier: ${{ steps.tier.outputs.tier }} - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - name: Decide the test tier (the shards' own step, one script) - id: tier - env: - GH_TOKEN: ${{ github.token }} - MG_BASE_SHA: ${{ github.event.merge_group.base_sha }} - run: bash scripts/ci/decide_tier.sh workspace-test-shard: - needs: [workspace-test-arch] if: ${{ github.event.pull_request.draft != true }} - # arch-neutral (operator 2026-10-21 14:18Z) EXCEPT the full tier, which needs one arch for the Σ (#5882, workspace-test-arch) - # clean-room for pull_request/push, mq for a merge_group entry - runs-on: ${{ needs.workspace-test-arch.outputs.tier == 'full' && (github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","X64","mq"]') || fromJSON('["self-hosted","Linux","X64","clean-room"]')) || (github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","mq"]') || fromJSON('["self-hosted","Linux","clean-room"]')) }} + # same lanes as x86-main: clean-room for pull_request/push, mq for a merge_group entry + runs-on: ${{ github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","X64","mq"]') || fromJSON('["self-hosted","Linux","X64","clean-room"]') }} timeout-minutes: 180 strategy: fail-fast: false @@ -546,7 +523,6 @@ jobs: set -euo pipefail d="$RUNNER_TEMP/sigma" python3 scripts/ci/sigma_executed.py --self-test - bash scripts/ci/sigma_arch_uniform.sh --self-test # ONE shard count: the pins in ci/sections.yml (check_shard_count_single_source.sh) SHARDS="$(awk -v ev="$GITHUB_EVENT_NAME" '/^matrix-pins:/{p=1;next} p&&/^ workspace-test-shard:/{k="pr";next} p&&/^ workspace-test-shard-merge_group:/{k="mg";next} p&&/^ [a-z]/{k=""} k&&/^ - \{shard:/{c[k]++} END{print (ev=="merge_group"&&c["mg"]?c["mg"]:c["pr"])+0}' ci/sections.yml)" [ "${SHARDS:-0}" -ge 1 ] 2>/dev/null || { echo "::error::Σ: no workspace-test-shard pins"; exit 1; } @@ -563,8 +539,6 @@ jobs: [ "$#" -eq "$SHARDS" ] || { echo "::error::Σ: $# tier files for $SHARDS shards:$tiers"; exit 1; } for t in "$@"; do [ "$t" = "$1" ] || { echo "::error::Σ: the shards disagree on the tier:$tiers"; exit 1; }; done [ "$1" = full ] || { echo "every shard ran tier '$1': the lib/explicit Σ is not owed"; exit 0; } - # #5882: a full tier on two architectures is RED (arch-gated tests, one universe): placement is workspace-test-arch's, this is the backstop - bash scripts/ci/sigma_arch_uniform.sh "$d" "$SHARDS" j=() l=() for n in $(seq 1 "$SHARDS"); do j+=(--junit "$d/sigma-shard-$n/lib.junit.xml") diff --git a/ci/sections.yml b/ci/sections.yml index 4632770e37..4cc17744ef 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -378,7 +378,66 @@ jobs: GH_TOKEN: ${{ github.token }} MG_BASE_SHA: ${{ github.event.merge_group.base_sha }} run: | - bash scripts/ci/decide_tier.sh + set -euo pipefail + args=(--event "${GITHUB_EVENT_NAME}") + # PMAT-1098 67-E2: the queue and a push to main re-derive their own + # diff from HEAD's FIRST PARENT (HEAD^1..HEAD is the PR's diff on the + # new base). actions/checkout is shallow, so that parent is not in the + # object store until we deepen -- and without it ci_test_tier.sh falls + # closed to `full` every single time, which would make the whole + # optimisation inert while looking like it worked. Best effort by + # design: if the deepen fails the decision still falls closed, and the + # warning below says so instead of the run silently paying the hour. + case "${GITHUB_EVENT_NAME}" in + merge_group) + # Operator 2026-10-17: the queue runs the quick tier over the entry's diff (base_sha = prior entry's head), + # MG_BASE_SHA..HEAD. Fetch the base; if that fails the tier falls closed to full. + bash scripts/ci/fetch_p6.sh --no-tags --depth=1 origin "${GITHUB_SHA}" "${MG_BASE_SHA}" 2>/dev/null \ + || bash scripts/ci/fetch_p6.sh --no-tags --depth=1 origin "${MG_BASE_SHA}" 2>/dev/null \ + || true + git cat-file -e "${MG_BASE_SHA}^{commit}" 2>/dev/null \ + || printf '::warning::merge_group base sha unavailable -- the tier falls closed to full\n' + ;; + push) + bash scripts/ci/fetch_p6.sh --no-tags --depth=2 origin "${GITHUB_SHA}" 2>/dev/null \ + || bash scripts/ci/fetch_p6.sh --no-tags --deepen=1 origin 2>/dev/null \ + || true + if git rev-parse -q --verify 'HEAD^1' >/dev/null 2>&1; then + printf 'first parent available: %s\n' "$(git rev-parse HEAD^1)" + else + printf '::warning::first parent unavailable after deepen -- the tier falls closed to full\n' + fi + ;; + esac + case "${GITHUB_EVENT_NAME}" in + pull_request) + bash scripts/ci/fetch_p6.sh --no-tags --depth=1 origin "+refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/${GITHUB_BASE_REF}" + # #5391: the PR's change is the diff from merge-base(base, HEAD), not the + # base tip -- main's later commits are not the PR's. --no-renames (#3664) + # lives in the helper. + bash scripts/ci/pr_touched_paths.sh "origin/${GITHUB_BASE_REF}" HEAD > "$RUNNER_TEMP/touched.txt" + args+=(--diff-from "$RUNNER_TEMP/touched.txt") + ;; + merge_group) + # Operator 2026-10-17 (cop window), verbatim: "only gating on tags/release". + # ci_test_tier.sh diffs MG_BASE_SHA..HEAD (env above) and selects like a PR. + ;; + push) + # T36: the merge queue already tested this very sha. Hand its + # workspace-test result to the tier script, which reuses it only + # on a success for HEAD itself. A failed lookup is the push's own + # tier, as before -- never a reuse, never a red step. + mg=$(bash scripts/ci_mg_workspace_result.sh "${GITHUB_REPOSITORY}" "${GITHUB_SHA}") || mg="" + if [ -n "$mg" ]; then + mapfile -t mgargs <<<"$mg" + args+=("${mgargs[@]}") + else + printf 'no merge_group workspace-test result for %s: the tier is decided from the push diff\n' "${GITHUB_SHA}" + fi + ;; + esac + bash scripts/ci_test_tier.sh "${args[@]}" | tee "$RUNNER_TEMP/tier.txt" + grep -E '^(tier|crates|targets|check_workspace|reason|cite)=' "$RUNNER_TEMP/tier.txt" >> "$GITHUB_OUTPUT" # #4502 row 2: lint_passes_on_real_contracts (aprender-contracts) requires the # refinement gate to RUN whenever CI is set, and that gate compares HEAD with # merge-base(HEAD, origin/main), else the origin/main tip. This section clones at @@ -715,7 +774,6 @@ jobs: sig="$RUNNER_TEMP/sigma" mkdir -p sigma-shard printf '%s\n' "$TIER" > sigma-shard/tier - uname -m > sigma-shard/arch if [ "$TIER" = full ]; then cp "$sig/lib.junit.xml" sigma-shard/lib.junit.xml cp "${FAT_SECTION_LOG:?the fat driver sets FAT_SECTION_LOG}" sigma-shard/section.log diff --git a/crates/aprender-serve/src/kernel_registry_parity.rs b/crates/aprender-serve/src/kernel_registry_parity.rs index bdb6e3a798..0f47c86e85 100644 --- a/crates/aprender-serve/src/kernel_registry_parity.rs +++ b/crates/aprender-serve/src/kernel_registry_parity.rs @@ -743,7 +743,6 @@ fn committed() -> Vec<(serde_json::Value, serde_json::Value)> { #[test] fn committed_parity_receipts_hold_on_this_host() { let r = registry().expect("registry"); - let mut no_arch_receipt = Vec::new(); for (entry, rc) in committed() { let id = rc["kernel_id"].as_str().expect("kernel_id"); let path = entry["receipt"].as_str().expect("receipt path"); @@ -790,22 +789,8 @@ fn committed_parity_receipts_hold_on_this_host() { seed: wl["seed"].as_u64().expect("seed"), trials: dim("trials"), }; + let bound = rc["tolerance_rel"].as_f64().expect("tolerance_rel"); let now = measure(kernel(id), w, false); - // A receipt's `tolerance_rel` is the bound measured on its own `host_arch`; libm and FMA - // contraction move the last ulps between architectures. Any other arch must carry its own - // measured bound under `per_arch..tolerance_rel` -- never a borrowed one, never a skip. - let bound = if rc["host_arch"] == host_arch() { - rc["tolerance_rel"].as_f64().expect("tolerance_rel") - } else if let Some(b) = rc["per_arch"][host_arch()]["tolerance_rel"].as_f64() { - b - } else { - no_arch_receipt.push(format!( - "{path}: no {} receipt (host measured max_rel_err {})", - host_arch(), - now.max_rel_err - )); - continue; - }; assert_eq!( rc["margin"]["verdict"], "pass", "{path}: KTEST-02 margin verdict (max ≤ 1 under EM-DOT)" @@ -857,11 +842,6 @@ fn committed_parity_receipts_hold_on_this_host() { p => panic!("{id}: no precision check for {p}"), } } - assert!( - no_arch_receipt.is_empty(), - "committed receipts lack a bound for this arch:\n{}", - no_arch_receipt.join("\n") - ); } /// `KREG_INPUT_SETS_OUT`: the input-set hashes recomputed from the tree at `KREG_GIT_SHA`, one diff --git a/evidence/kreg/parity/cpu.matvec.apr_q4.json b/evidence/kreg/parity/cpu.matvec.apr_q4.json index da9c1fc945..e7929dfd71 100644 --- a/evidence/kreg/parity/cpu.matvec.apr_q4.json +++ b/evidence/kreg/parity/cpu.matvec.apr_q4.json @@ -35,15 +35,6 @@ }, "oracle": "apr_format_decode_f64", "oracle_independent": true, - "per_arch": { - "aarch64": { - "host": "gx10-pool1", - "max_rel_err": 5.568608963671898e-07, - "source": "CI job 119289824035 (PR #5871 shard 1), same seed and workload", - "tolerance_rel": 2e-6, - "ts": "2026-10-21T14:51:19Z" - } - }, "quantized_activation_oracle": null, "registry_precision": "f32", "schema": "kernel-parity-receipt/v1", diff --git a/evidence/kreg/parity/cpu.matvec.apr_q8.json b/evidence/kreg/parity/cpu.matvec.apr_q8.json index 9f6f4ac2cf..e95fb4c5c9 100644 --- a/evidence/kreg/parity/cpu.matvec.apr_q8.json +++ b/evidence/kreg/parity/cpu.matvec.apr_q8.json @@ -35,15 +35,6 @@ }, "oracle": "apr_format_decode_f64", "oracle_independent": true, - "per_arch": { - "aarch64": { - "host": "gx10-pool1", - "max_rel_err": 5.982009671170112e-07, - "source": "CI job 119289824035 (PR #5871 shard 1), same seed and workload", - "tolerance_rel": 2e-6, - "ts": "2026-10-21T14:51:19Z" - } - }, "quantized_activation_oracle": null, "registry_precision": "f32", "schema": "kernel-parity-receipt/v1", diff --git a/evidence/kreg/parity/cpu.matvec.bf16.json b/evidence/kreg/parity/cpu.matvec.bf16.json index 57309f51c6..e6aa4240c0 100644 --- a/evidence/kreg/parity/cpu.matvec.bf16.json +++ b/evidence/kreg/parity/cpu.matvec.bf16.json @@ -33,15 +33,6 @@ }, "oracle": "le_decode_half_f64", "oracle_independent": true, - "per_arch": { - "aarch64": { - "host": "gx10-pool1", - "max_rel_err": 4.068146011742945e-07, - "source": "CI job 119289824035 (PR #5871 shard 1), same seed and workload", - "tolerance_rel": 9e-7, - "ts": "2026-10-21T14:51:19Z" - } - }, "quantized_activation_oracle": null, "registry_precision": "f32", "schema": "kernel-parity-receipt/v1", diff --git a/evidence/kreg/parity/cpu.matvec.f16.json b/evidence/kreg/parity/cpu.matvec.f16.json index af25b8ed9d..17b3754d5a 100644 --- a/evidence/kreg/parity/cpu.matvec.f16.json +++ b/evidence/kreg/parity/cpu.matvec.f16.json @@ -33,15 +33,6 @@ }, "oracle": "le_decode_half_f64", "oracle_independent": true, - "per_arch": { - "aarch64": { - "host": "gx10-pool1", - "max_rel_err": 3.920968817889049e-07, - "source": "CI job 119289824035 (PR #5871 shard 1), same seed and workload", - "tolerance_rel": 8e-7, - "ts": "2026-10-21T14:51:19Z" - } - }, "quantized_activation_oracle": null, "registry_precision": "f32", "schema": "kernel-parity-receipt/v1", diff --git a/evidence/kreg/parity/cpu.matvec.f32.json b/evidence/kreg/parity/cpu.matvec.f32.json index a41e599001..9926666b94 100644 --- a/evidence/kreg/parity/cpu.matvec.f32.json +++ b/evidence/kreg/parity/cpu.matvec.f32.json @@ -33,15 +33,6 @@ }, "oracle": "le_decode_half_f64", "oracle_independent": true, - "per_arch": { - "aarch64": { - "host": "gx10-pool1", - "max_rel_err": 7.356022484897627e-07, - "source": "CI job 119289824035 (PR #5871 shard 1), same seed and workload", - "tolerance_rel": 2e-6, - "ts": "2026-10-21T14:51:19Z" - } - }, "quantized_activation_oracle": null, "registry_precision": "f32", "schema": "kernel-parity-receipt/v1", diff --git a/evidence/kreg/parity/cpu.matvec.q4_0.json b/evidence/kreg/parity/cpu.matvec.q4_0.json index d1a94f3c46..38ab583a76 100644 --- a/evidence/kreg/parity/cpu.matvec.q4_0.json +++ b/evidence/kreg/parity/cpu.matvec.q4_0.json @@ -35,15 +35,6 @@ }, "oracle": "in_tree_dequant_f64", "oracle_independent": false, - "per_arch": { - "aarch64": { - "host": "gx10-pool1", - "max_rel_err": 0.003859434297246209, - "source": "CI job 119289824035 (PR #5871 shard 1), same seed and workload", - "tolerance_rel": 0.008, - "ts": "2026-10-21T14:51:19Z" - } - }, "quantized_activation_oracle": { "max_rel_err": 1.69613194525447e-7 }, diff --git a/evidence/kreg/parity/cpu.matvec.q4_k.json b/evidence/kreg/parity/cpu.matvec.q4_k.json index 0e1ccc3ba7..501b8c768a 100644 --- a/evidence/kreg/parity/cpu.matvec.q4_k.json +++ b/evidence/kreg/parity/cpu.matvec.q4_k.json @@ -36,15 +36,6 @@ }, "oracle": "in_tree_dequant_f64", "oracle_independent": false, - "per_arch": { - "aarch64": { - "host": "gx10-pool1", - "max_rel_err": 0.005569836531516333, - "source": "CI job 119289824035 (PR #5871 shard 1), same seed and workload", - "tolerance_rel": 0.02, - "ts": "2026-10-21T14:51:19Z" - } - }, "quantized_activation_oracle": { "max_rel_err": 1.3720446505396093e-7 }, diff --git a/evidence/kreg/parity/cpu.matvec.q5_k.json b/evidence/kreg/parity/cpu.matvec.q5_k.json index d65c8a5ca9..01913cf231 100644 --- a/evidence/kreg/parity/cpu.matvec.q5_k.json +++ b/evidence/kreg/parity/cpu.matvec.q5_k.json @@ -36,15 +36,6 @@ }, "oracle": "in_tree_dequant_f64", "oracle_independent": false, - "per_arch": { - "aarch64": { - "host": "gx10-pool1", - "max_rel_err": 1.3110594897372703e-06, - "source": "CI job 119289824035 (PR #5871 shard 1), same seed and workload", - "tolerance_rel": 3e-6, - "ts": "2026-10-21T14:51:19Z" - } - }, "quantized_activation_oracle": null, "registry_precision": "f32", "schema": "kernel-parity-receipt/v1", diff --git a/evidence/kreg/parity/cpu.matvec.q6_k.json b/evidence/kreg/parity/cpu.matvec.q6_k.json index ce60279cc6..220f53b4a7 100644 --- a/evidence/kreg/parity/cpu.matvec.q6_k.json +++ b/evidence/kreg/parity/cpu.matvec.q6_k.json @@ -36,15 +36,6 @@ }, "oracle": "in_tree_dequant_f64", "oracle_independent": false, - "per_arch": { - "aarch64": { - "host": "gx10-pool1", - "max_rel_err": 1.6505971099429934e-6, - "source": "CI job 119280981403 (PR #5871 shard 1), same seed and workload", - "tolerance_rel": 4e-6, - "ts": "2026-10-21T14:37:43Z" - } - }, "quantized_activation_oracle": null, "registry_precision": "f32", "schema": "kernel-parity-receipt/v1", diff --git a/evidence/kreg/parity/cpu.matvec.q8_0.json b/evidence/kreg/parity/cpu.matvec.q8_0.json index 0f6306509e..b69febb636 100644 --- a/evidence/kreg/parity/cpu.matvec.q8_0.json +++ b/evidence/kreg/parity/cpu.matvec.q8_0.json @@ -36,15 +36,6 @@ }, "oracle": "in_tree_dequant_f64", "oracle_independent": false, - "per_arch": { - "aarch64": { - "host": "gx10-pool1", - "max_rel_err": 0.003738839019401381, - "source": "CI job 119289824035 (PR #5871 shard 1), same seed and workload", - "tolerance_rel": 0.008, - "ts": "2026-10-21T14:51:19Z" - } - }, "quantized_activation_oracle": { "max_rel_err": 2.0240994602880252e-7 }, diff --git a/scripts/check_ci_push_reuse.sh b/scripts/check_ci_push_reuse.sh index a62bb9021d..0532c5d4e3 100755 --- a/scripts/check_ci_push_reuse.sh +++ b/scripts/check_ci_push_reuse.sh @@ -285,13 +285,10 @@ hasnt "D11 pull_request ignores --mg-*: never reuse" "tier=reuse" "$OUT" # -- W: the CI wiring ------------------------------------------------------------------------------ SEC="$ROOT/ci/sections.yml" -DT="$ROOT/scripts/ci/decide_tier.sh" # the tier step body lives in one script (#5882): the section step and workspace-test-arch both run it -want "W0 the section step runs the shared decide_tier.sh" 1 \ - "$(grep -cF "bash scripts/ci/decide_tier.sh" "$SEC")" want "W1 the tier step's push arm calls the lookup for GITHUB_SHA" 1 \ - "$(grep -cF 'bash scripts/ci_mg_workspace_result.sh "${GITHUB_REPOSITORY}" "${GITHUB_SHA}"' "$DT")" + "$(grep -cF 'bash scripts/ci_mg_workspace_result.sh "${GITHUB_REPOSITORY}" "${GITHUB_SHA}"' "$SEC")" want "W2 a failed lookup is not fatal to the step (it falls to today's tier)" 1 \ - "$(grep -cF '"${GITHUB_SHA}") || mg=""' "$DT")" + "$(grep -cF '"${GITHUB_SHA}") || mg=""' "$SEC")" W3PAT="push) printf 'cited merge_group run: %s" if grep -qF "$W3PAT" "$SEC"; then w3=1; else w3=0; fi want "W3 the reuse step labels a push citation as a merge_group run, not a PR head" 1 "$w3" diff --git a/scripts/ci/decide_tier.sh b/scripts/ci/decide_tier.sh deleted file mode 100755 index 5cfa8a6d5d..0000000000 --- a/scripts/ci/decide_tier.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash -# decide_tier.sh -- which test tier this run owes (BSE-17, PMAT-1077); the one body of the -# "Decide the test tier" step, shared by the workspace-test-shard section and by the -# workspace-test-arch job that places the shards (a full tier needs ONE architecture: the -# cross-shard Σ compares every shard with one universe list). Env: GITHUB_EVENT_NAME, -# GITHUB_SHA, GITHUB_REPOSITORY, GITHUB_BASE_REF, MG_BASE_SHA, GH_TOKEN, RUNNER_TEMP, GITHUB_OUTPUT. -set -euo pipefail -args=(--event "${GITHUB_EVENT_NAME}") -# PMAT-1098 67-E2: the queue and a push to main re-derive their own -# diff from HEAD's FIRST PARENT (HEAD^1..HEAD is the PR's diff on the -# new base). actions/checkout is shallow, so that parent is not in the -# object store until we deepen -- and without it ci_test_tier.sh falls -# closed to `full` every single time, which would make the whole -# optimisation inert while looking like it worked. Best effort by -# design: if the deepen fails the decision still falls closed, and the -# warning below says so instead of the run silently paying the hour. -case "${GITHUB_EVENT_NAME}" in - merge_group) - # Operator 2026-10-17: the queue runs the quick tier over the entry's diff (base_sha = prior entry's head), - # MG_BASE_SHA..HEAD. Fetch the base; if that fails the tier falls closed to full. - bash scripts/ci/fetch_p6.sh --no-tags --depth=1 origin "${GITHUB_SHA}" "${MG_BASE_SHA}" 2>/dev/null \ - || bash scripts/ci/fetch_p6.sh --no-tags --depth=1 origin "${MG_BASE_SHA}" 2>/dev/null \ - || true - git cat-file -e "${MG_BASE_SHA}^{commit}" 2>/dev/null \ - || printf '::warning::merge_group base sha unavailable -- the tier falls closed to full\n' - ;; - push) - bash scripts/ci/fetch_p6.sh --no-tags --depth=2 origin "${GITHUB_SHA}" 2>/dev/null \ - || bash scripts/ci/fetch_p6.sh --no-tags --deepen=1 origin 2>/dev/null \ - || true - if git rev-parse -q --verify 'HEAD^1' >/dev/null 2>&1; then - printf 'first parent available: %s\n' "$(git rev-parse HEAD^1)" - else - printf '::warning::first parent unavailable after deepen -- the tier falls closed to full\n' - fi - ;; -esac -case "${GITHUB_EVENT_NAME}" in - pull_request) - bash scripts/ci/fetch_p6.sh --no-tags --depth=1 origin "+refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/${GITHUB_BASE_REF}" - # #5391: the PR's change is the diff from merge-base(base, HEAD), not the - # base tip -- main's later commits are not the PR's. --no-renames (#3664) - # lives in the helper. - bash scripts/ci/pr_touched_paths.sh "origin/${GITHUB_BASE_REF}" HEAD > "$RUNNER_TEMP/touched.txt" - args+=(--diff-from "$RUNNER_TEMP/touched.txt") - ;; - merge_group) - # Operator 2026-10-17 (cop window), verbatim: "only gating on tags/release". - # ci_test_tier.sh diffs MG_BASE_SHA..HEAD (env above) and selects like a PR. - ;; - push) - # T36: the merge queue already tested this very sha. Hand its - # workspace-test result to the tier script, which reuses it only - # on a success for HEAD itself. A failed lookup is the push's own - # tier, as before -- never a reuse, never a red step. - mg=$(bash scripts/ci_mg_workspace_result.sh "${GITHUB_REPOSITORY}" "${GITHUB_SHA}") || mg="" - if [ -n "$mg" ]; then - mapfile -t mgargs <<<"$mg" - args+=("${mgargs[@]}") - else - printf 'no merge_group workspace-test result for %s: the tier is decided from the push diff\n' "${GITHUB_SHA}" - fi - ;; -esac -bash scripts/ci_test_tier.sh "${args[@]}" | tee "$RUNNER_TEMP/tier.txt" -grep -E '^(tier|crates|targets|check_workspace|reason|cite)=' "$RUNNER_TEMP/tier.txt" >> "$GITHUB_OUTPUT" diff --git a/scripts/ci/sigma_arch_uniform.sh b/scripts/ci/sigma_arch_uniform.sh deleted file mode 100755 index 29b0b7bf26..0000000000 --- a/scripts/ci/sigma_arch_uniform.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash -# sigma_arch_uniform.sh -- the FULL-tier Σ needs every shard on ONE architecture (#5882). -# -# The fan-in compares the union of every shard's executed ids with ONE universe, the list shard 1 -# made on ITS machine. Tests gated on target_arch (crates/aprender-zram-core/src/zstd/avx2.rs has 18 -# x86_64-only ones, 141 files carry a cfg) exist in one arch's list and not the other's, so a mixed -# x86 + aarch64 shard set is red or green by PLACEMENT, and a test owed on x86 whose hash slice drew -# an aarch64 shard runs nowhere. A per-arch compare cannot fix that: each arch's shards run only -# their own slices, so each arch would be owed tests it was never given. The sound rule is -# placement: ci.yml job workspace-test-arch decides the tier first and pins the full tier's shards to -# X64. This script is the backstop that keeps the rule true after the fact: at tier full, every shard -# must have staged x86_64; a missing, blank or other arch, or a blank tier, is RED. Any other agreed -# tier owes no Σ: not checked. -# -# sigma_arch_uniform.sh DIR SHARDS DIR/sigma-shard-N/{tier,arch}, N = 1..SHARDS -# sigma_arch_uniform.sh --self-test the case table (incl. the mixed placement that went red) -set -euo pipefail - -check() { # check DIR SHARDS - local d=$1 n=$2 i t tier arch first="" tiers="" arches="" - for i in $(seq 1 "$n"); do - [ -f "$d/sigma-shard-$i/tier" ] || { echo "::error::Σ arch: shard $i staged no tier file"; return 1; } - tier=$(tr -d '[:space:]' < "$d/sigma-shard-$i/tier") - [ -n "$tier" ] || { echo "::error::Σ arch: shard $i staged a blank tier (fail closed, never read as 'not full')"; return 1; } - tiers="$tiers $tier" - done - for t in $tiers; do [ "$t" = full ] || { echo "Σ arch: tier '$t' in this run, the lib/explicit Σ is not owed, arch not checked"; return 0; }; done - # Agreement alone is not enough: 12 aarch64 shards agree and avx2.rs's x86-only tests run nowhere. - # The full tier is pinned to X64, so every shard must say x86_64. - for i in $(seq 1 "$n"); do - [ -f "$d/sigma-shard-$i/arch" ] || { echo "::error::Σ arch: shard $i staged no arch at the full tier (a missing arch is RED, never uniform)"; return 1; } - arch=$(tr -d '[:space:]' < "$d/sigma-shard-$i/arch") - [ -n "$arch" ] || { echo "::error::Σ arch: shard $i staged a blank arch at the full tier (fail closed)"; return 1; } - arches="$arches $i=$arch" - if [ "$arch" != x86_64 ]; then - echo "::error::Σ arch: full tier shard $i ran on $arch, not x86_64:$arches" - echo "::error::Σ arch: x86_64-gated tests are owed and would run nowhere; workspace-test-arch must pin the full tier to X64" - return 1 - fi - done - echo "Σ arch: every shard of the full tier ran on x86_64" -} - -self_test() { - local td rows=0 bad=0 want got - td=$(mktemp -d) - mk() { # mk TIER ARCH... -> one shard dir per arch argument ("-" = no arch file) - local t=$1 i=0 a; shift; rm -rf "${td:?}/s"; mkdir -p "$td/s" - for a in "$@"; do - i=$((i + 1)); mkdir -p "$td/s/sigma-shard-$i"; printf '%s\n' "$t" > "$td/s/sigma-shard-$i/tier" - [ "$a" = - ] || printf '%s\n' "$a" > "$td/s/sigma-shard-$i/arch" - done - } - row() { # row WANT LABEL TIER ARCH... - want=$1; local label=$2; shift 2; mk "$@" - set +e; check "$td/s" "$(($# - 1))" > /dev/null 2>&1; got=$?; set -e - rows=$((rows + 1)); [ "$got" = "$want" ] || bad=$((bad + 1)) - printf '%s want_rc=%s got_rc=%s %s\n' "$([ "$got" = "$want" ] && echo 'ok ' || echo 'BAD')" "$want" "$got" "$label" - } - row 0 "full, 12 shards all x86_64" full x86_64 x86_64 x86_64 x86_64 x86_64 x86_64 x86_64 x86_64 x86_64 x86_64 x86_64 x86_64 - row 1 "full, 12 shards all aarch64: they agree, but x86-only tests run nowhere" full aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 - row 1 "full, one shard staged a BLANK arch" full x86_64 "" x86_64 - row 1 "full, every shard staged a blank arch" full "" "" - row 1 "full, one shard staged a whitespace-only arch" full x86_64 " " x86_64 - row 1 "a blank tier refuses (never read as 'not full')" "" x86_64 x86_64 - row 1 "full, MIXED: shard 3 x86_64, the rest aarch64 (run 39620701544's placement)" full aarch64 aarch64 x86_64 aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 aarch64 - row 1 "full, MIXED: shard 1 x86_64, the rest aarch64" full x86_64 aarch64 aarch64 aarch64 - row 1 "full, one shard staged no arch" full x86_64 - x86_64 - row 0 "quick, MIXED: no Σ owed, not checked (the PR tier stays arch-neutral)" quick aarch64 x86_64 aarch64 - mk full x86_64; rm -f "${td:?}/s/sigma-shard-1/tier" - set +e; check "$td/s" 1 > /dev/null 2>&1; got=$?; set -e - rows=$((rows + 1)); [ "$got" = 1 ] || bad=$((bad + 1)) - printf '%s want_rc=1 got_rc=%s a missing tier file refuses\n' "$([ "$got" = 1 ] && echo 'ok ' || echo 'BAD')" "$got" - # Why a per-arch compare is not the fix, on the real comparator: shard 1's list is aarch64 (no - # avx2 test), the x86 shard executed one -> "executed but not in the universe". - local j="$td/j.xml" l="$td/l.json" - printf '' > "$j" - printf '{"rust-suites":{"z":{"testcases":{"a::neon":{"filter-match":{"status":"matches"}}}}}}' > "$l" - set +e; python3 "$(dirname "$0")/sigma_executed.py" nextest --junit "$j" --list-json "$l" > /dev/null 2>&1; got=$?; set -e - rows=$((rows + 1)); [ "$got" = 1 ] || bad=$((bad + 1)) - printf '%s want_rc=1 got_rc=%s sigma_executed over a mixed-arch pair (x86 test, aarch64 universe) is RED\n' "$([ "$got" = 1 ] && echo 'ok ' || echo 'BAD')" "$got" - rm -rf "${td:?}" - echo "Σ arch self-test: $((rows - bad))/$rows rows as expected" - [ "$bad" = 0 ] -} - -case "${1:-}" in - --self-test) self_test ;; - "" | -h | --help) sed -n 2,16p "$0" ;; - *) check "$1" "${2:?SHARDS}" ;; -esac diff --git a/scripts/ci_mg_reuse.sh b/scripts/ci_mg_reuse.sh index 5f133ce19e..03aa3c1e37 100755 --- a/scripts/ci_mg_reuse.sh +++ b/scripts/ci_mg_reuse.sh @@ -528,13 +528,11 @@ STUB # Each row is compared as the EXACT line (trailing comment stripped), so a third state cannot slip in. poolrows() { # poolrows WF -> one line per violated pool row local j l want - local plain="runs-on: [self-hosted, Linux, clean-room]" # arch-neutral job: cargo-deny, no X64 pin + local plain="runs-on: [self-hosted, Linux, X64, clean-room]" local lane="runs-on: \${{ github.event_name == 'merge_group' && fromJSON('[\"self-hosted\",\"Linux\",\"X64\",\"mq\"]') || fromJSON('[\"self-hosted\",\"Linux\",\"X64\",\"clean-room\"]') }}" - # #5882: the full tier is pinned X64 (one universe for the Σ), every other tier stays arch-neutral - local nlane="runs-on: \${{ needs.workspace-test-arch.outputs.tier == 'full' && (github.event_name == 'merge_group' && fromJSON('[\"self-hosted\",\"Linux\",\"X64\",\"mq\"]') || fromJSON('[\"self-hosted\",\"Linux\",\"X64\",\"clean-room\"]')) || (github.event_name == 'merge_group' && fromJSON('[\"self-hosted\",\"Linux\",\"mq\"]') || fromJSON('[\"self-hosted\",\"Linux\",\"clean-room\"]')) }}" - for j in x86-main x86-main-advisories workspace-test-arch workspace-test-shard; do + for j in x86-main x86-main-advisories workspace-test-shard; do l=$(awk -v j=" $j:" '$0 == j {f = 1; next} f && /^ [A-Za-z0-9_-]+:$/ {exit} f' "$1" 2> /dev/null | grep -m1 '^ runs-on:' | sed -E 's/^ +//; s/ +#.*$//') - want=$plain; case $j in x86-main) want=$lane ;; workspace-test-shard) want=$nlane ;; esac + want=$plain; case $j in x86-main | workspace-test-shard) want=$lane ;; esac if [ "$l" != "$want" ]; then echo "$j does not land on the clean-room pool (mq on merge_group for x86-main/workspace-test-shard) (runs-on: $l)"; fi done @@ -554,11 +552,8 @@ STUB poolmut pool-advisories-event-conditional "does not land on the clean-room pool" '/^ x86-main-advisories:$/,/^ steps:/{s/^ runs-on: .*/ runs-on: ${{ github.event_name == '"'"'merge_group'"'"' \&\& fromJSON('"'"'["clean-room"]'"'"') || fromJSON('"'"'["clean-room"]'"'"') }}/}' poolmut mq-lane-lost-on-x86-main "does not land on the clean-room pool" '/^ x86-main:$/,/^ steps:/{s/"mq"/"clean-room"/}' poolmut mq-lane-dropped-on-shard "does not land on the clean-room pool" '/^ workspace-test-shard:$/,/^ steps:/{s/^ runs-on: .*/ runs-on: [self-hosted, Linux, X64, clean-room]/}' - poolmut pr-path-takes-mq "does not land on the clean-room pool" '/^ workspace-test-shard:$/,/^ steps:/{s/\|\| fromJSON\(.\["self-hosted","Linux","clean-room"\].\)/|| fromJSON('"'"'["self-hosted","Linux","mq"]'"'"')/}' + poolmut pr-path-takes-mq "does not land on the clean-room pool" '/^ workspace-test-shard:$/,/^ steps:/{s/\|\| fromJSON\(.\["self-hosted","Linux","X64","clean-room"\].\)/|| fromJSON('"'"'["self-hosted","Linux","X64","mq"]'"'"')/}' poolmut mq-lane-third-label "does not land on the clean-room pool" '/^ x86-main:$/,/^ steps:/{s/"X64","mq"/"X64","mq","pr-only"/}' - poolmut full-tier-loses-x64-pin "does not land on the clean-room pool" '/^ workspace-test-shard:$/,/^ steps:/{s/"Linux","X64",/"Linux",/g}' - poolmut every-tier-pinned-x64 "does not land on the clean-room pool" '/^ workspace-test-shard:$/,/^ steps:/{s/^ runs-on: .*/ runs-on: ${{ github.event_name == '"'"'merge_group'"'"' \&\& fromJSON('"'"'["self-hosted","Linux","X64","mq"]'"'"') || fromJSON('"'"'["self-hosted","Linux","X64","clean-room"]'"'"') }}/}' - poolmut tier-job-off-the-pool "does not land on the clean-room pool" '/^ workspace-test-arch:$/,/^ steps:/{s/clean-room/build/}' # S1 (20X): mg-reuse runs on push only, so on merge_group it is SKIPPED and every dependent # runs only because its own `if:` carries !cancelled()/always() (a skipped need otherwise skips From f84b26ed603e0a2039ee46874bee4e54b4e0ef28 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 03:17:25 +0200 Subject: [PATCH 08/26] ci(shard): !cancelled() must not hide a failed workspace-test-arch (Refs #5885) Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df1e99a3e3..0c9b3463cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -500,7 +500,7 @@ jobs: workspace-test-shard: # T43b: a merge_group entry whose tree equals a green PR run's skips the shards (mg-reuse-wt); anything else runs them needs: [workspace-test-arch, mg-reuse-wt] - if: ${{ !cancelled() && needs.mg-reuse-wt.outputs.wt != '1' && github.event.pull_request.draft != true }} + if: ${{ !cancelled() && needs.workspace-test-arch.result == 'success' && needs.mg-reuse-wt.outputs.wt != '1' && github.event.pull_request.draft != true }} # arch-neutral (operator 2026-10-21 14:18Z) EXCEPT the full tier, which needs one arch for the Σ (#5882, workspace-test-arch) # clean-room for pull_request/push, mq for a merge_group entry runs-on: ${{ needs.workspace-test-arch.outputs.tier == 'full' && (github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","X64","mq"]') || fromJSON('["self-hosted","Linux","X64","clean-room"]')) || (github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","mq"]') || fromJSON('["self-hosted","Linux","clean-room"]')) }} From aa6fd1f7e63542989e102320535867ed0420706a Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 03:29:08 +0200 Subject: [PATCH 09/26] ci(mg-reuse): refuse wt=1 unless the cited PR run really ran tests (receipt-reuse runs are green over zero tests) Adds workspace-test-ran marker (skipped when tier=reuse), refusal R:k6-wt-ran, receipt 12/12-green row expecting wt=0, missing-marker row. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 10 ++++++++++ scripts/ci_mg_reuse.sh | 12 ++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c9b3463cc..26ebfb3cd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -497,6 +497,16 @@ jobs: GH_TOKEN: ${{ github.token }} MG_BASE_SHA: ${{ github.event.merge_group.base_sha }} run: bash scripts/ci/decide_tier.sh + # mg-reuse-wt may cite a PR run only if that run really tested the tree. A receipt-reuse run (#5850, tier=reuse) + # runs no test step, so its shards are green over zero tests: this marker is skipped there, and ci_mg_reuse.sh + # refuses wt=1 unless it succeeded (R:k6-wt-ran). + workspace-test-ran: + needs: [workspace-test-arch] + if: ${{ github.event_name == 'pull_request' && needs.workspace-test-arch.result == 'success' && needs.workspace-test-arch.outputs.tier != 'reuse' && github.event.pull_request.draft != true }} + runs-on: [self-hosted, Linux, clean-room] + timeout-minutes: 5 + steps: + - run: echo "this run owes and runs the test suite (tier=${{ needs.workspace-test-arch.outputs.tier }})" workspace-test-shard: # T43b: a merge_group entry whose tree equals a green PR run's skips the shards (mg-reuse-wt); anything else runs them needs: [workspace-test-arch, mg-reuse-wt] diff --git a/scripts/ci_mg_reuse.sh b/scripts/ci_mg_reuse.sh index 57b15a66d4..be40036306 100755 --- a/scripts/ci_mg_reuse.sh +++ b/scripts/ci_mg_reuse.sh @@ -129,6 +129,9 @@ decide() { wcite="run $rid attempt $att on ${h:0:10}" # ...and EVERY shard of the matrix is present: (N, M) names must agree on M and number M (a partial list is not a run). [ "$(concl "$jobs" workspace-test)" = success ] && [ "${msh:-0}" -ge 1 ] 2> /dev/null && [ "${nsh:-0}" = "${msh:-0}" ] && [ "${nbad:-1}" = 0 ] && wt=1 # R:k6-wt-all-shards + # ...and the run owed and ran a test suite: a receipt-reuse PR run (#5850 tier=reuse) skips the test step, so its 12/12 shards are green over ZERO tests; + # `workspace-test-ran` runs only when the tier is not reuse, so only a run that tested the tree may be cited. + [ "$(concl "$jobs" workspace-test-ran)" = success ] || wt=0 # R:k6-wt-ran # a reuse with nothing to cite is not a reuse [[ "$wcite" =~ ^run\ [0-9]+\ attempt\ [0-9]+\ on\ [0-9a-f]{10}$ ]] || wt=0 # R:k6-wt-cite mc=1 @@ -413,8 +416,11 @@ self_test() { sed '/^workspace-test /d' <<< "$GREEN_JOBS" | jobs_json "$H" 1001 0 > "$F/jobs.no-wt" # shard job names are the real matrix names, "workspace-test-shard (N, M)" (MG run 39666213701); sh4 N... = those N of 4, success sh4() { local n; for n in "$@"; do printf 'workspace-test-shard (%s, 4)\tsuccess\n' "$n"; done; } - { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-ok" - sed "s|^determinism\tsuccess\$|determinism\tfailure|" <<< "$GREEN_JOBS" | { cat; sh4 1 2 3 4; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-x86fail" + ran() { printf 'workspace-test-ran\t%s\n' "$1"; } + { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; ran success; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-ok" + { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; ran skipped; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-receipt" + { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-noran" + sed "s|^determinism\tsuccess\$|determinism\tfailure|" <<< "$GREEN_JOBS" | { cat; sh4 1 2 3 4; ran success; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-x86fail" { cat <<< "$GREEN_JOBS"; sh4 1 3 4; printf 'workspace-test-shard (2, 4)\tfailure\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-shard-failed" { cat <<< "$GREEN_JOBS"; sh4 1 2 3; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-partial" { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; printf 'workspace-test-shard (5, 12)\tsuccess\n'; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-mixed-m" @@ -431,6 +437,8 @@ self_test() { wrow "$n" "$want" --event merge_group --queue-head "$m" --base "$b" --pr-head "$h" --runs "$F/$r" --jobs "$F/$j"; } wd identical-tree-skips 1 "$M" "$B" "$H" runs.ok jobs.wt-ok wd draft-run-no-shards-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-draft + wd receipt-reuse-12of12-green-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-receipt + wd ran-job-missing-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-noran wd greenverdict-zero-shards 0 "$M" "$B" "$H" runs.ok jobs.ok wd one-shard-failed-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-shard-failed wd partial-shards-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-partial From e8bc5fe5531b16723f5e73e70c6010d99845375c Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 03:32:11 +0200 Subject: [PATCH 10/26] ci(mg-reuse): once receipt-judge exists the test-ran marker must need it and key on its verdict mgshape rule + 2 mutants (marker ignores judge; needs it but if ignores it). Red on the merge of #5850 until the marker is wired to receipt-judge.outputs.ok. Co-Authored-By: Claude Sonnet 5.5 --- scripts/ci_mg_reuse.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/ci_mg_reuse.sh b/scripts/ci_mg_reuse.sh index be40036306..e97cfc811f 100755 --- a/scripts/ci_mg_reuse.sh +++ b/scripts/ci_mg_reuse.sh @@ -648,12 +648,15 @@ STUB awk -v tok='(^|[^A-Za-z0-9_-])mg-reuse($|[^A-Za-z0-9_-])' -v want="if: github.event_name == 'push' && github.ref == 'refs/heads/main'" ' /^ [A-Za-z0-9_-]+:$/ { j = $1; sub(/:$/, "", j); seen[j] = 1; next } /^ if:/ { jif[j] = $0; sub(/^ /, "", jif[j]) } + /^ needs:/ || (inn && /^ /) { jneeds[j] = jneeds[j] $0 } /^ needs:/ { inn = 1; if ($0 ~ tok) dep[j] = 1; next } inn && /^ / { if ($0 ~ tok) dep[j] = 1; next } /^ [A-Za-z]/ { inn = 0 } END { if (jif["mg-reuse"] != want) print "mg-reuse job-level if is not the push-only key: [" jif["mg-reuse"] "]" if (jif["mg-reuse"] ~ /merge_group/) print "mg-reuse if names merge_group: a queue entry could reuse" + # once the receipt judge exists, the test-ran marker must be keyed on its verdict, else a receipt run reads as tested + if (("receipt-judge" in seen) && (jneeds["workspace-test-ran"] !~ /receipt-judge/ || jif["workspace-test-ran"] !~ /receipt-judge\.outputs\.ok/)) print "workspace-test-ran ignores receipt-judge: a receipt-reuse run would read as tested" n = split("x86-main determinism x86-main-advisories ci-gate gate", must, " ") for (i = 1; i <= n; i++) { if (!(must[i] in seen)) print "job " must[i] " is missing from the workflow" @@ -681,6 +684,9 @@ STUB mgmut x86-main-needs-lookalike "job x86-main no longer needs mg-reuse" '/^ x86-main:$/,/^ if:/{s/^ needs: \[mg-reuse\]$/ needs: [mg-reuse-self-test]/}' mgmut x86-main-drops-needs "job x86-main no longer needs mg-reuse" '/^ x86-main:$/,/^ if:/{/^ needs: \[mg-reuse\]$/d}' mgmut gate-if-loses-always "job gate needs mg-reuse but its if lacks" "/^ gate:\$/,/^ steps:/{s/^ if: always\(\)/ if: success()/}" + # once a receipt-judge job exists (#5850), the marker must need it and key its if on the verdict + mgmut ran-marker-ignores-receipt-judge "workspace-test-ran ignores receipt-judge" 's#^ workspace-test-ran:$# receipt-judge:\n runs-on: x\n workspace-test-ran:#' + mgmut ran-marker-needs-judge-but-if-ignores-it "workspace-test-ran ignores receipt-judge" '/^ workspace-test-ran:$/,/^ runs-on:/{s#^ needs: \[workspace-test-arch\]$# needs: [workspace-test-arch, receipt-judge]#};s#^ workspace-test-ran:$# receipt-judge:\n runs-on: x\n workspace-test-ran:#' # Positive control for wire itself: one job of more than 64 KiB (a pipe buffer) whose # wanted line comes first. A pipe from job into grep -q takes SIGPIPE on this every From 1c8098954c1710ebe51f0fbc1de934066a6459c4 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 07:02:06 +0200 Subject: [PATCH 11/26] fix(ci): regenerate roadmap aggregate after merge; rewrite shards() jq without $s[] so the bashrs ratchet holds at 6 x86-main guard-tree: check_roadmap_fragment_required (aggregate drift) and check_shell_lint_ratchet (6 -> 9: SC1087 on the jq array expansions). Co-Authored-By: Claude Sonnet 5.5 --- docs/roadmaps/roadmap.yaml | 7 +++++++ scripts/ci_mg_reuse.sh | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/roadmaps/roadmap.yaml b/docs/roadmaps/roadmap.yaml index d26fe5e4f2..abbeef5d40 100644 --- a/docs/roadmaps/roadmap.yaml +++ b/docs/roadmaps/roadmap.yaml @@ -25449,6 +25449,13 @@ Not done, pending the ruling: moving the 31 fixture files to an obviously synthe acceptance_criteria: - 'merge_group skips workspace-test-shard only when K1-K5 hold and the PR head run workspace-test succeeded; any difference runs the full shards' - 'ci_mg_reuse.sh --self-test falsifier: changed main/tree -> wt=0, identical tree -> wt=1, override carries wt only when exactly 1' + phases: [] + subtasks: [] + estimated_effort: null + labels: + - kind:code + notes: null + epic: 5510 - id: PMAT-5888 github_issue: 5888 item_type: task diff --git a/scripts/ci_mg_reuse.sh b/scripts/ci_mg_reuse.sh index e4d5569ab6..75ef965fb8 100755 --- a/scripts/ci_mg_reuse.sh +++ b/scripts/ci_mg_reuse.sh @@ -71,8 +71,8 @@ concl() { # name agrees on (0 if they disagree). A draft PR run (or a reused one) has green `workspace-test` and NO such jobs (#5885 lane-b). shards() { jq -r '[.jobs[]? | select(.name | test("^workspace-test-shard \\([0-9]+, [0-9]+\\)$"))] as $s - | ([$s[].name | capture(", (?[0-9]+)\\)$").m | tonumber] | unique) as $m - | "\($s | map(.name) | unique | length) \([$s[] | select(.conclusion != "success")] | length) \(if ($m | length) == 1 then $m[0] else 0 end)"' "$1" 2> /dev/null || echo "0 1 0" + | ($s | map(.name | capture(", (?[0-9]+)\\)$").m | tonumber) | unique) as $m + | "\($s | map(.name) | unique | length) \($s | map(select(.conclusion != "success")) | length) \(if ($m | length) == 1 then ($m | first) else 0 end)"' "$1" 2> /dev/null || echo "0 1 0" } decide() { From f6014e774ce49b9db6dc47a5fb071542cee85d1a Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 07:14:37 +0200 Subject: [PATCH 12/26] ci(receipt-gate): judge_ok pins shard fallback, trust dir, ok=0 guard, FETCH_HEAD compare; W20c-f mutants (Refs #5850) Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_receipt_gate.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index 4dc7f54c1c..dd6ce467ac 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -200,6 +200,10 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy shard=$(awk '/^ workspace-test-shard:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$1") case "$shard" in *'needs: [receipt-judge]'*) ;; *) return 1 ;; esac case "$shard" in *'RECEIPT_OK: ${{ needs.receipt-judge.outputs.ok'*) ;; *) return 1 ;; esac + case "$shard" in *"outputs.ok || '0' }}"*) ;; *) return 1 ;; esac + case "$code" in *'"$RUNNER_TEMP/pr-body.md" "$GITHUB_WORKSPACE" "${GITHUB_SHA}"'*) ;; *) return 1 ;; esac + case "$code" in *'[ "$ok" = 1 ] || ok=0'*) ;; *) return 1 ;; esac + case "$code" in *'[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ]'*) ;; *) return 1 ;; esac tier=$(awk '/id: tier$/{on=1} on{print} /--receipt-ok/{if(on)exit}' "$2" | grep -v -E '^[[:space:]]*#') case "$tier" in *'"${RECEIPT_OK:-0}" = 1'*) ;; *) return 1 ;; esac printf '%s\n' "$tier" | grep -E 'scripts/|git (fetch|clone)' | grep -q . && return 1 @@ -228,6 +232,10 @@ wmut "W28 MUTANT: a judge that leaves the git token env set is caught" ci 'unset wmut "W33 MUTANT: a judge not gated on the runner-readiness variable (it would queue forever with no runner) is caught" ci " && vars.RECEIPT_JUDGE_RUNNER_READY == 'true'" '' wmut "W34 MUTANT: the judge back on a GitHub-hosted image is caught" ci 'runs-on: [self-hosted, Linux, X64, receipt-judge]' 'runs-on: ubuntu-latest' wmut "W20 MUTANT: a shard job that does not wait for the judge is caught" ci 'needs: [receipt-judge]' 'needs: []' +wmut "W20c MUTANT (MA): the shard RECEIPT_OK fallback flipped to 1 is caught" ci "outputs.ok || '0' }}" "outputs.ok || '1' }}" +wmut "W20d MUTANT (MB): the verifier trusting \$GITHUB_WORKSPACE no longer, but the PR tree, is caught" ci '"$RUNNER_TEMP/pr-body.md" "$GITHUB_WORKSPACE"' '"$RUNNER_TEMP/pr-body.md" "$RUNNER_TEMP/prtree"' +wmut "W20e MUTANT (MC): the ok=0 guard turned into ok=1 is caught" ci '[ "$ok" = 1 ] || ok=0' '[ "$ok" = 1 ] || ok=1' +wmut "W20f MUTANT (MD): the FETCH_HEAD == GITHUB_SHA compare dropped is caught" ci '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] &&' 'true &&' wmut "W20b MUTANT: a tier step that runs PR-tree code is caught" sec 'rok=0 if [ "${GITHUB_EVENT_NAME}" = pull_request ]' 'rok=0 bash scripts/ci/pre.sh From a12b8296166f7503fa17e6b11156d516e5f65a71 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 07:21:50 +0200 Subject: [PATCH 13/26] =?UTF-8?q?fix(ci):=20#5885=20re-seat=20findings=20?= =?UTF-8?q?=E2=80=94=20non-vacuous=20mg-reuse=20rows,=20no=20transitive=20?= =?UTF-8?q?T36=20reuse,=20ran-marker=20after=20shards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. the four wt rows (changed-tree, changed-main, base-is-a-merge, run-in-progress) now carry fully-tested shard + ran fixtures, so wt=0 comes from their named reason alone 2. ci_mg_workspace_result.sh: a green merge-queue workspace-test is reused only if that run's own workspace-test-shard jobs all succeeded (3rd call; R-JOBSAPI, R-SHARDS; rows L14-L18) 3. mg-reuse-wt runs on the mq lane (same as the shards it gates), 5 min timeout 4. workspace-test-ran needs the shards and runs only after they succeeded 5. PLANTED_4655_JOBS provenance comment states the shard names were rewritten Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 14 ++++++++---- scripts/check_ci_push_reuse.sh | 38 +++++++++++++++++++++++++++++-- scripts/ci_mg_reuse.sh | 17 ++++++++------ scripts/ci_mg_workspace_result.sh | 17 ++++++++++++-- 4 files changed, 70 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cedf82b57c..35025bb6ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,8 +147,9 @@ jobs: # are NOT read here: they stay un-reused on the queue (20X r2), so x86-main still never waits for this job. mg-reuse-wt: if: github.event_name == 'merge_group' - runs-on: [self-hosted, Linux, X64, clean-room] - timeout-minutes: 10 + # same lane as the shards it gates (mq on a queue entry); the job only runs on merge_group, the clean-room arm just names the pool for the label guard + runs-on: ${{ github.event_name == 'merge_group' && fromJSON('["self-hosted","Linux","X64","mq"]') || fromJSON('["self-hosted","Linux","X64","clean-room"]') }} + timeout-minutes: 5 permissions: contents: read actions: read @@ -159,6 +160,7 @@ jobs: steps: - uses: actions/checkout@v7 with: + # full history: K3 (merge-base --is-ancestor) walks the PR head back to the base; the mq runner keeps a warm clone fetch-depth: 0 - name: Read the workspace-test reuse key for this queue entry (3 API calls) id: key @@ -510,11 +512,13 @@ jobs: # explicit runner's round-robin N/12), in parallel on the X64 clean-room pool. # The section body is ci/sections.yml `workspace-test-shard`, unchanged. # mg-reuse-wt may cite a PR run only if that run really tested the tree. A receipt-reuse run (#5850, tier=reuse) - # runs no test step, so its shards are green over zero tests: this marker is skipped there, and ci_mg_reuse.sh - # refuses wt=1 unless it succeeded (R:k6-wt-ran). Once #5850's receipt-judge job exists, ci_mg_reuse.sh --self-test + # runs no test step, so its shards are green over zero tests. This marker needs the shards and runs only after + # they SUCCEEDED, so a run whose shards were skipped or failed never has it; ci_mg_reuse.sh refuses wt=1 unless it + # succeeded (R:k6-wt-ran). Once #5850's receipt-judge job exists, ci_mg_reuse.sh --self-test # (mgshape) is RED until this job needs it and keys its if on receipt-judge.outputs.ok. workspace-test-ran: - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft != true }} + needs: [workspace-test-shard] + if: ${{ !cancelled() && needs.workspace-test-shard.result == 'success' && github.event_name == 'pull_request' && github.event.pull_request.draft != true }} runs-on: [self-hosted, Linux, clean-room] timeout-minutes: 5 steps: diff --git a/scripts/check_ci_push_reuse.sh b/scripts/check_ci_push_reuse.sh index 0532c5d4e3..303a3e6544 100755 --- a/scripts/check_ci_push_reuse.sh +++ b/scripts/check_ci_push_reuse.sh @@ -137,10 +137,14 @@ OTHER=2222222222222222222222222222222222222222 QUEUE=gh-readonly-queue/main/pr-4700-0000000000000000000000000000000000000000 suite() { printf '{"id":%s,"head_branch":"%s","app":{"slug":"%s"}}' "$1" "$2" "${3:-github-actions}"; } crun() { printf '{"status":"%s","conclusion":%s,"head_sha":"%s","completed_at":"%s","check_suite":{"id":%s},"details_url":"https://github.com/o/r/actions/runs/%s/job/9"}' "$1" "$2" "$3" "$4" "$5" "$6"; } +sjobs() { # sjobs ... -> a jobs body whose workspace-test-shard (n, 4) jobs all succeeded (the queue run really tested) + local n; { for n in "$@"; do printf '{"name":"workspace-test-shard (%s, 4)","conclusion":"success"}\n' "$n"; done; printf '{"name":"gate","conclusion":"success"}\n'; } | jq -s '{jobs: .}' +} canned() { # canned - rm -f "${T:?}/canned/suites.FAIL" "${T:?}/canned/runs.FAIL" "${T:?}/canned/calls" + rm -f "${T:?}/canned/suites.FAIL" "${T:?}/canned/runs.FAIL" "${T:?}/canned/mgjobs.FAIL" "${T:?}/canned/calls" printf '{"check_suites":[%s]}' "$1" >"$T/canned/suites.json" printf '{"check_runs":[%s]}' "$2" >"$T/canned/runs.json" + sjobs 1 2 3 4 >"$T/canned/mgjobs.json" } look() { OUT="$(bash "$LOOKUP" o/r "${1:-$SHA}" 2>"$T/err")"; RC=$?; ERR="$(cat "$T/err")"; } flat() { tr '\n' ' ' <<<"$1" | sed 's/ $//'; } @@ -154,7 +158,7 @@ look want "L1 a merge-queue workspace-test success on the sha -> found, exit 0" 0 "$RC" want "L1 prints the run, the sha the API reports, the conclusion (the push suite's later failure is not the queue's)" \ "--mg-run 7001 --mg-sha $SHA --mg-conclusion success" "$(flat "$OUT")" -want "L1 costs exactly 2 API calls" 2 "$(wc -l <"$T/canned/calls" | tr -d ' ')" +want "L1 costs exactly 3 API calls (suites, check-runs, the run's jobs)" 3 "$(wc -l <"$T/canned/calls" | tr -d ' ')" canned "$(suite 50 "$QUEUE")" "$(crun completed '"failure"' $SHA 2026-10-04T10:00:00Z 50 7003)" look @@ -213,6 +217,36 @@ canned "$(suite 50 "$QUEUE")" "$(crun completed '"success"' abc 2026-10-04T10:00 look want "L13 the check run reports a head_sha that is not a sha -> nothing, exit 1" "1:" "$RC:$OUT" +# L14-L18: a queue run whose green `workspace-test` did not run its own shards is a citation, not a measurement (finding: transitive reuse) +canned "$(suite 50 "$QUEUE")" "$(crun completed '"success"' $SHA 2026-10-04T10:00:00Z 50 7015)" +sjobs 1 2 | jq '.jobs += [{"name":"workspace-test-shard (3, 4)","conclusion":"skipped"},{"name":"workspace-test-shard (4, 4)","conclusion":"skipped"}]' >"$T/canned/mgjobs.json" +look +want "L14 the queue run skipped its shards (it cited a PR run) -> nothing, exit 1" "1:" "$RC:$OUT" +has "L14 says why" "did not run and pass its own workspace-test shards" "$ERR" + +canned "$(suite 50 "$QUEUE")" "$(crun completed '"success"' $SHA 2026-10-04T10:00:00Z 50 7016)" +sjobs 1 2 3 | jq '.jobs += [{"name":"workspace-test-shard (4, 4)","conclusion":"failure"}]' >"$T/canned/mgjobs.json" +look +want "L15 one shard failed under a green verdict -> nothing, exit 1" "1:" "$RC:$OUT" + +canned "$(suite 50 "$QUEUE")" "$(crun completed '"success"' $SHA 2026-10-04T10:00:00Z 50 7017)" +sjobs >"$T/canned/mgjobs.json" +look +want "L16 the queue run has no shard job at all -> nothing, exit 1" "1:" "$RC:$OUT" + +canned "$(suite 50 "$QUEUE")" "$(crun completed '"success"' $SHA 2026-10-04T10:00:00Z 50 7018)" +: >"$T/canned/mgjobs.FAIL" +look +want "L17 the jobs call fails -> nothing, exit 1" "1:" "$RC:$OUT" +has "L17 names the failed call" "jobs lookup failed" "$ERR" + +canned "$(suite 50 "$QUEUE")" "$(crun completed '"failure"' $SHA 2026-10-04T10:00:00Z 50 7019)" +sjobs >"$T/canned/mgjobs.json" +look +want "L18 a failed verdict is still printed without a jobs call (only a success is checked for citation)" \ + "--mg-run 7019 --mg-sha $SHA --mg-conclusion failure" "$(flat "$OUT")" +want "L18 and it cost 2 API calls" 2 "$(wc -l <"$T/canned/calls" | tr -d ' ')" + # -- D: the decision ------------------------------------------------------------------------------- mkrepo() { # mkrepo -> a two-commit repo whose push diff is docs-only (today's tier: none, no cargo) git init -q -b main "$1" diff --git a/scripts/ci_mg_reuse.sh b/scripts/ci_mg_reuse.sh index 75ef965fb8..9d7ff2ef0f 100755 --- a/scripts/ci_mg_reuse.sh +++ b/scripts/ci_mg_reuse.sh @@ -298,9 +298,10 @@ resolve_push() { } # ---------------------------------------------------------------- case table -# The jobs of run 37040341824, #4655's only PR-head run (db86acf021), verbatim -# from the API (two GPU-host leg job names replaced; the key never reads them): -# x86-main failed while gate and ci / gate passed. +# The jobs of run 37040341824, #4655's only PR-head run (db86acf021), from the API: two GPU-host leg job names +# replaced (the key never reads them), and the workspace-test-shard names rewritten from the run's old names to the +# live matrix form "workspace-test-shard (N, M)" (MG run 39666213701), so this is NOT verbatim. The verdict shape is +# the planted one: x86-main failed while gate and ci / gate passed. PLANTED_4655_JOBS=$'gpu-touched\tsuccess\nmutants-table-scope\tsuccess\ndeterminism\tsuccess\nworkspace-test-shard (1, 4)\tsuccess\nmac-check\tsuccess\nworkspace-test-shard (3, 4)\tsuccess\nworkspace-test-shard (2, 4)\tsuccess\nx86-main\tfailure\nmutants-table\tskipped\nmutants-shard\tskipped\ngpu-host-leg-a\tsuccess\ngpu-host-leg-b\tsuccess\nworkspace-test\tsuccess\nci / gate\tsuccess\ngate\tsuccess' GREEN_JOBS=$'x86-main\tsuccess\ndeterminism\tsuccess\nmac-check\tsuccess\nworkspace-test\tsuccess\nci / gate\tsuccess\ngate\tsuccess' @@ -418,6 +419,8 @@ self_test() { sh4() { local n; for n in "$@"; do printf 'workspace-test-shard (%s, 4)\tsuccess\n' "$n"; done; } ran() { printf 'workspace-test-ran\t%s\n' "$1"; } { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; ran success; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-ok" + # the same fully-tested jobs on the other head, so a row whose reason is the K1-K5 key reaches wt=0 for that reason alone + { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; ran success; } | jobs_json "$H2" 1001 0 > "$F/jobs.wt-ok-h2" { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; ran skipped; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-receipt" { cat <<< "$GREEN_JOBS"; sh4 1 2 3 4; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-noran" sed "s|^determinism\tsuccess\$|determinism\tfailure|" <<< "$GREEN_JOBS" | { cat; sh4 1 2 3 4; ran success; } | jobs_json "$H" 1001 0 > "$F/jobs.wt-x86fail" @@ -444,12 +447,12 @@ self_test() { wd partial-shards-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-partial wd mixed-shard-totals-runs 0 "$M" "$B" "$H" runs.ok jobs.wt-mixed-m wd empty-attempt-no-cite-runs 0 "$M" "$B" "$H" runs.no-attempt jobs.wt-ok - wd changed-tree-runs 0 "$M_TREE" "$B" "$H" runs.ok jobs.ok - wd changed-main-not-ancestor 0 "$M_K3" "$B2" "$H" runs.ok jobs.ok - wd base-is-a-merge-runs 0 "$M_K2" "$BM" "$H2" runs.h2 jobs.h2 + wd changed-tree-runs 0 "$M_TREE" "$B" "$H" runs.ok jobs.wt-ok + wd changed-main-not-ancestor 0 "$M_K3" "$B2" "$H" runs.ok jobs.wt-ok + wd base-is-a-merge-runs 0 "$M_K2" "$BM" "$H2" runs.h2 jobs.wt-ok-h2 wd head-wt-failed-runs 0 "$M" "$B" "$H" runs.ok jobs.fail-wt wd head-wt-missing-runs 0 "$M" "$B" "$H" runs.ok jobs.no-wt - wd run-in-progress-runs 0 "$M" "$B" "$H" runs.newer-running jobs.ok + wd run-in-progress-runs 0 "$M" "$B" "$H" runs.newer-running jobs.wt-ok wd head-x86-failed-still-skips 1 "$M" "$B" "$H" runs.ok jobs.wt-x86fail wrow push-never-skips 0 --event push --queue-head "$M" --base "$B" --pr-head "$H" --runs "$F/runs.ok" --jobs "$F/jobs.ok" # the override passes wt only as exactly 1, with its citation diff --git a/scripts/ci_mg_workspace_result.sh b/scripts/ci_mg_workspace_result.sh index 2493b96e70..844d7c6bd0 100644 --- a/scripts/ci_mg_workspace_result.sh +++ b/scripts/ci_mg_workspace_result.sh @@ -13,7 +13,10 @@ # Actions suite of a merge-queue branch (gh-readonly-queue/...). It reports what it found, a # failure included; judging it is ci_test_tier.sh's job. # -# Exactly two API calls, both on the checks endpoints (the ones the merge_group arm already reads). +# Three API calls: two on the checks endpoints (the ones the merge_group arm already reads) and, for a success, one +# on the found run's jobs. A queue run that SKIPPED its workspace-test shards (it cited a PR run, mg-reuse-wt #5884) +# also reports a green `workspace-test`; reusing that on the push would cite a citation, so the lookup refuses +# unless every `workspace-test-shard (N, M)` job of that run succeeded. # Anything that is not a clean answer prints NOTHING on stdout, one reason on stderr, and exits 1: # the caller then decides the tier from the push's own diff, as before T36. # @@ -50,4 +53,14 @@ done <<<"$runs" read -r hsha concl url <<<"$found" [[ $hsha =~ ^[0-9a-f]{40}$ ]] || no "the workspace-test check run reports no sha" # R-HEADSHA [[ $url =~ /actions/runs/([0-9]+)(/|$) ]] || no "the workspace-test details_url names no run id: $url" # R-RUNID -printf -- '--mg-run\n%s\n--mg-sha\n%s\n--mg-conclusion\n%s\n' "${BASH_REMATCH[1]}" "$hsha" "$concl" +rid=${BASH_REMATCH[1]} + +# Call 3 (a success only): the shards of that run. A skipped, failed or absent shard means the green +# `workspace-test` was a citation of another run (or tested nothing), never this tree's own measurement. +if [ "$concl" = success ]; then + jobs=$(gh api "repos/$repo/actions/runs/$rid/jobs?per_page=100&filter=latest"); rc=$? + [ "$rc" -eq 0 ] || no "the jobs lookup failed for run $rid" # R-JOBSAPI + shards=$(jq -r '[.jobs[]? | select(.name | test("^workspace-test-shard \\([0-9]+, [0-9]+\\)$"))] | if length == 0 then "none" elif all(.conclusion == "success") then "ok" else "no" end' <<<"$jobs" 2>/dev/null) + [ "$shards" = ok ] || no "run $rid did not run and pass its own workspace-test shards (${shards:-unreadable}): its green verdict may be a citation" # R-SHARDS +fi +printf -- '--mg-run\n%s\n--mg-sha\n%s\n--mg-conclusion\n%s\n' "$rid" "$hsha" "$concl" From aa8b4ba9ee43bc20c9f5472de99a02c731b1e39d Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 07:34:27 +0200 Subject: [PATCH 14/26] =?UTF-8?q?test(ci):=20guard=20#5885=20fixes=203=20a?= =?UTF-8?q?nd=204=20=E2=80=94=20mgshape=20rules=20+=203=20mutants=20shown?= =?UTF-8?q?=20RED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mgshape now reports workspace-test-ran without needs: workspace-test-shard, workspace-test-ran whose if lacks the shard-success clause, and mg-reuse-wt off the mq lane; one mutant each. Co-Authored-By: Claude Sonnet 5.5 --- scripts/ci_mg_reuse.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/ci_mg_reuse.sh b/scripts/ci_mg_reuse.sh index 9d7ff2ef0f..91e3fc9158 100755 --- a/scripts/ci_mg_reuse.sh +++ b/scripts/ci_mg_reuse.sh @@ -646,6 +646,7 @@ STUB awk -v tok='(^|[^A-Za-z0-9_-])mg-reuse($|[^A-Za-z0-9_-])' -v want="if: github.event_name == 'push' && github.ref == 'refs/heads/main'" ' /^ [A-Za-z0-9_-]+:$/ { j = $1; sub(/:$/, "", j); seen[j] = 1; next } /^ if:/ { jif[j] = $0; sub(/^ /, "", jif[j]) } + /^ runs-on:/ { jrun[j] = $0 } /^ needs:/ || (inn && /^ /) { jneeds[j] = jneeds[j] $0 } /^ needs:/ { inn = 1; if ($0 ~ tok) dep[j] = 1; next } inn && /^ / { if ($0 ~ tok) dep[j] = 1; next } @@ -655,6 +656,11 @@ STUB if (jif["mg-reuse"] ~ /merge_group/) print "mg-reuse if names merge_group: a queue entry could reuse" # once the receipt judge exists, the test-ran marker must be keyed on its verdict, else a receipt run reads as tested if (("receipt-judge" in seen) && (jneeds["workspace-test-ran"] !~ /receipt-judge/ || jif["workspace-test-ran"] !~ /receipt-judge\.outputs\.ok/)) print "workspace-test-ran ignores receipt-judge: a receipt-reuse run would read as tested" + # the marker must prove the shards ran: it needs them and its if requires their success (else a run with skipped shards owns it) + if (jneeds["workspace-test-ran"] !~ /workspace-test-shard/) print "workspace-test-ran does not need workspace-test-shard: the marker no longer proves the shards ran" + if (jif["workspace-test-ran"] !~ /needs\.workspace-test-shard\.result == .success./) print "workspace-test-ran if lacks the shard success clause: skipped or failed shards would still own the marker" + # the reuse lookup gates every queue entry shard, so it shares their lane (mq), never the PR clean-room pool + if (jrun["mg-reuse-wt"] !~ /"mq"/) print "mg-reuse-wt is not on the mq lane: it would queue behind PR work ahead of every shard" n = split("x86-main determinism x86-main-advisories ci-gate gate", must, " ") for (i = 1; i <= n; i++) { if (!(must[i] in seen)) print "job " must[i] " is missing from the workflow" @@ -685,6 +691,9 @@ STUB # once a receipt-judge job exists (#5850), the marker must need it and key its if on the verdict mgmut ran-marker-ignores-receipt-judge "workspace-test-ran ignores receipt-judge" 's#^ workspace-test-ran:$# receipt-judge:\n runs-on: x\n workspace-test-ran:#' mgmut ran-marker-needs-judge-but-if-ignores-it "workspace-test-ran ignores receipt-judge" 's#^ workspace-test-ran:$# receipt-judge:\n runs-on: x\n workspace-test-ran:\n needs: [receipt-judge]#' + mgmut ran-marker-drops-shard-needs "workspace-test-ran does not need workspace-test-shard" '/^ workspace-test-ran:$/,/^ runs-on:/{/^ needs: \[workspace-test-shard\]$/d}' + mgmut ran-marker-if-drops-shard-result "workspace-test-ran if lacks the shard success clause" "/^ workspace-test-ran:\$/,/^ runs-on:/{s/ \&\& needs.workspace-test-shard.result == 'success'//}" + mgmut mg-reuse-wt-leaves-mq-lane "mg-reuse-wt is not on the mq lane" '/^ mg-reuse-wt:$/,/^ timeout-minutes:/{s/,"mq"\]/,"clean-room"]/}' # Positive control for wire itself: one job of more than 64 KiB (a pipe buffer) whose # wanted line comes first. A pipe from job into grep -q takes SIGPIPE on this every From c753ff149c3b36016a5547ac9817b1a4a1c15ef9 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 08:01:56 +0200 Subject: [PATCH 15/26] ci(receipt-gate): judge_ok pins whole lines, shard comment-stripped; W20g-j E1-E4 mutants (Refs #5850) Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_receipt_gate.sh | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index dd6ce467ac..b411542fbb 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -197,13 +197,14 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy case "$code" in *'worktree add -q --no-checkout'*) ;; *) return 1 ;; esac case "$code" in *'core.hooksPath=/dev/null'*) ;; *) return 1 ;; esac case "$code" in *'unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0'*) ;; *) return 1 ;; esac - shard=$(awk '/^ workspace-test-shard:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$1") + shard=$(awk '/^ workspace-test-shard:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$1" | grep -v -E '^[[:space:]]*#') case "$shard" in *'needs: [receipt-judge]'*) ;; *) return 1 ;; esac - case "$shard" in *'RECEIPT_OK: ${{ needs.receipt-judge.outputs.ok'*) ;; *) return 1 ;; esac - case "$shard" in *"outputs.ok || '0' }}"*) ;; *) return 1 ;; esac - case "$code" in *'"$RUNNER_TEMP/pr-body.md" "$GITHUB_WORKSPACE" "${GITHUB_SHA}"'*) ;; *) return 1 ;; esac - case "$code" in *'[ "$ok" = 1 ] || ok=0'*) ;; *) return 1 ;; esac - case "$code" in *'[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ]'*) ;; *) return 1 ;; esac + # whole-line pins: a substring pin lets "; ok=1", a flipped "&&" or another cd target ride along + hasl() { local t; t=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<< "$1"); grep -qxF -- "$2" <<< "$t"; } + hasl "$shard" "RECEIPT_OK: \${{ needs.receipt-judge.outputs.ok || '0' }}" || return 1 + hasl "$code" '[ "$ok" = 1 ] || ok=0' || return 1 + hasl "$code" '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] &&' || return 1 + hasl "$code" 'ok="$(cd "$RUNNER_TEMP/prtree" && env -i HOME="$HOME" PATH=/usr/local/bin:/usr/bin:/bin /usr/bin/bash "$RUNNER_TEMP/rok.sh" "$RUNNER_TEMP/pr-body.md" "$GITHUB_WORKSPACE" "${GITHUB_SHA}")" || ok=0' || return 1 tier=$(awk '/id: tier$/{on=1} on{print} /--receipt-ok/{if(on)exit}' "$2" | grep -v -E '^[[:space:]]*#') case "$tier" in *'"${RECEIPT_OK:-0}" = 1'*) ;; *) return 1 ;; esac printf '%s\n' "$tier" | grep -E 'scripts/|git (fetch|clone)' | grep -q . && return 1 @@ -236,6 +237,11 @@ wmut "W20c MUTANT (MA): the shard RECEIPT_OK fallback flipped to 1 is caught" ci wmut "W20d MUTANT (MB): the verifier trusting \$GITHUB_WORKSPACE no longer, but the PR tree, is caught" ci '"$RUNNER_TEMP/pr-body.md" "$GITHUB_WORKSPACE"' '"$RUNNER_TEMP/pr-body.md" "$RUNNER_TEMP/prtree"' wmut "W20e MUTANT (MC): the ok=0 guard turned into ok=1 is caught" ci '[ "$ok" = 1 ] || ok=0' '[ "$ok" = 1 ] || ok=1' wmut "W20f MUTANT (MD): the FETCH_HEAD == GITHUB_SHA compare dropped is caught" ci '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] &&' 'true &&' +wmut "W20g MUTANT (E1): a comment-only RECEIPT_OK in the shard does not satisfy the pin" ci " RECEIPT_OK: \${{ needs.receipt-judge.outputs.ok || '0' }}" " RECEIPT_OK: \${{ needs.receipt-judge.outputs.ok || '1' }} + # RECEIPT_OK: \${{ needs.receipt-judge.outputs.ok || '0' }}" +wmut "W20h MUTANT (E2): the ok=0 guard kept but followed by ; ok=1 is caught" ci '[ "$ok" = 1 ] || ok=0' '[ "$ok" = 1 ] || ok=0; ok=1' +wmut "W20i MUTANT (E3): the FETCH_HEAD compare joined with || instead of && is caught" ci '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] &&' '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] ||' +wmut "W20j MUTANT (E4): the verifier run from the trusted workspace, not the PR tree, is caught" ci 'ok="$(cd "$RUNNER_TEMP/prtree" &&' 'ok="$(cd "$GITHUB_WORKSPACE" &&' wmut "W20b MUTANT: a tier step that runs PR-tree code is caught" sec 'rok=0 if [ "${GITHUB_EVENT_NAME}" = pull_request ]' 'rok=0 bash scripts/ci/pre.sh From 93e1dd9652ecb5ed8d0fb61476daf1a273ff2dd0 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 08:06:20 +0200 Subject: [PATCH 16/26] ci(receipt-gate): judge_ok pins the GITHUB_OUTPUT line; W20k E5 mutant (Refs #5850) Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_receipt_gate.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index b411542fbb..ff56fd70b7 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -205,6 +205,7 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy hasl "$code" '[ "$ok" = 1 ] || ok=0' || return 1 hasl "$code" '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] &&' || return 1 hasl "$code" 'ok="$(cd "$RUNNER_TEMP/prtree" && env -i HOME="$HOME" PATH=/usr/local/bin:/usr/bin:/bin /usr/bin/bash "$RUNNER_TEMP/rok.sh" "$RUNNER_TEMP/pr-body.md" "$GITHUB_WORKSPACE" "${GITHUB_SHA}")" || ok=0' || return 1 + hasl "$code" "printf 'ok=%s\\n' \"\$ok\" >> \"\$GITHUB_OUTPUT\"" || return 1 tier=$(awk '/id: tier$/{on=1} on{print} /--receipt-ok/{if(on)exit}' "$2" | grep -v -E '^[[:space:]]*#') case "$tier" in *'"${RECEIPT_OK:-0}" = 1'*) ;; *) return 1 ;; esac printf '%s\n' "$tier" | grep -E 'scripts/|git (fetch|clone)' | grep -q . && return 1 @@ -242,6 +243,7 @@ wmut "W20g MUTANT (E1): a comment-only RECEIPT_OK in the shard does not satisfy wmut "W20h MUTANT (E2): the ok=0 guard kept but followed by ; ok=1 is caught" ci '[ "$ok" = 1 ] || ok=0' '[ "$ok" = 1 ] || ok=0; ok=1' wmut "W20i MUTANT (E3): the FETCH_HEAD compare joined with || instead of && is caught" ci '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] &&' '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] ||' wmut "W20j MUTANT (E4): the verifier run from the trusted workspace, not the PR tree, is caught" ci 'ok="$(cd "$RUNNER_TEMP/prtree" &&' 'ok="$(cd "$GITHUB_WORKSPACE" &&' +wmut "W20k MUTANT (E5): the judge's output line hard-coded to ok=1 is caught" ci "printf 'ok=%s\\n' \"\$ok\" >> \"\$GITHUB_OUTPUT\"" 'printf "ok=1\n" >> "$GITHUB_OUTPUT"' wmut "W20b MUTANT: a tier step that runs PR-tree code is caught" sec 'rok=0 if [ "${GITHUB_EVENT_NAME}" = pull_request ]' 'rok=0 bash scripts/ci/pre.sh From 82a12c346ecaf9b6bb8410e3eee2d9dd87944126 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 08:10:57 +0200 Subject: [PATCH 17/26] ci(receipt-gate): hash-pin the whole judge job, ok=0 init pin, behavioural fail-closed rows W23 (E6) (Refs #5850) Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_receipt_gate.sh | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index ff56fd70b7..5c793911e1 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -206,6 +206,9 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy hasl "$code" '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] &&' || return 1 hasl "$code" 'ok="$(cd "$RUNNER_TEMP/prtree" && env -i HOME="$HOME" PATH=/usr/local/bin:/usr/bin:/bin /usr/bin/bash "$RUNNER_TEMP/rok.sh" "$RUNNER_TEMP/pr-body.md" "$GITHUB_WORKSPACE" "${GITHUB_SHA}")" || ok=0' || return 1 hasl "$code" "printf 'ok=%s\\n' \"\$ok\" >> \"\$GITHUB_OUTPUT\"" || return 1 + hasl "$code" 'ok=0' || return 1 + # the whole comment-stripped judge job is hash-pinned: a line-by-line pin chase never ends (E1-E6). Edit the job -> update this sha256 (gate content, quorum). + [ "$(printf '%s\n' "$code" | sha256sum | cut -d' ' -f1)" = "ee14f458b0d4a68335ae1d1560777b42c97a11be4ebfd9af3320cc3b1d161ca4" ] || return 1 tier=$(awk '/id: tier$/{on=1} on{print} /--receipt-ok/{if(on)exit}' "$2" | grep -v -E '^[[:space:]]*#') case "$tier" in *'"${RECEIPT_OK:-0}" = 1'*) ;; *) return 1 ;; esac printf '%s\n' "$tier" | grep -E 'scripts/|git (fetch|clone)' | grep -q . && return 1 @@ -244,6 +247,7 @@ wmut "W20h MUTANT (E2): the ok=0 guard kept but followed by ; ok=1 is caught" ci wmut "W20i MUTANT (E3): the FETCH_HEAD compare joined with || instead of && is caught" ci '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] &&' '[ "$(git rev-parse FETCH_HEAD)" = "${GITHUB_SHA}" ] ||' wmut "W20j MUTANT (E4): the verifier run from the trusted workspace, not the PR tree, is caught" ci 'ok="$(cd "$RUNNER_TEMP/prtree" &&' 'ok="$(cd "$GITHUB_WORKSPACE" &&' wmut "W20k MUTANT (E5): the judge's output line hard-coded to ok=1 is caught" ci "printf 'ok=%s\\n' \"\$ok\" >> \"\$GITHUB_OUTPUT\"" 'printf "ok=1\n" >> "$GITHUB_OUTPUT"' +wmut "W20l MUTANT (E6): the judge's initial ok=0 set to ok=1 is caught" ci ' ok=0' ' ok=1' wmut "W20b MUTANT: a tier step that runs PR-tree code is caught" sec 'rok=0 if [ "${GITHUB_EVENT_NAME}" = pull_request ]' 'rok=0 bash scripts/ci/pre.sh @@ -320,5 +324,37 @@ t0=$(tier_of 0); case "$t0" in tier=quick|tier=full) t0=notreuse ;; esac wrow "W8 --receipt-ok 0 on a core diff runs quick or full, never reuse" notreuse "$t0" wrow "W9 --receipt-ok 1 on the same diff is reuse" tier=reuse "$(tier_of 1)" + +# W23: BEHAVIOURAL. Run the judge's real `run:` script (extracted from ci.yml) against a local origin, so a +# fail-closed property is measured, not pattern-matched: a failed fetch, a failed verifier and a garbage verifier +# answer must each leave ok=0; only a verifier that exits 0 printing a literal 1 may give ok=1. +judge_run() { # CI_FILE SCENARIO(good|nofetch|vfail|vgarbage|vfail1) -> prints the ok= value the judge wrote + local ci=$1 sc=$2 d="$td/jo-$2-$RANDOM" pr=7 out + mkdir -p "$d/w/scripts/ci" "$d/rt" && : > "$d/out" + awk '/id: judge$/{s=1} s&&/^ run: \|$/{r=1;next} r&&/^ /{sub(/^ /,"");print;next} r&&!/^[[:space:]]*$/{exit} r{print}' "$ci" > "$d/judge.sh" + [ -s "$d/judge.sh" ] || { echo "noscript"; return; } + case "$sc" in + good) printf 'echo 1\n' ;; vfail) printf 'exit 1\n' ;; vfail1) printf 'echo 1\nexit 1\n' ;; vgarbage) printf 'echo 2\n' ;; *) printf 'echo 1\n' ;; + esac > "$d/w/scripts/ci/receipt_ok_arg.sh" + ( + unset GIT_DIR GIT_INDEX_FILE GIT_WORK_TREE GIT_COMMON_DIR + export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t + git init -q --bare "$d/origin.git" && cd "$d/w" && git init -q -b main . && + git add -A && git commit -q -m base && git remote add origin "file://$d/origin.git" && + git commit -q --allow-empty -m merge && git push -q origin "HEAD:refs/pull/$pr/merge" && git reset -q --hard HEAD~1 + ) > /dev/null 2>&1 || { echo "setup"; return; } + local sha; sha=$(git -C "$d/origin.git" rev-parse "refs/pull/$pr/merge") + local prn=$pr; [ "$sc" = nofetch ] && prn=99 + (cd "$d/w" && env -u GIT_DIR -u GIT_INDEX_FILE -u GIT_WORK_TREE GH_TOKEN=x PR_BODY= PR_NUMBER=$prn GITHUB_SHA="$sha" \ + GITHUB_WORKSPACE="$d/w" RUNNER_TEMP="$d/rt" GITHUB_OUTPUT="$d/out" bash -e "$d/judge.sh") > "$d/log" 2>&1 + out=$(sed -n 's/^ok=//p' "$d/out" | tail -n1); echo "${out:-none}" +} +wrow "W23a judge run: verifier prints 1 and exits 0 -> ok=1 (the mechanism engages)" 1 "$(judge_run "$CI" good)" +wrow "W23b judge run: forced fetch failure -> ok=0" 0 "$(judge_run "$CI" nofetch)" +wrow "W23c judge run: forced verifier failure (exit 1) -> ok=0" 0 "$(judge_run "$CI" vfail)" +wrow "W23d judge run: verifier prints 1 then exits 1 -> ok=0" 0 "$(judge_run "$CI" vfail1)" +wrow "W23e judge run: verifier prints 2 -> ok=0" 0 "$(judge_run "$CI" vgarbage)" +if mutate "$CI" ' ok=0' ' ok=1' "$td/b6.yml"; then wrow "W23f MUTANT (E6): initial ok=1 leaves ok=1 on a failed fetch, behaviourally" 1 "$(judge_run "$td/b6.yml" nofetch)"; else fail=$((fail + 1)); echo "FAIL W21f: mutant did not apply"; fi +if mutate "$CI" '[ "$ok" = 1 ] || ok=0' '[ "$ok" = 1 ] || ok=1' "$td/b7.yml"; then wrow "W23g MUTANT (E2): a dead guard passes the garbage verifier answer, behaviourally" 2 "$(judge_run "$td/b7.yml" vgarbage | sed 's/^1$/2/')"; else fail=$((fail + 1)); echo "FAIL W21g: mutant did not apply"; fi echo "receipt gate rows: $pass passed, $fail failed" [ "$fail" -eq 0 ] From 7e3e72c7d55005462f03870bc27fd9822f51769f Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 08:39:42 +0200 Subject: [PATCH 18/26] ci(receipt-gate): hash-pin the tier consumer chain, refuse BASH_ENV/defaults/env/GITHUB_ENV outside pinned lines; W40a-g (Refs #5850) Note: PR-tree scripts run in the shard can still choose the tier. Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_receipt_gate.sh | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index 5c793911e1..350d691862 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -180,7 +180,7 @@ else fail=$((fail + 1)); echo "FAIL W10: mutant did not apply to $W"; fi CI="$HERE/../.github/workflows/ci.yml" SEC="$HERE/../ci/sections.yml" judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy - local job code shard tier + local job code shard tier step ciall secall n job=$(awk '/^ receipt-judge:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$1") [ -n "$job" ] || return 1 code=$(printf '%s\n' "$job" | grep -v -E '^[[:space:]]*#') @@ -212,6 +212,15 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy tier=$(awk '/id: tier$/{on=1} on{print} /--receipt-ok/{if(on)exit}' "$2" | grep -v -E '^[[:space:]]*#') case "$tier" in *'"${RECEIPT_OK:-0}" = 1'*) ;; *) return 1 ;; esac printf '%s\n' "$tier" | grep -E 'scripts/|git (fetch|clone)' | grep -q . && return 1 + # the consumer chain (tier step: RECEIPT_OK -> rok -> --receipt-ok -> ci_test_tier.sh) is hash-pinned whole; edit it -> update this sha256 (gate content, quorum) + step=$(awk '/- name: Decide the test tier/{on=1;print;next} on&&/^ - name:/{exit} on{print}' "$2" | grep -v -E '^[[:space:]]*#') + [ "$(printf '%s\n' "$step" | sha256sum | cut -d' ' -f1)" = "0d3225cbff907f20208c821901c72e70a4c66cef603d5aa5b100144beec45d50" ] || return 1 + # nothing outside the pinned lines may set the verdict, the shell or the environment + ciall=$(grep -v -E '^[[:space:]]*#' "$1"); secall=$(grep -v -E '^[[:space:]]*#' "$2") + n=$(grep -c -E 'RECEIPT_OK|receipt-ok|BASH_ENV|GITHUB_ENV|^defaults:|^env:' <<< "$ciall" || true); [ "$n" = 1 ] || return 1 + n=$(grep -c -E 'RECEIPT_OK|receipt-ok' <<< "$secall" || true); [ "$n" = 2 ] || return 1 + n=$(grep -c -E 'BASH_ENV|^defaults:|^env:' <<< "$secall" || true); [ "$n" = 0 ] || return 1 + n=$(grep -c 'GITHUB_ENV' <<< "$secall" || true); [ "$n" = 3 ] || return 1 return 0 } if judge_ok "$CI" "$SEC"; then wrow "W16 receipt judge is its own job: base checkout only, no PR-tree code, shard waits for it" ok ok @@ -248,6 +257,23 @@ wmut "W20i MUTANT (E3): the FETCH_HEAD compare joined with || instead of && is c wmut "W20j MUTANT (E4): the verifier run from the trusted workspace, not the PR tree, is caught" ci 'ok="$(cd "$RUNNER_TEMP/prtree" &&' 'ok="$(cd "$GITHUB_WORKSPACE" &&' wmut "W20k MUTANT (E5): the judge's output line hard-coded to ok=1 is caught" ci "printf 'ok=%s\\n' \"\$ok\" >> \"\$GITHUB_OUTPUT\"" 'printf "ok=1\n" >> "$GITHUB_OUTPUT"' wmut "W20l MUTANT (E6): the judge's initial ok=0 set to ok=1 is caught" ci ' ok=0' ' ok=1' +wmut "W40a MUTANT: tier step env RECEIPT_OK: 1 is caught" sec ' PR_BODY: ${{ github.event.pull_request.body }} + run: | + set -euo pipefail + args=(--event' ' PR_BODY: ${{ github.event.pull_request.body }} + RECEIPT_OK: "1" + run: | + set -euo pipefail + args=(--event' +wmut "W40b MUTANT: the tier gate && turned into || is caught" sec '[ "${GITHUB_EVENT_NAME}" = pull_request ] && [ "${RECEIPT_OK:-0}" = 1 ]' '[ "${GITHUB_EVENT_NAME}" = pull_request ] || [ "${RECEIPT_OK:-0}" = 1 ]' +wmut "W40c MUTANT: hard-coded --receipt-ok 1 is caught" sec 'args+=(--receipt-ok "${rok:-0}")' 'args+=(--receipt-ok 1)' +wmut "W40d MUTANT: workflow-level defaults: run: shell is caught" ci $'\njobs:\n' $'\ndefaults:\n run:\n shell: bash -c "export RECEIPT_OK=1; . {0}"\njobs:\n' +wmut "W40e MUTANT: workflow-level env BASH_ENV is caught" ci $'\njobs:\n' $'\nenv:\n BASH_ENV: /tmp/x\njobs:\n' +wmut "W40f MUTANT: a GITHUB_ENV write of RECEIPT_OK in a shard step is caught" ci ' - name: Judge the receipt in the PR body' ' - run: echo RECEIPT_OK=1 >> "$GITHUB_ENV" + - name: Judge the receipt in the PR body' +wmut "W40g MUTANT: a second RECEIPT_OK override in a shard step env is caught" ci " RECEIPT_OK: \${{ needs.receipt-judge.outputs.ok || '0' }}" " RECEIPT_OK: \${{ needs.receipt-judge.outputs.ok || '0' }} + RECEIPT_OK_X: 1 + RECEIPT_OK: 1" wmut "W20b MUTANT: a tier step that runs PR-tree code is caught" sec 'rok=0 if [ "${GITHUB_EVENT_NAME}" = pull_request ]' 'rok=0 bash scripts/ci/pre.sh From 1f19d49ed9fb255bd5e22a195758d0f5315aac8c Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 08:53:20 +0200 Subject: [PATCH 19/26] ci(receipt-gate): sha256-pin the shard job, its sections.yml block, fat_driver.py, ci_test_tier.sh, receipt_ok_arg.sh; W41 escaped-key M1/M2 (Refs #5850) Any byte change is RED unless the pin moves in the same diff. A YAML structural check is a follow-up ticket. Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_receipt_gate.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index 350d691862..6324a25aea 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -221,6 +221,15 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy n=$(grep -c -E 'RECEIPT_OK|receipt-ok' <<< "$secall" || true); [ "$n" = 2 ] || return 1 n=$(grep -c -E 'BASH_ENV|^defaults:|^env:' <<< "$secall" || true); [ "$n" = 0 ] || return 1 n=$(grep -c 'GITHUB_ENV' <<< "$secall" || true); [ "$n" = 3 ] || return 1 + # hash pins (E7, escaped keys and every other spelling): the shard job in ci.yml, its block in sections.yml and the three scripts the verdict flows through. + # Any byte change is RED unless the pin moves in the same diff (gate content, quorum). PIN_ROOT lets the case table pin a mutated copy. + local pr=${PIN_ROOT:-$HERE/..} blk + [ "$(printf '%s\n' "$shard" | sha256sum | cut -d' ' -f1)" = "6c7c538c950899c60f7436868ee80a003f904e3ed3a4286877976964f4ddc216" ] || return 1 + blk=$(awk '/^jobs:/{j=1} j&&/^ workspace-test-shard:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$2" | grep -v -E '^[[:space:]]*#') + [ "$(printf '%s\n' "$blk" | sha256sum | cut -d' ' -f1)" = "e44b8057e38d6d7eaf6b45e1d3e184e4396d329582e7cb3cd5f6790bb29e552c" ] || return 1 + [ "$(sha256sum < "$pr/scripts/ci/fat_driver.py" | cut -d' ' -f1)" = "cc268073ff250d9bfb0048a480126b7e45051413f6505fe46e814a4ecb3ef282" ] || return 1 + [ "$(sha256sum < "$pr/scripts/ci_test_tier.sh" | cut -d' ' -f1)" = "1680bab97243fb7b5ce6907daf3c8a00db4968a2e892095260152a09cc127866" ] || return 1 + [ "$(sha256sum < "$pr/scripts/ci/receipt_ok_arg.sh" | cut -d' ' -f1)" = "1795d4df36fa1c3f223dd054d93392aff7584bb5063bed1d7a11bd3f18144820" ] || return 1 return 0 } if judge_ok "$CI" "$SEC"; then wrow "W16 receipt judge is its own job: base checkout only, no PR-tree code, shard waits for it" ok ok @@ -381,6 +390,20 @@ wrow "W23c judge run: forced verifier failure (exit 1) -> ok=0" 0 "$(judge_run " wrow "W23d judge run: verifier prints 1 then exits 1 -> ok=0" 0 "$(judge_run "$CI" vfail1)" wrow "W23e judge run: verifier prints 2 -> ok=0" 0 "$(judge_run "$CI" vgarbage)" if mutate "$CI" ' ok=0' ' ok=1' "$td/b6.yml"; then wrow "W23f MUTANT (E6): initial ok=1 leaves ok=1 on a failed fetch, behaviourally" 1 "$(judge_run "$td/b6.yml" nofetch)"; else fail=$((fail + 1)); echo "FAIL W21f: mutant did not apply"; fi + +# W41: hash pins on the shard job (ci.yml + sections.yml block) and the three scripts the verdict flows through +rl=" RECEIPT_OK: \${{ needs.receipt-judge.outputs.ok || '0' }}" +wmut "W41a MUTANT (M1): an escaped RECEIPT\x5FOK env key in the ci.yml shard env is caught" ci "$rl" "$rl"$'\n'' "RECEIPT\x5FOK": "1"' +sc=' SCCACHE_HOST_DIR: /home/noah/data/sccache' +wmut "W41b MUTANT (M2): an escaped RECEIPT\x5FOK env key in the sections.yml shard env is caught" sec "$sc" "$sc"$'\n'' "RECEIPT\x5FOK": "1"' +# a byte change in any verdict script is RED unless its pin moves (PIN_ROOT = a copy of the tree with one file mutated) +for rel in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh; do + pr="$td/pin-$(basename "$rel")"; mkdir -p "$pr/scripts/ci" + for g in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh; do cp "$HERE/../$g" "$pr/$g"; done + printf '\n# touched\n' >> "$pr/$rel" + if PIN_ROOT="$pr" judge_ok "$CI" "$SEC"; then r=missed; else r=caught; fi + wrow "W41c MUTANT: a byte appended to $rel is caught by the hash pin" caught "$r" +done if mutate "$CI" '[ "$ok" = 1 ] || ok=0' '[ "$ok" = 1 ] || ok=1' "$td/b7.yml"; then wrow "W23g MUTANT (E2): a dead guard passes the garbage verifier answer, behaviourally" 2 "$(judge_run "$td/b7.yml" vgarbage | sed 's/^1$/2/')"; else fail=$((fail + 1)); echo "FAIL W21g: mutant did not apply"; fi echo "receipt gate rows: $pass passed, $fail failed" [ "$fail" -eq 0 ] From 004c2105f93b080e5340ea8963e25727d7598829 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 09:23:34 +0200 Subject: [PATCH 20/26] ci(receipt-gate): spelling-proof structure pins (top-level keys, job keys, dup shard) + GITHUB_ENV/OUTPUT rows (Refs #5850) Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_receipt_gate.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index 6324a25aea..c0ce1a3d47 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -179,6 +179,9 @@ else fail=$((fail + 1)); echo "FAIL W10: mutant did not apply to $W"; fi # plants PATH or the env, a shard job that does not wait for the judge, a tier step that runs code. CI="$HERE/../.github/workflows/ci.yml" SEC="$HERE/../ci/sections.yml" +tl() { grep -v -E '^[[:space:]]*#' "$1" | grep -E '^[^[:space:]-]'; } +jl() { grep -v -E '^[[:space:]]*#' "$1" | awk '/^jobs:/{j=1;next} j&&/^[^[:space:]]/{j=0} j&&/^ [^ ]/{print}'; } +h() { { printf '%s\n' "$(cat)"; } | sha256sum | cut -d' ' -f1; } judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy local job code shard tier step ciall secall n job=$(awk '/^ receipt-judge:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$1") @@ -230,6 +233,11 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy [ "$(sha256sum < "$pr/scripts/ci/fat_driver.py" | cut -d' ' -f1)" = "cc268073ff250d9bfb0048a480126b7e45051413f6505fe46e814a4ecb3ef282" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci_test_tier.sh" | cut -d' ' -f1)" = "1680bab97243fb7b5ce6907daf3c8a00db4968a2e892095260152a09cc127866" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci/receipt_ok_arg.sh" | cut -d' ' -f1)" = "1795d4df36fa1c3f223dd054d93392aff7584bb5063bed1d7a11bd3f18144820" ] || return 1 + # structure pins: the top-level keys and the job keys of both files, in any spelling (quoted, ? keys, duplicates). A new top-level key or job, or a second shard block, moves the hash. + [ "$(tl "$1" | h)" = "7b45344e526135d0ab4f5c04ca82ba2d1112f6286b1ba2024a18017536446ce5" ] || return 1 + [ "$(jl "$1" | h)" = "1d370235776861592b57ff7c935a327fbf70d28427ac084baa4392f5b70d8250" ] || return 1 + [ "$(tl "$2" | h)" = "e90e5a61fd66429fec4eab76974c56bcdd3f253610a6fe9c1f63b7fd8959b18f" ] || return 1 + [ "$(jl "$2" | h)" = "c988f167ca3654fa20a9df52cea6196fb7032606c2a7a32425ad2740f14c5925" ] || return 1 return 0 } if judge_ok "$CI" "$SEC"; then wrow "W16 receipt judge is its own job: base checkout only, no PR-tree code, shard waits for it" ok ok @@ -404,6 +412,19 @@ for rel in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_o if PIN_ROOT="$pr" judge_ok "$CI" "$SEC"; then r=missed; else r=caught; fi wrow "W41c MUTANT: a byte appended to $rel is caught by the hash pin" caught "$r" done + +# W42: spelling-proof structure rows (quoted / ? top-level keys, a duplicate shard block) +wmut "W42a MUTANT: a quoted top-level \"env\" key with BASH\x5FENV in ci.yml is caught" ci $'\njobs:\n' $'\n"env":\n "BASH\\x5FENV": /tmp/x\njobs:\n' +wmut "W42b MUTANT: a ? top-level defaults key in ci.yml is caught" ci $'\njobs:\n' $'\n? defaults\n: {run: {shell: bash}}\njobs:\n' +wmut "W42c MUTANT: a duplicate workspace-test-shard job after the first in ci.yml is caught" ci $'\n workspace-test-shard:\n' $'\n workspace-test-shard:\n runs-on: x\n workspace-test-shard:\n' +# W42d/e: a shard script that writes the runner env/output files is a pinned-byte change, so it is RED +for w in 'echo RECEIPT_OK=1 >> "$GITHUB_ENV"' 'echo ok=1 >> "$GITHUB_OUTPUT"'; do + pr="$td/pin-w"; rm -rf -- "${pr:?}"; mkdir -p "$pr/scripts/ci" + for g in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh; do cp "$HERE/../$g" "$pr/$g"; done + printf '\n%s\n' "$w" >> "$pr/scripts/ci_test_tier.sh" + if PIN_ROOT="$pr" judge_ok "$CI" "$SEC"; then r=missed; else r=caught; fi + wrow "W42d MUTANT: ci_test_tier.sh writing ${w##* } is caught" caught "$r" +done if mutate "$CI" '[ "$ok" = 1 ] || ok=0' '[ "$ok" = 1 ] || ok=1' "$td/b7.yml"; then wrow "W23g MUTANT (E2): a dead guard passes the garbage verifier answer, behaviourally" 2 "$(judge_run "$td/b7.yml" vgarbage | sed 's/^1$/2/')"; else fail=$((fail + 1)); echo "FAIL W21g: mutant did not apply"; fi echo "receipt gate rows: $pass passed, $fail failed" [ "$fail" -eq 0 ] From 1df6517313a1c1cfa3c68474cb715b66a8f13c74 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 09:31:28 +0200 Subject: [PATCH 21/26] ci(receipt-gate): fat_driver drops RECEIPT_OK from GITHUB_ENV; pin the 3 pre-tier scripts; RED rows (Refs #5850) Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_receipt_gate.sh | 20 ++++++++++++-------- scripts/ci/fat_driver.py | 2 ++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index c0ce1a3d47..59acb0df0d 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -230,9 +230,13 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy [ "$(printf '%s\n' "$shard" | sha256sum | cut -d' ' -f1)" = "6c7c538c950899c60f7436868ee80a003f904e3ed3a4286877976964f4ddc216" ] || return 1 blk=$(awk '/^jobs:/{j=1} j&&/^ workspace-test-shard:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$2" | grep -v -E '^[[:space:]]*#') [ "$(printf '%s\n' "$blk" | sha256sum | cut -d' ' -f1)" = "e44b8057e38d6d7eaf6b45e1d3e184e4396d329582e7cb3cd5f6790bb29e552c" ] || return 1 - [ "$(sha256sum < "$pr/scripts/ci/fat_driver.py" | cut -d' ' -f1)" = "cc268073ff250d9bfb0048a480126b7e45051413f6505fe46e814a4ecb3ef282" ] || return 1 + [ "$(sha256sum < "$pr/scripts/ci/fat_driver.py" | cut -d' ' -f1)" = "ed63b68c671497c70607ddc07613dac956b56df9da2e557410cd14900171be27" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci_test_tier.sh" | cut -d' ' -f1)" = "1680bab97243fb7b5ce6907daf3c8a00db4968a2e892095260152a09cc127866" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci/receipt_ok_arg.sh" | cut -d' ' -f1)" = "1795d4df36fa1c3f223dd054d93392aff7584bb5063bed1d7a11bd3f18144820" ] || return 1 + # scripts that run before the tier step in the shard (they could append RECEIPT_OK to $GITHUB_ENV): byte-pinned too + [ "$(sha256sum < "$pr/scripts/ci_reclaim_target_dirs.sh" | cut -d' ' -f1)" = "454aec9194c8d90aa0587e469fca9c8d801fa211c0a4aacdf9c2dc5a820c5449" ] || return 1 + [ "$(sha256sum < "$pr/scripts/ci/fixture_quiet.sh" | cut -d' ' -f1)" = "0571ef87e0859c9dc9299c6cb57b1d3ed11991fb5e3c890cf56c76e385df730c" ] || return 1 + [ "$(sha256sum < "$pr/scripts/ci/pr_touched_paths.sh" | cut -d' ' -f1)" = "eed39751351685073888dd9e1ae9e993a30cdc933f97eb8e92aa8f01b36e42a4" ] || return 1 # structure pins: the top-level keys and the job keys of both files, in any spelling (quoted, ? keys, duplicates). A new top-level key or job, or a second shard block, moves the hash. [ "$(tl "$1" | h)" = "7b45344e526135d0ab4f5c04ca82ba2d1112f6286b1ba2024a18017536446ce5" ] || return 1 [ "$(jl "$1" | h)" = "1d370235776861592b57ff7c935a327fbf70d28427ac084baa4392f5b70d8250" ] || return 1 @@ -405,9 +409,9 @@ wmut "W41a MUTANT (M1): an escaped RECEIPT\x5FOK env key in the ci.yml shard env sc=' SCCACHE_HOST_DIR: /home/noah/data/sccache' wmut "W41b MUTANT (M2): an escaped RECEIPT\x5FOK env key in the sections.yml shard env is caught" sec "$sc" "$sc"$'\n'' "RECEIPT\x5FOK": "1"' # a byte change in any verdict script is RED unless its pin moves (PIN_ROOT = a copy of the tree with one file mutated) -for rel in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh; do +for rel in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh; do pr="$td/pin-$(basename "$rel")"; mkdir -p "$pr/scripts/ci" - for g in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh; do cp "$HERE/../$g" "$pr/$g"; done + for g in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh; do cp "$HERE/../$g" "$pr/$g"; done printf '\n# touched\n' >> "$pr/$rel" if PIN_ROOT="$pr" judge_ok "$CI" "$SEC"; then r=missed; else r=caught; fi wrow "W41c MUTANT: a byte appended to $rel is caught by the hash pin" caught "$r" @@ -418,13 +422,13 @@ wmut "W42a MUTANT: a quoted top-level \"env\" key with BASH\x5FENV in ci.yml is wmut "W42b MUTANT: a ? top-level defaults key in ci.yml is caught" ci $'\njobs:\n' $'\n? defaults\n: {run: {shell: bash}}\njobs:\n' wmut "W42c MUTANT: a duplicate workspace-test-shard job after the first in ci.yml is caught" ci $'\n workspace-test-shard:\n' $'\n workspace-test-shard:\n runs-on: x\n workspace-test-shard:\n' # W42d/e: a shard script that writes the runner env/output files is a pinned-byte change, so it is RED -for w in 'echo RECEIPT_OK=1 >> "$GITHUB_ENV"' 'echo ok=1 >> "$GITHUB_OUTPUT"'; do +for rel in scripts/ci_test_tier.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh; do for w in 'echo RECEIPT_OK=1 >> "$GITHUB_ENV"' 'echo ok=1 >> "$GITHUB_OUTPUT"'; do pr="$td/pin-w"; rm -rf -- "${pr:?}"; mkdir -p "$pr/scripts/ci" - for g in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh; do cp "$HERE/../$g" "$pr/$g"; done - printf '\n%s\n' "$w" >> "$pr/scripts/ci_test_tier.sh" + for g in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh; do cp "$HERE/../$g" "$pr/$g"; done + printf "\n%s\n" "$w" >> "$pr/$rel" if PIN_ROOT="$pr" judge_ok "$CI" "$SEC"; then r=missed; else r=caught; fi - wrow "W42d MUTANT: ci_test_tier.sh writing ${w##* } is caught" caught "$r" -done + wrow "W42d MUTANT: $rel writing ${w##* } is caught" caught "$r" +done; done if mutate "$CI" '[ "$ok" = 1 ] || ok=0' '[ "$ok" = 1 ] || ok=1' "$td/b7.yml"; then wrow "W23g MUTANT (E2): a dead guard passes the garbage verifier answer, behaviourally" 2 "$(judge_run "$td/b7.yml" vgarbage | sed 's/^1$/2/')"; else fail=$((fail + 1)); echo "FAIL W21g: mutant did not apply"; fi echo "receipt gate rows: $pass passed, $fail failed" [ "$fail" -eq 0 ] diff --git a/scripts/ci/fat_driver.py b/scripts/ci/fat_driver.py index 5f9de2b80b..a7f93ed08a 100644 --- a/scripts/ci/fat_driver.py +++ b/scripts/ci/fat_driver.py @@ -893,6 +893,8 @@ def _container_argv(self, argv, env, step, cwd) -> list: def _apply_file_commands(self, files) -> dict: outputs = parse_file_command(files["GITHUB_OUTPUT"]) for k, v in parse_file_command(files["GITHUB_ENV"]).items(): + if k.upper() == "RECEIPT_OK": + continue # the receipt verdict comes only from the judge job (#5850); a step cannot write it self.env[k] = v for line in files["GITHUB_PATH"].read_text().splitlines(): if line.strip(): From 52f6d508f04354ab1a5a2b25707bb515600d8e38 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 09:39:05 +0200 Subject: [PATCH 22/26] ci(receipt-gate): tier-step helpers run with scratch GITHUB_OUTPUT/ENV; parse_file_command fails closed; exec list + RED rows (Refs #5850) Co-Authored-By: Claude Sonnet 5.5 --- ci/sections.yml | 6 ++++- scripts/check_receipt_gate.sh | 43 ++++++++++++++++++++++++++++------- scripts/ci/fat_driver.py | 29 ++++++++++++++++------- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/ci/sections.yml b/ci/sections.yml index 18a9c03b0e..addc86ebab 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -382,6 +382,10 @@ jobs: PR_BODY: ${{ github.event.pull_request.body }} run: | set -euo pipefail + # #5850: every helper below runs with a SCRATCH GITHUB_OUTPUT/GITHUB_ENV; only the last line of this step writes + # the real file, so no helper can plant tier=/cite= lines or a heredoc in front of ours. + real_out="$GITHUB_OUTPUT" + GITHUB_OUTPUT="$(mktemp)"; GITHUB_ENV="$(mktemp)"; export GITHUB_OUTPUT GITHUB_ENV args=(--event "${GITHUB_EVENT_NAME}") # #5797: a signed batch receipt in the PR body (signature, tree hash == this # merge tree, toolchain hash) lets the PR run check/fmt/clippy instead of the @@ -450,7 +454,7 @@ jobs: ;; esac bash scripts/ci_test_tier.sh "${args[@]}" | tee "$RUNNER_TEMP/tier.txt" - grep -E '^(tier|crates|targets|check_workspace|reason|cite)=' "$RUNNER_TEMP/tier.txt" >> "$GITHUB_OUTPUT" + grep -E '^(tier|crates|targets|check_workspace|reason|cite)=' "$RUNNER_TEMP/tier.txt" >> "$real_out" # #4502 row 2: lint_passes_on_real_contracts (aprender-contracts) requires the # refinement gate to RUN whenever CI is set, and that gate compares HEAD with # merge-base(HEAD, origin/main), else the origin/main tip. This section clones at diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index 59acb0df0d..001c3c2920 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -217,20 +217,20 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy printf '%s\n' "$tier" | grep -E 'scripts/|git (fetch|clone)' | grep -q . && return 1 # the consumer chain (tier step: RECEIPT_OK -> rok -> --receipt-ok -> ci_test_tier.sh) is hash-pinned whole; edit it -> update this sha256 (gate content, quorum) step=$(awk '/- name: Decide the test tier/{on=1;print;next} on&&/^ - name:/{exit} on{print}' "$2" | grep -v -E '^[[:space:]]*#') - [ "$(printf '%s\n' "$step" | sha256sum | cut -d' ' -f1)" = "0d3225cbff907f20208c821901c72e70a4c66cef603d5aa5b100144beec45d50" ] || return 1 + [ "$(printf '%s\n' "$step" | sha256sum | cut -d' ' -f1)" = "a6c4a1aa70c61821a7118c191b810093cf31021b686e2cc6e9eb138b602729a2" ] || return 1 # nothing outside the pinned lines may set the verdict, the shell or the environment ciall=$(grep -v -E '^[[:space:]]*#' "$1"); secall=$(grep -v -E '^[[:space:]]*#' "$2") n=$(grep -c -E 'RECEIPT_OK|receipt-ok|BASH_ENV|GITHUB_ENV|^defaults:|^env:' <<< "$ciall" || true); [ "$n" = 1 ] || return 1 n=$(grep -c -E 'RECEIPT_OK|receipt-ok' <<< "$secall" || true); [ "$n" = 2 ] || return 1 n=$(grep -c -E 'BASH_ENV|^defaults:|^env:' <<< "$secall" || true); [ "$n" = 0 ] || return 1 - n=$(grep -c 'GITHUB_ENV' <<< "$secall" || true); [ "$n" = 3 ] || return 1 + n=$(grep -c 'GITHUB_ENV' <<< "$secall" || true); [ "$n" = 4 ] || return 1 # hash pins (E7, escaped keys and every other spelling): the shard job in ci.yml, its block in sections.yml and the three scripts the verdict flows through. # Any byte change is RED unless the pin moves in the same diff (gate content, quorum). PIN_ROOT lets the case table pin a mutated copy. local pr=${PIN_ROOT:-$HERE/..} blk [ "$(printf '%s\n' "$shard" | sha256sum | cut -d' ' -f1)" = "6c7c538c950899c60f7436868ee80a003f904e3ed3a4286877976964f4ddc216" ] || return 1 blk=$(awk '/^jobs:/{j=1} j&&/^ workspace-test-shard:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$2" | grep -v -E '^[[:space:]]*#') - [ "$(printf '%s\n' "$blk" | sha256sum | cut -d' ' -f1)" = "e44b8057e38d6d7eaf6b45e1d3e184e4396d329582e7cb3cd5f6790bb29e552c" ] || return 1 - [ "$(sha256sum < "$pr/scripts/ci/fat_driver.py" | cut -d' ' -f1)" = "ed63b68c671497c70607ddc07613dac956b56df9da2e557410cd14900171be27" ] || return 1 + [ "$(printf '%s\n' "$blk" | sha256sum | cut -d' ' -f1)" = "b27698ea49d1149e98cb8fca78d45c48a0f58322b89f9b99a87b6224d415d460" ] || return 1 + [ "$(sha256sum < "$pr/scripts/ci/fat_driver.py" | cut -d' ' -f1)" = "81662d808e4672b41b481f44361c10aaae308661f492b1aaaff47f87ffb27cfc" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci_test_tier.sh" | cut -d' ' -f1)" = "1680bab97243fb7b5ce6907daf3c8a00db4968a2e892095260152a09cc127866" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci/receipt_ok_arg.sh" | cut -d' ' -f1)" = "1795d4df36fa1c3f223dd054d93392aff7584bb5063bed1d7a11bd3f18144820" ] || return 1 # scripts that run before the tier step in the shard (they could append RECEIPT_OK to $GITHUB_ENV): byte-pinned too @@ -280,12 +280,10 @@ wmut "W20k MUTANT (E5): the judge's output line hard-coded to ok=1 is caught" ci wmut "W20l MUTANT (E6): the judge's initial ok=0 set to ok=1 is caught" ci ' ok=0' ' ok=1' wmut "W40a MUTANT: tier step env RECEIPT_OK: 1 is caught" sec ' PR_BODY: ${{ github.event.pull_request.body }} run: | - set -euo pipefail - args=(--event' ' PR_BODY: ${{ github.event.pull_request.body }} + set -euo pipefail' ' PR_BODY: ${{ github.event.pull_request.body }} RECEIPT_OK: "1" run: | - set -euo pipefail - args=(--event' + set -euo pipefail' wmut "W40b MUTANT: the tier gate && turned into || is caught" sec '[ "${GITHUB_EVENT_NAME}" = pull_request ] && [ "${RECEIPT_OK:-0}" = 1 ]' '[ "${GITHUB_EVENT_NAME}" = pull_request ] || [ "${RECEIPT_OK:-0}" = 1 ]' wmut "W40c MUTANT: hard-coded --receipt-ok 1 is caught" sec 'args+=(--receipt-ok "${rok:-0}")' 'args+=(--receipt-ok 1)' wmut "W40d MUTANT: workflow-level defaults: run: shell is caught" ci $'\njobs:\n' $'\ndefaults:\n run:\n shell: bash -c "export RECEIPT_OK=1; . {0}"\njobs:\n' @@ -429,6 +427,35 @@ for rel in scripts/ci_test_tier.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/ if PIN_ROOT="$pr" judge_ok "$CI" "$SEC"; then r=missed; else r=caught; fi wrow "W42d MUTANT: $rel writing ${w##* } is caught" caught "$r" done; done + +# W43: parse_file_command (the driver's GITHUB_OUTPUT/GITHUB_ENV reader) fails closed (#5850); the real function is run, no PyYAML needed +pfc_run() { # $1 = driver source, $2 = file body; prints reject|accept + local src="$td/pfc.py" o="$td/pfc.out" + sed -n '/^def _fc_put/,/^ return out$/p' "$1" > "$src"; printf '%b' "$2" > "$o" + if python3 -I -c 'import re,sys +from pathlib import Path +exec(open(sys.argv[1]).read()) +parse_file_command(Path(sys.argv[2]))' "$src" "$o" > /dev/null 2>&1 < /dev/null; then echo accept; else echo reject; fi +} +FD="$HERE/ci/fat_driver.py" +wrow "W43a an unterminated heredoc planted ahead of the real tier lines is rejected" reject "$(pfc_run "$FD" 'tier=reuse\ncite=receipt\nignore< "$td/fd-mut.py" +wrow "W43e MUTANT: the fail-closed raises removed accepts the planted heredoc (the test is sensitive)" accept "$(pfc_run "$td/fd-mut.py" 'tier=reuse\ncite=receipt\nignore<> "$real_out"' '"$RUNNER_TEMP/tier.txt" >> "$GITHUB_OUTPUT"' + +# W45: every file the tier step runs, marked pinned (hash) or isolated (runs under the scratch env). A new one is RED until it is classified here. +tier_exec() { awk '/- name: Decide the test tier/{on=1;print;next} on&&/^ - name:/{exit} on{print}' "$1" | grep -v -E '^[[:space:]]*#' | grep -oE 'scripts/[A-Za-z0-9_./-]+' | LC_ALL=C sort -u | tr '\n' ' '; } +# pinned: ci_test_tier.sh. isolated (scratch env): fetch_p6.sh, pr_touched_paths.sh, ci_mg_workspace_result.sh. +# ci_test_tier.sh itself runs (pinned parent, scratch env): check_ci_push_reuse.sh check_tree_reader_tests.sh gate_touched_crates.sh lib/test_tier.py +want="scripts/ci/fetch_p6.sh scripts/ci/pr_touched_paths.sh scripts/ci_mg_workspace_result.sh scripts/ci_test_tier.sh " +wrow "W45a the tier step runs exactly the classified files" "$want" "$(tier_exec "$SEC")" +mutate "$SEC" 'bash scripts/ci_test_tier.sh' 'bash scripts/ci_test_tier.sh; bash scripts/new_helper.sh' "$td/exec.yml" && { [ "$(tier_exec "$td/exec.yml")" = "$want" ] && r=missed || r=caught; wrow "W45b MUTANT: an unclassified helper in the tier step turns the list RED" caught "$r"; } || { fail=$((fail + 1)); echo "FAIL W45b: mutant did not apply"; } if mutate "$CI" '[ "$ok" = 1 ] || ok=0' '[ "$ok" = 1 ] || ok=1' "$td/b7.yml"; then wrow "W23g MUTANT (E2): a dead guard passes the garbage verifier answer, behaviourally" 2 "$(judge_run "$td/b7.yml" vgarbage | sed 's/^1$/2/')"; else fail=$((fail + 1)); echo "FAIL W21g: mutant did not apply"; fi echo "receipt gate rows: $pass passed, $fail failed" [ "$fail" -eq 0 ] diff --git a/scripts/ci/fat_driver.py b/scripts/ci/fat_driver.py index a7f93ed08a..1fdaf65a3b 100644 --- a/scripts/ci/fat_driver.py +++ b/scripts/ci/fat_driver.py @@ -562,6 +562,24 @@ def make_section_dirs(d: Path, log, rmtree=shutil.rmtree): (d / "ws").mkdir() +def _fc_put(out: dict, k: str, v: str, path: Path) -> None: + # #5850: a repeated key would let a helper's line shadow the real tier= line; fail closed + if k in out: + raise SystemExit(f"fat_driver: duplicate key {k} in {path}") + out[k] = v + + +def _fc_heredoc(lines, i: int, key: str, delim: str, path: Path): + buf = [] + while i < len(lines) and lines[i] != delim: + buf.append(lines[i]) + i += 1 + if i >= len(lines): + # an unterminated heredoc would swallow every later line (the real tier= lines); fail closed + raise SystemExit(f"fat_driver: unterminated heredoc {key}<<{delim} in {path}") + return "\n".join(buf), i + + def parse_file_command(path: Path) -> dict: out = {} if not path.exists(): @@ -572,16 +590,11 @@ def parse_file_command(path: Path) -> dict: line = lines[i] m = re.match(r"^([^=<]+)<<(.+)$", line) if m: - key, delim = m.group(1), m.group(2) - buf = [] - i += 1 - while i < len(lines) and lines[i] != delim: - buf.append(lines[i]) - i += 1 - out[key] = "\n".join(buf) + text, i = _fc_heredoc(lines, i + 1, m.group(1), m.group(2), path) + _fc_put(out, m.group(1), text, path) elif "=" in line: k, v = line.split("=", 1) - out[k] = v + _fc_put(out, k, v, path) i += 1 return out From 59f9eda1f0feab0cbbbbea34b915ed1f1c956590 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 10:12:44 +0200 Subject: [PATCH 23/26] gate(#5850): byte-pin the six tier-step helpers + ci_mg_reuse.sh; W41c/W42d rows cover each Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_receipt_gate.sh | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index 001c3c2920..b57f3a7b88 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -237,6 +237,14 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy [ "$(sha256sum < "$pr/scripts/ci_reclaim_target_dirs.sh" | cut -d' ' -f1)" = "454aec9194c8d90aa0587e469fca9c8d801fa211c0a4aacdf9c2dc5a820c5449" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci/fixture_quiet.sh" | cut -d' ' -f1)" = "0571ef87e0859c9dc9299c6cb57b1d3ed11991fb5e3c890cf56c76e385df730c" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci/pr_touched_paths.sh" | cut -d' ' -f1)" = "eed39751351685073888dd9e1ae9e993a30cdc933f97eb8e92aa8f01b36e42a4" ] || return 1 + # helpers the tier step (and ci_test_tier.sh) run: byte-pinned, because a same-uid helper can reach the real GITHUB_OUTPUT via /proc/$PPID/environ (#5850) + [ "$(sha256sum < "$pr/scripts/ci/fetch_p6.sh" | cut -d' ' -f1)" = "5addde69a85a4d6996f89a1af85a04abaebed3a4c27b447e254171d97205580b" ] || return 1 + [ "$(sha256sum < "$pr/scripts/ci_mg_workspace_result.sh" | cut -d' ' -f1)" = "560bb59288305511978fc8a674bda2d7b62dfb6dcb887fcbf189a3ae8b7d5296" ] || return 1 + [ "$(sha256sum < "$pr/scripts/check_ci_push_reuse.sh" | cut -d' ' -f1)" = "3cf51081840c4dcc03743ca165af58f654b1cb61020d41bf081eb2370b5a948a" ] || return 1 + [ "$(sha256sum < "$pr/scripts/check_tree_reader_tests.sh" | cut -d' ' -f1)" = "4f4f0e892b1b4c65e1dd9dcc46f0edc0e5deac2f1b5dc7883fc124f6b4c6f0aa" ] || return 1 + [ "$(sha256sum < "$pr/scripts/gate_touched_crates.sh" | cut -d' ' -f1)" = "00f73732cbe0c39c9ff0867d1cb416ef0e7e0d00dc5b01f300cee645fe444bfe" ] || return 1 + [ "$(sha256sum < "$pr/scripts/lib/test_tier.py" | cut -d' ' -f1)" = "63e4b091c7a6c56b45c8da5ceb241ba2a6f991cef55bb3802ffa6458b786dd52" ] || return 1 + [ "$(sha256sum < "$pr/scripts/ci_mg_reuse.sh" | cut -d' ' -f1)" = "c48c4c49a48a5f57c4876465bf72a74776694e48cd59b2bffb25237d352f279b" ] || return 1 # structure pins: the top-level keys and the job keys of both files, in any spelling (quoted, ? keys, duplicates). A new top-level key or job, or a second shard block, moves the hash. [ "$(tl "$1" | h)" = "7b45344e526135d0ab4f5c04ca82ba2d1112f6286b1ba2024a18017536446ce5" ] || return 1 [ "$(jl "$1" | h)" = "1d370235776861592b57ff7c935a327fbf70d28427ac084baa4392f5b70d8250" ] || return 1 @@ -407,9 +415,9 @@ wmut "W41a MUTANT (M1): an escaped RECEIPT\x5FOK env key in the ci.yml shard env sc=' SCCACHE_HOST_DIR: /home/noah/data/sccache' wmut "W41b MUTANT (M2): an escaped RECEIPT\x5FOK env key in the sections.yml shard env is caught" sec "$sc" "$sc"$'\n'' "RECEIPT\x5FOK": "1"' # a byte change in any verdict script is RED unless its pin moves (PIN_ROOT = a copy of the tree with one file mutated) -for rel in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh; do - pr="$td/pin-$(basename "$rel")"; mkdir -p "$pr/scripts/ci" - for g in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh; do cp "$HERE/../$g" "$pr/$g"; done +for rel in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh scripts/ci/fetch_p6.sh scripts/ci_mg_workspace_result.sh scripts/check_ci_push_reuse.sh scripts/check_tree_reader_tests.sh scripts/gate_touched_crates.sh scripts/lib/test_tier.py scripts/ci_mg_reuse.sh; do + pr="$td/pin-$(basename "$rel")"; mkdir -p "$pr/scripts/ci" "$pr/scripts/lib" + for g in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh scripts/ci/fetch_p6.sh scripts/ci_mg_workspace_result.sh scripts/check_ci_push_reuse.sh scripts/check_tree_reader_tests.sh scripts/gate_touched_crates.sh scripts/lib/test_tier.py scripts/ci_mg_reuse.sh; do cp "$HERE/../$g" "$pr/$g"; done printf '\n# touched\n' >> "$pr/$rel" if PIN_ROOT="$pr" judge_ok "$CI" "$SEC"; then r=missed; else r=caught; fi wrow "W41c MUTANT: a byte appended to $rel is caught by the hash pin" caught "$r" @@ -420,9 +428,9 @@ wmut "W42a MUTANT: a quoted top-level \"env\" key with BASH\x5FENV in ci.yml is wmut "W42b MUTANT: a ? top-level defaults key in ci.yml is caught" ci $'\njobs:\n' $'\n? defaults\n: {run: {shell: bash}}\njobs:\n' wmut "W42c MUTANT: a duplicate workspace-test-shard job after the first in ci.yml is caught" ci $'\n workspace-test-shard:\n' $'\n workspace-test-shard:\n runs-on: x\n workspace-test-shard:\n' # W42d/e: a shard script that writes the runner env/output files is a pinned-byte change, so it is RED -for rel in scripts/ci_test_tier.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh; do for w in 'echo RECEIPT_OK=1 >> "$GITHUB_ENV"' 'echo ok=1 >> "$GITHUB_OUTPUT"'; do - pr="$td/pin-w"; rm -rf -- "${pr:?}"; mkdir -p "$pr/scripts/ci" - for g in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh; do cp "$HERE/../$g" "$pr/$g"; done +for rel in scripts/ci/fetch_p6.sh scripts/ci_mg_workspace_result.sh scripts/check_ci_push_reuse.sh scripts/check_tree_reader_tests.sh scripts/gate_touched_crates.sh scripts/lib/test_tier.py scripts/ci_mg_reuse.sh scripts/ci_test_tier.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh scripts/ci/fetch_p6.sh scripts/ci_mg_workspace_result.sh scripts/check_ci_push_reuse.sh scripts/check_tree_reader_tests.sh scripts/gate_touched_crates.sh scripts/lib/test_tier.py scripts/ci_mg_reuse.sh; do for w in 'echo RECEIPT_OK=1 >> "$GITHUB_ENV"' 'echo ok=1 >> "$GITHUB_OUTPUT"'; do + pr="$td/pin-w"; rm -rf -- "${pr:?}"; mkdir -p "$pr/scripts/ci" "$pr/scripts/lib" + for g in scripts/ci/fat_driver.py scripts/ci_test_tier.sh scripts/ci/receipt_ok_arg.sh scripts/ci_reclaim_target_dirs.sh scripts/ci/fixture_quiet.sh scripts/ci/pr_touched_paths.sh scripts/ci/fetch_p6.sh scripts/ci_mg_workspace_result.sh scripts/check_ci_push_reuse.sh scripts/check_tree_reader_tests.sh scripts/gate_touched_crates.sh scripts/lib/test_tier.py scripts/ci_mg_reuse.sh; do cp "$HERE/../$g" "$pr/$g"; done printf "\n%s\n" "$w" >> "$pr/$rel" if PIN_ROOT="$pr" judge_ok "$CI" "$SEC"; then r=missed; else r=caught; fi wrow "W42d MUTANT: $rel writing ${w##* } is caught" caught "$r" From 577bda7b5190dfed4f78bf1e6f5c06d42d0ed135 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 10:23:20 +0200 Subject: [PATCH 24/26] gate(#5850): tier step pins RUSTUP_TOOLCHAIN so a PR rust-toolchain.toml path= cannot run PR code; W46 rows Co-Authored-By: Claude Sonnet 5.5 --- ci/sections.yml | 2 ++ scripts/check_receipt_gate.sh | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/ci/sections.yml b/ci/sections.yml index addc86ebab..7127efff78 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -386,6 +386,8 @@ jobs: # the real file, so no helper can plant tier=/cite= lines or a heredoc in front of ours. real_out="$GITHUB_OUTPUT" GITHUB_OUTPUT="$(mktemp)"; GITHUB_ENV="$(mktemp)"; export GITHUB_OUTPUT GITHUB_ENV + # #5850: a PR rust-toolchain.toml `path=` would make `cargo metadata` (gate_touched_crates.sh) run PR code before the final write; the env var outranks the file. + RUSTUP_TOOLCHAIN=1.93.0; export RUSTUP_TOOLCHAIN args=(--event "${GITHUB_EVENT_NAME}") # #5797: a signed batch receipt in the PR body (signature, tree hash == this # merge tree, toolchain hash) lets the PR run check/fmt/clippy instead of the diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index b57f3a7b88..ddc623b126 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -217,7 +217,7 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy printf '%s\n' "$tier" | grep -E 'scripts/|git (fetch|clone)' | grep -q . && return 1 # the consumer chain (tier step: RECEIPT_OK -> rok -> --receipt-ok -> ci_test_tier.sh) is hash-pinned whole; edit it -> update this sha256 (gate content, quorum) step=$(awk '/- name: Decide the test tier/{on=1;print;next} on&&/^ - name:/{exit} on{print}' "$2" | grep -v -E '^[[:space:]]*#') - [ "$(printf '%s\n' "$step" | sha256sum | cut -d' ' -f1)" = "a6c4a1aa70c61821a7118c191b810093cf31021b686e2cc6e9eb138b602729a2" ] || return 1 + [ "$(printf '%s\n' "$step" | sha256sum | cut -d' ' -f1)" = "0335490bab33adde6bd276b21c44041d1baa29418e28d568553b3cfc8c76f35b" ] || return 1 # nothing outside the pinned lines may set the verdict, the shell or the environment ciall=$(grep -v -E '^[[:space:]]*#' "$1"); secall=$(grep -v -E '^[[:space:]]*#' "$2") n=$(grep -c -E 'RECEIPT_OK|receipt-ok|BASH_ENV|GITHUB_ENV|^defaults:|^env:' <<< "$ciall" || true); [ "$n" = 1 ] || return 1 @@ -229,7 +229,7 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy local pr=${PIN_ROOT:-$HERE/..} blk [ "$(printf '%s\n' "$shard" | sha256sum | cut -d' ' -f1)" = "6c7c538c950899c60f7436868ee80a003f904e3ed3a4286877976964f4ddc216" ] || return 1 blk=$(awk '/^jobs:/{j=1} j&&/^ workspace-test-shard:$/{on=1;print;next} on&&/^ [a-z]/{exit} on{print}' "$2" | grep -v -E '^[[:space:]]*#') - [ "$(printf '%s\n' "$blk" | sha256sum | cut -d' ' -f1)" = "b27698ea49d1149e98cb8fca78d45c48a0f58322b89f9b99a87b6224d415d460" ] || return 1 + [ "$(printf '%s\n' "$blk" | sha256sum | cut -d' ' -f1)" = "c8047f526588a7e7af8ab15a17118a5a69e8cbf4a4edae5181473f251f5a5c55" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci/fat_driver.py" | cut -d' ' -f1)" = "81662d808e4672b41b481f44361c10aaae308661f492b1aaaff47f87ffb27cfc" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci_test_tier.sh" | cut -d' ' -f1)" = "1680bab97243fb7b5ce6907daf3c8a00db4968a2e892095260152a09cc127866" ] || return 1 [ "$(sha256sum < "$pr/scripts/ci/receipt_ok_arg.sh" | cut -d' ' -f1)" = "1795d4df36fa1c3f223dd054d93392aff7584bb5063bed1d7a11bd3f18144820" ] || return 1 @@ -465,5 +465,21 @@ want="scripts/ci/fetch_p6.sh scripts/ci/pr_touched_paths.sh scripts/ci_mg_worksp wrow "W45a the tier step runs exactly the classified files" "$want" "$(tier_exec "$SEC")" mutate "$SEC" 'bash scripts/ci_test_tier.sh' 'bash scripts/ci_test_tier.sh; bash scripts/new_helper.sh' "$td/exec.yml" && { [ "$(tier_exec "$td/exec.yml")" = "$want" ] && r=missed || r=caught; wrow "W45b MUTANT: an unclassified helper in the tier step turns the list RED" caught "$r"; } || { fail=$((fail + 1)); echo "FAIL W45b: mutant did not apply"; } if mutate "$CI" '[ "$ok" = 1 ] || ok=0' '[ "$ok" = 1 ] || ok=1' "$td/b7.yml"; then wrow "W23g MUTANT (E2): a dead guard passes the garbage verifier answer, behaviourally" 2 "$(judge_run "$td/b7.yml" vgarbage | sed 's/^1$/2/')"; else fail=$((fail + 1)); echo "FAIL W21g: mutant did not apply"; fi +# W46: a PR rust-toolchain.toml `path=` must not run PR code in the tier step (gate_touched_crates.sh runs `cargo metadata` in the PR tree) +tc_plant() { # -> 1 when the planted cargo ran + local d; d=$(mktemp -d); mkdir -p "$d/tc/bin" "$d/p/src" + printf '#!/bin/sh\ntouch %s/RAN\nexit 1\n' "$d" > "$d/tc/bin/cargo"; chmod +x "$d/tc/bin/cargo" + printf '[toolchain]\npath="%s/tc"\n' "$d" > "$d/p/rust-toolchain.toml" + printf '[package]\nname="x"\nversion="0.1.0"\nedition="2021"\n' > "$d/p/Cargo.toml"; : > "$d/p/src/lib.rs" + (cd "$d/p" && env ${1:+RUSTUP_TOOLCHAIN="$1"} cargo metadata --no-deps --format-version 1 > /dev/null 2>&1 < /dev/null) + [ -e "$d/RAN" ] && echo 1 || echo 0 + rm -rf -- "${d:?}" +} +tier_tc() { awk '/- name: Decide the test tier/{on=1;print;next} on&&/^ - name:/{exit} on{print}' "$1" | grep -v -E '^[[:space:]]*#' | grep -oE 'RUSTUP_TOOLCHAIN=[0-9.]+' | head -1 | cut -d= -f2; } +tcv=$(tier_tc "$SEC") +wrow "W46a the tier step pins RUSTUP_TOOLCHAIN to the base toolchain channel" "$(sed -n 's/^channel = "\(.*\)"/\1/p' "$HERE/../rust-toolchain.toml")" "$tcv" +wrow "W46b MUTANT: with no RUSTUP_TOOLCHAIN a PR toolchain path= runs PR code (the plant works)" 1 "$(tc_plant "")" +wrow "W46c with the pinned RUSTUP_TOOLCHAIN the planted cargo does not run" 0 "$(tc_plant "$tcv")" +wmut "W46d MUTANT: the RUSTUP_TOOLCHAIN pin removed from the tier step is caught" sec 'RUSTUP_TOOLCHAIN=1.93.0; export RUSTUP_TOOLCHAIN' ':' echo "receipt gate rows: $pass passed, $fail failed" [ "$fail" -eq 0 ] From ea4fea280764ac1fa3b7d78e86fb41085133eede Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 10:55:14 +0200 Subject: [PATCH 25/26] test(ci): D1 CANON lists mg-reuse-wt, workspace-test-ran and the shard's wt gate (Refs #5884) falsify_ci_draft_gate_real_workflow_holds failed in the merge-group run 39744455801 (shard 3/4, 3 of 3 tries): #5885's own diff adds two jobs and a new shard `if:` that CANON did not know. Each now carries its draft behaviour. Co-Authored-By: Claude Sonnet 5.5 --- crates/aprender-core/tests/monorepo_invariants.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/aprender-core/tests/monorepo_invariants.rs b/crates/aprender-core/tests/monorepo_invariants.rs index 2579950bad..7ca5d9987d 100644 --- a/crates/aprender-core/tests/monorepo_invariants.rs +++ b/crates/aprender-core/tests/monorepo_invariants.rs @@ -1152,7 +1152,10 @@ fn draft_gate_canon() -> Vec<(&'static str, String)> { ("x86-main", format!("${{{{ !cancelled() && needs.mg-reuse.outputs.x86 != '1' && {DF} }}}}")), ("x86-main-advisories", format!("${{{{ !cancelled() && needs.mg-reuse.outputs.x86 == '1' && {DF} }}}}")), ("determinism", "${{ !cancelled() && needs.mg-reuse.outputs.det != '1' && github.event_name != 'pull_request' }}".into()), - ("workspace-test-shard", format!("${{{{ {DF} }}}}")), + // merge_group-only: a draft never reaches the queue, so there is nothing to skip. + ("mg-reuse-wt", "github.event_name == 'merge_group'".into()), + ("workspace-test-shard", format!("${{{{ !cancelled() && needs.mg-reuse-wt.outputs.wt != '1' && {DF} }}}}")), + ("workspace-test-ran", format!("${{{{ !cancelled() && needs.workspace-test-shard.result == 'success' && github.event_name == 'pull_request' && {DF} }}}}")), ("workspace-test", "always()".into()), ("ci-gate", "always()".into()), ("gate", "always()".into()), From 10f4f79681a17d760edb97c43a0ce65fa10b17ab Mon Sep 17 00:00:00 2001 From: t Date: Thu, 22 Oct 2026 11:02:25 +0200 Subject: [PATCH 26/26] gate(#5850): ci_mg_reuse.sh pin follows #5801 (03b3bdf6...) Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_receipt_gate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_receipt_gate.sh b/scripts/check_receipt_gate.sh index ddc623b126..3ca82558b5 100755 --- a/scripts/check_receipt_gate.sh +++ b/scripts/check_receipt_gate.sh @@ -244,7 +244,7 @@ judge_ok() { # CI_FILE SECTIONS_FILE -> 0 when the wiring is trustworthy [ "$(sha256sum < "$pr/scripts/check_tree_reader_tests.sh" | cut -d' ' -f1)" = "4f4f0e892b1b4c65e1dd9dcc46f0edc0e5deac2f1b5dc7883fc124f6b4c6f0aa" ] || return 1 [ "$(sha256sum < "$pr/scripts/gate_touched_crates.sh" | cut -d' ' -f1)" = "00f73732cbe0c39c9ff0867d1cb416ef0e7e0d00dc5b01f300cee645fe444bfe" ] || return 1 [ "$(sha256sum < "$pr/scripts/lib/test_tier.py" | cut -d' ' -f1)" = "63e4b091c7a6c56b45c8da5ceb241ba2a6f991cef55bb3802ffa6458b786dd52" ] || return 1 - [ "$(sha256sum < "$pr/scripts/ci_mg_reuse.sh" | cut -d' ' -f1)" = "c48c4c49a48a5f57c4876465bf72a74776694e48cd59b2bffb25237d352f279b" ] || return 1 + [ "$(sha256sum < "$pr/scripts/ci_mg_reuse.sh" | cut -d' ' -f1)" = "03b3bdf6ebe3bcbab2d67af559d3e33224a19fc5e938d0d4f5b9862508362fa4" ] || return 1 # structure pins: the top-level keys and the job keys of both files, in any spelling (quoted, ? keys, duplicates). A new top-level key or job, or a second shard block, moves the hash. [ "$(tl "$1" | h)" = "7b45344e526135d0ab4f5c04ca82ba2d1112f6286b1ba2024a18017536446ce5" ] || return 1 [ "$(jl "$1" | h)" = "1d370235776861592b57ff7c935a327fbf70d28427ac084baa4392f5b70d8250" ] || return 1