From 59a35c560e5da9db5e7ab37ffbcc39c7f37430eb Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 17:39:15 +0200 Subject: [PATCH 01/24] feat(flow-010): affected_set.sh - diff to crates + reverse-dep closure + nextest filter (E5) Rows checked against a cargo-tree oracle. Core crate, Cargo.lock, Cargo.toml and any unknown path give ALL. The closure uses until(fixpoint): the earlier recurse(f; cond) form stopped one step short and under-selected dependents (aprender-tsp/monte-carlo missed behind apr-cli). Co-Authored-By: Claude Sonnet 5.5 --- .../815-affected-set-rows.cmd | 2 + scripts/affected_set.sh | 54 +++++++++++++++++++ scripts/check_affected_set.sh | 37 +++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 ci/explicit-test-commands.d/815-affected-set-rows.cmd create mode 100755 scripts/affected_set.sh create mode 100755 scripts/check_affected_set.sh diff --git a/ci/explicit-test-commands.d/815-affected-set-rows.cmd b/ci/explicit-test-commands.d/815-affected-set-rows.cmd new file mode 100644 index 0000000000..639899fb2a --- /dev/null +++ b/ci/explicit-test-commands.d/815-affected-set-rows.cmd @@ -0,0 +1,2 @@ +# FLOW-010 E5: affected-set tool rows (leaf+dependents vs cargo tree oracle, core/Cargo.lock/unknown -> ALL). +bash scripts/check_affected_set.sh diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh new file mode 100755 index 0000000000..5582dd1618 --- /dev/null +++ b/scripts/affected_set.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# FLOW-010 E5: the diff -> the set of workspace packages whose tests it can change. +# +# scripts/affected_set.sh [--filter] [--diff-from FILE] [BASE [HEAD]] +# +# Prints one package per line, or the single word ALL. With --filter it prints the +# nextest filterset instead (`package(a) | package(b)`, or `all()` for ALL). +# Fail-safe: anything this tool cannot place in a package is ALL. +# crates//... the package + its reverse-dependency closure (cargo metadata) +# a CORE crate ALL (its closure is the workspace; naming it would only hide that) +# docs/, *.md, ci/sections.yml, ci/shard-step-costs.tsv, ci/explicit-test-commands.d/ +# contribute nothing (no crate code) +# Cargo.lock, Cargo.toml, rust-toolchain.toml, .cargo/, .github/, Makefile, any other path +# ALL +# An empty diff prints nothing. --diff-from reads the changed paths from FILE (tests). +set -euo pipefail +CORE="${AFFECTED_CORE:-aprender-core aprender-compute aprender-contracts}" +filter=0 from="" +while [ $# -gt 0 ]; do + case "$1" in + --filter) filter=1; shift ;; + --diff-from) from="$2"; shift 2 ;; + *) break ;; + esac +done +if [ -n "$from" ]; then files="$(cat "$from")"; else files="$(git diff --name-only "${1:-origin/main}...${2:-HEAD}")"; fi +emit() { # $@ = package names, or ALL + if [ "${1:-}" = ALL ]; then [ "$filter" = 1 ] && echo 'all()' || echo ALL; return; fi + if [ "$filter" = 1 ]; then printf '%s\n' "$@" | sed 's/.*/package(&)/' | paste -sd'|' | sed 's/|/ | /g'; else printf '%s\n' "$@"; fi +} +[ -n "$files" ] || exit 0 +meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null || cargo metadata --no-deps --format-version 1)" +root="$(git rev-parse --show-toplevel)" +dirs="$(jq -r --arg root "$root/" '.packages[] | [.name, (.manifest_path | sub($root;"") | sub("/?Cargo.toml$";""))] | @tsv' <<<"$meta")" +touched="" +while IFS= read -r f; do + case "$f" in + '') continue ;; + docs/* | *.md | ci/sections.yml | ci/shard-step-costs.tsv | ci/explicit-test-commands.d/*) continue ;; + esac + # longest manifest dir that prefixes the path + pkg="$(awk -F'\t' -v f="$f" 'index(f, $2 "/") == 1 && length($2) > best { best = length($2); n = $1 } END { print n }' <<<"$dirs")" + [ -n "$pkg" ] || { emit ALL; exit 0; } + case " $CORE " in *" $pkg "*) emit ALL; exit 0 ;; esac + touched="$touched"$'\n'"$pkg" +done <<<"$files" +touched="$(printf '%s\n' "$touched" | grep -v '^$' | sort -u || true)" +[ -n "$touched" ] || exit 0 +mapfile -t out < <(jq -r --argjson t "$(printf '%s\n' "$touched" | jq -R . | jq -s .)" ' + (.packages | map({name, deps: [.dependencies[].name]})) as $p + | def grow($s): ($s + [$p[] | select(any(.deps[]; . as $d | $s | index($d))) | .name] | unique); + (.packages | map(.name)) as $m + | ($t | until(grow(.) == .; grow(.))) | .[] | select(. as $n | $m | index($n))' <<<"$meta") +emit "${out[@]}" diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh new file mode 100755 index 0000000000..47c6d561a2 --- /dev/null +++ b/scripts/check_affected_set.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# FLOW-010 E5 RED rows for scripts/affected_set.sh. Oracle for the closure is +# `cargo tree -i` (a different code path from the tool's cargo-metadata jq). +set -euo pipefail +A=scripts/affected_set.sh +t=$(mktemp -d); trap 'rm -rf "$t"' EXIT +fail=0 +row() { # name want got + if [ "$2" = "$3" ]; then echo "ok $1"; else echo "FAIL $1"; echo " want: $2" | head -c 300; echo; echo " got: $3" | head -c 300; echo; fail=1; fi +} +run() { printf '%s\n' "$@" >"$t/d"; $A --diff-from "$t/d"; } +core_list=" aprender-core aprender-compute aprender-contracts " +meta=$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null) +members=$(jq -r '.packages[].name' <<<"$meta" | sort) +# a non-core crate that has dependents, and a leaf with none +withdep="" leaf="" +for p in $members; do + case "$core_list" in *" $p "*) continue ;; esac + n=$(cargo tree --offline -i "$p" --workspace -e normal,dev,build --prefix none 2>/dev/null | sed 's/ v.*//' | sort -u | grep -vc "^$p$" || true) + dir=$(jq -r --arg p "$p" '.packages[]|select(.name==$p)|.manifest_path' <<<"$meta" | sed "s|$(git rev-parse --show-toplevel)/||; s|/Cargo.toml||") + [ -z "$withdep" ] && [ "$n" -gt 0 ] && [ "$n" -lt 10 ] && withdep="$p:$dir" + [ -z "$leaf" ] && [ "$n" -eq 0 ] && leaf="$p:$dir" + [ -n "$withdep" ] && [ -n "$leaf" ] && break +done +[ -n "$withdep" ] && [ -n "$leaf" ] || { echo "FAIL could not find a leaf and a crate with dependents"; exit 1; } +wp=${withdep%%:*}; wd=${withdep#*:}; lp=${leaf%%:*}; ld=${leaf#*:} +want=$(cargo tree --offline -i "$wp" --workspace -e normal,dev,build --prefix none 2>/dev/null | sed 's/ v.*//' | sort -u) +row "crate with dependents ($wp) -> itself + dependents" "$want" "$(run "$wd/src/lib.rs" | sort -u)" +row "leaf crate ($lp) -> itself only" "$lp" "$(run "$ld/src/lib.rs")" +row "core crate -> ALL" ALL "$(run crates/aprender-core/src/lib.rs)" +row "Cargo.lock -> ALL" ALL "$(run Cargo.lock)" +row "unknown path -> ALL" ALL "$(run some/unknown/path.txt)" +row "unknown path next to a leaf -> ALL" ALL "$(run "$ld/src/lib.rs" scripts/whatever.sh)" +row "docs only -> nothing" "" "$(run docs/x.md)" +row "--filter names the package" "package($lp)" "$($A --filter --diff-from <(echo "$ld/src/lib.rs"))" +row "--filter ALL -> all()" "all()" "$($A --filter --diff-from <(echo Cargo.lock))" +exit "$fail" From ad30656062253c0a49f052291476500b73e1eee8 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 18:19:20 +0200 Subject: [PATCH 02/24] fix(flow-010): empty diff and bad base are ALL; pin dev-dep counting (Refs #5802) Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 15 ++++++++++----- scripts/check_affected_set.sh | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index 5582dd1618..22b2028a76 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -12,7 +12,10 @@ # contribute nothing (no crate code) # Cargo.lock, Cargo.toml, rust-toolchain.toml, .cargo/, .github/, Makefile, any other path # ALL -# An empty diff prints nothing. --diff-from reads the changed paths from FILE (tests). +# An empty diff, or a diff git cannot compute (bad base: exit 128), is ALL: nothing was +# measured, so nothing may be skipped. A diff of only no-crate-code paths prints nothing. +# --diff-from reads the changed paths from FILE (tests). AFFECTED_META / AFFECTED_ROOT +# replace `cargo metadata` and the checkout root (tests; synthetic graphs). set -euo pipefail CORE="${AFFECTED_CORE:-aprender-core aprender-compute aprender-contracts}" filter=0 from="" @@ -23,14 +26,16 @@ while [ $# -gt 0 ]; do *) break ;; esac done -if [ -n "$from" ]; then files="$(cat "$from")"; else files="$(git diff --name-only "${1:-origin/main}...${2:-HEAD}")"; fi +if [ -n "$from" ]; then files="$(cat "$from")" +else files="$(git diff --name-only "${1:-origin/main}...${2:-HEAD}")" || files=""; fi emit() { # $@ = package names, or ALL if [ "${1:-}" = ALL ]; then [ "$filter" = 1 ] && echo 'all()' || echo ALL; return; fi if [ "$filter" = 1 ]; then printf '%s\n' "$@" | sed 's/.*/package(&)/' | paste -sd'|' | sed 's/|/ | /g'; else printf '%s\n' "$@"; fi } -[ -n "$files" ] || exit 0 -meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null || cargo metadata --no-deps --format-version 1)" -root="$(git rev-parse --show-toplevel)" +[ -n "$files" ] || { emit ALL; exit 0; } +if [ -n "${AFFECTED_META:-}" ]; then meta="$(cat "$AFFECTED_META")" +else meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null || cargo metadata --no-deps --format-version 1)"; fi +root="${AFFECTED_ROOT:-$(git rev-parse --show-toplevel)}" dirs="$(jq -r --arg root "$root/" '.packages[] | [.name, (.manifest_path | sub($root;"") | sub("/?Cargo.toml$";""))] | @tsv' <<<"$meta")" touched="" while IFS= read -r f; do diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index 47c6d561a2..a0919ae580 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -34,4 +34,25 @@ row "unknown path next to a leaf -> ALL" ALL "$(run "$ld/src/lib.rs" scripts/wha row "docs only -> nothing" "" "$(run docs/x.md)" row "--filter names the package" "package($lp)" "$($A --filter --diff-from <(echo "$ld/src/lib.rs"))" row "--filter ALL -> all()" "all()" "$($A --filter --diff-from <(echo Cargo.lock))" +# empty diff and a bad base are ALL (nothing measured, nothing skipped) +: >"$t/empty" +row "empty diff -> ALL" ALL "$($A --diff-from "$t/empty")" +row "bad base (git exit 128) -> ALL" ALL "$($A no-such-base-ref-5802 HEAD 2>/dev/null)" +# dev-dep counting: b depends on a ONLY as a dev-dependency; touching a must reach b. +cat >"$t/meta.json" <<'JSON' +{"packages":[ + {"name":"a","manifest_path":"/r/crates/a/Cargo.toml","dependencies":[]}, + {"name":"b","manifest_path":"/r/crates/b/Cargo.toml","dependencies":[{"name":"a","kind":"dev"}]}, + {"name":"c","manifest_path":"/r/crates/c/Cargo.toml","dependencies":[]}]} +JSON +row "dev-only edge is counted: touching a reaches b" "a +b" "$(AFFECTED_META="$t/meta.json" AFFECTED_ROOT=/r run crates/a/src/lib.rs | sort -u)" +row "an unrelated crate is not reached" "c" "$(AFFECTED_META="$t/meta.json" AFFECTED_ROOT=/r run crates/c/src/lib.rs)" +# mutant: a tool that drops dev edges misses b, and the dev-dep row must see it +sed 's/\[\.dependencies\[\]\.name\]/[.dependencies[]|select(.kind!="dev")|.name]/' "$A" >"$t/mut.sh" +if cmp -s "$A" "$t/mut.sh"; then echo "FAIL mutant did not apply"; fail=1 +else + got=$(AFFECTED_META="$t/meta.json" AFFECTED_ROOT=/r bash "$t/mut.sh" --diff-from <(echo crates/a/src/lib.rs) | sort -u) + row "MUTANT: ignoring dev edges is caught by the dev-dep row" "a" "$got" +fi exit "$fail" From dea7901cccfa1c5eb82b0d2efa2538cfd8cd292e Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 18:23:49 +0200 Subject: [PATCH 03/24] fix(flow-010): affected_set --no-renames and include_str!/include_bytes! mapping (Refs #5802) A rename out of a crate showed only its destination (git rename detection), so the source crate was skipped; --no-renames reports both paths. A docs/*.md file a crate embeds with include_str!/include_bytes!(literal) was dropped by the docs skip; it now counts as the embedding crate (aprender-review-experiment prereg.rs). Rows plus a RED mutant for each. Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 23 +++++++++++++++++++++-- scripts/check_affected_set.sh | 30 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index 22b2028a76..ec0c162635 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -14,6 +14,9 @@ # ALL # An empty diff, or a diff git cannot compute (bad base: exit 128), is ALL: nothing was # measured, so nothing may be skipped. A diff of only no-crate-code paths prints nothing. +# git diff runs with --no-renames: a moved file is its old path AND its new path (a rename +# out of a crate would otherwise show only the destination and the source crate would be skipped). +# A docs/*.md file that a crate embeds with include_str!/include_bytes!("literal") counts as that crate. # --diff-from reads the changed paths from FILE (tests). AFFECTED_META / AFFECTED_ROOT # replace `cargo metadata` and the checkout root (tests; synthetic graphs). set -euo pipefail @@ -27,7 +30,7 @@ while [ $# -gt 0 ]; do esac done if [ -n "$from" ]; then files="$(cat "$from")" -else files="$(git diff --name-only "${1:-origin/main}...${2:-HEAD}")" || files=""; fi +else files="$(git diff --name-only --no-renames "${1:-origin/main}...${2:-HEAD}")" || files=""; fi emit() { # $@ = package names, or ALL if [ "${1:-}" = ALL ]; then [ "$filter" = 1 ] && echo 'all()' || echo ALL; return; fi if [ "$filter" = 1 ]; then printf '%s\n' "$@" | sed 's/.*/package(&)/' | paste -sd'|' | sed 's/|/ | /g'; else printf '%s\n' "$@"; fi @@ -37,11 +40,27 @@ if [ -n "${AFFECTED_META:-}" ]; then meta="$(cat "$AFFECTED_META")" else meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null || cargo metadata --no-deps --format-version 1)"; fi root="${AFFECTED_ROOT:-$(git rev-parse --show-toplevel)}" dirs="$(jq -r --arg root "$root/" '.packages[] | [.name, (.manifest_path | sub($root;"") | sub("/?Cargo.toml$";""))] | @tsv' <<<"$meta")" +# "\t" for every include_str!/include_bytes!("literal") under crates/, +# the literal resolved against the embedding file's directory. Non-literal (concat!/macro) args are not resolved. +INCL_PL='use File::Basename; for $f (@ARGV) { open F, "<", $f or next; local $/; $s = ; close F; + while ($s =~ /include_(?:str|bytes)!\s*\(\s*"([^"]*)"/g) { @o = (); + for (split m{/}, dirname($f) . "/" . $1) { next if $_ eq "" || $_ eq "."; if ($_ eq "..") { pop @o } else { push @o, $_ } } + print join("/", @o) . "\t" . $f . "\n" } }' +incl="$( (cd "$root" 2>/dev/null && find crates -name "*.rs" -type f -print0 2>/dev/null | xargs -0 -r perl -0777 -e "$INCL_PL") || true )" touched="" while IFS= read -r f; do case "$f" in '') continue ;; - docs/* | *.md | ci/sections.yml | ci/shard-step-costs.tsv | ci/explicit-test-commands.d/*) continue ;; + docs/* | *.md | ci/sections.yml | ci/shard-step-costs.tsv | ci/explicit-test-commands.d/*) + # skipped as "no crate code" unless a crate embeds it: then it is that crate's input + while IFS=$'\t' read -r _ src; do + [ -n "$src" ] || continue + pkg="$(awk -F'\t' -v f="$src" 'index(f, $2 "/") == 1 && length($2) > best { best = length($2); n = $1 } END { print n }' <<<"$dirs")" + [ -n "$pkg" ] || { emit ALL; exit 0; } + case " $CORE " in *" $pkg "*) emit ALL; exit 0 ;; esac + touched="$touched"$'\n'"$pkg" + done < <(awk -F'\t' -v f="$f" '$1 == f' <<<"$incl") + continue ;; esac # longest manifest dir that prefixes the path pkg="$(awk -F'\t' -v f="$f" 'index(f, $2 "/") == 1 && length($2) > best { best = length($2); n = $1 } END { print n }' <<<"$dirs")" diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index a0919ae580..ad06571a26 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -55,4 +55,34 @@ else got=$(AFFECTED_META="$t/meta.json" AFFECTED_ROOT=/r bash "$t/mut.sh" --diff-from <(echo crates/a/src/lib.rs) | sort -u) row "MUTANT: ignoring dev edges is caught by the dev-dep row" "a" "$got" fi +# --no-renames: moving a file out of crate a into crate c touches BOTH (git's rename detection would show only c). +rt="$t/repo"; mkdir -p "$rt/crates/a/src" "$rt/crates/b" "$rt/crates/c/src"; abs="$(cd "$(dirname "$A")" && pwd)/$(basename "$A")" +sed "s|/r/|$rt/|" "$t/meta.json" >"$t/meta-rt.json" +printf 'pub fn moved() -> u32 { 1 }\n// padding so git sees a rename, not a delete+add\n// padding\n' >"$rt/crates/a/src/x.rs" +# plumbing (write-tree/commit-tree) so the scratch repo runs no commit hooks +(cd "$rt" && git init -q && git add -A && b=$(GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t git commit-tree "$(git write-tree)" -m base) && git mv crates/a/src/x.rs crates/c/src/x.rs && git update-ref HEAD "$b" && git add -A && GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t git update-ref HEAD "$(GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t git commit-tree "$(git write-tree)" -p "$b" -m move)") +mv_run() { (cd "$rt" && AFFECTED_META="$t/meta-rt.json" AFFECTED_ROOT="$rt" bash "$1" HEAD~1 HEAD | sort -u); } +row "rename a->c touches both ends (a, its dependent b, and c)" "a +b +c" "$(mv_run "$abs")" +sed 's/--name-only --no-renames/--name-only/' "$A" >"$t/mut-rn.sh" +if cmp -s "$A" "$t/mut-rn.sh"; then echo "FAIL mutant did not apply"; fail=1 +else row "MUTANT: letting git detect renames is caught by the rename row" "c" "$(mv_run "$t/mut-rn.sh")"; fi +# include_str!: a docs file a crate embeds is that crate's input (and reaches its dependents); an unembedded one is not. +it="$t/inc"; mkdir -p "$it/crates/a/src" "$it/docs" +printf 'pub const D: &str = include_str!("../../../docs/embedded.md");\n' >"$it/crates/a/src/lib.rs" +sed "s|/r/|$it/|" "$t/meta.json" >"$t/meta-inc.json" +inc_run() { printf '%s\n' "$2" >"$t/dinc"; AFFECTED_META="$t/meta-inc.json" AFFECTED_ROOT="$it" bash "$1" --diff-from "$t/dinc" | sort -u; } +row "docs file embedded by crate a -> a and its dependent b" "a +b" "$(inc_run "$A" docs/embedded.md)" +row "docs file nobody embeds -> nothing" "" "$(inc_run "$A" docs/other.md)" +sed "s/'\\\$1 == f'/'0'/" "$A" >"$t/mut-inc.sh" +if cmp -s "$A" "$t/mut-inc.sh"; then echo "FAIL include mutant did not apply"; fail=1 +else row "MUTANT: ignoring include_str! is caught by the embedded-doc row" "" "$(inc_run "$t/mut-inc.sh" docs/embedded.md)"; fi +real="$($A --diff-from <(echo docs/audits/prm-001/prereg.lock) | sort -u)" +case " +$real +" in *" +aprender-review-experiment +"*) echo "ok real tree: prereg.lock reaches aprender-review-experiment" ;; *) echo "FAIL real tree: prereg.lock -> '$real'"; fail=1 ;; esac exit "$fail" From 5f272c850a455dea6ceccb737931dd91a05e3ec1 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 18:47:29 +0200 Subject: [PATCH 04/24] fix(e5): wire check_affected_set.sh, SC1087 and SEC011 in the affected-set scripts Co-Authored-By: Claude Sonnet 5.5 --- ci/sections.yml | 2 ++ scripts/affected_set.sh | 2 +- scripts/check_affected_set.sh | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ci/sections.yml b/ci/sections.yml index cdb4bea410..f85871681f 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -1698,6 +1698,8 @@ jobs: # a secrets. read reaches it only through ci.yml's FAT_SECRET_. Case table first. - name: "Fat-job secret pass-through case table (6 rows)" run: setsid --wait bash scripts/check_fat_secrets_passed.sh --self-test + - name: "Affected-set tool case table (FLOW-010 E5, RED rows and mutants)" + run: setsid --wait bash scripts/check_affected_set.sh - name: Every secret a section reads is passed to the fat job under its own name run: setsid --wait bash scripts/check_fat_secrets_passed.sh # PRQ-013 (PP-066 C0-5, #2982): the job that judges a PR's OWN receipt must be diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index ec0c162635..33704ad6df 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -72,7 +72,7 @@ touched="$(printf '%s\n' "$touched" | grep -v '^$' | sort -u || true)" [ -n "$touched" ] || exit 0 mapfile -t out < <(jq -r --argjson t "$(printf '%s\n' "$touched" | jq -R . | jq -s .)" ' (.packages | map({name, deps: [.dependencies[].name]})) as $p - | def grow($s): ($s + [$p[] | select(any(.deps[]; . as $d | $s | index($d))) | .name] | unique); + | def grow($s): ($s + [$p|.[] | select(any(.deps[]; . as $d | $s | index($d))) | .name] | unique); (.packages | map(.name)) as $m | ($t | until(grow(.) == .; grow(.))) | .[] | select(. as $n | $m | index($n))' <<<"$meta") emit "${out[@]}" diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index ad06571a26..c540aec3ef 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -3,7 +3,7 @@ # `cargo tree -i` (a different code path from the tool's cargo-metadata jq). set -euo pipefail A=scripts/affected_set.sh -t=$(mktemp -d); trap 'rm -rf "$t"' EXIT +t=$(mktemp -d); trap 'rm -rf -- "${t:?}"' EXIT fail=0 row() { # name want got if [ "$2" = "$3" ]; then echo "ok $1"; else echo "FAIL $1"; echo " want: $2" | head -c 300; echo; echo " got: $3" | head -c 300; echo; fail=1; fi From 1e73c59a2838cb646f8ccb4cfd496a8b323fd5dc Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 18:59:37 +0200 Subject: [PATCH 05/24] fix(e5): docs, root *.md and ci/ select ALL; a crate Cargo.toml selects ALL; check runs with safe.directory Tests read README.md, CLAUDE.md, ci/sections.yml and docs/ at run time, so skipping them emptied the set. The include_str! scan is gone: docs are ALL. Rows and mutants for each. Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 30 +++++++----------------------- scripts/check_affected_set.sh | 34 ++++++++++++++++------------------ 2 files changed, 23 insertions(+), 41 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index 33704ad6df..4afee9fdcb 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -8,15 +8,15 @@ # Fail-safe: anything this tool cannot place in a package is ALL. # crates//... the package + its reverse-dependency closure (cargo metadata) # a CORE crate ALL (its closure is the workspace; naming it would only hide that) -# docs/, *.md, ci/sections.yml, ci/shard-step-costs.tsv, ci/explicit-test-commands.d/ -# contribute nothing (no crate code) -# Cargo.lock, Cargo.toml, rust-toolchain.toml, .cargo/, .github/, Makefile, any other path -# ALL +# Cargo.toml of a crate ALL (a manifest edit changes features/deps, which no path can bound) +# docs/, root *.md, ci/ (sections.yml, explicit-test-commands.d/, ...), Cargo.lock, root Cargo.toml, +# rust-toolchain.toml, .cargo/, .github/, Makefile, any other path outside a crate +# ALL (tests read README.md, CLAUDE.md, ci/sections.yml and docs/ at run time) +# A *.md under crates// is that crate's input like any file there. # An empty diff, or a diff git cannot compute (bad base: exit 128), is ALL: nothing was -# measured, so nothing may be skipped. A diff of only no-crate-code paths prints nothing. +# measured, so nothing may be skipped. # git diff runs with --no-renames: a moved file is its old path AND its new path (a rename # out of a crate would otherwise show only the destination and the source crate would be skipped). -# A docs/*.md file that a crate embeds with include_str!/include_bytes!("literal") counts as that crate. # --diff-from reads the changed paths from FILE (tests). AFFECTED_META / AFFECTED_ROOT # replace `cargo metadata` and the checkout root (tests; synthetic graphs). set -euo pipefail @@ -40,27 +40,11 @@ if [ -n "${AFFECTED_META:-}" ]; then meta="$(cat "$AFFECTED_META")" else meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null || cargo metadata --no-deps --format-version 1)"; fi root="${AFFECTED_ROOT:-$(git rev-parse --show-toplevel)}" dirs="$(jq -r --arg root "$root/" '.packages[] | [.name, (.manifest_path | sub($root;"") | sub("/?Cargo.toml$";""))] | @tsv' <<<"$meta")" -# "\t" for every include_str!/include_bytes!("literal") under crates/, -# the literal resolved against the embedding file's directory. Non-literal (concat!/macro) args are not resolved. -INCL_PL='use File::Basename; for $f (@ARGV) { open F, "<", $f or next; local $/; $s = ; close F; - while ($s =~ /include_(?:str|bytes)!\s*\(\s*"([^"]*)"/g) { @o = (); - for (split m{/}, dirname($f) . "/" . $1) { next if $_ eq "" || $_ eq "."; if ($_ eq "..") { pop @o } else { push @o, $_ } } - print join("/", @o) . "\t" . $f . "\n" } }' -incl="$( (cd "$root" 2>/dev/null && find crates -name "*.rs" -type f -print0 2>/dev/null | xargs -0 -r perl -0777 -e "$INCL_PL") || true )" touched="" while IFS= read -r f; do case "$f" in '') continue ;; - docs/* | *.md | ci/sections.yml | ci/shard-step-costs.tsv | ci/explicit-test-commands.d/*) - # skipped as "no crate code" unless a crate embeds it: then it is that crate's input - while IFS=$'\t' read -r _ src; do - [ -n "$src" ] || continue - pkg="$(awk -F'\t' -v f="$src" 'index(f, $2 "/") == 1 && length($2) > best { best = length($2); n = $1 } END { print n }' <<<"$dirs")" - [ -n "$pkg" ] || { emit ALL; exit 0; } - case " $CORE " in *" $pkg "*) emit ALL; exit 0 ;; esac - touched="$touched"$'\n'"$pkg" - done < <(awk -F'\t' -v f="$f" '$1 == f' <<<"$incl") - continue ;; + */Cargo.toml) case "$f" in crates/*) emit ALL; exit 0 ;; esac ;; esac # longest manifest dir that prefixes the path pkg="$(awk -F'\t' -v f="$f" 'index(f, $2 "/") == 1 && length($2) > best { best = length($2); n = $1 } END { print n }' <<<"$dirs")" diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index c540aec3ef..e7f433be03 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -2,6 +2,8 @@ # FLOW-010 E5 RED rows for scripts/affected_set.sh. Oracle for the closure is # `cargo tree -i` (a different code path from the tool's cargo-metadata jq). set -euo pipefail +# the CI container runs as root over a host-owned checkout: git would refuse it (dubious ownership) +export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=safe.directory GIT_CONFIG_VALUE_0='*' A=scripts/affected_set.sh t=$(mktemp -d); trap 'rm -rf -- "${t:?}"' EXIT fail=0 @@ -31,7 +33,13 @@ row "core crate -> ALL" ALL "$(run crates/aprender-core/src/lib.rs)" row "Cargo.lock -> ALL" ALL "$(run Cargo.lock)" row "unknown path -> ALL" ALL "$(run some/unknown/path.txt)" row "unknown path next to a leaf -> ALL" ALL "$(run "$ld/src/lib.rs" scripts/whatever.sh)" -row "docs only -> nothing" "" "$(run docs/x.md)" +row "docs file -> ALL (tests read docs/ at run time)" ALL "$(run docs/x.md)" +row "root README.md -> ALL" ALL "$(run README.md)" +row "root CLAUDE.md -> ALL" ALL "$(run CLAUDE.md)" +row "ci/sections.yml -> ALL" ALL "$(run ci/sections.yml)" +row "ci/explicit-test-commands.d fragment -> ALL" ALL "$(run ci/explicit-test-commands.d/815-affected-set-rows.cmd)" +row "a crate's own Cargo.toml -> ALL" ALL "$(run "$ld/Cargo.toml")" +row "a .md inside a leaf crate -> that crate (not skipped by the *.md glob)" "$lp" "$(run "$ld/playbooks/x.md")" row "--filter names the package" "package($lp)" "$($A --filter --diff-from <(echo "$ld/src/lib.rs"))" row "--filter ALL -> all()" "all()" "$($A --filter --diff-from <(echo Cargo.lock))" # empty diff and a bad base are ALL (nothing measured, nothing skipped) @@ -68,21 +76,11 @@ c" "$(mv_run "$abs")" sed 's/--name-only --no-renames/--name-only/' "$A" >"$t/mut-rn.sh" if cmp -s "$A" "$t/mut-rn.sh"; then echo "FAIL mutant did not apply"; fail=1 else row "MUTANT: letting git detect renames is caught by the rename row" "c" "$(mv_run "$t/mut-rn.sh")"; fi -# include_str!: a docs file a crate embeds is that crate's input (and reaches its dependents); an unembedded one is not. -it="$t/inc"; mkdir -p "$it/crates/a/src" "$it/docs" -printf 'pub const D: &str = include_str!("../../../docs/embedded.md");\n' >"$it/crates/a/src/lib.rs" -sed "s|/r/|$it/|" "$t/meta.json" >"$t/meta-inc.json" -inc_run() { printf '%s\n' "$2" >"$t/dinc"; AFFECTED_META="$t/meta-inc.json" AFFECTED_ROOT="$it" bash "$1" --diff-from "$t/dinc" | sort -u; } -row "docs file embedded by crate a -> a and its dependent b" "a -b" "$(inc_run "$A" docs/embedded.md)" -row "docs file nobody embeds -> nothing" "" "$(inc_run "$A" docs/other.md)" -sed "s/'\\\$1 == f'/'0'/" "$A" >"$t/mut-inc.sh" -if cmp -s "$A" "$t/mut-inc.sh"; then echo "FAIL include mutant did not apply"; fail=1 -else row "MUTANT: ignoring include_str! is caught by the embedded-doc row" "" "$(inc_run "$t/mut-inc.sh" docs/embedded.md)"; fi -real="$($A --diff-from <(echo docs/audits/prm-001/prereg.lock) | sort -u)" -case " -$real -" in *" -aprender-review-experiment -"*) echo "ok real tree: prereg.lock reaches aprender-review-experiment" ;; *) echo "FAIL real tree: prereg.lock -> '$real'"; fail=1 ;; esac +# mutants: the skip list that used to exist (docs/*, *.md) must be caught by the ALL rows above. +sed 's#^ \*/Cargo.toml)# docs/* | *.md) continue ;;\n */Cargo.toml)#' "$A" >"$t/mut-docs.sh" +if cmp -s "$A" "$t/mut-docs.sh"; then echo "FAIL docs-skip mutant did not apply"; fail=1 +else printf '%s\n' docs/x.md >"$t/dd"; row "MUTANT: skipping docs/*.md is caught by the docs-ALL row" "" "$(bash "$t/mut-docs.sh" --diff-from "$t/dd" | head -n1 || true)"; fi +sed 's#^ \*/Cargo.toml) case.*# */Cargo.toml) continue ;;#' "$A" >"$t/mut-toml.sh" +if cmp -s "$A" "$t/mut-toml.sh"; then echo "FAIL manifest mutant did not apply"; fail=1 +else printf '%s\n' "$ld/Cargo.toml" >"$t/dt"; row "MUTANT: skipping crate Cargo.toml is caught by the manifest row" "" "$(bash "$t/mut-toml.sh" --diff-from "$t/dt" | head -n1 || true)"; fi exit "$fail" From 2ae2e4ebcc2a4ffc143d7743ae9fda0f97b56933 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 19:21:11 +0200 Subject: [PATCH 06/24] fix(e5): any non-.rs file under crates/ selects ALL; move the check to guard-cargo-steps Other crates' tests read crates/*/README.md and aprender-serve/kernel-registry.json. check_affected_set.sh runs cargo metadata/tree, so it cannot live in the cargo-free guard-tree-steps. Co-Authored-By: Claude Sonnet 5.5 --- ci/sections.yml | 5 +++-- scripts/affected_set.sh | 6 ++++-- scripts/check_affected_set.sh | 10 ++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ci/sections.yml b/ci/sections.yml index f85871681f..cefb8919a2 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -1698,8 +1698,6 @@ jobs: # a secrets. read reaches it only through ci.yml's FAT_SECRET_. Case table first. - name: "Fat-job secret pass-through case table (6 rows)" run: setsid --wait bash scripts/check_fat_secrets_passed.sh --self-test - - name: "Affected-set tool case table (FLOW-010 E5, RED rows and mutants)" - run: setsid --wait bash scripts/check_affected_set.sh - name: Every secret a section reads is passed to the fat job under its own name run: setsid --wait bash scripts/check_fat_secrets_passed.sh # PRQ-013 (PP-066 C0-5, #2982): the job that judges a PR's OWN receipt must be @@ -1938,6 +1936,9 @@ jobs: run: setsid --wait bash scripts/check_lockfile_current.sh - name: "lockfile guard must still turn RED (case table)" run: setsid --wait bash scripts/check_lockfile_current.sh --self-test + # FLOW-010 E5: runs cargo metadata/tree, so it lives in the cargo job, not guard-tree-steps. + - name: "Affected-set tool case table (FLOW-010 E5, RED rows and mutants)" + run: setsid --wait bash scripts/check_affected_set.sh # #2640: the SAME defect class as the two steps above, one level up. The # dogfood release runner existed as two 1170-line copies whose divergence # was, in every hunk, a pin for a release verifier that one copy had and diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index 4afee9fdcb..aab7fc1aef 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -12,7 +12,8 @@ # docs/, root *.md, ci/ (sections.yml, explicit-test-commands.d/, ...), Cargo.lock, root Cargo.toml, # rust-toolchain.toml, .cargo/, .github/, Makefile, any other path outside a crate # ALL (tests read README.md, CLAUDE.md, ci/sections.yml and docs/ at run time) -# A *.md under crates// is that crate's input like any file there. +# Any non-.rs file under crates// (README.md, *.json, *.toml, ...) is ALL: other crates' tests read them +# (aprender-core readme_contract.rs reads every crates/*/README.md; aprender-contracts reads aprender-serve/kernel-registry.json). # An empty diff, or a diff git cannot compute (bad base: exit 128), is ALL: nothing was # measured, so nothing may be skipped. # git diff runs with --no-renames: a moved file is its old path AND its new path (a rename @@ -44,7 +45,8 @@ touched="" while IFS= read -r f; do case "$f" in '') continue ;; - */Cargo.toml) case "$f" in crates/*) emit ALL; exit 0 ;; esac ;; + crates/*.rs) ;; + crates/*) emit ALL; exit 0 ;; esac # longest manifest dir that prefixes the path pkg="$(awk -F'\t' -v f="$f" 'index(f, $2 "/") == 1 && length($2) > best { best = length($2); n = $1 } END { print n }' <<<"$dirs")" diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index e7f433be03..8b40594ae2 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -39,7 +39,9 @@ row "root CLAUDE.md -> ALL" ALL "$(run CLAUDE.md)" row "ci/sections.yml -> ALL" ALL "$(run ci/sections.yml)" row "ci/explicit-test-commands.d fragment -> ALL" ALL "$(run ci/explicit-test-commands.d/815-affected-set-rows.cmd)" row "a crate's own Cargo.toml -> ALL" ALL "$(run "$ld/Cargo.toml")" -row "a .md inside a leaf crate -> that crate (not skipped by the *.md glob)" "$lp" "$(run "$ld/playbooks/x.md")" +row "a .md inside a leaf crate -> ALL (aprender-core reads crates/*/README.md)" ALL "$(run "$ld/README.md")" +row "a .json inside a leaf crate -> ALL (kernel-registry.json is read by aprender-contracts)" ALL "$(run "$ld/kernel-registry.json")" +row "a .rs inside a leaf crate stays scoped" "$lp" "$(run "$ld/src/other.rs")" row "--filter names the package" "package($lp)" "$($A --filter --diff-from <(echo "$ld/src/lib.rs"))" row "--filter ALL -> all()" "all()" "$($A --filter --diff-from <(echo Cargo.lock))" # empty diff and a bad base are ALL (nothing measured, nothing skipped) @@ -77,10 +79,10 @@ sed 's/--name-only --no-renames/--name-only/' "$A" >"$t/mut-rn.sh" if cmp -s "$A" "$t/mut-rn.sh"; then echo "FAIL mutant did not apply"; fail=1 else row "MUTANT: letting git detect renames is caught by the rename row" "c" "$(mv_run "$t/mut-rn.sh")"; fi # mutants: the skip list that used to exist (docs/*, *.md) must be caught by the ALL rows above. -sed 's#^ \*/Cargo.toml)# docs/* | *.md) continue ;;\n */Cargo.toml)#' "$A" >"$t/mut-docs.sh" +sed 's#^ crates/\*.rs) ;;# docs/* | *.md) continue ;;\n crates/*.rs) ;;#' "$A" >"$t/mut-docs.sh" if cmp -s "$A" "$t/mut-docs.sh"; then echo "FAIL docs-skip mutant did not apply"; fail=1 else printf '%s\n' docs/x.md >"$t/dd"; row "MUTANT: skipping docs/*.md is caught by the docs-ALL row" "" "$(bash "$t/mut-docs.sh" --diff-from "$t/dd" | head -n1 || true)"; fi -sed 's#^ \*/Cargo.toml) case.*# */Cargo.toml) continue ;;#' "$A" >"$t/mut-toml.sh" +sed 's#^ crates/\*) emit ALL.*# crates/*) continue ;;#' "$A" >"$t/mut-toml.sh" if cmp -s "$A" "$t/mut-toml.sh"; then echo "FAIL manifest mutant did not apply"; fail=1 -else printf '%s\n' "$ld/Cargo.toml" >"$t/dt"; row "MUTANT: skipping crate Cargo.toml is caught by the manifest row" "" "$(bash "$t/mut-toml.sh" --diff-from "$t/dt" | head -n1 || true)"; fi +else printf '%s\n' "$ld/Cargo.toml" >"$t/dt"; row "MUTANT: skipping non-.rs crate files is caught by the manifest row" "" "$(bash "$t/mut-toml.sh" --diff-from "$t/dt" | head -n1 || true)"; fi exit "$fail" From cf174b863d8a07ef01f1a4c16fdf96dc18c0e010 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 19:44:25 +0200 Subject: [PATCH 07/24] fix(e5): refuse '..' in --diff-from (bashrs SEC010); apr-cli is CORE (other crates' tests read its sources) Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 6 +++++- scripts/check_affected_set.sh | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index aab7fc1aef..7bc1e77836 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -21,7 +21,10 @@ # --diff-from reads the changed paths from FILE (tests). AFFECTED_META / AFFECTED_ROOT # replace `cargo metadata` and the checkout root (tests; synthetic graphs). set -euo pipefail -CORE="${AFFECTED_CORE:-aprender-core aprender-compute aprender-contracts}" +# CORE = ALL on touch. apr-cli is here because other crates' tests read its sources (aprender-core readme_contract.rs reads +# apr-cli/src/commands_enum.rs; spec_checklist_*, toyota_principles_tests). Reader edges are not in cargo metadata, so a +# crate whose source another crate reads at run time must be listed. +CORE="${AFFECTED_CORE:-aprender-core aprender-compute aprender-contracts apr-cli}" filter=0 from="" while [ $# -gt 0 ]; do case "$1" in @@ -30,6 +33,7 @@ while [ $# -gt 0 ]; do *) break ;; esac done +case "$from" in *..*) echo "affected_set: refusing a --diff-from with \"..\": $from" >&2; exit 2 ;; esac # bashrs SEC010 if [ -n "$from" ]; then files="$(cat "$from")" else files="$(git diff --name-only --no-renames "${1:-origin/main}...${2:-HEAD}")" || files=""; fi emit() { # $@ = package names, or ALL diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index 8b40594ae2..fe72595799 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -11,7 +11,7 @@ row() { # name want got if [ "$2" = "$3" ]; then echo "ok $1"; else echo "FAIL $1"; echo " want: $2" | head -c 300; echo; echo " got: $3" | head -c 300; echo; fail=1; fi } run() { printf '%s\n' "$@" >"$t/d"; $A --diff-from "$t/d"; } -core_list=" aprender-core aprender-compute aprender-contracts " +core_list=" aprender-core aprender-compute aprender-contracts apr-cli " meta=$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null) members=$(jq -r '.packages[].name' <<<"$meta" | sort) # a non-core crate that has dependents, and a leaf with none @@ -20,6 +20,7 @@ for p in $members; do case "$core_list" in *" $p "*) continue ;; esac n=$(cargo tree --offline -i "$p" --workspace -e normal,dev,build --prefix none 2>/dev/null | sed 's/ v.*//' | sort -u | grep -vc "^$p$" || true) dir=$(jq -r --arg p "$p" '.packages[]|select(.name==$p)|.manifest_path' <<<"$meta" | sed "s|$(git rev-parse --show-toplevel)/||; s|/Cargo.toml||") + case "$dir" in crates/*) ;; *) continue ;; esac [ -z "$withdep" ] && [ "$n" -gt 0 ] && [ "$n" -lt 10 ] && withdep="$p:$dir" [ -z "$leaf" ] && [ "$n" -eq 0 ] && leaf="$p:$dir" [ -n "$withdep" ] && [ -n "$leaf" ] && break @@ -30,6 +31,8 @@ want=$(cargo tree --offline -i "$wp" --workspace -e normal,dev,build --prefix no row "crate with dependents ($wp) -> itself + dependents" "$want" "$(run "$wd/src/lib.rs" | sort -u)" row "leaf crate ($lp) -> itself only" "$lp" "$(run "$ld/src/lib.rs")" row "core crate -> ALL" ALL "$(run crates/aprender-core/src/lib.rs)" +row "apr-cli source -> ALL (aprender-core tests read apr-cli/src/commands_enum.rs)" ALL "$(run crates/apr-cli/src/commands_enum.rs)" +row "--diff-from with .. is refused (SEC010)" "2" "$($A --diff-from ../x >/dev/null 2>&1; echo $?)" row "Cargo.lock -> ALL" ALL "$(run Cargo.lock)" row "unknown path -> ALL" ALL "$(run some/unknown/path.txt)" row "unknown path next to a leaf -> ALL" ALL "$(run "$ld/src/lib.rs" scripts/whatever.sh)" From 132b80d037f7bf56e873450cb0647a92295b6853 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 20:28:06 +0200 Subject: [PATCH 08/24] fix(e5): aprender-quant is CORE; any crates/*/build.rs selects ALL (reader edges), with rows and a mutant Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 6 ++++-- scripts/check_affected_set.sh | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index 7bc1e77836..8a6b5d72fa 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -23,8 +23,9 @@ set -euo pipefail # CORE = ALL on touch. apr-cli is here because other crates' tests read its sources (aprender-core readme_contract.rs reads # apr-cli/src/commands_enum.rs; spec_checklist_*, toyota_principles_tests). Reader edges are not in cargo metadata, so a -# crate whose source another crate reads at run time must be listed. -CORE="${AFFECTED_CORE:-aprender-core aprender-compute aprender-contracts apr-cli}" +# crate whose source another crate reads at run time must be listed (aprender-quant: aprender-serve/capability.rs reads +# aprender-quant/src/ggml_type.rs). Any crates/*/build.rs is ALL: contracts/build_helper.rs reads crates/*/build.rs. +CORE="${AFFECTED_CORE:-aprender-core aprender-compute aprender-contracts apr-cli aprender-quant}" filter=0 from="" while [ $# -gt 0 ]; do case "$1" in @@ -49,6 +50,7 @@ touched="" while IFS= read -r f; do case "$f" in '') continue ;; + crates/*/build.rs) emit ALL; exit 0 ;; crates/*.rs) ;; crates/*) emit ALL; exit 0 ;; esac diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index fe72595799..8c29b21200 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -11,7 +11,7 @@ row() { # name want got if [ "$2" = "$3" ]; then echo "ok $1"; else echo "FAIL $1"; echo " want: $2" | head -c 300; echo; echo " got: $3" | head -c 300; echo; fail=1; fi } run() { printf '%s\n' "$@" >"$t/d"; $A --diff-from "$t/d"; } -core_list=" aprender-core aprender-compute aprender-contracts apr-cli " +core_list=" aprender-core aprender-compute aprender-contracts apr-cli aprender-quant " meta=$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null) members=$(jq -r '.packages[].name' <<<"$meta" | sort) # a non-core crate that has dependents, and a leaf with none @@ -31,6 +31,8 @@ want=$(cargo tree --offline -i "$wp" --workspace -e normal,dev,build --prefix no row "crate with dependents ($wp) -> itself + dependents" "$want" "$(run "$wd/src/lib.rs" | sort -u)" row "leaf crate ($lp) -> itself only" "$lp" "$(run "$ld/src/lib.rs")" row "core crate -> ALL" ALL "$(run crates/aprender-core/src/lib.rs)" +row "aprender-quant source -> ALL (aprender-serve capability.rs reads aprender-quant/src/ggml_type.rs)" ALL "$(run crates/aprender-quant/src/ggml_type.rs)" +row "a crate's build.rs -> ALL (contracts build_helper.rs reads crates/*/build.rs)" ALL "$(run "$ld/build.rs")" row "apr-cli source -> ALL (aprender-core tests read apr-cli/src/commands_enum.rs)" ALL "$(run crates/apr-cli/src/commands_enum.rs)" row "--diff-from with .. is refused (SEC010)" "2" "$($A --diff-from ../x >/dev/null 2>&1; echo $?)" row "Cargo.lock -> ALL" ALL "$(run Cargo.lock)" @@ -81,6 +83,9 @@ c" "$(mv_run "$abs")" sed 's/--name-only --no-renames/--name-only/' "$A" >"$t/mut-rn.sh" if cmp -s "$A" "$t/mut-rn.sh"; then echo "FAIL mutant did not apply"; fail=1 else row "MUTANT: letting git detect renames is caught by the rename row" "c" "$(mv_run "$t/mut-rn.sh")"; fi +sed '/^ crates\/\*\/build.rs)/d' "$A" >"$t/mut-build.sh" +if cmp -s "$A" "$t/mut-build.sh"; then echo "FAIL build.rs mutant did not apply"; fail=1 +else printf '%s\n' "$ld/build.rs" >"$t/db"; row "MUTANT: dropping the build.rs arm is caught by the build.rs row" "$lp" "$(bash "$t/mut-build.sh" --diff-from "$t/db" | head -n1 || true)"; fi # mutants: the skip list that used to exist (docs/*, *.md) must be caught by the ALL rows above. sed 's#^ crates/\*.rs) ;;# docs/* | *.md) continue ;;\n crates/*.rs) ;;#' "$A" >"$t/mut-docs.sh" if cmp -s "$A" "$t/mut-docs.sh"; then echo "FAIL docs-skip mutant did not apply"; fail=1 From 2308a7c219e4026dc8d9905491fe5c0869c81fde Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 20:38:56 +0200 Subject: [PATCH 09/24] fix(affected-set): every non-ALL set carries aprender-core; empty touched or jq failure prints ALL; rows + RED mutants Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 12 +++++--- scripts/check_affected_set.sh | 55 ++++++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index 8a6b5d72fa..e61424cba6 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -45,7 +45,8 @@ emit() { # $@ = package names, or ALL if [ -n "${AFFECTED_META:-}" ]; then meta="$(cat "$AFFECTED_META")" else meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null || cargo metadata --no-deps --format-version 1)"; fi root="${AFFECTED_ROOT:-$(git rev-parse --show-toplevel)}" -dirs="$(jq -r --arg root "$root/" '.packages[] | [.name, (.manifest_path | sub($root;"") | sub("/?Cargo.toml$";""))] | @tsv' <<<"$meta")" +dirs="$(jq -r --arg root "$root/" '.packages[] | [.name, (.manifest_path | sub($root;"") | sub("/?Cargo.toml$";""))] | @tsv' <<<"$meta")" || { emit ALL; exit 0; } +[ -n "$dirs" ] || { emit ALL; exit 0; } touched="" while IFS= read -r f; do case "$f" in @@ -61,10 +62,13 @@ while IFS= read -r f; do touched="$touched"$'\n'"$pkg" done <<<"$files" touched="$(printf '%s\n' "$touched" | grep -v '^$' | sort -u || true)" -[ -n "$touched" ] || exit 0 -mapfile -t out < <(jq -r --argjson t "$(printf '%s\n' "$touched" | jq -R . | jq -s .)" ' +[ -n "$touched" ] || { emit ALL; exit 0; } # nothing placed in a package: nothing may be skipped +# The closure, then aprender-core: its falsification_spec_v10 test scans every crate's src, so ANY non-ALL set includes it. +res="$(jq -r --argjson t "$(printf '%s\n' "$touched" | jq -R . | jq -s .)" ' (.packages | map({name, deps: [.dependencies[].name]})) as $p | def grow($s): ($s + [$p|.[] | select(any(.deps[]; . as $d | $s | index($d))) | .name] | unique); (.packages | map(.name)) as $m - | ($t | until(grow(.) == .; grow(.))) | .[] | select(. as $n | $m | index($n))' <<<"$meta") + | (($t | until(grow(.) == .; grow(.))) + ["aprender-core"] | unique) | .[] | select(. as $n | $m | index($n))' <<<"$meta")" || { emit ALL; exit 0; } +[ -n "$res" ] || { emit ALL; exit 0; } +mapfile -t out <<<"$res" emit "${out[@]}" diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index 8c29b21200..63e60d19d3 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -27,9 +27,11 @@ for p in $members; do done [ -n "$withdep" ] && [ -n "$leaf" ] || { echo "FAIL could not find a leaf and a crate with dependents"; exit 1; } wp=${withdep%%:*}; wd=${withdep#*:}; lp=${leaf%%:*}; ld=${leaf#*:} -want=$(cargo tree --offline -i "$wp" --workspace -e normal,dev,build --prefix none 2>/dev/null | sed 's/ v.*//' | sort -u) -row "crate with dependents ($wp) -> itself + dependents" "$want" "$(run "$wd/src/lib.rs" | sort -u)" -row "leaf crate ($lp) -> itself only" "$lp" "$(run "$ld/src/lib.rs")" +# every non-ALL set also carries aprender-core (its falsification_spec_v10 test scans every crate's src) +want=$( { cargo tree --offline -i "$wp" --workspace -e normal,dev,build --prefix none 2>/dev/null | sed 's/ v.*//'; echo aprender-core; } | sort -u) +lc="$(printf '%s\n' "$lp" aprender-core | sort -u)" +row "crate with dependents ($wp) -> itself + dependents + aprender-core" "$want" "$(run "$wd/src/lib.rs" | sort -u)" +row "leaf crate ($lp) -> itself + aprender-core" "$lc" "$(run "$ld/src/lib.rs" | sort -u)" row "core crate -> ALL" ALL "$(run crates/aprender-core/src/lib.rs)" row "aprender-quant source -> ALL (aprender-serve capability.rs reads aprender-quant/src/ggml_type.rs)" ALL "$(run crates/aprender-quant/src/ggml_type.rs)" row "a crate's build.rs -> ALL (contracts build_helper.rs reads crates/*/build.rs)" ALL "$(run "$ld/build.rs")" @@ -46,8 +48,8 @@ row "ci/explicit-test-commands.d fragment -> ALL" ALL "$(run ci/explicit-test-co row "a crate's own Cargo.toml -> ALL" ALL "$(run "$ld/Cargo.toml")" row "a .md inside a leaf crate -> ALL (aprender-core reads crates/*/README.md)" ALL "$(run "$ld/README.md")" row "a .json inside a leaf crate -> ALL (kernel-registry.json is read by aprender-contracts)" ALL "$(run "$ld/kernel-registry.json")" -row "a .rs inside a leaf crate stays scoped" "$lp" "$(run "$ld/src/other.rs")" -row "--filter names the package" "package($lp)" "$($A --filter --diff-from <(echo "$ld/src/lib.rs"))" +row "a .rs inside a leaf crate stays scoped (itself + aprender-core)" "$lc" "$(run "$ld/src/other.rs" | sort -u)" +row "--filter names the package and aprender-core" "$(printf 'package(%s)\n' $lc | paste -sd'|' | sed 's/|/ | /g')" "$($A --filter --diff-from <(echo "$ld/src/lib.rs"))" row "--filter ALL -> all()" "all()" "$($A --filter --diff-from <(echo Cargo.lock))" # empty diff and a bad base are ALL (nothing measured, nothing skipped) : >"$t/empty" @@ -85,12 +87,49 @@ if cmp -s "$A" "$t/mut-rn.sh"; then echo "FAIL mutant did not apply"; fail=1 else row "MUTANT: letting git detect renames is caught by the rename row" "c" "$(mv_run "$t/mut-rn.sh")"; fi sed '/^ crates\/\*\/build.rs)/d' "$A" >"$t/mut-build.sh" if cmp -s "$A" "$t/mut-build.sh"; then echo "FAIL build.rs mutant did not apply"; fail=1 -else printf '%s\n' "$ld/build.rs" >"$t/db"; row "MUTANT: dropping the build.rs arm is caught by the build.rs row" "$lp" "$(bash "$t/mut-build.sh" --diff-from "$t/db" | head -n1 || true)"; fi +else printf '%s\n' "$ld/build.rs" >"$t/db"; row "MUTANT: dropping the build.rs arm is caught by the build.rs row" "$lc" "$(bash "$t/mut-build.sh" --diff-from "$t/db" | sort -u || true)"; fi # mutants: the skip list that used to exist (docs/*, *.md) must be caught by the ALL rows above. sed 's#^ crates/\*.rs) ;;# docs/* | *.md) continue ;;\n crates/*.rs) ;;#' "$A" >"$t/mut-docs.sh" if cmp -s "$A" "$t/mut-docs.sh"; then echo "FAIL docs-skip mutant did not apply"; fail=1 -else printf '%s\n' docs/x.md >"$t/dd"; row "MUTANT: skipping docs/*.md is caught by the docs-ALL row" "" "$(bash "$t/mut-docs.sh" --diff-from "$t/dd" | head -n1 || true)"; fi +else printf '%s\n%s\n' docs/x.md "$ld/src/lib.rs" >"$t/dd"; row "MUTANT: skipping docs/*.md is caught by the docs-ALL row" "$lc" "$(bash "$t/mut-docs.sh" --diff-from "$t/dd" | sort -u || true)"; fi sed 's#^ crates/\*) emit ALL.*# crates/*) continue ;;#' "$A" >"$t/mut-toml.sh" if cmp -s "$A" "$t/mut-toml.sh"; then echo "FAIL manifest mutant did not apply"; fail=1 -else printf '%s\n' "$ld/Cargo.toml" >"$t/dt"; row "MUTANT: skipping non-.rs crate files is caught by the manifest row" "" "$(bash "$t/mut-toml.sh" --diff-from "$t/dt" | head -n1 || true)"; fi +else printf '%s\n%s\n' "$ld/Cargo.toml" "$ld/src/lib.rs" >"$t/dt"; row "MUTANT: skipping non-.rs crate files is caught by the manifest row" "$lc" "$(bash "$t/mut-toml.sh" --diff-from "$t/dt" | sort -u || true)"; fi +# CORE crates: each must give ALL, and dropping each from CORE must be seen (its dependents alone are not the workspace) +for c in aprender-compute aprender-contracts aprender-quant; do + cd_=$(jq -r --arg p "$c" '.packages[]|select(.name==$p)|.manifest_path' <<<"$meta" | sed "s|$(git rev-parse --show-toplevel)/||; s|/Cargo.toml||") + row "CORE crate $c -> ALL" ALL "$(run "$cd_/src/lib.rs")" + sed "s/ $c//" "$A" >"$t/mut-core.sh" + if cmp -s "$A" "$t/mut-core.sh"; then echo "FAIL core mutant ($c) did not apply"; fail=1 + else printf '%s\n' "$cd_/src/lib.rs" >"$t/dcore"; row "MUTANT: dropping $c from CORE is caught" "not-ALL" "$([ "$(bash "$t/mut-core.sh" --diff-from "$t/dcore" | head -n1)" != ALL ] && echo not-ALL || echo ALL)"; fi +done +# a 2-hop chain (c -> b -> a) and the multi-package --filter format, on a synthetic graph +cat >"$t/chain.json" <<'JSON' +{"packages":[ + {"name":"a","manifest_path":"/r/crates/a/Cargo.toml","dependencies":[]}, + {"name":"b","manifest_path":"/r/crates/b/Cargo.toml","dependencies":[{"name":"a","kind":null}]}, + {"name":"c","manifest_path":"/r/crates/c/Cargo.toml","dependencies":[{"name":"b","kind":null}]}, + {"name":"aprender-core","manifest_path":"/r/crates/core/Cargo.toml","dependencies":[]}]} +JSON +chain() { AFFECTED_META="$t/chain.json" AFFECTED_ROOT=/r AFFECTED_CORE=none bash "$1" --diff-from <(echo crates/a/src/lib.rs) | sort -u; } +row "2-hop chain a<-b<-c: touching a reaches b and c (+ aprender-core)" "a +aprender-core +b +c" "$(chain "$A")" +sed 's/until(grow(.) == .; grow(.))/grow(.)/' "$A" >"$t/mut-hop.sh" +if cmp -s "$A" "$t/mut-hop.sh"; then echo "FAIL 1-hop mutant did not apply"; fail=1 +else row "MUTANT: a 1-hop closure is caught by the 2-hop row" "a +aprender-core +b" "$(chain "$t/mut-hop.sh")"; fi +want_f="package(a) | package(aprender-core) | package(b) | package(c)" +row "multi-package --filter is ' | ' joined" "$want_f" "$(AFFECTED_META="$t/chain.json" AFFECTED_ROOT=/r AFFECTED_CORE=none bash "$A" --filter --diff-from <(echo crates/a/src/lib.rs))" +sed "s/ | sed 's\/|\/ | \/g'//" "$A" >"$t/mut-fmt.sh" +if cmp -s "$A" "$t/mut-fmt.sh"; then echo "FAIL filter-format mutant did not apply"; fail=1 +else row "MUTANT: an unspaced multi-package filter is caught" "package(a)|package(aprender-core)|package(b)|package(c)" "$(AFFECTED_META="$t/chain.json" AFFECTED_ROOT=/r AFFECTED_CORE=none bash "$t/mut-fmt.sh" --filter --diff-from <(echo crates/a/src/lib.rs))"; fi +# an unreadable cargo metadata is ALL, not an empty answer +echo 'not json' >"$t/bad.json" +row "unreadable metadata -> ALL" ALL "$(AFFECTED_META="$t/bad.json" bash "$A" --diff-from <(echo crates/a/src/lib.rs) 2>/dev/null)" +sed 's/ || { emit ALL; exit 0; }$//' "$A" >"$t/mut-jq.sh" +if cmp -s "$A" "$t/mut-jq.sh"; then echo "FAIL jq-failure mutant did not apply"; fail=1 +else row "MUTANT: dropping the jq-failure fallback is caught" "" "$(AFFECTED_META="$t/bad.json" bash "$t/mut-jq.sh" --diff-from <(echo crates/a/src/lib.rs) 2>/dev/null || true)"; fi exit "$fail" From 29b1f3e70addaf9f7995c905d971a02fe33278cf Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 20:54:27 +0200 Subject: [PATCH 10/24] fix(affected-set): READERS list (crates whose tests read other crates) in every non-ALL set; readers guard; res= fallback row Co-Authored-By: Claude Sonnet 5.5 --- ci/sections.yml | 2 ++ scripts/affected_set.sh | 9 +++++--- scripts/check_affected_readers.sh | 37 +++++++++++++++++++++++++++++++ scripts/check_affected_set.sh | 24 +++++++++++++++----- 4 files changed, 64 insertions(+), 8 deletions(-) create mode 100755 scripts/check_affected_readers.sh diff --git a/ci/sections.yml b/ci/sections.yml index cefb8919a2..ded7307c99 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -1939,6 +1939,8 @@ jobs: # FLOW-010 E5: runs cargo metadata/tree, so it lives in the cargo job, not guard-tree-steps. - name: "Affected-set tool case table (FLOW-010 E5, RED rows and mutants)" run: setsid --wait bash scripts/check_affected_set.sh + - name: "Affected-set readers guard (every crate that reads other crates' files is listed; RED on a new one)" + run: setsid --wait bash scripts/check_affected_readers.sh && bash scripts/check_affected_readers.sh --self-test # #2640: the SAME defect class as the two steps above, one level up. The # dogfood release runner existed as two 1170-line copies whose divergence # was, in every hunk, a pin for a release verifier that one copy had and diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index e61424cba6..f9cdf4f9ba 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -26,6 +26,9 @@ set -euo pipefail # crate whose source another crate reads at run time must be listed (aprender-quant: aprender-serve/capability.rs reads # aprender-quant/src/ggml_type.rs). Any crates/*/build.rs is ALL: contracts/build_helper.rs reads crates/*/build.rs. CORE="${AFFECTED_CORE:-aprender-core aprender-compute aprender-contracts apr-cli aprender-quant}" +# READERS = crates whose tests read OTHER crates' files at run time (CARGO_MANIFEST_DIR/.., crates/, ../). CORE ones are ALL on touch, but that does not put them in a non-ALL set; every non-ALL set includes them, because the touched crate may be what they read. scripts/check_affected_readers.sh +# goes RED on a reader this list (or CORE) lacks. +READERS="${AFFECTED_READERS:-aprender-core aprender-contracts apr-cli aprender-serve aprender-test-lib aprender-orchestrate aprender-present-yaml aprender-decide aprender-cgp aprender-review-experiment aprender-qa-certify}" filter=0 from="" while [ $# -gt 0 ]; do case "$1" in @@ -63,12 +66,12 @@ while IFS= read -r f; do done <<<"$files" touched="$(printf '%s\n' "$touched" | grep -v '^$' | sort -u || true)" [ -n "$touched" ] || { emit ALL; exit 0; } # nothing placed in a package: nothing may be skipped -# The closure, then aprender-core: its falsification_spec_v10 test scans every crate's src, so ANY non-ALL set includes it. -res="$(jq -r --argjson t "$(printf '%s\n' "$touched" | jq -R . | jq -s .)" ' +# The closure, then aprender-core: its falsification_spec_v10 test scans every crate's src, so ANY non-ALL set includes it (and the READERS). +res="$(jq -r --argjson t "$(printf '%s\n' "$touched" | jq -R . | jq -s .)" --arg rd "$READERS" ' (.packages | map({name, deps: [.dependencies[].name]})) as $p | def grow($s): ($s + [$p|.[] | select(any(.deps[]; . as $d | $s | index($d))) | .name] | unique); (.packages | map(.name)) as $m - | (($t | until(grow(.) == .; grow(.))) + ["aprender-core"] | unique) | .[] | select(. as $n | $m | index($n))' <<<"$meta")" || { emit ALL; exit 0; } + | (($t | until(grow(.) == .; grow(.))) + ($rd | split(" ")) | unique) | .[] | select(. as $n | $m | index($n))' <<<"$meta")" || { emit ALL; exit 0; } [ -n "$res" ] || { emit ALL; exit 0; } mapfile -t out <<<"$res" emit "${out[@]}" diff --git a/scripts/check_affected_readers.sh b/scripts/check_affected_readers.sh new file mode 100755 index 0000000000..5950af023f --- /dev/null +++ b/scripts/check_affected_readers.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# FLOW-010 E5: a crate whose Rust reads OTHER crates' files at run time (join("crates"), ..//.., +# CARGO_MANIFEST_DIR/../..) is a reader edge that cargo metadata cannot see. scripts/affected_set.sh must +# list it in CORE or READERS, or a change to the crate it reads would skip its tests. RED on a reader +# neither list names. Usage: check_affected_readers.sh [crates-dir] (self-test: --self-test) +set -euo pipefail +dir="${1:-crates}" +SET="${AFFECTED_SET_SH:-scripts/affected_set.sh}" +listed() { sed -n 's/^CORE="${AFFECTED_CORE:-\(.*\)}"$/\1/p; s/^READERS="${AFFECTED_READERS:-\(.*\)}"$/\1/p' "$SET" | tr ' ' '\n' | grep -v '^$' | sort -u; } +# reader patterns; lines about target/, .config, contracts/, docs/ read non-crate paths (an unknown root path is ALL anyway) +readers() { + grep -rEn --include='*.rs' 'join\("crates"\)|\.\./aprender-|MANIFEST_DIR.*\.\.|join\("\.\./\.\."\)' "$1" 2>/dev/null \ + | grep -vE 'target|\.config|contracts|docs/|^\S+:[0-9]+:\s*//' | awk -F/ '{print $2}' | sort -u || true +} +# aprender-bench-tokenizer joins ../.. only to find tokenizer.json / models/ at the repo root, never another crate's file +ALLOW=" aprender-bench-tokenizer " +check() { + local bad="" l; l="$(listed)" + for c in $(readers "$1"); do + case "$ALLOW" in *" $c "*) continue ;; esac + printf '%s\n' "$l" | grep -qx "$c" || bad="$bad $c" + done + if [ -n "$bad" ]; then echo "RED: crates read other crates' files but are in neither CORE nor READERS (scripts/affected_set.sh):$bad"; return 1; fi + echo "ok: every cross-crate reader is listed" +} +if [ "${1:-}" = "--self-test" ]; then + t=$(mktemp -d); trap 'rm -rf -- "${t:?}"' EXIT + mkdir -p "$t/crates/zz-new/tests" "$t/crates/aprender-serve/src" + echo 'fn f() { let _ = root.join("crates"); }' >"$t/crates/zz-new/tests/x.rs" + echo 'fn f() { let _ = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); }' >"$t/crates/aprender-serve/src/y.rs" + cd "$t"; SET="${OLDPWD}/${SET}" + if (check crates >/dev/null 2>&1); then echo "FAIL self-test: an unlisted reader was not caught"; exit 1; fi + rm -r crates/zz-new + (check crates >/dev/null 2>&1) || { echo "FAIL self-test: a listed reader was flagged"; exit 1; } + echo "ok: self-test"; exit 0 +fi +check "$dir" diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index 63e60d19d3..132832da63 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -12,6 +12,7 @@ row() { # name want got } run() { printf '%s\n' "$@" >"$t/d"; $A --diff-from "$t/d"; } core_list=" aprender-core aprender-compute aprender-contracts apr-cli aprender-quant " +readers="aprender-core aprender-contracts apr-cli aprender-serve aprender-test-lib aprender-orchestrate aprender-present-yaml aprender-decide aprender-cgp aprender-review-experiment aprender-qa-certify" meta=$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null) members=$(jq -r '.packages[].name' <<<"$meta" | sort) # a non-core crate that has dependents, and a leaf with none @@ -28,8 +29,8 @@ done [ -n "$withdep" ] && [ -n "$leaf" ] || { echo "FAIL could not find a leaf and a crate with dependents"; exit 1; } wp=${withdep%%:*}; wd=${withdep#*:}; lp=${leaf%%:*}; ld=${leaf#*:} # every non-ALL set also carries aprender-core (its falsification_spec_v10 test scans every crate's src) -want=$( { cargo tree --offline -i "$wp" --workspace -e normal,dev,build --prefix none 2>/dev/null | sed 's/ v.*//'; echo aprender-core; } | sort -u) -lc="$(printf '%s\n' "$lp" aprender-core | sort -u)" +want=$( { cargo tree --offline -i "$wp" --workspace -e normal,dev,build --prefix none 2>/dev/null | sed 's/ v.*//'; printf '%s\n' $readers; } | sort -u) +lc="$(printf '%s\n' "$lp" $readers | sort -u)" row "crate with dependents ($wp) -> itself + dependents + aprender-core" "$want" "$(run "$wd/src/lib.rs" | sort -u)" row "leaf crate ($lp) -> itself + aprender-core" "$lc" "$(run "$ld/src/lib.rs" | sort -u)" row "core crate -> ALL" ALL "$(run crates/aprender-core/src/lib.rs)" @@ -111,7 +112,7 @@ cat >"$t/chain.json" <<'JSON' {"name":"c","manifest_path":"/r/crates/c/Cargo.toml","dependencies":[{"name":"b","kind":null}]}, {"name":"aprender-core","manifest_path":"/r/crates/core/Cargo.toml","dependencies":[]}]} JSON -chain() { AFFECTED_META="$t/chain.json" AFFECTED_ROOT=/r AFFECTED_CORE=none bash "$1" --diff-from <(echo crates/a/src/lib.rs) | sort -u; } +chain() { AFFECTED_META="$t/chain.json" AFFECTED_ROOT=/r AFFECTED_CORE=none AFFECTED_READERS=aprender-core bash "$1" --diff-from <(echo crates/a/src/lib.rs) | sort -u; } row "2-hop chain a<-b<-c: touching a reaches b and c (+ aprender-core)" "a aprender-core b @@ -122,14 +123,27 @@ else row "MUTANT: a 1-hop closure is caught by the 2-hop row" "a aprender-core b" "$(chain "$t/mut-hop.sh")"; fi want_f="package(a) | package(aprender-core) | package(b) | package(c)" -row "multi-package --filter is ' | ' joined" "$want_f" "$(AFFECTED_META="$t/chain.json" AFFECTED_ROOT=/r AFFECTED_CORE=none bash "$A" --filter --diff-from <(echo crates/a/src/lib.rs))" +row "multi-package --filter is ' | ' joined" "$want_f" "$(AFFECTED_META="$t/chain.json" AFFECTED_ROOT=/r AFFECTED_CORE=none AFFECTED_READERS=aprender-core bash "$A" --filter --diff-from <(echo crates/a/src/lib.rs))" sed "s/ | sed 's\/|\/ | \/g'//" "$A" >"$t/mut-fmt.sh" if cmp -s "$A" "$t/mut-fmt.sh"; then echo "FAIL filter-format mutant did not apply"; fail=1 -else row "MUTANT: an unspaced multi-package filter is caught" "package(a)|package(aprender-core)|package(b)|package(c)" "$(AFFECTED_META="$t/chain.json" AFFECTED_ROOT=/r AFFECTED_CORE=none bash "$t/mut-fmt.sh" --filter --diff-from <(echo crates/a/src/lib.rs))"; fi +else row "MUTANT: an unspaced multi-package filter is caught" "package(a)|package(aprender-core)|package(b)|package(c)" "$(AFFECTED_META="$t/chain.json" AFFECTED_ROOT=/r AFFECTED_CORE=none AFFECTED_READERS=aprender-core bash "$t/mut-fmt.sh" --filter --diff-from <(echo crates/a/src/lib.rs))"; fi # an unreadable cargo metadata is ALL, not an empty answer echo 'not json' >"$t/bad.json" row "unreadable metadata -> ALL" ALL "$(AFFECTED_META="$t/bad.json" bash "$A" --diff-from <(echo crates/a/src/lib.rs) 2>/dev/null)" sed 's/ || { emit ALL; exit 0; }$//' "$A" >"$t/mut-jq.sh" if cmp -s "$A" "$t/mut-jq.sh"; then echo "FAIL jq-failure mutant did not apply"; fail=1 else row "MUTANT: dropping the jq-failure fallback is caught" "" "$(AFFECTED_META="$t/bad.json" bash "$t/mut-jq.sh" --diff-from <(echo crates/a/src/lib.rs) 2>/dev/null || true)"; fi +# READERS: each reader is in every non-ALL set (real tree), and dropping the READERS list is caught +for r in $readers; do + case " $(run "$ld/src/lib.rs" | tr '\n' ' ')" in *" $r "*) echo "ok reader $r is in a non-ALL set" ;; *) echo "FAIL reader $r missing from a non-ALL set"; fail=1 ;; esac +done +sed 's#^READERS=.*#READERS="aprender-core"#' "$A" >"$t/mut-readers.sh" +if cmp -s "$A" "$t/mut-readers.sh"; then echo "FAIL readers mutant did not apply"; fail=1 +else row "MUTANT: an emptied READERS list is caught" "$(printf '%s\n' aprender-core "$lp" | sort -u)" "$(printf '%s\n' "$ld/src/lib.rs" >"$t/drd"; bash "$t/mut-readers.sh" --diff-from "$t/drd" | sort -u)"; fi +# the res= jq fallback ALONE: metadata with no `dependencies` key makes that jq fail -> ALL +echo '{"packages":[{"name":"a","manifest_path":"/r/crates/a/Cargo.toml"}]}' >"$t/nodeps.json" +row "no dependencies key -> ALL (res= fallback)" ALL "$(AFFECTED_META="$t/nodeps.json" AFFECTED_ROOT=/r bash "$A" --diff-from <(echo crates/a/src/lib.rs) 2>/dev/null)" +sed '/<<<"$meta")" || { emit ALL; exit 0; }$/s/ || { emit ALL; exit 0; }$//' "$A" >"$t/mut-res.sh" +if cmp -s "$A" "$t/mut-res.sh"; then echo "FAIL res-fallback mutant did not apply"; fail=1 +else row "MUTANT: dropping only the res= fallback is caught" "" "$(AFFECTED_META="$t/nodeps.json" AFFECTED_ROOT=/r bash "$t/mut-res.sh" --diff-from <(echo crates/a/src/lib.rs) 2>/dev/null || true)"; fi exit "$fail" From 99fc513412241508a98dff31e6af9460c8ec981a Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 21:02:23 +0200 Subject: [PATCH 11/24] fix(affected-set): readers guard over-approximates path escapes; READERS is the only list; file:line allow; one case per w5404 probe Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 2 +- scripts/check_affected_readers.sh | 82 ++++++++++++++++++++----------- scripts/check_affected_set.sh | 4 +- 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index f9cdf4f9ba..8f069414c3 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -28,7 +28,7 @@ set -euo pipefail CORE="${AFFECTED_CORE:-aprender-core aprender-compute aprender-contracts apr-cli aprender-quant}" # READERS = crates whose tests read OTHER crates' files at run time (CARGO_MANIFEST_DIR/.., crates/, ../). CORE ones are ALL on touch, but that does not put them in a non-ALL set; every non-ALL set includes them, because the touched crate may be what they read. scripts/check_affected_readers.sh # goes RED on a reader this list (or CORE) lacks. -READERS="${AFFECTED_READERS:-aprender-core aprender-contracts apr-cli aprender-serve aprender-test-lib aprender-orchestrate aprender-present-yaml aprender-decide aprender-cgp aprender-review-experiment aprender-qa-certify}" +READERS="${AFFECTED_READERS:-apr-cli aprender-build-sha aprender-cbtop aprender-cgp aprender-ci-tools aprender-compute aprender-compute-xtask aprender-contracts aprender-contracts-cli aprender-contrastive-data aprender-core aprender-data aprender-db aprender-decide aprender-distribute aprender-explain aprender-gpu aprender-mcp aprender-orchestrate aprender-present-cli aprender-present-core aprender-present-lib aprender-present-terminal aprender-present-yaml aprender-profile aprender-qa-certify aprender-qa-cli aprender-qa-gen aprender-qa-report aprender-qa-runner aprender-quant aprender-rag aprender-registry aprender-review-experiment aprender-rollup aprender-serve aprender-shell aprender-simulate aprender-test-cli aprender-test-lib aprender-train aprender-train-distill aprender-train-inspect aprender-train-shell aprender-tsp aprender-update aprender-verify-ml aprender-viz aprender-viz-ttop facades}" filter=0 from="" while [ $# -gt 0 ]; do case "$1" in diff --git a/scripts/check_affected_readers.sh b/scripts/check_affected_readers.sh index 5950af023f..4ae41274d5 100755 --- a/scripts/check_affected_readers.sh +++ b/scripts/check_affected_readers.sh @@ -1,37 +1,63 @@ #!/usr/bin/env bash -# FLOW-010 E5: a crate whose Rust reads OTHER crates' files at run time (join("crates"), ..//.., -# CARGO_MANIFEST_DIR/../..) is a reader edge that cargo metadata cannot see. scripts/affected_set.sh must -# list it in CORE or READERS, or a change to the crate it reads would skip its tests. RED on a reader -# neither list names. Usage: check_affected_readers.sh [crates-dir] (self-test: --self-test) +# FLOW-010 E5: a crate whose Rust can reach OTHER crates' files at run time is a reader edge that cargo metadata +# cannot see. scripts/affected_set.sh must list it in READERS (the only list that puts a crate in a non-ALL set), +# or a change to the crate it reads would skip its tests. RED on a crate with any path-escape form and no entry. +# Over-approximates on purpose: a false positive only adds a crate to READERS (more tests run); a miss skips tests. +# Flagged in any .rs: a string holding `..`, parent(), ancestors(), current_dir, a "crates/" string or "crates", +# CARGO_WORKSPACE_DIR, workspace_path_or_skip, read_dir(...) naming MANIFEST/crates/root/workspace, format!(...) +# naming MANIFEST_DIR or crates/. Comment-only lines and files under a target/ path component are skipped. +# Usage: check_affected_readers.sh [crates-dir] | --self-test | --list set -euo pipefail -dir="${1:-crates}" SET="${AFFECTED_SET_SH:-scripts/affected_set.sh}" -listed() { sed -n 's/^CORE="${AFFECTED_CORE:-\(.*\)}"$/\1/p; s/^READERS="${AFFECTED_READERS:-\(.*\)}"$/\1/p' "$SET" | tr ' ' '\n' | grep -v '^$' | sort -u; } -# reader patterns; lines about target/, .config, contracts/, docs/ read non-crate paths (an unknown root path is ALL anyway) -readers() { - grep -rEn --include='*.rs' 'join\("crates"\)|\.\./aprender-|MANIFEST_DIR.*\.\.|join\("\.\./\.\."\)' "$1" 2>/dev/null \ - | grep -vE 'target|\.config|contracts|docs/|^\S+:[0-9]+:\s*//' | awk -F/ '{print $2}' | sort -u || true +# the exact file:line exemptions (a crate-wide exemption hides a later reader in the same crate) +ALLOW="${AFFECTED_READERS_ALLOW:-crates/aprender-bench-tokenizer/src/lib.rs:50}" # joins ../.. only for /tokenizer.json and /models/ +PAT='"\.\."|"[^"]*(\.\./|/\.\.)[^"]*"|\bparent\(\)|ancestors\(\)|current_dir|"[^"]*crates/|"crates"|CARGO_WORKSPACE_DIR|workspace_path_or_skip|read_dir\([^)]*(MANIFEST|crates|root|workspace)|format!\([^)]*(MANIFEST_DIR|crates/)' +listed() { sed -n 's/^READERS="${AFFECTED_READERS:-\(.*\)}"$/\1/p' "$SET" | tr ' ' '\n' | grep -v '^$' | sort -u; } +hits() { # $1 = crates dir; prints crate names + { grep -rEn --include='*.rs' "$PAT" "$1" 2>/dev/null || true; } \ + | grep -vE '^[^:]*/target/' | grep -vE '^[^:]+:[0-9]+:[[:space:]]*//' \ + | awk -F: -v allow=" $ALLOW " '{ k = $1 ":" $2; if (index(allow, " " k " ")) next; n = split($1, a, "/"); print a[2] }' | sort -u } -# aprender-bench-tokenizer joins ../.. only to find tokenizer.json / models/ at the repo root, never another crate's file -ALLOW=" aprender-bench-tokenizer " check() { - local bad="" l; l="$(listed)" - for c in $(readers "$1"); do - case "$ALLOW" in *" $c "*) continue ;; esac - printf '%s\n' "$l" | grep -qx "$c" || bad="$bad $c" - done - if [ -n "$bad" ]; then echo "RED: crates read other crates' files but are in neither CORE nor READERS (scripts/affected_set.sh):$bad"; return 1; fi - echo "ok: every cross-crate reader is listed" + local bad="" l c; l="$(listed)" + for c in $(hits "$1"); do printf '%s\n' "$l" | grep -qx "$c" || bad="$bad $c"; done + if [ -n "$bad" ]; then echo "RED: crates can reach other crates' files but are not in READERS (scripts/affected_set.sh):$bad"; return 1; fi + echo "ok: every crate with a path-escape form is in READERS" } +if [ "${1:-}" = "--list" ]; then hits crates; exit 0; fi if [ "${1:-}" = "--self-test" ]; then t=$(mktemp -d); trap 'rm -rf -- "${t:?}"' EXIT - mkdir -p "$t/crates/zz-new/tests" "$t/crates/aprender-serve/src" - echo 'fn f() { let _ = root.join("crates"); }' >"$t/crates/zz-new/tests/x.rs" - echo 'fn f() { let _ = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); }' >"$t/crates/aprender-serve/src/y.rs" - cd "$t"; SET="${OLDPWD}/${SET}" - if (check crates >/dev/null 2>&1); then echo "FAIL self-test: an unlisted reader was not caught"; exit 1; fi - rm -r crates/zz-new - (check crates >/dev/null 2>&1) || { echo "FAIL self-test: a listed reader was flagged"; exit 1; } - echo "ok: self-test"; exit 0 + SET="$(pwd)/$SET"; cd "$t" + fail=0 + fresh() { rm -rf -- "${t:?}/crates"; mkdir -p "$t/crates/$1/src"; } # one unlisted crate holding $2 + expect_red() { # name crate body + fresh "$2"; printf '%s\n' "fn f() { $3 }" >"$t/crates/$2/src/x.rs" + if (check crates >/dev/null 2>&1); then echo "FAIL self-test: not caught: $1"; fail=1; fi + } + expect_red 'join("crates")' zz-a 'let _ = root.join("crates");' + expect_red 'MANIFEST_DIR.parent().parent()' zz-b 'let _ = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent();' + expect_red 'root.join("crates/aprender-core")' zz-c 'let _ = root.join("crates/aprender-core");' + expect_red 'format! with ../../crates' zz-d 'let _ = format!("{}/../../crates/x", env!("CARGO_MANIFEST_DIR"));' + expect_red 'ancestors().nth(2)' zz-e 'let _ = here.ancestors().nth(2);' + expect_red 'read_dir("../")' zz-f 'let _ = std::fs::read_dir("../");' + expect_red 'current_dir().join("..")' zz-g 'let _ = std::env::current_dir().unwrap().join("..");' + expect_red 'relative read_to_string' zz-h 'let _ = std::fs::read_to_string("../../crates/x/src/lib.rs");' + expect_red 'MANIFEST_DIR.parent().join(apr-cli)' zz-i 'let _ = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().join("apr-cli");' + expect_red 'workspace_path_or_skip!' zz-j 'let _ = workspace_path_or_skip!("crates/x/..");' + expect_red 'read_dir(workspace_root)' zz-k 'let _ = std::fs::read_dir(workspace_root);' + expect_red 'CARGO_WORKSPACE_DIR' zz-l 'let _ = env!("CARGO_WORKSPACE_DIR");' + expect_red 'format! with MANIFEST_DIR' zz-m 'let _ = format!("{}/x", env!("CARGO_MANIFEST_DIR"));' + expect_red 'a *contracts*/*target* crate name' zz-contracts-target-new 'let _ = root.join("crates");' + expect_red 'a line that mentions target/contracts' zz-n 'let _ = root.join("crates"); /* target contracts docs/ */' + # the bench-tokenizer exemption is the one line, not the crate + fresh aprender-bench-tokenizer; printf 'fn f() {}\nfn g() { let _ = root.join("crates"); }\n' >"$t/crates/aprender-bench-tokenizer/src/lib.rs" + if (AFFECTED_READERS_ALLOW="crates/aprender-bench-tokenizer/src/lib.rs:1" check crates >/dev/null 2>&1); then echo "FAIL self-test: an allow entry exempted a different line"; fail=1; fi + # a listed reader, a comment-only mention and a target/ file are green + fresh aprender-serve; echo 'fn f() { let _ = root.join("crates"); }' >"$t/crates/aprender-serve/src/y.rs" + (check crates >/dev/null 2>&1) || { echo "FAIL self-test: a listed reader was flagged"; fail=1; } + fresh zz-o; echo '// root.join("crates")' >"$t/crates/zz-o/src/y.rs" + (check crates >/dev/null 2>&1) || { echo "FAIL self-test: a comment-only line was flagged"; fail=1; } + [ "$fail" -eq 0 ] && echo "ok: self-test" + exit "$fail" fi -check "$dir" +check "${1:-crates}" diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index 132832da63..8909af7794 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -12,9 +12,11 @@ row() { # name want got } run() { printf '%s\n' "$@" >"$t/d"; $A --diff-from "$t/d"; } core_list=" aprender-core aprender-compute aprender-contracts apr-cli aprender-quant " -readers="aprender-core aprender-contracts apr-cli aprender-serve aprender-test-lib aprender-orchestrate aprender-present-yaml aprender-decide aprender-cgp aprender-review-experiment aprender-qa-certify" +readers="$(sed -n 's/^READERS="${AFFECTED_READERS:-\(.*\)}"$/\1/p' "$A")" meta=$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null) members=$(jq -r '.packages[].name' <<<"$meta" | sort) +# a READERS entry that is a directory and not a package (e.g. facades) is dropped by the tool; drop it from the oracle too +readers="$(for r in $readers; do if grep -qx "$r" <<<"$members"; then echo "$r"; fi; done)" # a non-core crate that has dependents, and a leaf with none withdep="" leaf="" for p in $members; do From 64602c82c77e56104ac46f23ca73fa63fd926d01 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Tue, 20 Oct 2026 21:03:53 +0200 Subject: [PATCH 12/24] fix(affected-set): oracle readers filter without a bashrs SC2135 false positive Co-Authored-By: Claude Sonnet 5.5 --- scripts/check_affected_set.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index 8909af7794..a547491d75 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -16,7 +16,7 @@ readers="$(sed -n 's/^READERS="${AFFECTED_READERS:-\(.*\)}"$/\1/p' "$A")" meta=$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null) members=$(jq -r '.packages[].name' <<<"$meta" | sort) # a READERS entry that is a directory and not a package (e.g. facades) is dropped by the tool; drop it from the oracle too -readers="$(for r in $readers; do if grep -qx "$r" <<<"$members"; then echo "$r"; fi; done)" +readers="$(printf '%s\n' $readers | grep -Fxf <(printf '%s\n' "$members") || true)" # a non-core crate that has dependents, and a leaf with none withdep="" leaf="" for p in $members; do From 074bc4c7a65f1c7fde41b2f07c0441c86addfe3f Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 00:17:31 +0200 Subject: [PATCH 13/24] feat(affected-set): ci/sections.yml, ci/shard-step-costs.tsv, .claude/, docs/findings/ give READERS, not ALL (Refs #5809) Run-time readers of these paths (grep of crates/**/*.rs) are all in READERS, which every non-ALL set already carries. Last 20 first-parent main merges: ALL 18 -> 14; the 4 that flip go from 85 to 48 crates. Rows + a widening mutant added (102 ok). Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 9 +++++++-- scripts/check_affected_set.sh | 14 +++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index 8f069414c3..d8d286488c 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -12,6 +12,10 @@ # docs/, root *.md, ci/ (sections.yml, explicit-test-commands.d/, ...), Cargo.lock, root Cargo.toml, # rust-toolchain.toml, .cargo/, .github/, Makefile, any other path outside a crate # ALL (tests read README.md, CLAUDE.md, ci/sections.yml and docs/ at run time) +# docs/findings/, .claude/, ci/shard-step-costs.tsv, ci/sections.yml +# NO-READER list (#5809): the only run-time readers of these paths (grep of crates/**/*.rs: contracts-cli, +# orchestrate, core, mcp, apr-cli; sections.yml: ci-tools, contracts, core, qa-runner, review-experiment) are all in READERS, so the set is READERS alone. Alone or +# beside crate .rs files; any other path in the diff still decides ALL. # Any non-.rs file under crates// (README.md, *.json, *.toml, ...) is ALL: other crates' tests read them # (aprender-core readme_contract.rs reads every crates/*/README.md; aprender-contracts reads aprender-serve/kernel-registry.json). # An empty diff, or a diff git cannot compute (bad base: exit 128), is ALL: nothing was @@ -50,10 +54,11 @@ else meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null | root="${AFFECTED_ROOT:-$(git rev-parse --show-toplevel)}" dirs="$(jq -r --arg root "$root/" '.packages[] | [.name, (.manifest_path | sub($root;"") | sub("/?Cargo.toml$";""))] | @tsv' <<<"$meta")" || { emit ALL; exit 0; } [ -n "$dirs" ] || { emit ALL; exit 0; } -touched="" +touched="" skipped=0 while IFS= read -r f; do case "$f" in '') continue ;; + docs/findings/* | .claude/* | ci/shard-step-costs.tsv | ci/sections.yml) skipped=1; continue ;; crates/*/build.rs) emit ALL; exit 0 ;; crates/*.rs) ;; crates/*) emit ALL; exit 0 ;; @@ -65,7 +70,7 @@ while IFS= read -r f; do touched="$touched"$'\n'"$pkg" done <<<"$files" touched="$(printf '%s\n' "$touched" | grep -v '^$' | sort -u || true)" -[ -n "$touched" ] || { emit ALL; exit 0; } # nothing placed in a package: nothing may be skipped +[ -n "$touched" ] || [ "$skipped" = 1 ] || { emit ALL; exit 0; } # nothing placed in a package and nothing on the no-reader list: nothing may be skipped # The closure, then aprender-core: its falsification_spec_v10 test scans every crate's src, so ANY non-ALL set includes it (and the READERS). res="$(jq -r --argjson t "$(printf '%s\n' "$touched" | jq -R . | jq -s .)" --arg rd "$READERS" ' (.packages | map({name, deps: [.dependencies[].name]})) as $p diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index a547491d75..ec56c40ffd 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -43,10 +43,18 @@ row "--diff-from with .. is refused (SEC010)" "2" "$($A --diff-from ../x >/dev/n row "Cargo.lock -> ALL" ALL "$(run Cargo.lock)" row "unknown path -> ALL" ALL "$(run some/unknown/path.txt)" row "unknown path next to a leaf -> ALL" ALL "$(run "$ld/src/lib.rs" scripts/whatever.sh)" +row "docs/findings ledger alone -> READERS only, not ALL (#5809)" "$(printf '%s\n' $readers | sort -u)" "$(run docs/findings/s1.jsonl | sort -u)" +row ".claude agent-memory alone -> READERS only" "$(printf '%s\n' $readers | sort -u)" "$(run .claude/agent-memory/x/MEMORY.md | sort -u)" +row "shard cost table alone -> READERS only" "$(printf '%s\n' $readers | sort -u)" "$(run ci/shard-step-costs.tsv | sort -u)" +row "no-reader path next to a leaf .rs -> leaf + READERS" "$lc" "$(run docs/findings/s1.jsonl "$ld/src/lib.rs" | sort -u)" +row "no-reader path next to docs/x.md -> ALL" ALL "$(run docs/findings/s1.jsonl docs/x.md)" +row "no-reader path next to a CORE crate -> ALL" ALL "$(run .claude/x docs/findings/s1.jsonl crates/aprender-core/src/lib.rs)" +row "docs/findings lookalike (docs/findings-x.md) -> ALL" ALL "$(run docs/findings-x.md)" row "docs file -> ALL (tests read docs/ at run time)" ALL "$(run docs/x.md)" row "root README.md -> ALL" ALL "$(run README.md)" row "root CLAUDE.md -> ALL" ALL "$(run CLAUDE.md)" -row "ci/sections.yml -> ALL" ALL "$(run ci/sections.yml)" +row "ci/sections.yml alone -> READERS only (its readers ci-tools, contracts, core, qa-runner, review-experiment are all READERS) (#5809)" "$(printf '%s\n' $readers | sort -u)" "$(run ci/sections.yml | sort -u)" +row "ci/sections.yml next to ci/explicit-test-commands.d -> ALL" ALL "$(run ci/sections.yml ci/explicit-test-commands.d/815-affected-set-rows.cmd)" row "ci/explicit-test-commands.d fragment -> ALL" ALL "$(run ci/explicit-test-commands.d/815-affected-set-rows.cmd)" row "a crate's own Cargo.toml -> ALL" ALL "$(run "$ld/Cargo.toml")" row "a .md inside a leaf crate -> ALL (aprender-core reads crates/*/README.md)" ALL "$(run "$ld/README.md")" @@ -98,6 +106,10 @@ else printf '%s\n%s\n' docs/x.md "$ld/src/lib.rs" >"$t/dd"; row "MUTANT: skippin sed 's#^ crates/\*) emit ALL.*# crates/*) continue ;;#' "$A" >"$t/mut-toml.sh" if cmp -s "$A" "$t/mut-toml.sh"; then echo "FAIL manifest mutant did not apply"; fail=1 else printf '%s\n%s\n' "$ld/Cargo.toml" "$ld/src/lib.rs" >"$t/dt"; row "MUTANT: skipping non-.rs crate files is caught by the manifest row" "$lc" "$(bash "$t/mut-toml.sh" --diff-from "$t/dt" | sort -u || true)"; fi +# mutant: a no-reader arm that also skips docs/* must be seen by the docs-next-to-no-reader row +sed 's#^ docs\/findings\/\* # docs/* | docs/findings/* #' "$A" >"$t/mut-nr.sh" +if cmp -s "$A" "$t/mut-nr.sh"; then echo "FAIL no-reader mutant did not apply"; fail=1 +else printf '%s\n%s\n' docs/findings/s1.jsonl docs/x.md >"$t/dnr"; row "MUTANT: widening the no-reader list to docs/* is caught by the docs-next-to-no-reader row" "not-ALL" "$([ "$(bash "$t/mut-nr.sh" --diff-from "$t/dnr" | head -n1)" != ALL ] && echo not-ALL || echo ALL)"; fi # CORE crates: each must give ALL, and dropping each from CORE must be seen (its dependents alone are not the workspace) for c in aprender-compute aprender-contracts aprender-quant; do cd_=$(jq -r --arg p "$c" '.packages[]|select(.name==$p)|.manifest_path' <<<"$meta" | sed "s|$(git rev-parse --show-toplevel)/||; s|/Cargo.toml||") From 4f4861682975347c8afecac36919f466c4ebb01a Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 01:17:47 +0200 Subject: [PATCH 14/24] fix(affected-set): ci/sections.yml stays ALL (it holds the CI job bodies) (Refs #5809) Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 6 +++--- scripts/check_affected_set.sh | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index d8d286488c..f1fe6eb006 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -12,9 +12,9 @@ # docs/, root *.md, ci/ (sections.yml, explicit-test-commands.d/, ...), Cargo.lock, root Cargo.toml, # rust-toolchain.toml, .cargo/, .github/, Makefile, any other path outside a crate # ALL (tests read README.md, CLAUDE.md, ci/sections.yml and docs/ at run time) -# docs/findings/, .claude/, ci/shard-step-costs.tsv, ci/sections.yml +# docs/findings/, .claude/, ci/shard-step-costs.tsv # NO-READER list (#5809): the only run-time readers of these paths (grep of crates/**/*.rs: contracts-cli, -# orchestrate, core, mcp, apr-cli; sections.yml: ci-tools, contracts, core, qa-runner, review-experiment) are all in READERS, so the set is READERS alone. Alone or +# orchestrate, core, mcp, apr-cli) are all in READERS, so the set is READERS alone. Alone or # beside crate .rs files; any other path in the diff still decides ALL. # Any non-.rs file under crates// (README.md, *.json, *.toml, ...) is ALL: other crates' tests read them # (aprender-core readme_contract.rs reads every crates/*/README.md; aprender-contracts reads aprender-serve/kernel-registry.json). @@ -58,7 +58,7 @@ touched="" skipped=0 while IFS= read -r f; do case "$f" in '') continue ;; - docs/findings/* | .claude/* | ci/shard-step-costs.tsv | ci/sections.yml) skipped=1; continue ;; + docs/findings/* | .claude/* | ci/shard-step-costs.tsv) skipped=1; continue ;; crates/*/build.rs) emit ALL; exit 0 ;; crates/*.rs) ;; crates/*) emit ALL; exit 0 ;; diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index ec56c40ffd..b0e642c779 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -53,8 +53,7 @@ row "docs/findings lookalike (docs/findings-x.md) -> ALL" ALL "$(run docs/findin row "docs file -> ALL (tests read docs/ at run time)" ALL "$(run docs/x.md)" row "root README.md -> ALL" ALL "$(run README.md)" row "root CLAUDE.md -> ALL" ALL "$(run CLAUDE.md)" -row "ci/sections.yml alone -> READERS only (its readers ci-tools, contracts, core, qa-runner, review-experiment are all READERS) (#5809)" "$(printf '%s\n' $readers | sort -u)" "$(run ci/sections.yml | sort -u)" -row "ci/sections.yml next to ci/explicit-test-commands.d -> ALL" ALL "$(run ci/sections.yml ci/explicit-test-commands.d/815-affected-set-rows.cmd)" +row "ci/sections.yml -> ALL (it holds the CI job bodies: editing it changes what CI runs, not only what tests read)" ALL "$(run ci/sections.yml)" row "ci/explicit-test-commands.d fragment -> ALL" ALL "$(run ci/explicit-test-commands.d/815-affected-set-rows.cmd)" row "a crate's own Cargo.toml -> ALL" ALL "$(run "$ld/Cargo.toml")" row "a .md inside a leaf crate -> ALL (aprender-core reads crates/*/README.md)" ALL "$(run "$ld/README.md")" From 5fefef4538aae02bd4aada62ff715d3360e81a04 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 03:57:51 +0200 Subject: [PATCH 15/24] ci: retrigger (no CI ran on 4f48616829) Co-Authored-By: Claude Sonnet 5.5 From ad042a5353081309af3b694e23a922521d0f8c96 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 04:13:54 +0200 Subject: [PATCH 16/24] ci: retrigger after retarget to main (pull_request is filtered to main/master) Co-Authored-By: Claude Sonnet 5.5 From a1ba24ed8c48460a120d66f954e2b3b8d1e6794a Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 04:36:59 +0200 Subject: [PATCH 17/24] ci(affected): contracts/, docs/, root *.md -> READERS not ALL (FLOW-010 b) (#5823) * ci(affected): contracts/, docs/ and root *.md give READERS, not ALL (FLOW-010 b) Adds the 12 crates that read contracts/, docs/ or README at run time to READERS. contracts/**/binding.yaml and contracts/model-families stay ALL: build.rs inputs whose generated code reaches dependents. Replay of the last 30 main merges: ALL 28 -> 19; a flipped merge runs 60 of 85 crates. Co-Authored-By: Claude Sonnet 5.5 * ci(affected): root contracts files compiled into a crate stay ALL; checker derives them (Refs #5823) aprender-serve build.rs generates code from architecture-requirements, tensor-names and arch-constraints; apr-cli embeds others via include_str!. BUILD_CONTRACTS names them (ALL); check_affected_readers.sh scans build.rs and include*! for root contracts files and is RED on one the list omits. Stale header lines fixed. Co-Authored-By: Claude Sonnet 5.5 --------- Co-authored-by: Claude Sonnet 5.5 --- scripts/affected_set.sh | 17 ++++++++++--- scripts/check_affected_readers.sh | 9 +++++++ scripts/check_affected_set.sh | 41 +++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index f1fe6eb006..fb476ae92a 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -9,9 +9,10 @@ # crates//... the package + its reverse-dependency closure (cargo metadata) # a CORE crate ALL (its closure is the workspace; naming it would only hide that) # Cargo.toml of a crate ALL (a manifest edit changes features/deps, which no path can bound) -# docs/, root *.md, ci/ (sections.yml, explicit-test-commands.d/, ...), Cargo.lock, root Cargo.toml, -# rust-toolchain.toml, .cargo/, .github/, Makefile, any other path outside a crate -# ALL (tests read README.md, CLAUDE.md, ci/sections.yml and docs/ at run time) +# ci/ (sections.yml, explicit-test-commands.d/, ...), Cargo.lock, root Cargo.toml, rust-toolchain.toml, .cargo/, .github/, Makefile, +# any other path outside a crate ALL +# docs/, contracts/, root *.md READERS only (FLOW-010 b): their run-time readers are all in READERS. Exceptions, ALL: +# contracts binding.yaml, contracts model-families and the BUILD_CONTRACTS files (compiled into a crate) # docs/findings/, .claude/, ci/shard-step-costs.tsv # NO-READER list (#5809): the only run-time readers of these paths (grep of crates/**/*.rs: contracts-cli, # orchestrate, core, mcp, apr-cli) are all in READERS, so the set is READERS alone. Alone or @@ -32,7 +33,7 @@ set -euo pipefail CORE="${AFFECTED_CORE:-aprender-core aprender-compute aprender-contracts apr-cli aprender-quant}" # READERS = crates whose tests read OTHER crates' files at run time (CARGO_MANIFEST_DIR/.., crates/, ../). CORE ones are ALL on touch, but that does not put them in a non-ALL set; every non-ALL set includes them, because the touched crate may be what they read. scripts/check_affected_readers.sh # goes RED on a reader this list (or CORE) lacks. -READERS="${AFFECTED_READERS:-apr-cli aprender-build-sha aprender-cbtop aprender-cgp aprender-ci-tools aprender-compute aprender-compute-xtask aprender-contracts aprender-contracts-cli aprender-contrastive-data aprender-core aprender-data aprender-db aprender-decide aprender-distribute aprender-explain aprender-gpu aprender-mcp aprender-orchestrate aprender-present-cli aprender-present-core aprender-present-lib aprender-present-terminal aprender-present-yaml aprender-profile aprender-qa-certify aprender-qa-cli aprender-qa-gen aprender-qa-report aprender-qa-runner aprender-quant aprender-rag aprender-registry aprender-review-experiment aprender-rollup aprender-serve aprender-shell aprender-simulate aprender-test-cli aprender-test-lib aprender-train aprender-train-distill aprender-train-inspect aprender-train-shell aprender-tsp aprender-update aprender-verify-ml aprender-viz aprender-viz-ttop facades}" +READERS="${AFFECTED_READERS:-apr-cli apr-format aprender-build-sha aprender-cbtop aprender-cgp aprender-ci-tools aprender-compute aprender-compute-xtask aprender-contracts aprender-contracts-cli aprender-contracts-macros aprender-contrastive-data aprender-core aprender-data aprender-db aprender-decide aprender-distribute aprender-explain aprender-gpu aprender-graph aprender-mcp aprender-orchestrate aprender-present-cli aprender-present-core aprender-present-layout aprender-present-lib aprender-present-terminal aprender-present-widgets aprender-present-yaml aprender-profile aprender-ptx-debug aprender-qa-certify aprender-qa-cli aprender-qa-gen aprender-qa-report aprender-qa-runner aprender-quant aprender-rag aprender-rag-cli aprender-registry aprender-review-experiment aprender-rollup aprender-serve aprender-shell aprender-simulate aprender-test-cli aprender-test-lib aprender-train aprender-train-bench aprender-train-distill aprender-train-inspect aprender-train-lora aprender-train-shell aprender-tsp aprender-update aprender-verify-ml aprender-viz aprender-viz-ttop aprender-zram aprender-zram-cli aprender-zram-generator facades}" filter=0 from="" while [ $# -gt 0 ]; do case "$1" in @@ -54,10 +55,18 @@ else meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null | root="${AFFECTED_ROOT:-$(git rev-parse --show-toplevel)}" dirs="$(jq -r --arg root "$root/" '.packages[] | [.name, (.manifest_path | sub($root;"") | sub("/?Cargo.toml$";""))] | @tsv' <<<"$meta")" || { emit ALL; exit 0; } [ -n "$dirs" ] || { emit ALL; exit 0; } +# Root contracts/ files that build.rs or include_str!/include! turn into compiled code or constants (aprender-serve build.rs generates +# arch/tensor-name code from three of them; apr-cli and mcp embed others). A change reaches dependents of the compiling crate, so these are ALL. +# check_affected_readers.sh derives this list from the tree and is RED on a file it omits. +BUILD_CONTRACTS="${AFFECTED_BUILD_CONTRACTS:-apr-mcp-tool-schemas-v1.yaml apr-model-capability-v1.yaml arch-constraints-v1.yaml architecture-requirements-v1.yaml kernel-fusion-v1.yaml layer-parity-v1.yaml softmax-kernel-v1.yaml tensor-names-v1.yaml thinking-budgets-v1.yaml}" touched="" skipped=0 while IFS= read -r f; do + case "$f" in */*) ;; *.md) skipped=1; continue ;; esac # root *.md: its run-time readers are in READERS + case "$f" in contracts/*) case " $BUILD_CONTRACTS " in *" ${f#contracts/} "*) emit ALL; exit 0 ;; esac ;; esac case "$f" in '') continue ;; + contracts/binding.yaml | contracts/*/binding.yaml | contracts/model-families/* | contracts/*/model-families/*) emit ALL; exit 0 ;; # build inputs of core/compute/contracts/simulate/train: their generated code reaches dependents + contracts/* | docs/*) skipped=1; continue ;; # FLOW-010(b): run-time readers are in READERS docs/findings/* | .claude/* | ci/shard-step-costs.tsv) skipped=1; continue ;; crates/*/build.rs) emit ALL; exit 0 ;; crates/*.rs) ;; diff --git a/scripts/check_affected_readers.sh b/scripts/check_affected_readers.sh index 4ae41274d5..d2230943eb 100755 --- a/scripts/check_affected_readers.sh +++ b/scripts/check_affected_readers.sh @@ -18,9 +18,18 @@ hits() { # $1 = crates dir; prints crate names | grep -vE '^[^:]*/target/' | grep -vE '^[^:]+:[0-9]+:[[:space:]]*//' \ | awk -F: -v allow=" $ALLOW " '{ k = $1 ":" $2; if (index(allow, " " k " ")) next; n = split($1, a, "/"); print a[2] }' | sort -u } +build_listed() { sed -n 's/^BUILD_CONTRACTS="\${AFFECTED_BUILD_CONTRACTS:-\(.*\)}"$/\1/p' "$SET" | tr ' ' '\n' | grep -v '^$' | sort -u; } +# Root contracts/ that a build.rs names (any quoted *-vN.yaml) or an include*! macro embeds: compiled into a crate, so ALL. +build_inputs() { # $1 = crates dir; root contracts dir = ${CONTRACTS_DIR:-contracts} + { grep -rhE --include=build.rs '[A-Za-z0-9_-]+\.yaml"' "$1" 2>/dev/null | grep -vE '^\s*//' | grep -oE '[A-Za-z0-9_-]+\.yaml"' | tr -d '"' + grep -rhE --include='*.rs' 'include(_str|_bytes)?!' "$1" 2>/dev/null | grep -vE '^\s*//' | grep -oE 'contracts/[A-Za-z0-9_-]+\.yaml' | sed 's#contracts/##' + } | sort -u | while read -r f; do [ -f "${CONTRACTS_DIR:-contracts}/$f" ] && echo "$f"; done | grep -v 'binding\.yaml$' || true +} check() { local bad="" l c; l="$(listed)" for c in $(hits "$1"); do printf '%s\n' "$l" | grep -qx "$c" || bad="$bad $c"; done + local bl; bl="$(build_listed)" + for c in $(build_inputs "$1"); do printf '%s\n' "$bl" | grep -qx "$c" || bad="$bad contracts/$c(BUILD_CONTRACTS)"; done if [ -n "$bad" ]; then echo "RED: crates can reach other crates' files but are not in READERS (scripts/affected_set.sh):$bad"; return 1; fi echo "ok: every crate with a path-escape form is in READERS" } diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index b0e642c779..0f5d0e0c15 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -2,6 +2,7 @@ # FLOW-010 E5 RED rows for scripts/affected_set.sh. Oracle for the closure is # `cargo tree -i` (a different code path from the tool's cargo-metadata jq). set -euo pipefail +export LC_ALL=C # sort order must match jq's codepoint order (apr-format vs aprender-*) # the CI container runs as root over a host-owned checkout: git would refuse it (dubious ownership) export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=safe.directory GIT_CONFIG_VALUE_0='*' A=scripts/affected_set.sh @@ -47,12 +48,21 @@ row "docs/findings ledger alone -> READERS only, not ALL (#5809)" "$(printf '%s\ row ".claude agent-memory alone -> READERS only" "$(printf '%s\n' $readers | sort -u)" "$(run .claude/agent-memory/x/MEMORY.md | sort -u)" row "shard cost table alone -> READERS only" "$(printf '%s\n' $readers | sort -u)" "$(run ci/shard-step-costs.tsv | sort -u)" row "no-reader path next to a leaf .rs -> leaf + READERS" "$lc" "$(run docs/findings/s1.jsonl "$ld/src/lib.rs" | sort -u)" -row "no-reader path next to docs/x.md -> ALL" ALL "$(run docs/findings/s1.jsonl docs/x.md)" +row "no-reader path next to docs/x.md -> READERS only (FLOW-010 b)" "$(printf '%s\n' $readers | sort -u)" "$(run docs/findings/s1.jsonl docs/x.md | sort -u)" row "no-reader path next to a CORE crate -> ALL" ALL "$(run .claude/x docs/findings/s1.jsonl crates/aprender-core/src/lib.rs)" -row "docs/findings lookalike (docs/findings-x.md) -> ALL" ALL "$(run docs/findings-x.md)" -row "docs file -> ALL (tests read docs/ at run time)" ALL "$(run docs/x.md)" -row "root README.md -> ALL" ALL "$(run README.md)" -row "root CLAUDE.md -> ALL" ALL "$(run CLAUDE.md)" +row "docs/findings lookalike (docs/findings-x.md) -> READERS only" "$(printf '%s\n' $readers | sort -u)" "$(run docs/findings-x.md | sort -u)" +row "docs file -> READERS only (their run-time readers are in READERS)" "$(printf '%s\n' $readers | sort -u)" "$(run docs/x.md | sort -u)" +row "contracts yaml -> READERS only" "$(printf '%s\n' $readers | sort -u)" "$(run contracts/foo-v1.yaml | sort -u)" +row "contracts//binding.yaml -> ALL (build input of core/compute/contracts/simulate/train)" ALL "$(run contracts/aprender/binding.yaml)" +row "serve build.rs input contracts/tensor-names-v1.yaml -> ALL" ALL "$(run contracts/tensor-names-v1.yaml)" +row "serve build.rs input contracts/architecture-requirements-v1.yaml -> ALL" ALL "$(run contracts/architecture-requirements-v1.yaml)" +row "serve build.rs input contracts/arch-constraints-v1.yaml -> ALL" ALL "$(run contracts/arch-constraints-v1.yaml)" +row "BUILD_CONTRACTS file next to a leaf .rs -> ALL" ALL "$(run contracts/tensor-names-v1.yaml "$ld/src/lib.rs")" +row "contracts/binding.yaml -> ALL" ALL "$(run contracts/binding.yaml)" +row "contracts/model-families -> ALL (aprender-core build.rs generates code from it)" ALL "$(run contracts/model-families/q.yaml)" +row "contracts yaml next to a leaf .rs -> leaf + READERS" "$lc" "$(run contracts/foo-v1.yaml "$ld/src/lib.rs" | sort -u)" +row "root README.md -> READERS only" "$(printf '%s\n' $readers | sort -u)" "$(run README.md | sort -u)" +row "root CLAUDE.md -> READERS only" "$(printf '%s\n' $readers | sort -u)" "$(run CLAUDE.md | sort -u)" row "ci/sections.yml -> ALL (it holds the CI job bodies: editing it changes what CI runs, not only what tests read)" ALL "$(run ci/sections.yml)" row "ci/explicit-test-commands.d fragment -> ALL" ALL "$(run ci/explicit-test-commands.d/815-affected-set-rows.cmd)" row "a crate's own Cargo.toml -> ALL" ALL "$(run "$ld/Cargo.toml")" @@ -98,17 +108,22 @@ else row "MUTANT: letting git detect renames is caught by the rename row" "c" "$ sed '/^ crates\/\*\/build.rs)/d' "$A" >"$t/mut-build.sh" if cmp -s "$A" "$t/mut-build.sh"; then echo "FAIL build.rs mutant did not apply"; fail=1 else printf '%s\n' "$ld/build.rs" >"$t/db"; row "MUTANT: dropping the build.rs arm is caught by the build.rs row" "$lc" "$(bash "$t/mut-build.sh" --diff-from "$t/db" | sort -u || true)"; fi -# mutants: the skip list that used to exist (docs/*, *.md) must be caught by the ALL rows above. -sed 's#^ crates/\*.rs) ;;# docs/* | *.md) continue ;;\n crates/*.rs) ;;#' "$A" >"$t/mut-docs.sh" -if cmp -s "$A" "$t/mut-docs.sh"; then echo "FAIL docs-skip mutant did not apply"; fail=1 -else printf '%s\n%s\n' docs/x.md "$ld/src/lib.rs" >"$t/dd"; row "MUTANT: skipping docs/*.md is caught by the docs-ALL row" "$lc" "$(bash "$t/mut-docs.sh" --diff-from "$t/dd" | sort -u || true)"; fi +# mutant: dropping the contracts build-input arm must be caught by the binding/model-families ALL rows +sed '/^ contracts\/binding.yaml /d' "$A" >"$t/mut-bind.sh" +if cmp -s "$A" "$t/mut-bind.sh"; then echo "FAIL binding mutant did not apply"; fail=1 +else printf '%s\n' contracts/aprender/binding.yaml >"$t/db2"; row "MUTANT: dropping the binding/model-families ALL arm is caught" "$(printf '%s\n' $readers | sort -u)" "$(bash "$t/mut-bind.sh" --diff-from "$t/db2" | sort -u)"; fi +# mutant: the checker must go RED when a build.rs-read root contract is missing from BUILD_CONTRACTS +sed 's/ tensor-names-v1.yaml//' "$A" >"$t/mut-bc.sh" +if cmp -s "$A" "$t/mut-bc.sh"; then echo "FAIL build-contracts mutant did not apply"; fail=1 +elif AFFECTED_SET_SH="$t/mut-bc.sh" bash scripts/check_affected_readers.sh >/dev/null 2>&1; then echo "FAIL MUTANT: a build.rs-read contract missing from BUILD_CONTRACTS is not caught"; fail=1 +else echo "ok MUTANT: a build.rs-read contract missing from BUILD_CONTRACTS is caught by check_affected_readers.sh"; fi +# mutant: root-md arm that also skips crate-level .md must be caught by the leaf README row +sed 's#case "\$f" in \*/\*) ;; \*.md)#case "\$f" in *.md)#' "$A" >"$t/mut-md.sh" +if cmp -s "$A" "$t/mut-md.sh"; then echo "FAIL md mutant did not apply"; fail=1 +else printf '%s\n%s\n' "$ld/README.md" "$ld/src/lib.rs" >"$t/dmd"; row "MUTANT: skipping crate-level .md is caught by the leaf README row" "$lc" "$(bash "$t/mut-md.sh" --diff-from "$t/dmd" | sort -u)"; fi sed 's#^ crates/\*) emit ALL.*# crates/*) continue ;;#' "$A" >"$t/mut-toml.sh" if cmp -s "$A" "$t/mut-toml.sh"; then echo "FAIL manifest mutant did not apply"; fail=1 else printf '%s\n%s\n' "$ld/Cargo.toml" "$ld/src/lib.rs" >"$t/dt"; row "MUTANT: skipping non-.rs crate files is caught by the manifest row" "$lc" "$(bash "$t/mut-toml.sh" --diff-from "$t/dt" | sort -u || true)"; fi -# mutant: a no-reader arm that also skips docs/* must be seen by the docs-next-to-no-reader row -sed 's#^ docs\/findings\/\* # docs/* | docs/findings/* #' "$A" >"$t/mut-nr.sh" -if cmp -s "$A" "$t/mut-nr.sh"; then echo "FAIL no-reader mutant did not apply"; fail=1 -else printf '%s\n%s\n' docs/findings/s1.jsonl docs/x.md >"$t/dnr"; row "MUTANT: widening the no-reader list to docs/* is caught by the docs-next-to-no-reader row" "not-ALL" "$([ "$(bash "$t/mut-nr.sh" --diff-from "$t/dnr" | head -n1)" != ALL ] && echo not-ALL || echo ALL)"; fi # CORE crates: each must give ALL, and dropping each from CORE must be seen (its dependents alone are not the workspace) for c in aprender-compute aprender-contracts aprender-quant; do cd_=$(jq -r --arg p "$c" '.packages[]|select(.name==$p)|.manifest_path' <<<"$meta" | sed "s|$(git rev-parse --show-toplevel)/||; s|/Cargo.toml||") From 91b7b0018d5140d11ac1cadc25c821b778243f7b Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 04:56:29 +0200 Subject: [PATCH 18/24] fix(affected-set): every error path prints ALL, never nothing (Refs #5809) A missing --diff-from file (rc 1), --diff-from with no FILE, no cargo (rc 127) and a failing git rev-parse (rc 128) exited with no output, which a caller reads as 'nothing affected'. Each is now ALL; a RED row for each plus two mutants. Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 14 ++++++++------ scripts/check_affected_set.sh | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index fb476ae92a..1eaf445011 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -34,25 +34,27 @@ CORE="${AFFECTED_CORE:-aprender-core aprender-compute aprender-contracts apr-cli # READERS = crates whose tests read OTHER crates' files at run time (CARGO_MANIFEST_DIR/.., crates/, ../). CORE ones are ALL on touch, but that does not put them in a non-ALL set; every non-ALL set includes them, because the touched crate may be what they read. scripts/check_affected_readers.sh # goes RED on a reader this list (or CORE) lacks. READERS="${AFFECTED_READERS:-apr-cli apr-format aprender-build-sha aprender-cbtop aprender-cgp aprender-ci-tools aprender-compute aprender-compute-xtask aprender-contracts aprender-contracts-cli aprender-contracts-macros aprender-contrastive-data aprender-core aprender-data aprender-db aprender-decide aprender-distribute aprender-explain aprender-gpu aprender-graph aprender-mcp aprender-orchestrate aprender-present-cli aprender-present-core aprender-present-layout aprender-present-lib aprender-present-terminal aprender-present-widgets aprender-present-yaml aprender-profile aprender-ptx-debug aprender-qa-certify aprender-qa-cli aprender-qa-gen aprender-qa-report aprender-qa-runner aprender-quant aprender-rag aprender-rag-cli aprender-registry aprender-review-experiment aprender-rollup aprender-serve aprender-shell aprender-simulate aprender-test-cli aprender-test-lib aprender-train aprender-train-bench aprender-train-distill aprender-train-inspect aprender-train-lora aprender-train-shell aprender-tsp aprender-update aprender-verify-ml aprender-viz aprender-viz-ttop aprender-zram aprender-zram-cli aprender-zram-generator facades}" -filter=0 from="" +filter=0 from="" bad=0 while [ $# -gt 0 ]; do case "$1" in --filter) filter=1; shift ;; - --diff-from) from="$2"; shift 2 ;; + --diff-from) [ $# -ge 2 ] || { bad=1; break; }; from="$2"; shift 2 ;; *) break ;; esac done case "$from" in *..*) echo "affected_set: refusing a --diff-from with \"..\": $from" >&2; exit 2 ;; esac # bashrs SEC010 -if [ -n "$from" ]; then files="$(cat "$from")" +if [ -n "$from" ]; then files="$(cat "$from")" || files="" else files="$(git diff --name-only --no-renames "${1:-origin/main}...${2:-HEAD}")" || files=""; fi emit() { # $@ = package names, or ALL if [ "${1:-}" = ALL ]; then [ "$filter" = 1 ] && echo 'all()' || echo ALL; return; fi if [ "$filter" = 1 ]; then printf '%s\n' "$@" | sed 's/.*/package(&)/' | paste -sd'|' | sed 's/|/ | /g'; else printf '%s\n' "$@"; fi } +[ "$bad" = 0 ] || { emit ALL; exit 0; } # --diff-from with no FILE [ -n "$files" ] || { emit ALL; exit 0; } -if [ -n "${AFFECTED_META:-}" ]; then meta="$(cat "$AFFECTED_META")" -else meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null || cargo metadata --no-deps --format-version 1)"; fi -root="${AFFECTED_ROOT:-$(git rev-parse --show-toplevel)}" +if [ -n "${AFFECTED_META:-}" ]; then meta="$(cat "$AFFECTED_META")" || meta="" +else meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null || cargo metadata --no-deps --format-version 1)" || meta=""; fi +root="${AFFECTED_ROOT:-$(git rev-parse --show-toplevel)}" || root="" +[ -n "$meta" ] && [ -n "$root" ] || { emit ALL; exit 0; } # no metadata, no checkout root: nothing measured dirs="$(jq -r --arg root "$root/" '.packages[] | [.name, (.manifest_path | sub($root;"") | sub("/?Cargo.toml$";""))] | @tsv' <<<"$meta")" || { emit ALL; exit 0; } [ -n "$dirs" ] || { emit ALL; exit 0; } # Root contracts/ files that build.rs or include_str!/include! turn into compiled code or constants (aprender-serve build.rs generates diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index 0f5d0e0c15..3098bcbe08 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -155,6 +155,22 @@ row "multi-package --filter is ' | ' joined" "$want_f" "$(AFFECTED_META="$t/chai sed "s/ | sed 's\/|\/ | \/g'//" "$A" >"$t/mut-fmt.sh" if cmp -s "$A" "$t/mut-fmt.sh"; then echo "FAIL filter-format mutant did not apply"; fail=1 else row "MUTANT: an unspaced multi-package filter is caught" "package(a)|package(aprender-core)|package(b)|package(c)" "$(AFFECTED_META="$t/chain.json" AFFECTED_ROOT=/r AFFECTED_CORE=none AFFECTED_READERS=aprender-core bash "$t/mut-fmt.sh" --filter --diff-from <(echo crates/a/src/lib.rs))"; fi +# fail-closed: every error path prints ALL (never nothing) -- a silent exit would read as "no tests affected" (#5815 quorum) +abs_a="$(cd "$(dirname "$A")" && pwd)/$(basename "$A")" +row "missing --diff-from file -> ALL" ALL "$($A --diff-from "$t/no-such-file" 2>/dev/null || true)" +row "--diff-from with no FILE -> ALL" ALL "$($A --diff-from 2>/dev/null || true)" +row "--filter, missing --diff-from file -> all()" "all()" "$($A --filter --diff-from "$t/no-such-file" 2>/dev/null || true)" +mkdir -p "$t/nocargo"; for b in cat git jq sed awk paste grep sort head dirname basename mktemp; do ln -sf "$(command -v $b)" "$t/nocargo/$b"; done +printf 'crates/a/src/lib.rs\n' >"$t/one" +row "no cargo on PATH (rc 127) -> ALL" ALL "$(PATH="$t/nocargo" "$(command -v bash)" "$abs_a" --diff-from "$t/one" 2>/dev/null || true)" +mkdir -p "$t/notgit" +row "git rev-parse fails (not a checkout, rc 128) -> ALL" ALL "$(cd "$t/notgit" && AFFECTED_META="$t/meta.json" bash "$abs_a" --diff-from "$t/one" 2>/dev/null || true)" +sed 's/ || root=""$//' "$A" >"$t/mut-root.sh" +if cmp -s "$A" "$t/mut-root.sh"; then echo "FAIL root-fallback mutant did not apply"; fail=1 +else row "MUTANT: dropping the rev-parse fallback is caught (prints nothing)" "" "$(cd "$t/notgit" && AFFECTED_META="$t/meta.json" bash "$t/mut-root.sh" --diff-from "$t/one" 2>/dev/null || true)"; fi +sed 's/files="\$(cat "\$from")" || files=""/files="$(cat "$from")"/' "$A" >"$t/mut-cat.sh" +if cmp -s "$A" "$t/mut-cat.sh"; then echo "FAIL cat-fallback mutant did not apply"; fail=1 +else row "MUTANT: dropping the missing-file fallback is caught (prints nothing)" "" "$(bash "$t/mut-cat.sh" --diff-from "$t/no-such-file" 2>/dev/null || true)"; fi # an unreadable cargo metadata is ALL, not an empty answer echo 'not json' >"$t/bad.json" row "unreadable metadata -> ALL" ALL "$(AFFECTED_META="$t/bad.json" bash "$A" --diff-from <(echo crates/a/src/lib.rs) 2>/dev/null)" From 8554ed31c8253c9be8572adebb15672a3421b004 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 05:12:18 +0200 Subject: [PATCH 19/24] fix(affected-set): a --diff-from with .. fails closed to ALL instead of rc 2 (Refs #5809) Co-Authored-By: Claude Sonnet 5.5 --- scripts/affected_set.sh | 6 +++--- scripts/check_affected_set.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index 1eaf445011..4b11232f85 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -42,14 +42,14 @@ while [ $# -gt 0 ]; do *) break ;; esac done -case "$from" in *..*) echo "affected_set: refusing a --diff-from with \"..\": $from" >&2; exit 2 ;; esac # bashrs SEC010 -if [ -n "$from" ]; then files="$(cat "$from")" || files="" -else files="$(git diff --name-only --no-renames "${1:-origin/main}...${2:-HEAD}")" || files=""; fi emit() { # $@ = package names, or ALL if [ "${1:-}" = ALL ]; then [ "$filter" = 1 ] && echo 'all()' || echo ALL; return; fi if [ "$filter" = 1 ]; then printf '%s\n' "$@" | sed 's/.*/package(&)/' | paste -sd'|' | sed 's/|/ | /g'; else printf '%s\n' "$@"; fi } [ "$bad" = 0 ] || { emit ALL; exit 0; } # --diff-from with no FILE +case "$from" in *..*) echo "affected_set: refusing a --diff-from with \"..\": $from" >&2; emit ALL; exit 0 ;; esac # bashrs SEC010: refuse the path (never read it), fail closed with ALL +if [ -n "$from" ]; then files="$(cat "$from")" || files="" +else files="$(git diff --name-only --no-renames "${1:-origin/main}...${2:-HEAD}")" || files=""; fi [ -n "$files" ] || { emit ALL; exit 0; } if [ -n "${AFFECTED_META:-}" ]; then meta="$(cat "$AFFECTED_META")" || meta="" else meta="$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null || cargo metadata --no-deps --format-version 1)" || meta=""; fi diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index 3098bcbe08..5591b54f0b 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -40,7 +40,7 @@ row "core crate -> ALL" ALL "$(run crates/aprender-core/src/lib.rs)" row "aprender-quant source -> ALL (aprender-serve capability.rs reads aprender-quant/src/ggml_type.rs)" ALL "$(run crates/aprender-quant/src/ggml_type.rs)" row "a crate's build.rs -> ALL (contracts build_helper.rs reads crates/*/build.rs)" ALL "$(run "$ld/build.rs")" row "apr-cli source -> ALL (aprender-core tests read apr-cli/src/commands_enum.rs)" ALL "$(run crates/apr-cli/src/commands_enum.rs)" -row "--diff-from with .. is refused (SEC010)" "2" "$($A --diff-from ../x >/dev/null 2>&1; echo $?)" +row "--diff-from with .. is refused and fails closed to ALL (SEC010)" "ALL:0" "$(o=$($A --diff-from ../x 2>/dev/null); echo "$o:$?")" row "Cargo.lock -> ALL" ALL "$(run Cargo.lock)" row "unknown path -> ALL" ALL "$(run some/unknown/path.txt)" row "unknown path next to a leaf -> ALL" ALL "$(run "$ld/src/lib.rs" scripts/whatever.sh)" From 8cfd74b1ef9518b2689e2aabf3e14bd62e6800e5 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 07:00:25 +0200 Subject: [PATCH 20/24] ci: affected-set guards - no pipe into grep -q, setsid wraps the self-test too, SEC010-clean mutant (Refs #5809) Co-Authored-By: Claude Sonnet 5.5 --- ci/sections.yml | 2 +- scripts/check_affected_readers.sh | 4 ++-- scripts/check_affected_set.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ci/sections.yml b/ci/sections.yml index eec0b24e08..b67bd7d778 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -1950,7 +1950,7 @@ jobs: - name: "Affected-set tool case table (FLOW-010 E5, RED rows and mutants)" run: setsid --wait bash scripts/check_affected_set.sh - name: "Affected-set readers guard (every crate that reads other crates' files is listed; RED on a new one)" - run: setsid --wait bash scripts/check_affected_readers.sh && bash scripts/check_affected_readers.sh --self-test + run: setsid --wait bash -c 'bash scripts/check_affected_readers.sh && bash scripts/check_affected_readers.sh --self-test' # #2640: the SAME defect class as the two steps above, one level up. The # dogfood release runner existed as two 1170-line copies whose divergence # was, in every hunk, a pin for a release verifier that one copy had and diff --git a/scripts/check_affected_readers.sh b/scripts/check_affected_readers.sh index d2230943eb..f65291ab32 100755 --- a/scripts/check_affected_readers.sh +++ b/scripts/check_affected_readers.sh @@ -27,9 +27,9 @@ build_inputs() { # $1 = crates dir; root contracts dir = ${CONTRACTS_DIR:-contra } check() { local bad="" l c; l="$(listed)" - for c in $(hits "$1"); do printf '%s\n' "$l" | grep -qx "$c" || bad="$bad $c"; done + for c in $(hits "$1"); do grep -qx "$c" <<<"$l" || bad="$bad $c"; done local bl; bl="$(build_listed)" - for c in $(build_inputs "$1"); do printf '%s\n' "$bl" | grep -qx "$c" || bad="$bad contracts/$c(BUILD_CONTRACTS)"; done + for c in $(build_inputs "$1"); do grep -qx "$c" <<<"$bl" || bad="$bad contracts/$c(BUILD_CONTRACTS)"; done if [ -n "$bad" ]; then echo "RED: crates can reach other crates' files but are not in READERS (scripts/affected_set.sh):$bad"; return 1; fi echo "ok: every crate with a path-escape form is in READERS" } diff --git a/scripts/check_affected_set.sh b/scripts/check_affected_set.sh index 5591b54f0b..2a2b3afe24 100755 --- a/scripts/check_affected_set.sh +++ b/scripts/check_affected_set.sh @@ -168,7 +168,7 @@ row "git rev-parse fails (not a checkout, rc 128) -> ALL" ALL "$(cd "$t/notgit" sed 's/ || root=""$//' "$A" >"$t/mut-root.sh" if cmp -s "$A" "$t/mut-root.sh"; then echo "FAIL root-fallback mutant did not apply"; fail=1 else row "MUTANT: dropping the rev-parse fallback is caught (prints nothing)" "" "$(cd "$t/notgit" && AFFECTED_META="$t/meta.json" bash "$t/mut-root.sh" --diff-from "$t/one" 2>/dev/null || true)"; fi -sed 's/files="\$(cat "\$from")" || files=""/files="$(cat "$from")"/' "$A" >"$t/mut-cat.sh" +sed 's/ || files=""$//' "$A" >"$t/mut-cat.sh" if cmp -s "$A" "$t/mut-cat.sh"; then echo "FAIL cat-fallback mutant did not apply"; fail=1 else row "MUTANT: dropping the missing-file fallback is caught (prints nothing)" "" "$(bash "$t/mut-cat.sh" --diff-from "$t/no-such-file" 2>/dev/null || true)"; fi # an unreadable cargo metadata is ALL, not an empty answer From ae952c8a38af18d969ddb8e586f15674b452c73a Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 07:43:37 +0200 Subject: [PATCH 21/24] ci: wrap each affected-readers guard invocation in setsid --wait (Refs #5809) Co-Authored-By: Claude Sonnet 5.5 --- ci/sections.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/sections.yml b/ci/sections.yml index b67bd7d778..ad895f4950 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -1950,7 +1950,7 @@ jobs: - name: "Affected-set tool case table (FLOW-010 E5, RED rows and mutants)" run: setsid --wait bash scripts/check_affected_set.sh - name: "Affected-set readers guard (every crate that reads other crates' files is listed; RED on a new one)" - run: setsid --wait bash -c 'bash scripts/check_affected_readers.sh && bash scripts/check_affected_readers.sh --self-test' + run: setsid --wait bash scripts/check_affected_readers.sh && setsid --wait bash scripts/check_affected_readers.sh --self-test # #2640: the SAME defect class as the two steps above, one level up. The # dogfood release runner existed as two 1170-line copies whose divergence # was, in every hunk, a pin for a release verifier that one copy had and From a871ed8ea2929d27255f6afcafb5e526e287a048 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 07:54:29 +0200 Subject: [PATCH 22/24] ci: drop duplicate 815 fragment and dead docs/findings arm in affected_set (Refs #5809) Co-Authored-By: Claude Sonnet 5.5 --- ci/explicit-test-commands.d/815-affected-set-rows.cmd | 2 -- scripts/affected_set.sh | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 ci/explicit-test-commands.d/815-affected-set-rows.cmd diff --git a/ci/explicit-test-commands.d/815-affected-set-rows.cmd b/ci/explicit-test-commands.d/815-affected-set-rows.cmd deleted file mode 100644 index 639899fb2a..0000000000 --- a/ci/explicit-test-commands.d/815-affected-set-rows.cmd +++ /dev/null @@ -1,2 +0,0 @@ -# FLOW-010 E5: affected-set tool rows (leaf+dependents vs cargo tree oracle, core/Cargo.lock/unknown -> ALL). -bash scripts/check_affected_set.sh diff --git a/scripts/affected_set.sh b/scripts/affected_set.sh index 4b11232f85..f9a8adb719 100755 --- a/scripts/affected_set.sh +++ b/scripts/affected_set.sh @@ -13,7 +13,7 @@ # any other path outside a crate ALL # docs/, contracts/, root *.md READERS only (FLOW-010 b): their run-time readers are all in READERS. Exceptions, ALL: # contracts binding.yaml, contracts model-families and the BUILD_CONTRACTS files (compiled into a crate) -# docs/findings/, .claude/, ci/shard-step-costs.tsv +# .claude/, ci/shard-step-costs.tsv # NO-READER list (#5809): the only run-time readers of these paths (grep of crates/**/*.rs: contracts-cli, # orchestrate, core, mcp, apr-cli) are all in READERS, so the set is READERS alone. Alone or # beside crate .rs files; any other path in the diff still decides ALL. @@ -69,7 +69,7 @@ while IFS= read -r f; do '') continue ;; contracts/binding.yaml | contracts/*/binding.yaml | contracts/model-families/* | contracts/*/model-families/*) emit ALL; exit 0 ;; # build inputs of core/compute/contracts/simulate/train: their generated code reaches dependents contracts/* | docs/*) skipped=1; continue ;; # FLOW-010(b): run-time readers are in READERS - docs/findings/* | .claude/* | ci/shard-step-costs.tsv) skipped=1; continue ;; + .claude/* | ci/shard-step-costs.tsv) skipped=1; continue ;; crates/*/build.rs) emit ALL; exit 0 ;; crates/*.rs) ;; crates/*) emit ALL; exit 0 ;; From 898f0946ce6299cfa473bc3d2a8553e7945086dd Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 08:04:58 +0200 Subject: [PATCH 23/24] ci: affected-set guards move from the PR path to the guards-nightly LAB lane (Refs #5809) Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/guards-nightly.yml | 9 +++++++++ ci/sections.yml | 5 ----- scripts/guards_nightly_manifest.txt | 2 ++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/guards-nightly.yml b/.github/workflows/guards-nightly.yml index af5fe6b1d5..e87ce19932 100644 --- a/.github/workflows/guards-nightly.yml +++ b/.github/workflows/guards-nightly.yml @@ -349,6 +349,15 @@ jobs: exec > >(tee -a "$RUNNER_TEMP/census.log") 2>&1 setsid --wait bash scripts/census_ratchet.sh --self-test CENSUS_BASE_REF="${RATCHET_24H_BASE:?no 24 h comparand (#5182)}" setsid --wait bash scripts/census_ratchet.sh + # #5809 (FLOW-010 E5): the affected-set tool's case tables. LAB, never on a PR path (C324 rules 5/7): + # the tool is not consumed by any gate yet, so a red here opens nothing that can block a merge. + - name: Affected-set tool and readers guards (FLOW-010 E5, #5809) + if: ${{ !cancelled() }} + run: | + set -euo pipefail + setsid --wait bash scripts/check_affected_set.sh + setsid --wait bash scripts/check_affected_readers.sh + setsid --wait bash scripts/check_affected_readers.sh --self-test - name: A red census night opens or updates the one LAB ticket (#4709) if: failure() && steps.ratchet_census.outcome == 'failure' env: diff --git a/ci/sections.yml b/ci/sections.yml index ad895f4950..43ca4364e3 100644 --- a/ci/sections.yml +++ b/ci/sections.yml @@ -1946,11 +1946,6 @@ jobs: run: setsid --wait bash scripts/check_lockfile_current.sh - name: "lockfile guard must still turn RED (case table)" run: setsid --wait bash scripts/check_lockfile_current.sh --self-test - # FLOW-010 E5: runs cargo metadata/tree, so it lives in the cargo job, not guard-tree-steps. - - name: "Affected-set tool case table (FLOW-010 E5, RED rows and mutants)" - run: setsid --wait bash scripts/check_affected_set.sh - - name: "Affected-set readers guard (every crate that reads other crates' files is listed; RED on a new one)" - run: setsid --wait bash scripts/check_affected_readers.sh && setsid --wait bash scripts/check_affected_readers.sh --self-test # #2640: the SAME defect class as the two steps above, one level up. The # dogfood release runner existed as two 1170-line copies whose divergence # was, in every hunk, a pin for a release verifier that one copy had and diff --git a/scripts/guards_nightly_manifest.txt b/scripts/guards_nightly_manifest.txt index 423ddca459..8f15c9fb35 100644 --- a/scripts/guards_nightly_manifest.txt +++ b/scripts/guards_nightly_manifest.txt @@ -49,3 +49,5 @@ # #4709 (C303): the pv census ratchet, LAB from its first night (never on a PR path, C324). # Seconds are a local wall-clock sample (2026-10-14, pv prebuilt); the first nightly replaces them. 6 The pv census bound share may only rise (#4709) +# #5809 (FLOW-010 E5): affected-set tool case tables, LAB from the first night (never on a PR path, C324). Seconds are a local sample. +40 Affected-set tool and readers guards (FLOW-010 E5, #5809) From 5094be196094b6007083824bb6a8b4b0ed2f5029 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 21 Oct 2026 08:25:45 +0200 Subject: [PATCH 24/24] ci: affected-set LAB step gets its one-ticket opener (C324 rule 5) (Refs #5809) Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/guards-nightly.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/guards-nightly.yml b/.github/workflows/guards-nightly.yml index e87ce19932..6201af784c 100644 --- a/.github/workflows/guards-nightly.yml +++ b/.github/workflows/guards-nightly.yml @@ -350,14 +350,26 @@ jobs: setsid --wait bash scripts/census_ratchet.sh --self-test CENSUS_BASE_REF="${RATCHET_24H_BASE:?no 24 h comparand (#5182)}" setsid --wait bash scripts/census_ratchet.sh # #5809 (FLOW-010 E5): the affected-set tool's case tables. LAB, never on a PR path (C324 rules 5/7): - # the tool is not consumed by any gate yet, so a red here opens nothing that can block a merge. + # the tool is not consumed by any gate yet; a red opens or updates the one LAB ticket below, never a block. - name: Affected-set tool and readers guards (FLOW-010 E5, #5809) + id: affected_set if: ${{ !cancelled() }} run: | set -euo pipefail + exec > >(tee -a "$RUNNER_TEMP/affected.log") 2>&1 setsid --wait bash scripts/check_affected_set.sh setsid --wait bash scripts/check_affected_readers.sh setsid --wait bash scripts/check_affected_readers.sh --self-test + - name: A red affected-set night opens or updates the one LAB ticket (#5809) + if: failure() && steps.affected_set.outcome == 'failure' + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + bash scripts/lab_ticket.sh --label lab-affected-set --title "LAB: nightly affected-set guards red (#5809)" \ + --owner aprender-w5018 --log "$RUNNER_TEMP/affected.log" --event "$GITHUB_EVENT_NAME" --run-url "$RUN_URL" - name: A red census night opens or updates the one LAB ticket (#4709) if: failure() && steps.ratchet_census.outcome == 'failure' env: