Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -1954,6 +1954,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 grep -qF "/crates/$d/" <<<"$m"; 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 grep -qx "$c" <<<"$l" || 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