Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
59a35c5
feat(flow-010): affected_set.sh - diff to crates + reverse-dep closur…
noahgift Oct 20, 2026
ad30656
fix(flow-010): empty diff and bad base are ALL; pin dev-dep counting …
noahgift Oct 20, 2026
dea7901
fix(flow-010): affected_set --no-renames and include_str!/include_byt…
noahgift Oct 20, 2026
5f272c8
fix(e5): wire check_affected_set.sh, SC1087 and SEC011 in the affecte…
noahgift Oct 20, 2026
1e73c59
fix(e5): docs, root *.md and ci/ select ALL; a crate Cargo.toml selec…
noahgift Oct 20, 2026
2ae2e4e
fix(e5): any non-.rs file under crates/ selects ALL; move the check t…
noahgift Oct 20, 2026
cf174b8
fix(e5): refuse '..' in --diff-from (bashrs SEC010); apr-cli is CORE …
noahgift Oct 20, 2026
132b80d
fix(e5): aprender-quant is CORE; any crates/*/build.rs selects ALL (r…
noahgift Oct 20, 2026
2308a7c
fix(affected-set): every non-ALL set carries aprender-core; empty tou…
noahgift Oct 20, 2026
29b1f3e
fix(affected-set): READERS list (crates whose tests read other crates…
noahgift Oct 20, 2026
99fc513
fix(affected-set): readers guard over-approximates path escapes; READ…
noahgift Oct 20, 2026
64602c8
fix(affected-set): oracle readers filter without a bashrs SC2135 fals…
noahgift Oct 20, 2026
6c53b54
Merge branch 'main' into work/e5-affected-set
noahgift Oct 21, 2026
e823787
fix(ci): affected-readers guard catches pop/push, ParentDir, show-top…
noahgift Oct 21, 2026
8dcadf5
fix(ci): readers guard catches rustfmt-split PathBuf pop/push; fails …
noahgift Oct 21, 2026
29d1072
fix(ci): readers guard: file-level CARGO_MANIFEST_DIR + .pop( rule re…
noahgift Oct 21, 2026
28a9312
fix(ci): setsid --wait on the readers self-test step (#4133); spaced …
noahgift Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/explicit-test-commands.d/815-affected-set-rows.cmd
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions ci/sections.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1951,6 +1951,11 @@ 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
Expand Down
77 changes: 77 additions & 0 deletions scripts/affected_set.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/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/<pkg>/... 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)
# Any non-.rs file under crates/<pkg>/ (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
# out of a crate would otherwise show only the destination and the source crate would be skipped).
# --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 = 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 (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/, ../<crate>). 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-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}"
filter=0 from=""
while [ $# -gt 0 ]; do
case "$1" in
--filter) filter=1; shift ;;
--diff-from) 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")"
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
}
[ -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")" || { emit ALL; exit 0; }
[ -n "$dirs" ] || { emit ALL; exit 0; }
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
# 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" ] || { 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 (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(.))) + ($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[@]}"
88 changes: 88 additions & 0 deletions scripts/check_affected_readers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# 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
SET="${AFFECTED_SET_SH:-scripts/affected_set.sh}"
# 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 <root>/tokenizer.json and <root>/models/
# crates/ dirs outside the workspace (excluded): a change there is unplaceable -> ALL, so they need no READERS entry
NONMEMBER="${AFFECTED_NONMEMBER:-facades}"
PAT='"\.\."|"[^"]*(\.\./|/\.\.)[^"]*"|\bparent[[:space:]]*\(|Path::parent\(|Component::ParentDir|show-toplevel|\.pop\(\);[^;]*\.push\(|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
# file-level: CARGO_MANIFEST_DIR and .pop( anywhere in one file (rustfmt may wrap the let or split pop/push across lines)
for f in $(grep -rlE --include='*.rs' 'CARGO_MANIFEST_DIR' "$1" 2>/dev/null || true); do
grep -qE '\.pop[[:space:]]*\(' "$f" && echo "$f:0:pop-with-MANIFEST_DIR"
done; } \
| grep -vE '^[^:]*/target/' | grep -vE '^[^:]+:[0-9]+:[[:space:]]*//' \
| awk -F: -v allow=" $ALLOW " -v nm=" $NONMEMBER " '{ k = $1 ":" $2; if (index(allow, " " k " ")) next; n = split($1, a, "/"); if (index(nm, " " a[2] " ")) next; print a[2] }' | sort -u
}
nonmember_in_workspace() { # a NONMEMBER dir that cargo metadata lists is a member: its exemption would hide a reader
local m d
m="$({ if [ -n "${AFFECTED_META_JSON:-}" ]; then cat "$AFFECTED_META_JSON"; else cargo metadata --no-deps --format-version 1 --offline 2>/dev/null; fi; } || true)"
[ -n "$m" ] || return 0
for d in $NONMEMBER; do
if printf '%s' "$m" | grep -qF "/crates/$d/"; then echo "RED: NONMEMBER dir crates/$d is a workspace member (cargo metadata lists it): drop it from NONMEMBER"; return 1; fi
done
}
check() {
nonmember_in_workspace || return 1
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
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/ */'
expect_red 'PathBuf pop then push' zz-p 'let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); p.pop(); p.push("aprender-core");'
expect_red 'Component::ParentDir' zz-q 'let _ = Component::ParentDir;'
expect_red 'git rev-parse --show-toplevel' zz-r 'let _ = Command::new("git").args(["rev-parse", "--show-toplevel"]);'
expect_red 'Path::parent(x)' zz-s 'let _ = Path::parent(&here);'
expect_red '.parent ()' zz-t 'let _ = here.parent ();'
expect_red 'a non-member dir is only exempt when named' facades-x 'let _ = here.ancestors();'
expect_red 'rustfmt-wrapped let + split pop/push' zz-u $'let mut m =\n PathBuf::from(env!("CARGO_MANIFEST_DIR"));\n m.pop();\n m.push("aprender-core");'
expect_red 'spaced .pop (' zz-w $'let mut m = PathBuf::from(env!("CARGO_MANIFEST_DIR"));\n m.pop ();'
printf '{"packages":[{"manifest_path":"/x/crates/facades/zz-evil/Cargo.toml"}]}' >"$t/meta.json"; fresh zz-v
if (AFFECTED_META_JSON="$t/meta.json" check crates >/dev/null 2>&1); then echo "FAIL self-test: a NONMEMBER dir listed by cargo metadata passed"; fail=1; fi
# 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 "${1:-crates}"
Loading
Loading