facett-core/src/law.rs
//! **ROOT LAW #0 — the rayon-free law**, and the machinery that can actually
//! report RED on it. Shared so korp and facett hold the *same* line from *one*
//! writer (LAW 5) instead of two drifting copies of a grep.
//!
//! # Why this module exists
//!
//! `znippy-zoomies/tests/rayon_free_law.rs` has guarded the law for the codec
//! tree for months. korp and facett had **no gate at all**: a `rayon = "1"` in
//! `facett-map/Cargo.toml` would have passed every test in both repos.
//!
//! # The hard part: which rayon?
//!
//! A naive lockfile grep is worse than no gate, because it is red on day one and
//! stays red forever. Measured on 2026-08-03, `Cargo.lock` in **both** repos
//! contains `rayon 1.12.0` — pulled in like this:
//!
//! ```text
//! rayon ← av-scenechange ← rav1e ← ravif ← image ← dify ← egui_kittest
//! ← nornir-robotui "snapshot" [dev-dependencies] ← korp / facett-*
//! ```
//!
//! Nobody chose that. It is the AVIF encoder inside the image crate that the
//! screenshot-diff crate inside the robot-UI harness happens to enable. LAW 3
//! forbids **us** using rayon; it does not forbid a third-party image decoder in
//! the fourth ring from using it in a test harness. A gate that goes red on that
//! edge gets switched off within a week, and a gate that ignores the whole axis
//! cannot see the day it stops being test-only.
//!
//! So this module separates **three** things and says which one it tripped on:
//!
//! 1. [`scan_source`] — **our own code** calling rayon. Always red.
//! 2. [`scan_manifests`] — **our own manifests** declaring rayon, in any
//! dependency table. Always red.
//! 3. [`shipped_rayon_tree`] — a **normal (non-dev, non-build) dependency
//! edge** that reaches rayon, i.e. rayon linked into something we ship.
//! Red. [`unshipped_rayon_chain`] finds the dev/build edge and is reported as
//! *context*, never as a failure.
//!
//! Measured 2026-08-03 with `cargo tree -e normal -i rayon --target all`:
//! "nothing to print" in korp and in facett. Check 3 is therefore **green
//! today** and still able to go red — which is the only kind of green worth
//! having (LAW 2).
//!
//! It is one feature flag away from red, and that is the point of keeping it:
//! `image` **is** already a normal dependency of korp (via `eframe`→`arboard`,
//! and via `facett-about`), just without its `avif` feature. Turning `avif` on
//! anywhere in the graph puts `rayon` in the shipped korp binary. Under
//! `--all-features` that is exactly what happens, measured on the same day —
//! which is why [`rayon_tree_all_features_chain`] exists: check 3 demonstrates its own
//! red on **every run** instead of asserting that it still could.
//!
//! # Which instrument decides which axis
//!
//! Check 3 reads `cargo tree`, **not** `cargo metadata`. The first cut of this
//! module decided it from the metadata resolve, and pointed at korp that resolve
//! reported a violation — `korp -> image -> rayon` — that does not exist:
//! `cargo metadata` unifies dev-dependency features into one graph, so `image`
//! appears with `avif` on and its `ravif → rayon` edge looks normal. The
//! dependency-KIND walk was right; the FEATURE resolution under it was not, and
//! it errs towards inventing violations. `cargo metadata` is kept for kind
//! attribution and for the dev-route context chain, where over-approximating
//! costs nothing. See [`overapproximated_normal_chain`].
//!
//! # Self-honesty
//!
//! The forbidden tokens below are built with [`concat!`] so this file does not
//! contain them literally. The scanner therefore covers itself — there is no
//! path carve-out for the guard, which is how a guard quietly stops guarding.
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
use std::process::Command;
/// One offending line, located.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Hit {
/// Absolute path of the file the hit is in.
pub file: PathBuf,
/// 1-based line number.
pub line: usize,
/// The offending token that matched.
pub token: String,
/// The source line, trimmed (comments already stripped).
pub text: String,
}
impl std::fmt::Display for Hit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}: `{}` — {}", self.file.display(), self.line, self.token, self.text)
}
}
/// Tokens that can only appear when rayon is actually being **used**. Method
/// calls are matched with their leading `.` and trailing `(` so an unrelated
/// identifier such as `par_sorted` is not a false positive.
///
/// Written through [`concat!`] so the constant does not match itself — this file
/// is scanned like every other (see the module docs).
fn rayon_call_tokens() -> Vec<String> {
[
concat!("use ", "rayon"),
concat!("extern crate ", "rayon"),
concat!("rayon", "::"),
concat!(".", "par_iter("),
concat!(".", "into_par_iter("),
concat!(".", "par_iter_mut("),
concat!(".", "par_bridge("),
concat!(".", "par_chunks("),
concat!(".", "par_chunks_mut("),
concat!(".", "par_sort("),
concat!(".", "par_sort_by("),
concat!(".", "par_sort_unstable("),
concat!(".", "par_extend("),
concat!("Parallel", "Iterator"),
concat!("IntoParallel", "Iterator"),
concat!("ThreadPool", "Builder"),
]
.iter()
.map(|s| (*s).to_string())
.collect()
}
/// The crate name the manifest scan looks for as a dependency key.
fn rayon_dep_key() -> String {
concat!("ray", "on").to_string()
}
/// Strip a `//`-to-end-of-line comment so only live code is scanned. The repos
/// are full of prose *about* the law (`// never rayon here`); commentary is not
/// a violation of it.
fn strip_line_comment(line: &str) -> &str {
match line.find("//") {
Some(i) => &line[..i],
None => line,
}
}
/// Walk up from `start` to the outermost directory whose `Cargo.toml` declares a
/// `[workspace]`. Handles both shapes in this tree: facett's virtual root and
/// korp's `[workspace]`-plus-`[package]` root.
pub fn workspace_root(start: &Path) -> PathBuf {
let mut found = start.to_path_buf();
let mut cursor = Some(start);
while let Some(dir) = cursor {
let manifest = dir.join("Cargo.toml");
if std::fs::read_to_string(&manifest)
.map(|text| text.lines().any(|l| l.trim() == "[workspace]"))
.unwrap_or(false)
{
found = dir.to_path_buf();
}
cursor = dir.parent();
}
found
}
/// Every `Cargo.toml` this workspace owns: the root, plus one per member listed
/// in the root `members = [...]` array, plus one per `exclude = [...]` leaf.
///
/// The excluded leaves are ours too. korp's root manifest excludes
/// `crates/korp-ontology`, `crates/korp-embed` and `crates/korp-demo` because
/// they carry their own empty `[workspace]` tables — and korp *depends* on
/// `korp-demo` by path, so a `rayon = "1"` in that leaf ships. Reading only the
/// members would have left first-party manifests and first-party `src/`
/// unscanned while the count still looked healthy.
pub fn workspace_manifests(root: &Path) -> Vec<PathBuf> {
let mut out = vec![root.join("Cargo.toml")];
for member in first_party_dirs(root) {
let m = root.join(&member).join("Cargo.toml");
if m.exists() {
out.push(m);
}
}
out
}
/// The `members = [...]` entries of the root manifest. Tolerates the array being
/// written on one line or spread over many.
pub fn workspace_members(root: &Path) -> Vec<String> {
manifest_string_array(root, "members")
}
/// The `exclude = [...]` entries of the root manifest — self-rooting leaves that
/// live in this repo and are therefore first-party even though cargo does not
/// call them members.
pub fn workspace_excludes(root: &Path) -> Vec<String> {
manifest_string_array(root, "exclude")
}
/// Members and excluded leaves together: every directory in this repo that holds
/// a package of ours.
pub fn first_party_dirs(root: &Path) -> Vec<String> {
let mut out = workspace_members(root);
for e in workspace_excludes(root) {
if !out.contains(&e) {
out.push(e);
}
}
out
}
/// Read a `key = [ "a", "b" ]` array out of the root manifest's `[workspace]`
/// table.
///
/// Two things this does that the first version did not, both of them defects
/// that were found by pointing the guard at korp:
///
/// 1. **It anchors on a LINE, not on the first occurrence of the word.** korp's
/// root manifest says "are NOT pulled in as members" in its header comment
/// three lines above `[workspace]`. A bare `text.find("members")` landed
/// there, then took `[workspace]`'s own brackets as the array and returned
/// `["workspace"]` — one member that does not exist and none of the real
/// ones. The manifest scan read 1 file where it should read 2.
/// 2. **It stays inside `[workspace]`.** `exclude` is also a `[package]` key
/// (packaging excludes), and korp's root manifest carries both tables.
///
/// Comments are `#` here, not `//` — this is TOML, and an entry may be followed
/// by a trailing `# why`.
fn manifest_string_array(root: &Path, key: &str) -> Vec<String> {
let Ok(text) = std::fs::read_to_string(root.join("Cargo.toml")) else {
return Vec::new();
};
let mut cursor = 0usize;
let mut start = None;
let mut in_workspace = false;
for line in text.split_inclusive('\n') {
let trimmed = line.trim_start();
if trimmed.starts_with('[') {
in_workspace = trimmed.starts_with("[workspace]");
} else if in_workspace
&& let Some(rest) = trimmed.strip_prefix(key)
&& rest.trim_start().starts_with('=')
{
start = Some(cursor + (line.len() - trimmed.len()));
break;
}
cursor += line.len();
}
let Some(start) = start else { return Vec::new() };
// Strip `#`-to-end-of-line on every line BEFORE locating the brackets, so a
// trailing `"a", # the first one` cannot swallow the rest of the array. The
// first cut of this function stripped per comma-separated piece instead, and
// its own new test caught it eating `"b"`.
let rest: String = text[start..]
.lines()
.map(|l| l.split('#').next().unwrap_or(""))
.collect::<Vec<_>>()
.join("\n");
let Some(open) = rest.find('[') else { return Vec::new() };
let Some(close) = rest[open..].find(']') else { return Vec::new() };
rest[open + 1..open + close]
.split(',')
.map(|s| s.trim().trim_matches('"').to_string())
.filter(|s| !s.is_empty())
.collect()
}
/// Every directory of ours that holds first-party Rust: each member's `src`,
/// `tests`, `benches` and `examples`, plus the root package's own if the root
/// manifest carries a `[package]`. `target/` is never walked.
pub fn source_roots(root: &Path) -> Vec<PathBuf> {
const SUBDIRS: &[&str] = &["src", "tests", "benches", "examples"];
let mut dirs: Vec<PathBuf> = Vec::new();
let push_for = |base: PathBuf, dirs: &mut Vec<PathBuf>| {
for sub in SUBDIRS {
let p = base.join(sub);
if p.is_dir() {
dirs.push(p);
}
}
};
push_for(root.to_path_buf(), &mut dirs);
for member in first_party_dirs(root) {
push_for(root.join(member), &mut dirs);
}
dirs
}
fn rs_files(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(rd) = std::fs::read_dir(dir) else { return };
for entry in rd.flatten() {
let path = entry.path();
if path.is_dir() {
if path.file_name().map(|n| n == "target").unwrap_or(false) {
continue;
}
rs_files(&path, out);
} else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
out.push(path);
}
}
}
/// What a source scan found, plus how much it looked at — a scan that found
/// nothing because it read nothing is the hollow green this whole module is
/// about, so the count travels with the verdict.
#[derive(Debug, Clone, Default)]
pub struct Scan {
/// Offending lines.
pub hits: Vec<Hit>,
/// How many files were actually read.
pub scanned: usize,
}
/// Scan every first-party `.rs` file under `root` for live rayon usage.
pub fn scan_source(root: &Path) -> Scan {
let tokens = rayon_call_tokens();
let mut files = Vec::new();
for dir in source_roots(root) {
rs_files(&dir, &mut files);
}
let mut hits = Vec::new();
for file in &files {
let Ok(text) = std::fs::read_to_string(file) else { continue };
for (i, raw) in text.lines().enumerate() {
let code = strip_line_comment(raw);
for tok in &tokens {
if code.contains(tok.as_str()) {
hits.push(Hit {
file: file.clone(),
line: i + 1,
token: tok.clone(),
text: code.trim().to_string(),
});
}
}
}
}
Scan { hits, scanned: files.len() }
}
/// Scan every first-party `Cargo.toml` for a `rayon` dependency key, in any
/// dependency table (`[dependencies]`, `[dev-dependencies]`,
/// `[build-dependencies]`, `[target.'…'.dependencies]`,
/// `[workspace.dependencies]`). Comments are stripped: a manifest may freely
/// explain *why* rayon is banned.
pub fn scan_manifests(root: &Path) -> Scan {
let key = rayon_dep_key();
let manifests = workspace_manifests(root);
let mut hits = Vec::new();
for manifest in &manifests {
let Ok(text) = std::fs::read_to_string(manifest) else { continue };
let mut in_deps = false;
for (i, raw) in text.lines().enumerate() {
let line = raw.trim();
if line.starts_with('[') {
in_deps = line.contains("dependencies]");
continue;
}
if !in_deps {
continue;
}
let code = strip_line_comment(line);
let name = code.split('=').next().unwrap_or("").trim().trim_matches('"');
if name == key {
hits.push(Hit {
file: manifest.clone(),
line: i + 1,
token: key.clone(),
text: code.trim().to_string(),
});
}
}
}
Scan { hits, scanned: manifests.len() }
}
/// Run `cargo metadata` for `root` and return the parsed document.
///
/// `--offline --locked` on purpose: the guard must never mutate `Cargo.lock`
/// (LAW 6) and must never reach the network from a test. A stale lock is a hard
/// error, not a skip — a guard that skips itself is the disease.
pub fn cargo_metadata(root: &Path) -> Result<serde_json::Value, String> {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let out = Command::new(cargo)
.args(["metadata", "--format-version", "1", "--offline", "--locked"])
.current_dir(root)
.output()
.map_err(|e| format!("could not run `cargo metadata` in {}: {e}", root.display()))?;
if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr);
return Err(format!(
"`cargo metadata --offline --locked` failed in {} ({}):\n{}",
root.display(),
out.status,
stderr.lines().take(8).collect::<Vec<_>>().join("\n"),
));
}
serde_json::from_slice(&out.stdout).map_err(|e| format!("cargo metadata is not JSON: {e}"))
}
/// Package-id → package-name index from a `cargo metadata` document.
fn id_to_name(md: &serde_json::Value) -> std::collections::HashMap<String, String> {
md["packages"]
.as_array()
.map(|ps| {
ps.iter()
.filter_map(|p| {
Some((p["id"].as_str()?.to_string(), p["name"].as_str()?.to_string()))
})
.collect()
})
.unwrap_or_default()
}
/// Which dependency kinds an edge carries. `cargo metadata` spells a normal
/// dependency as `kind: null`.
fn edge_kinds(dep: &serde_json::Value) -> Vec<String> {
dep["dep_kinds"]
.as_array()
.map(|ks| {
ks.iter()
.map(|k| k["kind"].as_str().unwrap_or("normal").to_string())
.collect()
})
.unwrap_or_default()
}
/// Breadth-first walk from the workspace members to `target_name`.
///
/// `require_non_normal` is what keeps the two answers from collapsing into one:
/// with it set, a chain only counts if it crosses at least one `dev` or `build`
/// edge, so "the dev-only route" cannot be silently answered with the shipped
/// route. The BFS state therefore carries that flag, and a package is re-visited
/// once per flag value.
///
/// Returns the shortest qualifying chain of package names, workspace member
/// first.
fn chain_to(
md: &serde_json::Value,
target_name: &str,
require_non_normal: bool,
) -> Option<Vec<String>> {
let names = id_to_name(md);
let nodes: std::collections::HashMap<&str, &serde_json::Value> = md["resolve"]["nodes"]
.as_array()?
.iter()
.filter_map(|n| Some((n["id"].as_str()?, n)))
.collect();
let members: Vec<&str> =
md["workspace_members"].as_array()?.iter().filter_map(|m| m.as_str()).collect();
let mut seen: std::collections::HashSet<(&str, bool)> = std::collections::HashSet::new();
let mut queue: VecDeque<(&str, bool, Vec<String>)> = VecDeque::new();
for m in members {
if seen.insert((m, false)) {
let label = names.get(m).cloned().unwrap_or_else(|| m.to_string());
queue.push_back((m, false, vec![label]));
}
}
while let Some((id, crossed, path)) = queue.pop_front() {
if names.get(id).map(|n| n == target_name).unwrap_or(false)
&& path.len() > 1
&& (crossed || !require_non_normal)
{
return Some(path);
}
let Some(node) = nodes.get(id) else { continue };
let Some(deps) = node["deps"].as_array() else { continue };
for dep in deps {
let kinds = edge_kinds(dep);
// A single dependency can carry several kinds at once (normal AND
// dev). Take the cheapest interpretation: if it is normal at all,
// the shipped walk may use it without crossing.
let has_normal = kinds.iter().any(|k| k == "normal");
let has_other = kinds.iter().any(|k| k == "dev" || k == "build");
let Some(pkg) = dep["pkg"].as_str() else { continue };
// Which flag values this edge can hand on. The shipped walk only
// ever follows a normal edge and never sets the flag, so it can
// never answer with a dev route.
let mut onward: Vec<bool> = Vec::new();
if has_normal {
onward.push(crossed);
}
if require_non_normal && has_other {
onward.push(true);
}
for next_crossed in onward {
if !seen.insert((pkg, next_crossed)) {
continue;
}
let mut next = path.clone();
next.push(names.get(pkg).cloned().unwrap_or_else(|| pkg.to_string()));
queue.push_back((pkg, next_crossed, next));
}
}
}
None
}
/// A **normal** dependency path from this workspace to rayon, as `cargo
/// metadata` sees it.
///
/// **Do not decide the shipped axis with this.** It over-approximates and it was
/// measured doing so: pointed at korp on 2026-08-03 it reported
/// `korp -> image -> rayon` while `cargo tree -e normal -i rayon --target all`
/// said "nothing to print". `cargo metadata`'s resolve unifies
/// dev-dependency features into one graph, so `image` shows up with `avif` on
/// and its `ravif -> rayon` edge looks normal. The dependency-KIND walk under
/// this function is right; the FEATURE resolution beneath it is not, and it errs
/// in the direction that invents violations. Kept for kind attribution and for
/// [`unshipped_rayon_chain`] (where over-approximating a *context* chain is
/// harmless); the verdict belongs to [`shipped_rayon_tree`].
pub fn overapproximated_normal_chain(md: &serde_json::Value) -> Option<Vec<String>> {
chain_to(md, &rayon_dep_key(), false)
}
/// A path to rayon that crosses at least one **dev** or **build** edge: real,
/// known, and **not** a violation — third-party test-harness weight, nothing we
/// chose and nothing that ships. Reported as context so the gate can say which
/// of the two axes it tripped on, and used as the walker's own liveness check.
pub fn unshipped_rayon_chain(md: &serde_json::Value) -> Option<Vec<String>> {
chain_to(md, &rayon_dep_key(), true)
}
// ───────────────────────── the shipped verdict, from cargo tree ──────────────
//
// `cargo tree -e normal -i rayon --target all` is the command that was actually
// measured on 2026-08-03, and it is the one that resolves features the way a
// build does. Everything below reads ITS output. `cargo metadata` stays where it
// belongs: dependency-kind attribution and the dev-route context chain.
/// Run `cargo tree -i <crate> --target all -e <edges>` in `root` and hand back
/// stdout.
///
/// `--offline --locked` for the same reason [`cargo_metadata`] uses them: a
/// guard must never mutate `Cargo.lock` (LAW 6) and never reach the network. A
/// stale lock is a hard error, not a skip.
///
/// `extra` carries the feature selection — `["--all-features"]`,
/// `["--no-default-features", "--features", "robot-watch"]`, or nothing for the
/// default shape. It is a parameter and not a bool because **the default shape
/// is not the only shape that ships**: measured on oden 2026-08-03, korp is
/// clean at default features and at five of its six single-feature builds, and
/// `--no-default-features --features robot-watch` puts rayon on a NORMAL edge —
/// and that is the build `.nornir/ingest-tab-app-robot.md:27` tells people to
/// make. A gate that only ever asks about the default answer is blind to
/// exactly the binary the documentation recommends.
pub fn rayon_tree(root: &Path, edges: &str, extra: &[&str]) -> Result<String, String> {
dep_tree(root, &rayon_dep_key(), edges, extra)
}
/// [`rayon_tree`], generalized to ANY banned crate — the ONE `cargo tree -i`
/// runner every law shares (LAW 5). LAW 3's guard asks it about rayon; LAW 4's
/// (`webgl_free_law.rs`, here and in korp) asks it about `glow` / `khronos-egl`,
/// with the identical `--offline --locked --target all` discipline and the same
/// "stderr is part of the answer" contract.
pub fn dep_tree(root: &Path, krate: &str, edges: &str, extra: &[&str]) -> Result<String, String> {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let mut cmd = Command::new(cargo);
cmd.args(["tree", "-i", krate, "--target", "all", "-e", edges])
.args(["--offline", "--locked"])
.args(extra)
.current_dir(root);
let out = cmd
.output()
.map_err(|e| format!("could not run `cargo tree` in {}: {e}", root.display()))?;
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
if !out.status.success() {
return Err(format!(
"`cargo tree -e {edges} -i {krate} --target all {}` failed in {} ({}):\n{}",
extra.join(" "),
root.display(),
out.status,
stderr.lines().take(8).collect::<Vec<_>>().join("\n"),
));
}
// Exit code is not the answer here. `cargo tree` says "nothing to print" on
// stderr and exits 0, and a command that failed to name a crate at all would
// also exit 0 with empty stdout. The caller gets stderr folded in so
// "nothing to print" is a fact it can read, not an absence it must guess at.
Ok(if stdout.trim().is_empty() { stderr } else { stdout })
}
/// Did `cargo tree -i` find no path at all? True for the "nothing to print"
/// notice and for genuinely empty output.
pub fn tree_found_nothing(out: &str) -> bool {
out.trim().is_empty() || out.contains("nothing to print")
}
/// The chain `cargo tree -i` drew, from the first package of OURS back up to
/// rayon.
///
/// `cargo tree -i` prints the inverted tree: rayon at depth 0, its dependents
/// below it. A package is ours when the path cargo printed in parentheses is
/// under `root`. Returns e.g.
/// `["korp", "eframe", "egui-winit", "arboard", "image", "ravif", "rav1e",
/// "av-scenechange", "rayon"]`.
///
/// `None` means the tree named nothing of ours — either it is empty (clean) or
/// the format changed, and the caller must not read those two as the same thing.
pub fn tree_chain_to_first_party(out: &str, root: &Path) -> Option<Vec<String>> {
let here = format!("({}", root.display());
let mut stack: Vec<String> = Vec::new();
for line in out.lines() {
// The tree prefix is drawn from these five characters, four per level.
let prefix: String =
line.chars().take_while(|c| matches!(c, '│' | ' ' | '├' | '└' | '─')).collect();
let body = &line[prefix.len()..];
if body.is_empty() || body.starts_with('[') {
// `[dev-dependencies]` / `[build-dependencies]` markers carry no
// package of their own.
continue;
}
let depth = prefix.chars().count() / 4;
let Some(name) = body.split_whitespace().next().map(str::to_string) else { continue };
stack.truncate(depth);
stack.push(name);
if body.contains(&here) {
let mut chain = stack.clone();
chain.reverse();
return Some(chain);
}
}
None
}
/// The verdict: is rayon reachable from this workspace over **normal** edges?
///
/// `Ok(None)` is clean. `Ok(Some(chain))` is a LAW 3 violation on a shipped
/// edge.
pub fn shipped_rayon_tree(root: &Path) -> Result<Option<Vec<String>>, String> {
shipped_rayon_tree_with(root, &[])
}
/// [`shipped_rayon_tree`] for ONE feature selection — the build shape is a
/// parameter, because rayon can sit behind a feature and in korp it does.
///
/// Pass `&[]` for the default shape, or e.g.
/// `&["--no-default-features", "--features", "server"]`.
pub fn shipped_rayon_tree_with(
root: &Path,
features: &[&str],
) -> Result<Option<Vec<String>>, String> {
let out = rayon_tree(root, "normal", features)?;
if tree_found_nothing(&out) {
return Ok(None);
}
Ok(Some(tree_chain_to_first_party(&out, root).unwrap_or_else(|| {
out.lines().map(str::trim).filter(|l| !l.is_empty()).map(str::to_string).collect()
})))
}
/// The `--no-default-features --features <name>` argv for one feature.
pub fn only_feature(name: &str) -> Vec<String> {
vec!["--no-default-features".into(), "--features".into(), name.into()]
}
/// The same instrument and the same parser, asked a question this tree is
/// **known** to answer yes to: reach rayon over edges of *any* kind.
///
/// Both repos really do carry rayon in `Cargo.lock`, through
/// `nornir-robotui`'s snapshot harness (`egui_kittest → dify → image → ravif →
/// rav1e → av-scenechange`), and every edge of that is a `[dev-dependencies]`
/// edge. So this call must always come back with a chain that ends at one of
/// OUR packages — and [`shipped_rayon_tree`] is the identical command with the
/// single `-e` flag narrowed to `normal`.
///
/// What it proves: cargo ran, rayon is in the graph, the parser read real
/// cargo output, and the walk reaches this workspace. What it does **not**
/// prove: that a *normal* edge would be detected — that is what
/// `the_tree_parser_reads_a_real_violation_chain_all_the_way_to_us` is for,
/// which is proven red against a captured violation. Stated plainly because a
/// liveness check dressed up as a red proof is the disease this module is about.
///
/// A guard whose clean answer is `None` cannot tell "clean" from "the command
/// stopped working". This makes it say which, on every run.
pub fn rayon_tree_liveness_chain(root: &Path) -> Result<Option<Vec<String>>, String> {
let out = rayon_tree(root, "normal,dev,build", &[])?;
if tree_found_nothing(&out) {
return Ok(None);
}
Ok(tree_chain_to_first_party(&out, root))
}
/// The `-e normal` query under `--all-features`, which in **korp** really does
/// produce a shipped violation: `--all-features` turns on `image`'s `avif` and
/// `ravif → rav1e → av-scenechange → rayon` becomes a normal edge reaching korp
/// itself. Measured on oden 2026-08-03 — the same command that says "nothing to
/// print" at default features prints that chain.
///
/// This is the strongest available proof for check 3, because the failing query
/// is byte-for-byte the query the verdict uses. It is **not** universal:
/// measured the same day, facett answers "nothing to print" even under
/// `--all-features`, because `--all-features` applies to workspace members only
/// and no facett member turns `avif` on. Callers that cannot get a chain here
/// must fall back to [`rayon_tree_liveness_chain`], not skip the axis.
pub fn rayon_tree_all_features_chain(root: &Path) -> Result<Option<Vec<String>>, String> {
let out = rayon_tree(root, "normal", &["--all-features"])?;
if tree_found_nothing(&out) {
return Ok(None);
}
Ok(tree_chain_to_first_party(&out, root))
}
/// The feature set cargo resolves for `pkg` when the **whole workspace** is
/// selected — exactly what `cargo test --workspace` compiles with.
///
/// This asks cargo rather than re-implementing feature unification, because a
/// hand-rolled resolver that quietly disagrees with cargo is a guard that
/// reports on a build nobody runs.
pub fn workspace_features(md: &serde_json::Value, pkg: &str) -> Option<Vec<String>> {
let names = id_to_name(md);
let mut out: Vec<String> = Vec::new();
let mut found = false;
for node in md["resolve"]["nodes"].as_array()? {
let id = node["id"].as_str()?;
if names.get(id).map(|n| n != pkg).unwrap_or(true) {
continue;
}
found = true;
if let Some(fs) = node["features"].as_array() {
out.extend(fs.iter().filter_map(|f| f.as_str().map(str::to_string)));
}
}
if !found {
return None;
}
out.sort();
out.dedup();
Some(out)
}
/// A test the default build never runs, because a `cfg` switches it off.
///
/// Two shapes, both silent, told apart by [`whole_file`](Self::whole_file):
///
/// * a crate-level `#![cfg(feature = "…")]` — the WHOLE target compiles to an
/// empty binary that prints `0 tests, 0 benchmarks`;
/// * a per-`fn` `#[cfg(feature = "…")]` on `#[test]` inside a file that otherwise
/// runs — libtest never lists the fn at all, so it does not even reach the
/// `filtered out` count.
///
/// The second shape is the more dangerous one: the target's OTHER tests report a
/// cheerful `ok`, so the file looks alive while the arm that needed the feature is
/// gone. MEASURED in this workspace 2026-08-22 — facett-map alone carries 3 files
/// of that shape (`cull_envelope`, `fill_extent_gate`, `paint_order_pixels`).
#[derive(Debug, Clone)]
pub struct GatedTestFile {
/// The file.
pub file: PathBuf,
/// The feature the `cfg` demands.
pub feature: String,
/// How many `#[test]`/`#[tokio::test]` functions this entry silences — the
/// number that disappears when the feature is off.
pub tests: usize,
/// `true` when a crate-level `#![cfg(…)]` takes the whole target down;
/// `false` when only the individual `#[test]` fns counted here are gated.
pub whole_file: bool,
}
/// Find every `tests/*.rs` under `crate_dir` that a feature `cfg` can silence —
/// crate-level and per-`fn` alike.
///
/// A gated file whose feature is off compiles to an empty binary that prints
/// `0 tests, 0 benchmarks` and exits 0 — indistinguishable from a passing file.
/// That is the exact shape of a green nobody has seen red. A per-`fn` gate is
/// quieter still: the target runs, reports `ok`, and the gated fns are not even
/// counted as `filtered out`.
///
/// One entry per (file, feature). A crate-level `#![cfg(all(feature = "a",
/// not(target_arch = "wasm32")))]` yields one entry for `a` — every feature the
/// `cfg` REQUIRES is a separate entry, because each one alone can silence the file.
/// `any(feature = …)` is deliberately NOT modelled (a file behind `any` is reachable
/// through either arm, which this shape cannot express); [`any_feature_cfgs`] finds
/// them so the day one is written it is a visible decision and not a silent miss.
pub fn gated_test_files(crate_dir: &Path) -> Vec<GatedTestFile> {
let mut out = Vec::new();
let Ok(rd) = std::fs::read_dir(crate_dir.join("tests")) else { return out };
for entry in rd.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
let Ok(text) = std::fs::read_to_string(&path) else { continue };
let lines: Vec<&str> = text.lines().collect();
let is_test_attr =
|l: &str| l.trim().starts_with("#[test]") || l.trim().starts_with("#[tokio::test");
// ── crate-level: the whole target goes silent ────────────────────────
let crate_feats: Vec<String> = lines
.iter()
.filter(|l| l.trim_start().starts_with("#![cfg("))
.flat_map(|l| required_features_of_cfg(l))
.collect();
if !crate_feats.is_empty() {
let tests = lines.iter().filter(|l| is_test_attr(l)).count();
let mut seen: Vec<String> = Vec::new();
for f in crate_feats {
if seen.contains(&f) {
continue;
}
seen.push(f.clone());
out.push(GatedTestFile { file: path.clone(), feature: f, tests, whole_file: true });
}
// A file already dead at the top cannot ALSO be counted per fn.
continue;
}
// ── per-fn: the target runs and the gated fns are simply not there ───
// Attributes may sit on either side of `#[test]`, so the contiguous run of
// `#[…]` lines around it is the attribute block to read.
let mut per_feature: Vec<(String, usize)> = Vec::new();
for (i, l) in lines.iter().enumerate() {
if !is_test_attr(l) {
continue;
}
let mut feats: Vec<String> = Vec::new();
let mut j = i;
while j > 0 && lines[j - 1].trim_start().starts_with("#[") {
j -= 1;
feats.extend(required_features_of_cfg(lines[j]));
}
let mut k = i + 1;
while k < lines.len() && lines[k].trim_start().starts_with("#[") {
feats.extend(required_features_of_cfg(lines[k]));
k += 1;
}
feats.sort();
feats.dedup();
for f in feats {
match per_feature.iter_mut().find(|(g, _)| *g == f) {
Some((_, n)) => *n += 1,
None => per_feature.push((f, 1)),
}
}
}
for (feature, tests) in per_feature {
out.push(GatedTestFile { file: path.clone(), feature, tests, whole_file: false });
}
}
out.sort_by(|a, b| (&a.file, &a.feature).cmp(&(&b.file, &b.feature)));
out
}
/// Every `cfg` line under `crate_dir/tests` that uses `any(feature = …)`.
///
/// [`gated_test_files`] models a `cfg` as a set of features that are ALL required.
/// `any(...)` breaks that: the file is reachable through either arm, and reporting
/// it as "needs feature A" would be a lie that could send someone declaring an arm
/// nobody needs. There are none in this workspace today; this is what says so, and
/// what will say otherwise the day one is written.
pub fn any_feature_cfgs(crate_dir: &Path) -> Vec<(PathBuf, String)> {
let mut out = Vec::new();
let Ok(rd) = std::fs::read_dir(crate_dir.join("tests")) else { return out };
for entry in rd.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
let Ok(text) = std::fs::read_to_string(&path) else { continue };
for l in text.lines() {
let t = l.trim_start();
if (t.starts_with("#[cfg(") || t.starts_with("#![cfg(")) && t.contains("any(") && t.contains("feature") {
out.push((path.clone(), t.to_string()));
}
}
}
out
}
/// Every feature a `cfg` attribute line REQUIRES: each `feature = "x"` that is not
/// inside a `not(…)` and not inside an `any(…)`.
///
/// `#![cfg(feature = "wgpu")]` → `["wgpu"]`
/// `#![cfg(all(feature = "gfx-v2-gpu", not(target_arch = "…")))]` → `["gfx-v2-gpu"]`
/// `#![cfg(not(target_arch = "wasm32"))]` → `[]` (no feature)
/// `#[cfg(any(feature = "a", feature = "b"))]` → `[]` (see
/// [`any_feature_cfgs`])
fn required_features_of_cfg(line: &str) -> Vec<String> {
let t = line.trim();
if !(t.starts_with("#[cfg(") || t.starts_with("#![cfg(")) {
return Vec::new();
}
let mut out = Vec::new();
// Walk the line tracking whether we are inside a `not(` or `any(` group. Both
// make a `feature = "x"` inside them NOT a requirement.
let bytes = t.as_bytes();
let mut depth_stack: Vec<bool> = Vec::new(); // true = this group negates/relaxes
let mut i = 0usize;
while i < bytes.len() {
if bytes[i] == b'(' {
let before = &t[..i];
let neg = before.ends_with("not") || before.ends_with("any");
depth_stack.push(neg);
i += 1;
continue;
}
if bytes[i] == b')' {
depth_stack.pop();
i += 1;
continue;
}
if t[i..].starts_with("feature") {
let rest = t[i + "feature".len()..].trim_start();
if let Some(rest) = rest.strip_prefix('=') {
let rest = rest.trim_start();
if let Some(rest) = rest.strip_prefix('"') && let Some(end) = rest.find('"') {
// Skip the outermost `cfg(` group itself: it is never a
// negation, and `depth_stack` already carries it.
let relaxed = depth_stack.iter().skip(1).any(|n| *n);
if !relaxed {
out.push(rest[..end].to_string());
}
}
}
i += "feature".len();
continue;
}
i += 1;
}
out
}
// ── The attribute-owner law ───────────────────────────────────────────────────
//
// **An attribute binds to the NEXT item.** Anything inserted between the two —
// a doc comment, a doc comment plus a whole new item — silently re-parents it.
// The code compiles, the tests stay green, and something stops existing.
//
// Three independent instances were measured on 2026-08-22, all found by
// accident:
//
// 1. `nornir/src/lib.rs` — a doc comment landed between `#[cfg(feature = "mcp")]`
// and `pub mod mcp_stdio;`. `mcp_stdio` became UNCONDITIONAL (281 rmcp/schemars
// errors in every default build) and the new `lens_chain` module inherited the
// gate, so it compiled ONLY with `mcp` — which is what hid the fact that it did
// not compile at all.
// 2. `edda/crates/nornir-service` — the same wedge moved `#[cfg(feature =
// "auth-provider")]` onto a `pub use`, and every default build of the crate
// failed on an unresolved `provider`.
// 3. `korp/src/main.rs` — a new test was pasted between an existing `#[test]` and
// its function. One function then carried two `#[test]`s and
// `korp_state_exposes_all_views` carried none: it had not run for months, with
// the whole suite green. The only signal was one `duplicate_macro_attributes`
// warning.
//
// Instance 3 is why this is a source scan and not only a lint switch. `rustc`
// does warn on the duplicate, but a warning in a 20 000-line build log is not a
// gate, and neither `duplicate_macro_attributes` nor `unused_attributes` says
// anything about instances 1 and 2 — there the attribute is *used*, just by the
// wrong item.
//
// Measured cost of the three rules over the whole constellation on 2026-08-22
// (26 repos, ~1.1 M lines of first-party Rust): 3 duplicate blocks, 9
// gate-before-doc blocks, 1 orphaned test function. Small enough to be a hard
// gate rather than a report.
/// Attributes that decide whether an item **exists**, or whether a function is a
/// test. These are the ones whose owner is worth a build failure: a re-parented
/// `#[derive]` fails to compile, a re-parented `#[cfg]` or `#[test]` does not.
fn attr_changes_existence(name: &str) -> bool {
matches!(name, "cfg" | "cfg_attr") || attr_is_test(name)
}
/// A test-declaring attribute, in any of the spellings this constellation uses.
/// Matched on the last `::` segment so `tokio::test` and a bare `test` are one
/// answer.
fn attr_is_test(name: &str) -> bool {
let last = name.rsplit("::").next().unwrap_or(name);
// `wasm_bindgen_test` is how garmr-webui declares all seventeen of its browser
// tests; a set of exact names missed every one of them and called the file
// seventeen dead tests.
last == "test"
|| last.ends_with("_test")
|| matches!(last, "bench" | "rstest" | "proptest" | "test_case" | "quickcheck")
|| matches!(name, "ignore" | "should_panic")
}
/// Attributes that may not legally appear twice on one item. `cfg`, `allow`,
/// `derive`, `serde` and friends repeat all the time and are not on this list —
/// a duplicate of those is caught by the byte-identical rule instead, which
/// cannot produce a false positive.
fn attr_is_non_repeatable(name: &str) -> bool {
let last = name.rsplit("::").next().unwrap_or(name);
matches!(
last,
"test"
| "bench"
| "rstest"
| "ignore"
| "should_panic"
| "no_mangle"
| "global_allocator"
| "panic_handler"
| "proc_macro"
| "proc_macro_derive"
| "proc_macro_attribute"
)
}
/// The attribute path at the head of `#[…]` / `#![…]`, e.g. `cfg`, `tokio::test`.
fn attr_path(line: &str) -> Option<String> {
let t = line.trim_start();
let rest = t.strip_prefix("#[").or_else(|| t.strip_prefix("#!["))?;
let mut out = String::new();
for c in rest.chars() {
if c.is_alphanumeric() || c == '_' || c == ':' {
out.push(c);
} else if c.is_whitespace() && out.is_empty() {
continue;
} else {
break;
}
}
if out.is_empty() { None } else { Some(out) }
}
/// One entry of an attribute block, in source order.
#[derive(Debug, Clone, PartialEq, Eq)]
enum Piece {
/// An outer `#[…]` attribute: (line, path, full text).
Attr(usize, String, String),
/// A `///` doc comment line.
Doc(usize),
}
/// Parse the attribute/doc block that starts at `lines[i]`, returning its pieces
/// and the index of the ITEM line it binds to.
///
/// Multi-line attributes are followed by bracket balance, which is what the first
/// cut of this scanner got wrong: `#[ignore = "…"]` spread over four lines made
/// the walk stop early and report two perfectly attributed heavy tests in
/// `nornir-build-thing` as orphans. Blank lines are also legal between an
/// attribute and its item (`znippy-zoomies/src/stree.rs` writes them), and
/// stopping there produced three more phantom orphans.
fn parse_block(lines: &[&str], start: usize) -> (Vec<Piece>, usize) {
let mut pieces = Vec::new();
let mut i = start;
while i < lines.len() {
let t = lines[i].trim_start();
if t.starts_with("#[") {
// Anything after the closing bracket on the same line means this is an
// item line carrying an inline attribute (`#[serde(default)] pub a: u8,`),
// not a block entry — otherwise struct fields read as one long block.
let mut depth: i32 = 0;
let mut end = i;
let mut tail_is_clean = false;
'outer: for (k, l) in lines.iter().enumerate().skip(i) {
for (ci, c) in l.char_indices() {
if c == '[' {
depth += 1;
} else if c == ']' {
depth -= 1;
if depth == 0 {
end = k;
let tail = l[ci + 1..].trim();
tail_is_clean = tail.is_empty() || tail.starts_with("//");
break 'outer;
}
}
}
}
if !tail_is_clean {
break;
}
let text: String = lines[i..=end].join(" ").split_whitespace().collect::<Vec<_>>().join(" ");
let path = attr_path(t).unwrap_or_default();
pieces.push(Piece::Attr(i + 1, path, text));
i = end + 1;
continue;
}
if t.starts_with("///") {
pieces.push(Piece::Doc(i + 1));
i += 1;
continue;
}
if t.starts_with("//") || t.is_empty() {
i += 1;
continue;
}
break;
}
(pieces, i)
}
/// Line ranges of every `#[cfg(test)] mod … { … }` in a file, by brace balance.
fn cfg_test_mod_ranges(lines: &[&str]) -> Vec<(usize, usize)> {
let mut out = Vec::new();
for (i, l) in lines.iter().enumerate() {
if !l.trim_start().starts_with("#[cfg(test)]") {
continue;
}
let (_, item) = parse_block(lines, i);
let Some(head) = lines.get(item) else { continue };
let h = head.trim_start();
if !(h.starts_with("mod ") || h.starts_with("pub mod ")) {
continue;
}
let mut depth: i32 = 0;
let mut opened = false;
for (k, l) in lines.iter().enumerate().skip(item) {
depth += l.matches('{').count() as i32 - l.matches('}').count() as i32;
if l.contains('{') {
opened = true;
}
if opened && depth <= 0 {
out.push((item, k));
break;
}
}
}
out
}
/// `fn some_name()` — a zero-argument free function, the shape every test has.
fn zero_arg_fn_name(line: &str) -> Option<&str> {
let t = line.trim_start();
let t = t.strip_prefix("pub ").unwrap_or(t).trim_start();
let t = t.strip_prefix("async ").unwrap_or(t).trim_start();
let t = t.strip_prefix("unsafe ").unwrap_or(t).trim_start();
let rest = t.strip_prefix("fn ")?;
let name_end = rest.find(|c: char| !(c.is_alphanumeric() || c == '_'))?;
let (name, tail) = rest.split_at(name_end);
let tail = tail.trim_start();
if !tail.starts_with("()") {
return None;
}
if name.is_empty() { None } else { Some(name) }
}
/// The three shapes, found in one `.rs` file. `path` is only used to label the
/// hits and to decide whether the file is a test surface.
///
/// Public so a guard can plant a shape in a string and watch this name it — a
/// scanner that has never been seen red is exactly the hollow green the module
/// docs are about.
pub fn attribute_hits(path: &Path, text: &str) -> Vec<Hit> {
let lines: Vec<&str> = text.lines().collect();
let mut hits = Vec::new();
let p = path.to_string_lossy().replace('\\', "/");
// cargo compiles `tests/*.rs` as test binaries and everything DEEPER as a shared
// helper module that sibling binaries pull in with `mod support;`. In a helper,
// "nothing in this file calls it" says nothing at all — `nornir`'s
// `tests/support/mcp_harness.rs` is called only from the binaries that include it.
let is_support = ["/tests/", "/benches/"].iter().any(|d| {
p.rsplit_once(d).is_some_and(|(_, rest)| rest.contains('/'))
});
let is_test_surface = p.contains("/tests/") || p.contains("/benches/");
let test_mods = cfg_test_mod_ranges(&lines);
let mut i = 0usize;
while i < lines.len() {
let t = lines[i].trim_start();
let starts_block = t.starts_with("#[") || t.starts_with("///");
if !starts_block {
// R3 — a test-shaped function that carries no attribute block at all.
if let Some(name) = zero_arg_fn_name(lines[i]) {
maybe_orphan_test(
path, &lines, text, i, name, &[], is_support, is_test_surface, &test_mods,
&mut hits,
);
}
i += 1;
continue;
}
let (pieces, item) = parse_block(&lines, i);
if pieces.is_empty() {
i += 1;
continue;
}
let item_line = lines.get(item).copied().unwrap_or("<end of file>");
// R1 — the same attribute twice on one item: either a non-repeatable
// attribute by name, or a byte-identical repeat of any attribute.
let mut by_name: Vec<(usize, &str)> = Vec::new();
let mut by_text: Vec<(usize, &str)> = Vec::new();
for piece in &pieces {
let Piece::Attr(ln, name, txt) = piece else { continue };
if attr_is_non_repeatable(name) {
if let Some((first, _)) = by_name.iter().find(|(_, n)| *n == name) {
hits.push(Hit {
file: path.to_path_buf(),
line: *ln,
token: format!("duplicate #[{name}]"),
text: format!(
"`{item_line}` carries #[{name}] twice (also line {first}) — the first \
one lost its item: whatever it was written for is now unattributed"
),
});
}
by_name.push((*ln, name));
}
if let Some((first, _)) = by_text.iter().find(|(_, x)| *x == txt) {
hits.push(Hit {
file: path.to_path_buf(),
line: *ln,
token: format!("duplicate {txt}"),
text: format!(
"`{item_line}` carries the identical attribute twice (also line {first}) — \
the first one was written for a different item"
),
});
}
by_text.push((*ln, txt));
}
// R2 — an existence-gating attribute above a doc comment. Docs come
// first, gates come last, so that pasting `/// doc` + a new item under an
// existing gate is impossible to do silently.
let mut gate: Option<(usize, &str)> = None;
for piece in &pieces {
match piece {
Piece::Attr(ln, name, txt) if attr_changes_existence(name) => {
gate = Some((*ln, txt));
}
Piece::Doc(ln) => {
if let Some((gl, gtxt)) = gate {
hits.push(Hit {
file: path.to_path_buf(),
line: gl,
token: format!("gate above a doc comment {gtxt}"),
text: format!(
"{gtxt} at line {gl} sits ABOVE the doc comment at line {ln}, and \
therefore gates `{item_line}` — check that is the item it was \
written for, then put the gate directly on it, below its doc"
),
});
break;
}
}
_ => {}
}
}
// R3 — the function this block binds to, with no test attribute on it.
if let Some(name) = zero_arg_fn_name(item_line) {
maybe_orphan_test(
path, &lines, text, item, name, &pieces, is_support, is_test_surface, &test_mods,
&mut hits,
);
}
// Continue AFTER the item, or the item line is re-read as a bare line with no
// attribute block — which reported every properly attributed test in
// `facet-wrapped` as an orphan the first time this ran.
i = item + 1;
}
hits
}
/// R3: a zero-argument function in a test scope, with a sentence-shaped name, no
/// test attribute, no `allow(dead_code)`, and no other mention of its own name in
/// the file — i.e. a test that stopped being a test and that nothing calls.
///
/// `korp_state_exposes_all_views` had been in exactly this state for months.
#[allow(clippy::too_many_arguments)]
fn maybe_orphan_test(
path: &Path,
lines: &[&str],
text: &str,
item: usize,
name: &str,
pieces: &[Piece],
is_support: bool,
is_test_surface: bool,
test_mods: &[(usize, usize)],
hits: &mut Vec<Hit>,
) {
if is_support {
return;
}
if !is_test_surface && !test_mods.iter().any(|(a, b)| item >= *a && item <= *b) {
return;
}
// Test names in this constellation are sentences; a two-word `fn setup()` is a
// helper, and guessing at helpers is how a guard earns its way to being off.
if name.matches('_').count() < 2 {
return;
}
for piece in pieces {
let Piece::Attr(_, attr_name, txt) = piece else { continue };
if attr_is_test(attr_name) || txt.contains("dead_code") {
return;
}
}
// Called from somewhere in this file ⇒ a helper, not a stranded test.
let mentions = text.match_indices(name).filter(|(i, _)| {
let before = text[..*i].chars().next_back();
let after = text[i + name.len()..].chars().next();
!before.is_some_and(|c| c.is_alphanumeric() || c == '_')
&& !after.is_some_and(|c| c.is_alphanumeric() || c == '_')
});
if mentions.count() > 1 {
return;
}
let _ = lines;
hits.push(Hit {
file: path.to_path_buf(),
line: item + 1,
token: "test function with no #[test]".to_string(),
text: format!(
"`fn {name}()` sits in a test scope, is named like a test, carries no test \
attribute and is called by nothing — it does not run, and the suite is green"
),
});
}
/// Scan every first-party `.rs` file under `root` for the attribute-owner law.
///
/// Reuses [`source_roots`], so it reads exactly the files the rayon law reads —
/// one walker, one definition of "ours" (LAW 5). `scanned` travels with the
/// verdict for the same reason it does there: a scan that read nothing finds
/// nothing, and that is the false green this constellation keeps producing.
pub fn scan_attributes(root: &Path) -> Scan {
let mut files = Vec::new();
for dir in source_roots(root) {
rs_files(&dir, &mut files);
}
let mut hits = Vec::new();
for file in &files {
let Ok(text) = std::fs::read_to_string(file) else { continue };
hits.extend(attribute_hits(file, &text));
}
Scan { hits, scanned: files.len() }
}
// ── The checkout-surface law ──────────────────────────────────────────────────
//
// Measured on oden 2026-08-22/23: **737 repo-escaping `path =` dependencies
// across 17 repos** (nornir 174, korp 127, dwarves 125). Every one of them is a
// build reaching *out of its own checkout* into a sibling directory, and cargo
// resolves it by PATH — it has no opinion at all about which commit that sibling
// is parked on.
//
// The consequence is not theoretical. Three separate "findings" filed the same
// night were phantoms of stale primary checkouts: a dwarves red that was a
// 10-commit-behind edda, a "`facett_core::law::dep_tree` does not exist" that
// was a 4-commit-behind facett (the fn is right here in this file), and a third.
// While thirteen lanes built against them the primaries stood at facett −4,
// korp −27, edda −10, dwarves −7, znippy-zoomies −7, knut −3 — and
// `~/git/facett`, the checkout every facett path dep in the
// constellation lands in, was sitting on *another lane's branch*, 400 lines of
// this file behind `origin/main`.
//
// A compile error is what that looks like from the inside, so the reflex is to
// go fix the code. The check below is the cheap thing that would have said the
// true sentence instead: **your sibling is not at the ref you think.**
//
// It deliberately answers about the CHECKOUT, not about the code. [`sibling_survey`]
// walks the repo-escaping path deps of a workspace, resolves each to the git
// checkout that actually contains it, and reports how that checkout's `HEAD`
// stands against its own `origin/HEAD`. [`sibling_offenders`] is the assertable
// form: empty means every sibling this build links against is at its remote's
// default ref, and anything else is the phantom, named, before it becomes a
// mystery compile failure.
//
// Why `origin/HEAD` and not `origin/main`: znippy's canonical branch is
// `master` and znippy-zoomies' is `main`, so the branch name is not knowable
// from outside the repo. `git symbolic-ref refs/remotes/origin/HEAD` asks the
// repo instead of a table — which is the same correction LAW 6 already makes in
// prose.
//
// This shells out to `git`, the way [`cargo_metadata`] shells out to cargo: the
// question is "what does the tool that owns this state say", and re-implementing
// ref resolution would be a second writer of it (LAW 5).
/// A `path = "…"` dependency whose target lies outside the repo that declared it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EscapingDep {
/// The dependency name as the manifest spells it.
pub name: String,
/// The manifest that declared it.
pub manifest: PathBuf,
/// The literal `path = "…"` value.
pub declared: String,
/// `declared`, resolved against the manifest's directory and normalised.
/// Lexical only — the target may not exist, and that is a finding, not an error.
pub resolved: PathBuf,
}
/// What a sibling checkout is actually doing, as opposed to what the builder assumed.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SiblingState {
/// The path does not exist. This is the "the build dies outright" case: it is
/// why one `cargo check` from a worktree needed twelve sibling worktrees,
/// discovered one cargo error at a time.
Missing,
/// The path exists but is in no git checkout — nothing pins it at all.
Untracked,
/// A detached HEAD. Not necessarily wrong, but nothing says which ref it means.
Detached { head: String },
/// The checkout has no `origin/HEAD`, so it cannot say what its own canonical
/// branch is. Ask the repo, not a table — and this repo declines to answer.
NoOriginHead,
/// HEAD and `origin/HEAD` name different commits. **This is the phantom.**
Drifted { head: String, expected: String, behind: usize, ahead: usize },
/// HEAD is exactly `origin/HEAD`. `dirty` still travels, because an aligned
/// ref with uncommitted source in it is a different build from the one the ref names.
Aligned { head: String, dirty: usize },
}
impl SiblingState {
/// Is this a state a build may rely on? Only [`SiblingState::Aligned`] with a
/// clean tree is. Everything else means the linked code is not the code the
/// ref names.
pub fn is_trustworthy(&self) -> bool {
matches!(self, SiblingState::Aligned { dirty: 0, .. })
}
}
/// One repo-escaping dependency, and the state of the checkout it landed in.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Sibling {
pub dep: EscapingDep,
/// The checkout root that contains [`EscapingDep::resolved`], if any.
pub checkout: Option<PathBuf>,
pub state: SiblingState,
}
impl Sibling {
/// One line, in the shape the report asks for: subject, status, detail.
pub fn describe(&self) -> String {
let where_ = self.checkout.clone().unwrap_or_else(|| self.dep.resolved.clone());
match &self.state {
SiblingState::Missing => {
format!("{} — MISSING — {} declares path {:?}, nothing is there", self.dep.name, self.dep.manifest.display(), self.dep.declared)
}
SiblingState::Untracked => {
format!("{} — UNTRACKED — {} is in no git checkout, so no ref pins it", self.dep.name, where_.display())
}
SiblingState::Detached { head } => {
format!("{} — DETACHED — {} is at {head} with no branch; nothing says which ref that is", self.dep.name, where_.display())
}
SiblingState::NoOriginHead => {
format!("{} — NO-ORIGIN-HEAD — {} cannot name its own canonical branch", self.dep.name, where_.display())
}
SiblingState::Drifted { head, expected, behind, ahead } => {
format!(
"{} — DRIFTED — {} is at {head}, origin/HEAD is {expected} (behind {behind}, ahead {ahead}); this build links THAT, not the ref you think",
self.dep.name,
where_.display()
)
}
SiblingState::Aligned { head, dirty } => {
format!("{} — DIRTY — {} is at origin/HEAD {head} but carries {dirty} uncommitted change(s)", self.dep.name, where_.display())
}
}
}
}
/// Every `path = "…"` dependency declared anywhere in `root`'s manifests, in
/// declaration order, without deduplication.
///
/// Text-based on purpose. `cargo metadata` would answer this too, but it needs a
/// resolvable graph — and the whole failure mode being guarded here is a graph
/// that does not resolve because a sibling is missing or stale. A check that can
/// only run once the build works is not a pre-build check.
pub fn declared_path_deps(root: &Path) -> Vec<EscapingDep> {
let mut out = Vec::new();
for manifest in workspace_manifests(root) {
let Ok(text) = std::fs::read_to_string(&manifest) else { continue };
let dir = manifest.parent().unwrap_or(root).to_path_buf();
for (name, declared) in path_deps_in_manifest(&text) {
let resolved = normalise(&dir.join(&declared));
out.push(EscapingDep { name, manifest: manifest.clone(), declared, resolved });
}
}
out
}
/// The subset of [`declared_path_deps`] that leaves `root` — the ones cargo
/// resolves into somebody else's checkout.
pub fn escaping_path_deps(root: &Path) -> Vec<EscapingDep> {
let root = normalise(root);
declared_path_deps(&root).into_iter().filter(|d| !d.resolved.starts_with(&root)).collect()
}
/// Parse `path = "…"` out of one manifest's text, pairing each with the
/// dependency name it belongs to.
///
/// Handles the three shapes this constellation actually writes: an inline table
/// (`edda = { path = "../edda" }`), a dotted section
/// (`[dependencies.edda]` … `path = "../edda"`), and a `[patch.crates-io]` entry —
/// which is the same escape wearing a different hat and is the one a
/// dependency-name-only parser silently drops.
pub fn path_deps_in_manifest(text: &str) -> Vec<(String, String)> {
let mut out = Vec::new();
let mut section_key: Option<String> = None;
for line in text.lines() {
let t = line.trim();
if t.starts_with('#') {
continue;
}
if t.starts_with('[') {
// `[dependencies.edda]`, `[target.'cfg(x)'.dependencies.edda]`,
// `[patch.crates-io.edda]` → the last segment is the dep name.
// A bare `[dependencies]` / `[patch.crates-io]` has no name of its own.
let head = t.trim_start_matches('[').trim_end_matches(']');
let last = head.rsplit('.').next().unwrap_or("").trim().trim_matches('"');
section_key = match last {
"dependencies" | "dev-dependencies" | "build-dependencies" | "crates-io"
| "workspace" | "package" | "features" | "lib" | "bin" | "patch" => None,
"" => None,
other => Some(other.to_string()),
};
continue;
}
let Some(value) = quoted_value_of(t, "path") else { continue };
// Inline table: the key before the first `=` is the dep name.
let name = match t.split_once('=') {
Some((k, rest)) if rest.trim_start().starts_with('{') => {
k.trim().trim_matches('"').to_string()
}
_ => section_key.clone().unwrap_or_else(|| "<unnamed>".to_string()),
};
out.push((name, value));
}
out
}
/// `key = "value"` anywhere in `line`, returning `value`. Refuses to match a
/// longer key that merely ends in `key` (`rustc-path = "…"` is not `path`).
fn quoted_value_of(line: &str, key: &str) -> Option<String> {
let mut from = 0usize;
while let Some(i) = line[from..].find(key) {
let at = from + i;
let before_ok = at == 0 || !matches!(line.as_bytes()[at - 1], b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'.');
let rest = &line[at + key.len()..];
let after = rest.trim_start();
if before_ok && after.starts_with('=') {
let v = after[1..].trim_start();
if let Some(stripped) = v.strip_prefix('"') && let Some(end) = stripped.find('"') {
return Some(stripped[..end].to_string());
}
}
from = at + key.len();
}
None
}
/// Lexical path normalisation: fold away `.` and `..` without touching the disk,
/// so a dep pointing at a directory that does not exist still gets an answer.
fn normalise(p: &Path) -> PathBuf {
let mut out = PathBuf::new();
for c in p.components() {
match c {
std::path::Component::CurDir => {}
// `..` above the root is still the root (POSIX): `/a/../../b` is `/b`,
// not `/../b`. Only a RELATIVE path may keep a leading `..`.
std::path::Component::ParentDir => {
if !out.pop() && !out.has_root() {
out.push("..");
}
}
other => out.push(other.as_os_str()),
}
}
out
}
/// The git checkout that contains `p`: the nearest ancestor holding a `.git`
/// entry. A worktree's `.git` is a file, not a directory — both count, which is
/// the whole point on a box with 264 registered worktrees.
pub fn checkout_root(p: &Path) -> Option<PathBuf> {
let mut cursor = Some(p);
while let Some(dir) = cursor {
if dir.join(".git").exists() {
return Some(dir.to_path_buf());
}
cursor = dir.parent();
}
None
}
fn git(repo: &Path, args: &[&str]) -> Option<String> {
let out = Command::new("git").arg("-C").arg(repo).args(args).output().ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
/// Where one checkout stands against its own `origin/HEAD`.
///
/// The SHA decides, not the branch. A lane worktree created with
/// `git worktree add --detach <repo> origin/main` is detached *and* exactly right,
/// and that is the standard shape on this box — grading it red would train
/// everyone to ignore the check. [`SiblingState::Detached`] therefore means
/// "detached **and** not at `origin/HEAD`", which is a real unknown.
pub fn checkout_state(repo: &Path) -> SiblingState {
let branch = git(repo, &["symbolic-ref", "--quiet", "--short", "HEAD"]);
let head = git(repo, &["rev-parse", "HEAD"]).unwrap_or_default();
let Some(origin_head) = git(repo, &["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]) else {
return SiblingState::NoOriginHead;
};
let expected = git(repo, &["rev-parse", &origin_head]).unwrap_or_default();
let dirty = git(repo, &["status", "--porcelain"])
.map(|s| s.lines().filter(|l| !l.trim().is_empty()).count())
.unwrap_or(0);
if head == expected && !head.is_empty() {
return SiblingState::Aligned { head: short(&head), dirty };
}
if branch.is_none() {
return SiblingState::Detached { head: short(&head) };
}
let (ahead, behind) = git(repo, &["rev-list", "--left-right", "--count", &format!("HEAD...{origin_head}")])
.and_then(|s| {
let mut it = s.split_whitespace();
Some((it.next()?.parse().ok()?, it.next()?.parse().ok()?))
})
.unwrap_or((0usize, 0usize));
SiblingState::Drifted { head: short(&head), expected: short(&expected), behind, ahead }
}
fn short(sha: &str) -> String {
sha.chars().take(12).collect()
}
/// Every repo-escaping path dep of `root`, with the state of the checkout it
/// resolves into. Deduplicated by checkout, because 127 escapes into the same
/// eight siblings is one answer repeated 127 times.
pub fn sibling_survey(root: &Path) -> Vec<Sibling> {
let mut seen: Vec<PathBuf> = Vec::new();
let mut out = Vec::new();
for dep in escaping_path_deps(root) {
let checkout = checkout_root(&dep.resolved);
let state = match &checkout {
_ if !dep.resolved.exists() => SiblingState::Missing,
None => SiblingState::Untracked,
Some(repo) => {
if seen.contains(repo) {
continue;
}
seen.push(repo.clone());
checkout_state(repo)
}
};
out.push(Sibling { dep, checkout, state });
}
out
}
/// The assertable form: every sibling this build links against that is **not**
/// at its remote's default ref with a clean tree.
///
/// ```no_run
/// # use std::path::Path;
/// # use facett_core::law;
/// let bad = law::sibling_offenders(Path::new("."));
/// assert!(bad.is_empty(), "{}", bad.iter().map(|s| s.describe()).collect::<Vec<_>>().join("\n"));
/// ```
pub fn sibling_offenders(root: &Path) -> Vec<Sibling> {
sibling_survey(root).into_iter().filter(|s| !s.state.is_trustworthy()).collect()
}
// ─── does the repo RESOLVE at all ──────────────────────────────────────────
/// A repo whose dependency graph cannot be resolved, and the first line cargo
/// gave as the reason.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Unresolvable {
/// The repo root that failed.
pub root: PathBuf,
/// Cargo's own first diagnostic line, kept verbatim.
pub reason: String,
}
impl Unresolvable {
/// One line for a failure message.
#[must_use]
pub fn describe(&self) -> String {
format!("{} does not resolve: {}", self.root.display(), self.reason)
}
}
/// **Does this repo's dependency graph resolve?** `cargo metadata --no-deps`,
/// which reads manifests and answers without compiling anything.
///
/// It exists because a repo that does not RESOLVE is invisible to everything
/// else. It is not red — a compile-coverage sweep cannot compile a target it
/// cannot enumerate, a dep-graph tool has no graph, and the warehouse indexer
/// skips it. MEASURED 2026-08-23: **modgunn and ordning could not resolve for
/// nine days** and nothing in the constellation said so. modgunn's cause was a
/// feature naming a feature that no longer existed (`skade/sql`, dropped
/// 2026-08-14) — which is a *resolution* error, not a build error, so no amount
/// of building would have found it.
///
/// Cheap on purpose: ~4 s per repo, no network (`--offline` is NOT passed —
/// a repo that needs the registry to resolve genuinely does not resolve
/// offline, and pretending otherwise would trade one blindness for another).
///
/// `Ok(())` means cargo answered. Anything else is the first line of why not.
pub fn resolves(root: &Path) -> Result<(), String> {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let out = Command::new(cargo)
.args(["metadata", "--no-deps", "--format-version", "1"])
.current_dir(root)
.output()
.map_err(|e| format!("could not run `cargo metadata` in {}: {e}", root.display()))?;
if out.status.success() {
return Ok(());
}
let stderr = String::from_utf8_lossy(&out.stderr);
// Cargo's first `error:` line is the cause; the rest is the trace. Fall back
// to the first non-empty line so an unfamiliar failure is still named rather
// than reported as an empty string, which would read like success.
let reason = stderr
.lines()
.find(|l| l.trim_start().starts_with("error"))
.or_else(|| stderr.lines().find(|l| !l.trim().is_empty()))
.unwrap_or("cargo metadata failed with no output")
.trim()
.to_string();
Err(reason)
}
/// [`resolves`] over many repo roots — the constellation sweep.
///
/// Returns only the failures, so an empty vec is the assertion. A root that is
/// not a cargo repo at all (no `Cargo.toml`) is skipped, not reported: this
/// guard is about repos that USED to resolve, not about directories.
pub fn unresolvable(roots: &[PathBuf]) -> Vec<Unresolvable> {
roots
.iter()
.filter(|r| r.join("Cargo.toml").is_file())
.filter_map(|r| resolves(r).err().map(|reason| Unresolvable { root: r.clone(), reason }))
.collect()
}
#[cfg(test)]
mod resolution_tests {
use super::*;
/// Write a minimal cargo repo and hand back its root.
fn repo(dir: &Path, manifest: &str) -> PathBuf {
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(dir.join("Cargo.toml"), manifest).unwrap();
std::fs::write(dir.join("src/lib.rs"), "").unwrap();
dir.to_path_buf()
}
/// A repo that resolves answers `Ok`; one whose feature names a feature that
/// does not exist does not — **and that is the exact shape that hid modgunn
/// for nine days** (`warehouse-sql = [..., "skade/sql"]` after skade dropped
/// `sql` on 2026-08-14). It is a RESOLUTION failure, so no amount of
/// building finds it: cargo cannot even enumerate the targets.
#[test]
fn a_feature_naming_a_feature_that_does_not_exist_does_not_resolve() {
let tmp = std::env::temp_dir().join(format!("law-resolves-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&tmp);
let good = repo(
&tmp.join("good"),
"[package]\nname = \"good\"\nversion = \"0.0.0\"\nedition = \"2021\"\n",
);
assert!(resolves(&good).is_ok(), "a plain repo resolves");
// The mutation: a feature that forwards to one its own dependency table
// cannot supply. Nothing else about the manifest changes.
let bad = repo(
&tmp.join("bad"),
"[package]\nname = \"bad\"\nversion = \"0.0.0\"\nedition = \"2021\"\n\
[features]\nwarehouse-sql = [\"nope/sql\"]\n",
);
let err = resolves(&bad).expect_err("a feature naming a missing dependency cannot resolve");
assert!(
err.to_lowercase().contains("error"),
"the reason is cargo's own first line, kept verbatim: {err}"
);
// The sweep reports only the failure, and reports it by name.
let bad_list = unresolvable(&[good.clone(), bad.clone()]);
assert_eq!(bad_list.len(), 1, "one of the two does not resolve: {bad_list:?}");
assert_eq!(bad_list[0].root, bad);
assert!(bad_list[0].describe().contains("does not resolve"));
// A directory that is not a cargo repo is SKIPPED, not reported — this
// guard is about repos that used to resolve, not about directories.
let plain = tmp.join("not-a-repo");
std::fs::create_dir_all(&plain).unwrap();
assert!(unresolvable(&[plain]).is_empty(), "a non-repo directory is not a failure");
let _ = std::fs::remove_dir_all(&tmp);
}
}
#[cfg(test)]
mod tests {
use super::*;
/// The `cfg` parser, on the four shapes this workspace actually contains plus
/// the two it must refuse to guess about.
///
/// This is the part that used to be a `strip_prefix("#![cfg(feature")`, which
/// meant `#![cfg(all(feature = "gfx-v2-gpu", …))]` — facett-demo's
/// `tests/gfx_v2_device.rs`, six test fns — was invisible to the whole
/// gated-test guard.
#[test]
fn a_cfg_names_the_features_it_requires_and_no_others() {
let f = |l: &str| required_features_of_cfg(l);
assert_eq!(f(r#"#![cfg(feature = "wgpu")]"#), ["wgpu"]);
assert_eq!(f(r#"#[cfg(feature = "wgpu")]"#), ["wgpu"]);
assert_eq!(
f(r#"#![cfg(all(feature = "gfx-v2-gpu", not(target_arch = "wasm32")))]"#),
["gfx-v2-gpu"]
);
assert_eq!(f(r#"#![cfg(all(feature = "a", feature = "b"))]"#), ["a", "b"]);
// NOT requirements: a negated feature, and an `any` alternative.
assert!(f(r#"#![cfg(not(feature = "wgpu"))]"#).is_empty());
assert!(f(r#"#[cfg(any(feature = "a", feature = "b"))]"#).is_empty());
// Not a cfg at all.
assert!(f(r#"#![cfg(not(target_arch = "wasm32"))]"#).is_empty());
assert!(f(r#"#[test]"#).is_empty());
assert!(f(r#"// #![cfg(feature = "wgpu")]"#).is_empty());
}
/// The scanner sees BOTH shapes, and tells them apart.
///
/// A per-`fn` gate is the quiet one: the target still reports `ok`, and the
/// gated fn is not even counted as `filtered out`. Before this, only the
/// crate-level shape was ever counted.
#[test]
fn the_scanner_finds_per_fn_gates_as_well_as_whole_file_ones() {
let dir = std::env::temp_dir().join(format!("facett_law_gated_{}", std::process::id()));
let tests = dir.join("tests");
std::fs::create_dir_all(&tests).expect("scratch dir");
std::fs::write(
tests.join("whole.rs"),
"#![cfg(all(feature = \"gfx\", not(target_arch = \"wasm32\")))]\n\
#[test]\nfn a() {}\n#[test]\nfn b() {}\n",
)
.unwrap();
std::fs::write(
tests.join("partial.rs"),
"#[test]\nfn cpu_one() {}\n\
#[cfg(feature = \"wgpu\")]\n#[test]\nfn gpu_before() {}\n\
#[test]\n#[cfg(feature = \"wgpu\")]\nfn gpu_after() {}\n\
#[test]\nfn cpu_two() {}\n",
)
.unwrap();
std::fs::write(tests.join("plain.rs"), "#[test]\nfn only() {}\n").unwrap();
let found = gated_test_files(&dir);
let _ = std::fs::remove_dir_all(&dir);
assert_eq!(found.len(), 2, "one whole-file entry and one per-fn entry: {found:#?}");
let whole = found.iter().find(|g| g.whole_file).expect("the crate-level cfg was seen");
assert_eq!(whole.feature, "gfx", "an `all(feature = …, not(…))` still names its feature");
assert_eq!(whole.tests, 2, "the whole target's fns go silent");
let partial = found.iter().find(|g| !g.whole_file).expect("the per-fn cfgs were seen");
assert_eq!(partial.feature, "wgpu");
assert_eq!(
partial.tests, 2,
"both `#[cfg]`-before-`#[test]` and `#[test]`-before-`#[cfg]` are gated fns"
);
assert!(
found.iter().all(|g| !g.file.ends_with("plain.rs")),
"an ungated file must not be reported as gated — that would make the guard cry wolf"
);
}
#[test]
fn comment_stripping_keeps_code_and_drops_prose() {
assert_eq!(strip_line_comment("let x = 1; // rayon::scope is banned").trim(), "let x = 1;");
assert_eq!(strip_line_comment("//! never rayon").trim(), "");
assert_eq!(strip_line_comment("let y = 2;").trim(), "let y = 2;");
}
#[test]
fn the_forbidden_tokens_are_not_spelled_literally_in_this_file() {
// The scanner covers its own source (no path carve-out), so the tokens
// must be assembled rather than written out — otherwise this module
// trips the law it enforces, and someone "fixes" it by adding an
// exemption that then hides real hits.
let me = include_str!("law.rs");
for tok in rayon_call_tokens() {
let literal_uses = me
.lines()
.filter(|l| !strip_line_comment(l).trim().is_empty())
.filter(|l| strip_line_comment(l).contains(&tok))
.count();
assert_eq!(literal_uses, 0, "`{tok}` appears literally in live code in law.rs");
}
}
/// **This test used to REQUIRE the blindness.** It asserted
/// `crate_level_cfg_feature("#[cfg(feature = \"wgpu\")]") == None` — i.e. that a
/// per-`fn` gate is not a gate — and so it was the reason nothing ever counted
/// the quiet shape. Both are gates; only the blast radius differs, and
/// [`GatedTestFile::whole_file`] carries that.
#[test]
fn both_gate_shapes_are_read_and_a_non_cfg_line_is_not() {
assert_eq!(required_features_of_cfg("#![cfg(feature = \"wgpu\")]"), ["wgpu"]);
assert_eq!(required_features_of_cfg(" #![cfg(feature=\"gpu\")] "), ["gpu"]);
assert_eq!(required_features_of_cfg("#[cfg(feature = \"wgpu\")]"), ["wgpu"]);
assert!(required_features_of_cfg("fn main() {}").is_empty());
}
/// The `guard-7(a)` case, verbatim in shape: korp's root manifest names
/// "members" in a header comment three lines above `[workspace]`. The old
/// `text.find("members")` landed in that comment, took `[workspace]`'s own
/// brackets as the array and returned `["workspace"]` — so korp's manifest
/// scan read 1 file where it should read 2, and the liveness assert in the
/// gate refused the green it could not stand behind.
#[test]
fn the_word_members_in_a_header_comment_is_not_the_members_key() {
let dir = scratch("header-comment");
std::fs::write(
dir.join("Cargo.toml"),
"# korp is the workspace ROOT. The `crates/*` leaves are NOT pulled in\n\
# as members — they are self-rooting.\n\
[workspace]\n\
resolver = \"3\"\n\
members = [\"xtask\"]\n\
exclude = [\"crates/korp-ontology\", \"crates/korp-demo\"]\n\
\n\
[package]\n\
name = \"korp\"\n\
exclude = [\"docs/*\"]\n",
)
.unwrap();
assert_eq!(workspace_members(&dir), vec!["xtask".to_string()]);
// `exclude` must come from `[workspace]`, not from `[package]`.
assert_eq!(
workspace_excludes(&dir),
vec!["crates/korp-ontology".to_string(), "crates/korp-demo".to_string()]
);
// Both, because korp *depends on* `crates/korp-demo` by path: a
// `rayon = "1"` there ships.
assert_eq!(
first_party_dirs(&dir),
vec![
"xtask".to_string(),
"crates/korp-ontology".to_string(),
"crates/korp-demo".to_string()
]
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_trailing_toml_comment_inside_the_array_is_not_a_member() {
let dir = scratch("array-comment");
std::fs::write(
dir.join("Cargo.toml"),
"[workspace]\nmembers = [\n \"a\", # the first one\n \"b\",\n]\n",
)
.unwrap();
assert_eq!(workspace_members(&dir), vec!["a".to_string(), "b".to_string()]);
std::fs::remove_dir_all(&dir).ok();
}
fn scratch(tag: &str) -> PathBuf {
let dir = std::env::temp_dir()
.join(format!("facett-law-{tag}-{}-{:?}", std::process::id(), std::thread::current().id()));
std::fs::create_dir_all(&dir).unwrap();
dir
}
/// The complete, verbatim stdout of
/// `cargo tree -e normal -i rayon --target all --all-features --offline
/// --locked`, run in korp on oden 2026-08-03. Not a hand-drawn tree: this is
/// the shape the guard has to read, dedupe markers (`(*)`), branch points
/// and all. A parser tested only against the clean case is a parser that has
/// never been asked to find anything.
const KORP_ALL_FEATURES_TREE: &str = "\
rayon v1.12.0
├── av-scenechange v0.14.1
│ └── rav1e v0.8.1
│ └── ravif v0.13.0
│ └── image v0.25.10
│ ├── arboard v3.6.1
│ │ └── egui-winit v0.35.0
│ │ └── eframe v0.35.0
│ │ ├── korp v0.1.0 (/home/alice/scratch/lawfix/korp)
│ │ └── nornir-robotui v0.3.0
│ │ └── korp v0.1.0 (/home/alice/scratch/lawfix/korp)
│ ├── dify v0.8.0
│ │ └── egui_kittest v0.35.0
│ │ └── nornir-robotui v0.3.0 (*)
│ ├── eframe v0.35.0 (*)
│ ├── egui_kittest v0.35.0 (*)
│ ├── facett-about v0.1.13 (/home/alice/scratch/lawfix/facett/facett-about)
│ │ └── korp v0.1.0 (/home/alice/scratch/lawfix/korp)
│ ├── korp v0.1.0 (/home/alice/scratch/lawfix/korp)
│ ├── nornir-robotui v0.3.0 (*)
│ └── webp v0.3.1
│ └── nornir-robotui v0.3.0 (*)
├── dify v0.8.0 (*)
├── image v0.25.10 (*)
├── maybe-rayon v0.1.1
│ └── rav1e v0.8.1 (*)
└── ravif v0.13.0 (*)
";
#[test]
fn the_tree_parser_reads_a_real_violation_chain_all_the_way_to_us() {
assert!(!tree_found_nothing(KORP_ALL_FEATURES_TREE));
// (1) A straight descent: rayon down to korp, first hit in the file.
let korp = Path::new("/home/alice/scratch/lawfix/korp");
let chain = tree_chain_to_first_party(KORP_ALL_FEATURES_TREE, korp)
.expect("the chain reaches korp and must be readable");
assert_eq!(
chain,
vec![
"korp",
"eframe",
"egui-winit",
"arboard",
"image",
"ravif",
"rav1e",
"av-scenechange",
"rayon"
]
);
// (2) The same capture read as FACETT's workspace. This is the case that
// makes the test able to fail: `facett-about` sits at depth 5, but the
// three lines before it descend to depth 7, so the walker must POP back
// up. A depth arithmetic that never pops still answers (1) correctly —
// every line in that path goes one level deeper — and answers this one
// with the stale `dify/egui_kittest/nornir-robotui` branch still on the
// stack. Measured: with the divisor wrong, (1) stayed green and this
// assert went red.
let facett = Path::new("/home/alice/scratch/lawfix/facett");
let via_facett = tree_chain_to_first_party(KORP_ALL_FEATURES_TREE, facett)
.expect("facett-about is first-party to the facett workspace");
assert_eq!(
via_facett,
vec!["facett-about", "image", "ravif", "rav1e", "av-scenechange", "rayon"]
);
// (3) …and it must not claim a chain for a workspace it is not looking at.
assert_eq!(tree_chain_to_first_party(KORP_ALL_FEATURES_TREE, Path::new("/nowhere")), None);
}
#[test]
fn nothing_to_print_is_the_clean_answer_and_empty_is_not_silently_the_same() {
// The literal notice cargo emits (on stderr) when `-i` finds no path.
let clean = "warning: nothing to print.\n\nTo find dependencies that require \
specific target platforms, try to use option `--target all` first.\n";
assert!(tree_found_nothing(clean));
assert!(tree_found_nothing(" \n"));
assert!(!tree_found_nothing(KORP_ALL_FEATURES_TREE));
// A clean tree names nothing of ours — the two answers must not collapse.
assert_eq!(tree_chain_to_first_party(clean, Path::new("/home/alice/scratch/lawfix/korp")), None);
}
#[test]
fn members_parse_from_a_multi_line_array() {
let dir = scratch("multiline");
std::fs::write(
dir.join("Cargo.toml"),
"[workspace]\nmembers = [\n \"a\",\n \"b\",\n]\n",
)
.unwrap();
assert_eq!(workspace_members(&dir), vec!["a".to_string(), "b".to_string()]);
std::fs::remove_dir_all(&dir).ok();
}
// ── the checkout-surface law ──────────────────────────────────────────────
/// The three manifest shapes this constellation actually writes, plus the two
/// this parser must not guess about.
///
/// The `[patch.crates-io]` case is the one that makes this test able to fail:
/// a parser that only reads `[dependencies]` tables drops every patch entry,
/// and a `[patch.crates-io]` path override is precisely the escape that
/// silently rebinds a build to a sibling checkout. `rustc-path` is the other
/// direction — a substring match on `path` claims it and invents an escape.
#[test]
fn a_manifest_names_the_paths_it_escapes_through_and_no_others() {
let text = r#"
[package]
name = "consumer"
# edda = { path = "../commented-out" }
[dependencies]
edda = { path = "../edda" }
facett-core = { version = "0.1", path = "../facett/facett-core" }
in-repo = { path = "crates/in-repo" }
plain = "1.0"
[dependencies.knut]
version = "0.2"
path = "../knut"
[target.'cfg(unix)'.dependencies.tunnr]
path = "../tunnr"
[patch.crates-io]
znippy = { path = "../znippy" }
[build]
rustc-path = "/usr/bin/rustc"
"#;
let got = path_deps_in_manifest(text);
assert_eq!(
got,
vec![
("edda".to_string(), "../edda".to_string()),
("facett-core".to_string(), "../facett/facett-core".to_string()),
("in-repo".to_string(), "crates/in-repo".to_string()),
("knut".to_string(), "../knut".to_string()),
("tunnr".to_string(), "../tunnr".to_string()),
("znippy".to_string(), "../znippy".to_string()),
],
"the commented line, the plain version dep and rustc-path must not appear"
);
}
/// `..` is folded lexically, because the target of a broken path dep does not
/// exist and `canonicalize` would only be able to say so by returning an error.
#[test]
fn a_path_that_does_not_exist_still_normalises() {
assert_eq!(normalise(Path::new("/a/b/../c/./d")), PathBuf::from("/a/c/d"));
assert_eq!(normalise(Path::new("/a/../../b")), PathBuf::from("/b"));
}
/// Run `git` with a fixed identity so the fixture does not depend on whatever
/// the box's global config happens to be.
fn gitx(repo: &Path, args: &[&str]) {
let out = Command::new("git")
.arg("-C")
.arg(repo)
.args(args)
.env("GIT_AUTHOR_NAME", "law fixture")
.env("GIT_AUTHOR_EMAIL", "law@example.invalid")
.env("GIT_COMMITTER_NAME", "law fixture")
.env("GIT_COMMITTER_EMAIL", "law@example.invalid")
.output()
.expect("git runs");
assert!(
out.status.success(),
"git {args:?} in {} failed: {}",
repo.display(),
String::from_utf8_lossy(&out.stderr)
);
}
fn write(p: &Path, text: &str) {
std::fs::create_dir_all(p.parent().unwrap()).unwrap();
std::fs::write(p, text).unwrap();
}
/// **The red proof.** A consumer workspace with one repo-escaping path dep,
/// pointed at a sibling checkout that is deliberately one commit behind its
/// own `origin/HEAD` — the exact shape of the three phantom findings of
/// 2026-08-22, where a stale primary checkout was read as a code defect.
///
/// The test asserts the guard reports **red first** and only then that the
/// same guard goes green once the sibling is fast-forwarded. A check proven
/// only in its green state cannot be told apart from a check that returns
/// the empty vector unconditionally — which is what nine guards on this box
/// were doing (LAW 2).
#[test]
fn a_sibling_parked_off_origin_head_is_named_before_the_build_can_lie_about_it() {
let root = scratch("siblings");
std::fs::remove_dir_all(&root).ok();
std::fs::create_dir_all(&root).unwrap();
// The sibling's remote: a normal repo we clone from, so `origin/HEAD` is
// set the way every real checkout on this box has it set.
let upstream = root.join("upstream-sib");
std::fs::create_dir_all(&upstream).unwrap();
gitx(&upstream, &["init", "-q", "-b", "main"]);
write(&upstream.join("Cargo.toml"), "[package]\nname = \"sib\"\nversion = \"0.1.0\"\n");
write(&upstream.join("src/lib.rs"), "pub fn v() -> u32 { 1 }\n");
gitx(&upstream, &["add", "Cargo.toml", "src/lib.rs"]);
gitx(&upstream, &["commit", "-qm", "sib v1"]);
// The sibling checkout the consumer's path dep will land in.
let sib = root.join("sib");
std::fs::remove_dir_all(&sib).ok();
gitx(&root, &["clone", "-q", upstream.to_str().unwrap(), "sib"]);
// Upstream moves on. The sibling does not — this is the whole disease.
write(&upstream.join("src/lib.rs"), "pub fn v() -> u32 { 2 }\npub fn added() {}\n");
gitx(&upstream, &["commit", "-qam", "sib v2"]);
gitx(&sib, &["fetch", "-q", "origin"]);
// The consumer: one escaping dep (`../sib`) and one in-repo dep, so the
// filter has something to be wrong about in both directions.
let consumer = root.join("consumer");
write(
&consumer.join("Cargo.toml"),
"[workspace]\nmembers = [\"crates/app\"]\n\n[package]\nname = \"consumer\"\n\n\
[dependencies]\nsib = { path = \"../sib\" }\napp = { path = \"crates/app\" }\n",
);
write(&consumer.join("crates/app/Cargo.toml"), "[package]\nname = \"app\"\n");
let escaping = escaping_path_deps(&consumer);
assert_eq!(escaping.len(), 1, "only `../sib` leaves the consumer repo, got {escaping:?}");
assert_eq!(escaping[0].name, "sib");
assert_eq!(escaping[0].resolved, normalise(&root.join("sib")));
// ── RED ──────────────────────────────────────────────────────────────
let bad = sibling_offenders(&consumer);
assert_eq!(bad.len(), 1, "the stale sibling must be reported, got {bad:?}");
match &bad[0].state {
SiblingState::Drifted { behind, ahead, .. } => {
assert_eq!((*behind, *ahead), (1, 0), "one commit behind origin/HEAD, none ahead");
}
other => panic!("expected Drifted, got {other:?}"),
}
assert!(
bad[0].describe().contains("DRIFTED"),
"the one-liner must say which way it failed: {}",
bad[0].describe()
);
assert_eq!(bad[0].checkout.as_deref(), Some(normalise(&root.join("sib")).as_path()));
// ── GREEN, and only now ──────────────────────────────────────────────
gitx(&sib, &["merge", "-q", "--ff-only", "origin/main"]);
let good = sibling_offenders(&consumer);
assert!(good.is_empty(), "a fast-forwarded sibling is clean, got {good:?}");
assert!(matches!(
checkout_state(&sib),
SiblingState::Aligned { dirty: 0, .. }
));
// ── the standard lane shape: DETACHED, and exactly right ─────────────
// `git worktree add --detach <repo> origin/main` is how nearly every lane
// on this box checks a sibling out. Grading that red would make the whole
// guard noise, so the SHA decides and only a detached HEAD that is NOT at
// origin/HEAD is unknown. Both directions asserted, or "detached is fine"
// would silently mean "detached is never checked".
gitx(&sib, &["checkout", "-q", "--detach", "origin/main"]);
assert!(
sibling_offenders(&consumer).is_empty(),
"a worktree detached AT origin/HEAD is the sanctioned lane shape"
);
gitx(&sib, &["checkout", "-q", "--detach", "origin/main~1"]);
let det = sibling_offenders(&consumer);
assert_eq!(det.len(), 1);
assert!(matches!(det[0].state, SiblingState::Detached { .. }), "{}", det[0].describe());
gitx(&sib, &["checkout", "-q", "main"]);
// ── and dirt on an aligned ref is still not the ref ───────────────────
write(&sib.join("src/lib.rs"), "pub fn v() -> u32 { 99 }\n");
let dirty = sibling_offenders(&consumer);
assert_eq!(dirty.len(), 1, "uncommitted source under an aligned ref is a different build");
assert!(dirty[0].describe().contains("DIRTY"), "{}", dirty[0].describe());
// ── and a sibling that is not there at all is the other real failure ──
std::fs::remove_dir_all(&sib).unwrap();
let gone = sibling_offenders(&consumer);
assert_eq!(gone.len(), 1);
assert_eq!(gone[0].state, SiblingState::Missing, "{}", gone[0].describe());
std::fs::remove_dir_all(&root).ok();
}
}