Skip to main content

xtask/
greenfield_status.rs

1//! `cargo xtask greenfield-status` — the probe harness (track doc §8, proposal #999).
2//!
3//! Twenty-one probes measuring the tree against `design/bynk-greenfield-compiler.md`:
4//! the twelve in track doc §8, `emit_abi_shapes` (ADR 0310's probe, #999 Decision E —
5//! this slice measures the emit-ABI enumeration guard but does not wire it; wiring is
6//! packaging-track work), phase 7's own four — `ts_writes`, `ts_any`,
7//! `verbatim_origins`, `verbatim_sites` (P7.0/#1296, P7.5/#1307 — see phase 7's own
8//! closing summary in `design/archive/retired-tracks.md`) — and phase 8's own two,
9//! `incremental_query_types` and `keystroke_latency` (P8.0/#1510, settled by #1509's
10//! Q5/ADR 0414 — see `design/tracks/incrementality.md` §5), plus the IR cutover
11//! track's own adoption probe, `unconsumed_ir_items` (Slice D3 of #1542 — the gate the
12//! 30 August 2026 post-restructuring review's Part 5 §8 asked for), and the
13//! runtime-semantics track's `diagnostic_coverage` (#1662, G2 of #1648).
14//!
15//! **Sixteen are gated**, committed and diffed: `workspace_lints`, `fs_below_driver`,
16//! `options_sources`, `hoist_sinks`, `span_keyed_maps`, `emit_diagnostics`,
17//! `ide_emit_edge`, `ast_importers`, `emit_abi_shapes`, `ts_writes`, `ts_any`,
18//! `verbatim_origins`, `verbatim_sites`, `incremental_query_types`,
19//! `unconsumed_ir_items`, `diagnostic_coverage`. Ten of these are
20//! zero/closure-shaped — a boolean, or a count pinned at a small, argued floor
21//! (`ast_importers` = 5, `emit_abi_shapes` = 1). Phase 7's own four are the same shape:
22//! each converged toward an argued floor over dozens of slices, the same trajectory
23//! `ast_importers` had throughout phase 6's 59 — retired at `ts_writes` = 809,
24//! `ts_any` = 26, `verbatim_origins` = 1, `verbatim_sites` = 2 (phase 7's own closing
25//! summary, `design/archive/retired-tracks.md`), none the literal 0 first proposed —
26//! gated throughout despite the churn that implies, a deliberate call argued in ADR
27//! 0389/ADR 0390 (review of #1297), not an oversight of #999 Decision D's
28//! churn-avoidance principle. `incremental_query_types` is a different shape again —
29//! phase 8's own probe reads a one-time existence proof, not a count trending toward a
30//! floor (settled §5/Q5, ADR 0414), re-settled by #1537 to certify that the two
31//! levels that never found a consumer stay deleted; see its own doc comment. A
32//! disagreement between a
33//! fresh run and the committed table fails `greenfield_status_table_is_current`
34//! (`xtask/tests/greenfield_status.rs`), which rides both the `test` job (`cargo test
35//! --workspace`, any Rust-touching PR) and the `drift` job's existing `cargo test -p
36//! xtask` (pending/decisions-only PRs) — no new CI wiring (#999 Decision D, which also
37//! explains why a `drift`-job *step* would have been silently skipped on the PRs that
38//! move these probes most).
39//!
40//! **Five are count/ratio trend probes**, recomputed and printed but never diffed:
41//! `wildcard_arms`, `keep_in_sync`, `test_density`, `fixture_kinds`,
42//! `keystroke_latency`. The first four move on nearly any ordinary Rust PR with no
43//! slice actively driving them toward a floor (§8 calls two of them "trends, not
44//! gates"); hard-gating them would make the committed table churn, and conflict, on
45//! routine work for no corresponding benefit. `keystroke_latency` moves on nothing
46//! yet — settled (Q3/ADR 0414) as staying "not measured" for phase 8's own whole
47//! lifetime, since no scheduler ships this phase to produce a real number; reported
48//! anyway so the trajectory's own §3.0 baseline has a live, CI-computed row.
49//!
50//! `Closes-Rule:` rule-id provenance (#999 Decision B) is deferred to a follow-on
51//! slice — the committed table below carries no rule-citation column yet.
52
53use std::collections::BTreeSet;
54use std::fmt::Write as _;
55use std::path::{Path, PathBuf};
56use std::process::Command;
57
58/// One probe's result. `gated` probes are diffed against the committed table by
59/// [`crate::greenfield_status::gated_disagreements`]; the rest are reported only.
60pub struct Probe {
61    pub name: &'static str,
62    pub gated: bool,
63    pub reads: String,
64}
65
66pub struct Report {
67    pub probes: Vec<Probe>,
68}
69
70impl Report {
71    pub fn get(&self, name: &str) -> &str {
72        self.probes
73            .iter()
74            .find(|p| p.name == name)
75            .map(|p| p.reads.as_str())
76            .unwrap_or_else(|| panic!("no probe named {name:?}"))
77    }
78}
79
80/// Run every probe against the tree rooted at `root` (the repo root). Used by the CLI's
81/// full report; the gating test uses the sixteen gated probes alone
82/// ([`gated_disagreements`]) so it never pays for a workspace-wide clippy pass
83/// (`wildcard_arms`) just to check the probes that are actually diffed.
84pub fn run(root: &Path) -> Report {
85    let mut probes = run_gated(root);
86    probes.extend(run_trend(root));
87    Report { probes }
88}
89
90/// The sixteen gated (zero/closure) probes only — what [`gated_disagreements`] diffs.
91fn run_gated(root: &Path) -> Vec<Probe> {
92    vec![
93        workspace_lints(root),
94        fs_below_driver(root),
95        options_sources(root),
96        hoist_sinks(root),
97        span_keyed_maps(root),
98        emit_diagnostics(root),
99        ide_emit_edge(root),
100        ast_importers(root),
101        emit_abi_shapes(root),
102        ts_writes(root),
103        ts_any(root),
104        verbatim_origins(root),
105        verbatim_sites(root),
106        incremental_query_types(root),
107        unconsumed_ir_items(root),
108        diagnostic_coverage(root),
109    ]
110}
111
112/// The five reported-only trend probes — never diffed, and notably including the one
113/// (`wildcard_arms`) that shells out to a full `cargo clippy --workspace` pass, which
114/// the gating test must not pay for on every run.
115fn run_trend(root: &Path) -> Vec<Probe> {
116    vec![
117        wildcard_arms(root),
118        keep_in_sync(root),
119        test_density(root),
120        fixture_kinds(root),
121        keystroke_latency(root),
122    ]
123}
124
125/// `design/greenfield-status.md` — the committed table this probe set regenerates.
126pub fn table_path(root: &Path) -> PathBuf {
127    root.join("design/greenfield-status.md")
128}
129
130// --- Filesystem helpers --------------------------------------------------
131
132/// Every `.rs` file under `dir`, recursively, as `(path, contents)`. Unreadable files
133/// (permissions, non-UTF-8) are skipped rather than failing the whole walk — this is a
134/// measurement tool, not a build step.
135fn rust_files(dir: &Path) -> Vec<(PathBuf, String)> {
136    let mut out = Vec::new();
137    walk(dir, &mut out);
138    out
139}
140
141fn walk(dir: &Path, out: &mut Vec<(PathBuf, String)>) {
142    let Ok(entries) = std::fs::read_dir(dir) else {
143        return;
144    };
145    let mut entries: Vec<_> = entries.flatten().collect();
146    entries.sort_by_key(|e| e.file_name());
147    for entry in entries {
148        let path = entry.path();
149        if path.is_dir() {
150            walk(&path, out);
151        } else if path.extension().is_some_and(|e| e == "rs")
152            && let Ok(contents) = std::fs::read_to_string(&path)
153        {
154            out.push((path, contents));
155        }
156    }
157}
158
159/// The inner text of every **standalone** `"bynk.<ident>"` string literal (the
160/// `bynk.*` convention used for diagnostic codes and commons/namespace paths alike).
161///
162/// Standalone, not merely prefix-matching: the identifier run must be immediately
163/// followed by the closing quote, matching the naive `rg -o '"bynk\.[a-zA-Z0-9_.]*"'`
164/// this probe is deliberately more careful than (#999 Decision A). Without that
165/// requirement this would also match the *start* of an unrelated, longer message that
166/// merely happens to begin with "bynk." — e.g. a panic string
167/// `"bynk.map itself uses bynk.list, so list must be injected too: {paths:?}"` is prose
168/// beginning with a namespace-shaped word, not a `"bynk.map"` code literal, and a
169/// dev-only compile-time error message split across lines with a `\`-continuation
170/// (`"bynk.emit.unresolved_cross_context_signature: no signature for \` ...) is one
171/// string, not a diagnostic-code literal, even though its first segment matches the
172/// identifier charset. Both were found — and wrongly counted — by an earlier,
173/// less careful version of this scan; the fix is requiring the closing quote.
174fn bynk_dotted_literals(src: &str) -> Vec<&str> {
175    let mut out = Vec::new();
176    let bytes = src.as_bytes();
177    let mut i = 0;
178    while let Some(rel) = src[i..].find("\"bynk.") {
179        let start = i + rel + 1; // skip the opening quote
180        let mut end = start;
181        while end < bytes.len()
182            && (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'_' || bytes[end] == b'.')
183        {
184            end += 1;
185        }
186        if end < bytes.len() && bytes[end] == b'"' {
187            out.push(&src[start..end]);
188        }
189        i = end.max(start + 1);
190    }
191    out
192}
193
194/// True if `line`, trimmed, is a `//` or `///` or `//!` line comment. Doesn't attempt
195/// block comments (`/* */`) — none of this codebase's `bynk.*`/dead-identifier
196/// mentions live in one.
197fn is_line_comment(line: &str) -> bool {
198    line.trim_start().starts_with("//")
199}
200
201// --- Gated probe 1: workspace_lints --------------------------------------
202
203/// R2.12. `[workspace.lints]` presence and `clippy::wildcard_enum_match_arm`'s level in
204/// the root `Cargo.toml`. A boolean-shaped probe (not a count) — gated because it only
205/// ever changes once, when T0.3 lands it.
206fn workspace_lints(root: &Path) -> Probe {
207    let cargo_toml = std::fs::read_to_string(root.join("Cargo.toml")).unwrap_or_default();
208    let has_section = cargo_toml
209        .lines()
210        .any(|l| l.trim() == "[workspace.lints.clippy]" || l.trim() == "[workspace.lints]");
211    let level = cargo_toml
212        .lines()
213        .find(|l| l.contains("wildcard_enum_match_arm"))
214        .map(|l| l.trim().to_string());
215    let reads = match (has_section, level) {
216        (true, Some(l)) => format!("present — {l}"),
217        (true, None) => "present, wildcard_enum_match_arm not set".to_string(),
218        (false, _) => "absent".to_string(),
219    };
220    Probe {
221        name: "workspace_lints",
222        gated: true,
223        reads,
224    }
225}
226
227// --- Gated probe 2: fs_below_driver --------------------------------------
228
229/// R2.3. Files under `bynk-emit/src`, `bynk-ide/src`, `bynk-fmt/src` (the crates below
230/// the `bynk` driver, which owns disk I/O) that touch `std::fs` in **production** code.
231///
232/// Excludes usage inside a trailing `#[cfg(test)] mod tests { ... }` block — the
233/// convention every file in this codebase uses, always the last item in the file. A
234/// line is production-scope unless it falls at or after the line following a
235/// `#[cfg(test)]` attribute whose very next non-empty line opens a `mod ... {` block
236/// (as opposed to a `mod name;` external-file declaration, which is not a scope at
237/// all). This mirrors the comment-exclusion discipline elsewhere in this probe set:
238/// tests writing fixtures to a tempdir are not "the driver's job" bypassed, and
239/// counting them would report a rule open that the production code has already closed.
240///
241/// A file counts if its own text names `std::fs` ([`has_production_std_fs`]), **or** if
242/// a bare `fs::`-style call site in it resolves to `std::fs` through its imports
243/// ([`production_std_fs_files`]) — a module-level `use std::fs;` in a parent module is
244/// visible to a child through `use super::*;` (module privacy is ancestor-scoped), so
245/// `bynk-emit/src/project/discovery.rs` reads and walks the filesystem while never
246/// spelling `std::fs` itself. The literal text scan alone missed exactly that file,
247/// so a probe reading `bynk-emit=0` would have asserted R2.3 closed on a false
248/// premise (#1013).
249///
250/// #1104 (a content-ownership (#1086) probe-precision follow-on): a flagged *count*
251/// alone can't tell a residual R2.3 violation from a documented, permanent exception —
252/// `bynk-emit` read 3 that way from the track's retirement (`design/archive/
253/// retired-tracks.md`'s closing summary) until P4.0 moved all three named files out of
254/// `bynk-emit` entirely, leaving [`NAMED_FS_EXCEPTIONS`] empty (#1561) — the mechanism
255/// stays live for whatever named exception is decided next. So each flagged file is
256/// additionally classified as a **named floor** file — every
257/// production-scope touch it has is either inside one of those named functions, or is
258/// a bare import declaration (no fn encloses it — [`enclosing_fn`] returns `None`) that
259/// performs no I/O of its own, existing only so a *descendant* module's bare `fs::`
260/// call can resolve (the motivating case, #1013: `project.rs`'s own `use std::fs;`,
261/// which `discovery.rs` and `paths.rs` glob-imported via `use super::*;` before P4.0
262/// moved both files out) — or a **residual** file: any other file
263/// touching `std::fs` in production scope, which still reads as a real R2.3 violation
264/// ([`file_is_named_fs_floor`]).
265fn fs_below_driver(root: &Path) -> Probe {
266    let crates = ["bynk-emit", "bynk-ide", "bynk-fmt"];
267    let mut per_crate = Vec::new();
268    let mut total = 0usize;
269    let mut total_floor = 0usize;
270    for krate in crates {
271        let dir = root.join(krate).join("src");
272        let files: Vec<(PathBuf, String)> = rust_files(&dir)
273            .into_iter()
274            .map(|(path, contents)| {
275                let rel = path.strip_prefix(&dir).unwrap_or(&path).to_path_buf();
276                (rel, contents)
277            })
278            .collect();
279        let flagged = production_std_fs_files(&files);
280        let count = flagged.len();
281        total += count;
282        let facts: Vec<FsImportFacts> = files.iter().map(|(_, s)| fs_import_facts(s)).collect();
283        let parents: Vec<Option<usize>> = files
284            .iter()
285            .map(|(p, _)| module_parent(p, &files))
286            .collect();
287        let floor = flagged
288            .iter()
289            .filter(|&&i| {
290                file_is_named_fs_floor(krate, &files, &facts, &parents, i, NAMED_FS_EXCEPTIONS)
291            })
292            .count();
293        total_floor += floor;
294        let residual = count - floor;
295        per_crate.push(if floor > 0 {
296            format!("{krate}={count} ({floor} named floor, {residual} residual)")
297        } else {
298            format!("{krate}={count}")
299        });
300    }
301    Probe {
302        name: "fs_below_driver",
303        gated: true,
304        reads: format!(
305            "{total} files ({}) — {total_floor} named floor, {} residual total",
306            per_crate.join(", "),
307            total - total_floor
308        ),
309    }
310}
311
312/// #1104: the specific, permanently-carved-out production functions whose
313/// `std::fs` touch is a *named* exception, not evidence of unfinished R2.3
314/// migration — settled in `design/tracks/content-ownership.md` §3.2 (retired) and
315/// its closing summary in `design/archive/retired-tracks.md`. `(crate, file path
316/// relative to that crate's `src/`, enclosing production fn name)`. A future
317/// carve-out decided the same deliberate way joins this list; anything touching
318/// `std::fs` in production scope that isn't listed here reads as a residual R2.3
319/// violation, per [`file_is_named_fs_floor`].
320///
321/// Empty since #1561: these three entries were real when #1104 (`769a60a3`,
322/// 6 Aug 2026) added them, but P4.0 (`69af8f2d`, the very next day) moved
323/// `discover_bynk_files`/`read_adapter_binding` (`project/discovery.rs`) and
324/// `try_read_project_paths` (`project/paths.rs`) out of `bynk-emit` into
325/// `bynk-project` entirely — dead from that point on, unnoticed for weeks.
326/// `fs_below_driver`'s `bynk-emit` reading was already 0 with them present
327/// (nothing in the tree matched the dead tuples), confirmed unchanged with
328/// them gone.
329const NAMED_FS_EXCEPTIONS: &[(&str, &str, &str)] = &[];
330
331/// Is flagged file `files[i]` (already known, by [`production_std_fs_files`], to touch
332/// `std::fs` in production scope) a **named floor** file — every production-scope touch
333/// it has is either inside a [`NAMED_FS_EXCEPTIONS`] function for this exact
334/// `(krate, file)`, or a bare `use` import declaration (which reads but performs no
335/// filesystem operation by itself, unlike a module-scope `static`/`const` initialiser or
336/// macro invocation that might)? `facts`/`parents` are the caller's already-computed
337/// [`fs_import_facts`]/[`module_parent`] vectors for `files`, threaded through rather
338/// than recomputed per flagged file.
339///
340/// A single disallowed touch — inside an unlisted fn, inside a listed fn's *file* but
341/// wrong *name*, or outside every fn and not a plain import — makes the whole file
342/// residual: partial credit isn't meaningful here, since the point is "can a reader stop
343/// cross-referencing track docs for this file," not a ratio. Likewise, a file this
344/// function attributes *no* touch line to at all (despite the caller already knowing it's
345/// flagged — [`line_touches_std_fs`]'s re-implementation of the file-level detection
346/// disagreeing with it) reads as residual, not floor: an unattributable touch means this
347/// classifier doesn't understand the file, which must fail loud, not quiet.
348///
349/// `exceptions` is [`NAMED_FS_EXCEPTIONS`] at the one production call site
350/// ([`fs_below_driver`]) — parameterised (#1561) so a test can exercise the
351/// matching logic against a synthetic tuple instead of real, currently-empty
352/// production data, the same way `facts`/`parents` are caller-supplied rather
353/// than recomputed.
354fn file_is_named_fs_floor(
355    krate: &str,
356    files: &[(PathBuf, String)],
357    facts: &[FsImportFacts],
358    parents: &[Option<usize>],
359    i: usize,
360    exceptions: &[(&str, &str, &str)],
361) -> bool {
362    let (path, _) = &files[i];
363    let rel = path.to_string_lossy().replace('\\', "/");
364    let lines: Vec<&str> = files[i].1.lines().collect();
365    let ranges = test_mod_ranges(&lines);
366    let fn_ranges = production_fn_ranges(&lines, &ranges);
367
368    let mut saw_touch = false;
369    for (li, line) in lines.iter().enumerate() {
370        if in_test_range(li, &ranges) {
371            continue;
372        }
373        if !line_touches_std_fs(i, line, facts, parents, files) {
374            continue;
375        }
376        saw_touch = true;
377        let Some(fn_name) = enclosing_fn(li, &fn_ranges) else {
378            // No enclosing fn is harmless only when the line is literally an import
379            // declaration. A module-scope `static`/`const` initialiser, a macro
380            // invocation, or a fn shape `fn_name_on_line` can't parse (`extern "C" fn`)
381            // does real I/O outside every known range and must read as residual.
382            if use_declaration(line).is_some() {
383                continue;
384            }
385            return false;
386        };
387        let named = exceptions
388            .iter()
389            .any(|&(c, f, func)| c == krate && f == rel && func == fn_name);
390        if !named {
391            return false;
392        }
393    }
394    saw_touch
395}
396
397/// Does `line` (already known to be production-scope) itself touch `std::fs` — by the
398/// same two means [`production_std_fs_files`] checks at file granularity, applied here
399/// to one line: a literal `std::fs` substring, or a bare/qualified path this line spells
400/// that resolves to `std::fs` through file `i`'s visible import bindings.
401fn line_touches_std_fs(
402    i: usize,
403    line: &str,
404    facts: &[FsImportFacts],
405    parents: &[Option<usize>],
406    files: &[(PathBuf, String)],
407) -> bool {
408    if line.contains("std::fs") {
409        return true;
410    }
411    let mut roots = BTreeSet::new();
412    collect_bare_path_roots(line, &mut roots);
413    if roots.iter().any(|name| {
414        matches!(
415            resolve_name_in_module(i, name, facts, parents),
416            NameResolution::StdFs
417        )
418    }) {
419        return true;
420    }
421    let mut chains = BTreeSet::new();
422    collect_qualified_paths(line, &mut chains);
423    chains
424        .iter()
425        .any(|chain| qualified_chain_reaches_std_fs(chain, i, facts, parents, files))
426}
427
428/// The name and inclusive body line-range of every production-scope `fn` in `lines`
429/// (`test_ranges` excluded, same as everywhere else in this probe) — used by
430/// [`file_is_named_fs_floor`] to attribute a flagged touch line to its enclosing
431/// function. A wrapped signature (the `{` arriving lines after the `fn` line, past a
432/// multi-line parameter list) is handled the same way [`test_mod_ranges`] handles a
433/// `mod` line: brace depth is tracked starting at the `fn` line itself, but a parameter
434/// list has no `{`/`}` in it, so `started` only flips true once the real body-opening
435/// brace arrives, however many lines later.
436fn production_fn_ranges(
437    lines: &[&str],
438    test_ranges: &[(usize, usize)],
439) -> Vec<(String, usize, usize)> {
440    let mut out = Vec::new();
441    for (i, line) in lines.iter().enumerate() {
442        if in_test_range(i, test_ranges) {
443            continue;
444        }
445        let Some(name) = fn_name_on_line(line) else {
446            continue;
447        };
448        let mut state = BraceScanState::Normal;
449        let mut depth = 0i32;
450        let mut started = false;
451        let mut end = lines.len() - 1;
452        for (j, l) in lines[i..].iter().enumerate() {
453            let (delta, new_state) = brace_delta(l, state);
454            state = new_state;
455            depth += delta;
456            if depth != 0 {
457                started = true;
458            }
459            if started && depth == 0 {
460                end = i + j;
461                break;
462            }
463        }
464        out.push((name, i, end));
465    }
466    out
467}
468
469/// The leading `fn NAME` on `line`, past an optional `pub`/`pub(...)`, `async`,
470/// `unsafe`, `const` modifier run (in any order/repetition, mirroring
471/// [`collect_declared_type_name`]'s `pub`-stripping) — `None` if `line` doesn't open a
472/// function at all (a call site, a doc comment mentioning "fn", a closure). Doesn't
473/// require a trailing `{` or even `(` on this same line — a wrapped signature's `fn`
474/// line can end right at the name.
475fn fn_name_on_line(line: &str) -> Option<String> {
476    let mut t = line.trim();
477    loop {
478        if let Some(rest) = t.strip_prefix("pub") {
479            let rest = rest.trim_start();
480            t = if let Some(after_paren) = rest.strip_prefix('(') {
481                after_paren.split_once(')')?.1.trim_start()
482            } else {
483                rest
484            };
485            continue;
486        }
487        let mut advanced = false;
488        for kw in ["async ", "unsafe ", "const "] {
489            if let Some(rest) = t.strip_prefix(kw) {
490                t = rest.trim_start();
491                advanced = true;
492                break;
493            }
494        }
495        if !advanced {
496            break;
497        }
498    }
499    let rest = t.strip_prefix("fn ")?;
500    let end = rest
501        .find(|c: char| !c.is_ascii_alphanumeric() && c != '_')
502        .unwrap_or(rest.len());
503    if end == 0 {
504        return None;
505    }
506    Some(rest[..end].to_string())
507}
508
509/// The innermost [`production_fn_ranges`] entry containing `line_idx`, by name — `None`
510/// if `line_idx` sits outside every production fn (module scope: a `use` declaration,
511/// a `const`/`static`, or a `struct`/`enum` body).
512fn enclosing_fn(line_idx: usize, fn_ranges: &[(String, usize, usize)]) -> Option<String> {
513    fn_ranges
514        .iter()
515        .filter(|(_, start, end)| line_idx >= *start && line_idx <= *end)
516        .min_by_key(|(_, start, end)| end - start)
517        .map(|(name, _, _)| name.clone())
518}
519
520/// The literal text component of [`fs_below_driver`]: some production-scope line names
521/// `std::fs`. Necessary but not sufficient (#1013) — a file can touch `std::fs`
522/// through a glob-imported parent binding without ever spelling it; that resolution
523/// lives in [`production_std_fs_files`], which layers on top of this scan.
524fn has_production_std_fs(src: &str) -> bool {
525    let lines: Vec<&str> = src.lines().collect();
526    let ranges = test_mod_ranges(&lines);
527    for (i, line) in lines.iter().enumerate() {
528        if in_test_range(i, &ranges) {
529            continue;
530        }
531        if line.contains("std::fs") {
532            return true;
533        }
534    }
535    false
536}
537
538/// Indices (into `files`, whose paths are relative to the crate's `src/` root) of the
539/// files that touch `std::fs` in production code — the union of the literal text scan
540/// ([`has_production_std_fs`]) and import resolution: a path whose leading module
541/// segment a production `use` declaration binds to `std::fs` (or an item under it),
542/// either a bare `NAME::` root resolved in the file itself (`use std::{fs, io};` — a
543/// form the substring scan can't see) or in an ancestor module reached through
544/// `use super::*;`, transitively (#1013), or a `super::`/`self::`/`crate::`-qualified
545/// path walked through the module tree to the same bindings (#1016 review — a
546/// descendant may spell `super::fs::read_to_string(p)` with no glob import at all,
547/// one disambiguating edit away from a currently-flagged bare call).
548///
549/// Resolution is Rust-shaped, not hand-tracked (#1013 rejects a special-case list):
550/// a private `use std::fs;` in a parent is visible to descendants because module
551/// privacy is ancestor-scoped, a chain of `use super::*;` globs re-reaches it from
552/// any depth, and a nearer binding of the same name shadows a farther one — whether
553/// that binding is another `use` or a locally-declared type-namespace item (`mod fs;`,
554/// `struct File`, …; value-namespace items like `fn` can't head a `NAME::` path, so
555/// they don't shadow one) — so a child that binds `fs` to something else keeps its
556/// bare `fs::` calls unflagged. Visibility is *not* modelled: a path that names a
557/// too-private binding wouldn't compile anyway, so over-approximating is safe.
558///
559/// Known remaining gaps, accepted as out of reach for a text-level scanner: an
560/// ancestor's `use std::fs::read_to_string;` item import called bare (`read_to_string(p)`)
561/// presents no `::` path segment to resolve — the same import used as a path root
562/// (`File::open`) **is** caught, since item bindings under `std::fs` participate in
563/// the same resolution — and a `use` declaration rustfmt has split across lines is
564/// not parsed. #1013 grepped the three scanned crates for the item-import form, and
565/// the #1016 review for the qualified-path and split-declaration forms — zero hits.
566fn production_std_fs_files(files: &[(PathBuf, String)]) -> Vec<usize> {
567    let facts: Vec<FsImportFacts> = files.iter().map(|(_, src)| fs_import_facts(src)).collect();
568    let parents: Vec<Option<usize>> = files
569        .iter()
570        .map(|(path, _)| module_parent(path, files))
571        .collect();
572    (0..files.len())
573        .filter(|&i| {
574            has_production_std_fs(&files[i].1)
575                || resolves_bare_std_fs(i, &facts, &parents)
576                || resolves_qualified_std_fs(i, &facts, &parents, files)
577        })
578        .collect()
579}
580
581/// Per-file production-scope import facts for [`production_std_fs_files`]'s
582/// resolution. All fields exclude `#[cfg(test)] mod` ranges — a test module's
583/// `use super::*;` or tempdir `fs::write` must not make the file, or its children,
584/// read as production `std::fs` (the `bynk-ide` files' shape).
585#[derive(Default)]
586struct FsImportFacts {
587    /// A production `use super::*;` (optionally `pub`-qualified) — the edge that lets
588    /// this file see its parent module's `use` bindings, and (chained) its ancestors'.
589    glob_imports_super: bool,
590    /// Names production `use` declarations bind to `std::fs` or an item under it:
591    /// `use std::fs;` → `fs`, `use std::fs as x;` → `x`, `use std::{fs, io};` → `fs`,
592    /// `use std::fs::File;` → `File`.
593    std_fs_bindings: BTreeSet<String>,
594    /// Every name a production `use` declaration binds, whatever the target — the
595    /// shadow set: a nearer non-`std::fs` binding of a candidate name stops resolution.
596    use_bound_names: BTreeSet<String>,
597    /// Type-namespace items the file declares (`mod fs;`, `struct File`, `enum`,
598    /// `trait`, `type`, `union`) — these beat a glob-imported name in real Rust, so
599    /// they join [`Self::use_bound_names`] on the shadow side of resolution (#1016
600    /// review). Value-namespace items (`fn`, `const`, `static`) can't head a `NAME::`
601    /// module path and are deliberately not collected.
602    declared_type_names: BTreeSet<String>,
603    /// Identifiers appearing as a bare path root `NAME::` (not preceded by another
604    /// path segment) on a production line — the call-site side of the resolution.
605    bare_path_roots: BTreeSet<String>,
606    /// Segment chains of `super::`/`self::`/`crate::`-qualified paths on production
607    /// lines — `super::fs::read_to_string` records `["super", "fs", "read_to_string"]`.
608    /// These need no glob import to reach an ancestor's binding (#1016 review).
609    qualified_paths: BTreeSet<Vec<String>>,
610}
611
612fn fs_import_facts(src: &str) -> FsImportFacts {
613    let lines: Vec<&str> = src.lines().collect();
614    let ranges = test_mod_ranges(&lines);
615    let mut facts = FsImportFacts::default();
616    for (i, line) in lines.iter().enumerate() {
617        if in_test_range(i, &ranges) {
618            continue;
619        }
620        if let Some(decl) = use_declaration(line) {
621            if decl == "super::*" {
622                facts.glob_imports_super = true;
623            }
624            collect_use_bindings("", decl, &mut facts);
625        }
626        collect_declared_type_name(line, &mut facts.declared_type_names);
627        collect_bare_path_roots(line, &mut facts.bare_path_roots);
628        collect_qualified_paths(line, &mut facts.qualified_paths);
629    }
630    facts
631}
632
633/// The path text of a single-line `use` declaration — `use std::fs;` → `std::fs`,
634/// with an optional `pub`/`pub(crate)`/`pub(in …)` prefix stripped and a trailing
635/// `//` comment tolerated (`use super::*; // parent's fs` must not silently sever
636/// the glob edge for a whole subtree — #1016 review; safe to split on `//` because
637/// a `use` path can contain neither a comment marker nor a string). A declaration
638/// rustfmt has split across lines has no trailing `;` here and is not recognised —
639/// none of the `std::fs` forms in the scanned crates are long enough to split.
640fn use_declaration(line: &str) -> Option<&str> {
641    let mut t = line.trim();
642    if let Some(rest) = t.strip_prefix("pub") {
643        let rest = rest.trim_start();
644        t = if let Some(after_paren) = rest.strip_prefix('(') {
645            after_paren.split_once(')')?.1.trim_start()
646        } else {
647            rest
648        };
649    }
650    let body = t.strip_prefix("use ")?;
651    let body = body.split("//").next().unwrap_or(body);
652    body.trim().strip_suffix(';').map(str::trim)
653}
654
655/// If `line` declares a type-namespace item — `mod`/`struct`/`enum`/`trait`/`type`/
656/// `union`, optionally `pub`-qualified, optionally `unsafe` (traits) — record its
657/// name. Field/variable positions can't start a trimmed line with these keywords, so
658/// a leading-keyword scan is enough for rustfmt-shaped code.
659fn collect_declared_type_name(line: &str, out: &mut BTreeSet<String>) {
660    let mut t = line.trim();
661    if let Some(rest) = t.strip_prefix("pub") {
662        let rest = rest.trim_start();
663        t = if let Some(after_paren) = rest.strip_prefix('(') {
664            match after_paren.split_once(')') {
665                Some((_, after)) => after.trim_start(),
666                None => return,
667            }
668        } else {
669            rest
670        };
671    }
672    if let Some(rest) = t.strip_prefix("unsafe ") {
673        t = rest.trim_start();
674    }
675    for kw in ["mod ", "struct ", "enum ", "trait ", "type ", "union "] {
676        if let Some(rest) = t.strip_prefix(kw) {
677            let rest = rest.trim_start();
678            let end = rest
679                .find(|c: char| !c.is_ascii_alphanumeric() && c != '_')
680                .unwrap_or(rest.len());
681            if end > 0 {
682                out.insert(rest[..end].to_string());
683            }
684            return;
685        }
686    }
687}
688
689/// Record the name(s) a `use` path binds into `facts` — `path::to::name`,
690/// `path as alias`, and brace groups (`std::{fs, path::PathBuf}`, nested one level
691/// per recursion). `prefix` is the already-consumed leading path (empty at the top).
692fn collect_use_bindings(prefix: &str, entry: &str, facts: &mut FsImportFacts) {
693    let entry = entry.trim();
694    if entry.is_empty() {
695        return;
696    }
697    if let Some((path_part, group)) = entry.split_once('{') {
698        let inner_prefix = join_use_path(prefix, path_part.trim().trim_end_matches("::"));
699        let group = group.strip_suffix('}').unwrap_or(group);
700        for part in split_group_entries(group) {
701            collect_use_bindings(&inner_prefix, part, facts);
702        }
703        return;
704    }
705    let (path_part, alias) = match entry.split_once(" as ") {
706        Some((p, a)) => (p.trim(), Some(a.trim())),
707        None => (entry, None),
708    };
709    let full = join_use_path(prefix, path_part);
710    // `use std::fs::{self};` binds `fs` — normalise the `self` leaf away.
711    let full = full.strip_suffix("::self").unwrap_or(&full);
712    let last = full.rsplit("::").next().unwrap_or(full);
713    let name = alias.unwrap_or(last);
714    if name.is_empty() || name == "*" {
715        return; // globs bind no single name; `super::*` is tracked separately
716    }
717    facts.use_bound_names.insert(name.to_string());
718    if full == "std::fs" || full.starts_with("std::fs::") {
719        facts.std_fs_bindings.insert(name.to_string());
720    }
721}
722
723fn join_use_path(prefix: &str, part: &str) -> String {
724    if prefix.is_empty() {
725        part.to_string()
726    } else {
727        format!("{prefix}::{part}")
728    }
729}
730
731/// Split a brace group's contents on top-level commas only — `fs::{self, File}, io`
732/// is two entries, not three.
733fn split_group_entries(s: &str) -> Vec<&str> {
734    let mut out = Vec::new();
735    let mut depth = 0i32;
736    let mut start = 0;
737    for (i, c) in s.char_indices() {
738        match c {
739            '{' => depth += 1,
740            '}' => depth -= 1,
741            ',' if depth == 0 => {
742                out.push(&s[start..i]);
743                start = i + 1;
744            }
745            _ => {}
746        }
747    }
748    out.push(&s[start..]);
749    out
750}
751
752/// Every identifier `NAME` occurring as `NAME::` where the character before `NAME` is
753/// not `:` — i.e. a path *root*, so `std::fs::read` contributes `std`, never `fs`.
754/// Same line discipline as the text scan: comments included, production scope only
755/// (the caller has already excluded test ranges).
756fn collect_bare_path_roots(line: &str, out: &mut BTreeSet<String>) {
757    let bytes = line.as_bytes();
758    let mut search_from = 0;
759    while let Some(rel) = line[search_from..].find("::") {
760        let pos = search_from + rel;
761        let mut start = pos;
762        while start > 0 && (bytes[start - 1].is_ascii_alphanumeric() || bytes[start - 1] == b'_') {
763            start -= 1;
764        }
765        if start < pos && (start == 0 || bytes[start - 1] != b':') {
766            out.insert(line[start..pos].to_string());
767        }
768        search_from = pos + 2;
769    }
770}
771
772/// Every `super::`/`self::`/`crate::`-rooted path on `line`, as its segment chain —
773/// `super::fs::read_to_string(p)` yields `["super", "fs", "read_to_string"]`. The
774/// root must sit at a bare word boundary (not `a_super::` or `a::super::`), so only
775/// genuine path roots are collected; `Self::` (capital) never matches, and `self.x`
776/// has no `::` to match.
777fn collect_qualified_paths(line: &str, out: &mut BTreeSet<Vec<String>>) {
778    let bytes = line.as_bytes();
779    for root in ["super", "self", "crate"] {
780        let mut from = 0;
781        while let Some(rel) = line[from..].find(root) {
782            let start = from + rel;
783            let root_end = start + root.len();
784            from = root_end;
785            let boundary_ok = start == 0 || {
786                let c = bytes[start - 1];
787                !(c.is_ascii_alphanumeric() || c == b'_' || c == b':')
788            };
789            if !boundary_ok || !line[root_end..].starts_with("::") {
790                continue;
791            }
792            let mut segments = vec![root.to_string()];
793            let mut pos = root_end;
794            while line[pos..].starts_with("::") {
795                let seg_start = pos + 2;
796                let mut seg_end = seg_start;
797                while seg_end < bytes.len()
798                    && (bytes[seg_end].is_ascii_alphanumeric() || bytes[seg_end] == b'_')
799                {
800                    seg_end += 1;
801                }
802                if seg_end == seg_start {
803                    break; // `super::*` and friends — no further identifier
804                }
805                segments.push(line[seg_start..seg_end].to_string());
806                pos = seg_end;
807            }
808            if segments.len() >= 2 {
809                out.insert(segments);
810            }
811        }
812    }
813}
814
815/// The file defining `path`'s parent module, by the standard layout: `a/b.rs`'s parent
816/// is `a.rs` (or `a/mod.rs`), `a/mod.rs`'s parent is the crate root, and the roots
817/// (`lib.rs`/`main.rs`) have none. `#[path]`-remapped modules are not handled — none
818/// exist below the driver, and a text-level probe can't chase them anyway.
819fn module_parent(path: &Path, files: &[(PathBuf, String)]) -> Option<usize> {
820    let stem = path.file_stem()?.to_str()?;
821    let dir = path.parent().filter(|d| !d.as_os_str().is_empty());
822    let parent_module: PathBuf = if stem == "mod" {
823        dir?.parent().map(Path::to_path_buf).unwrap_or_default()
824    } else if let Some(dir) = dir {
825        dir.to_path_buf()
826    } else {
827        if stem == "lib" || stem == "main" {
828            return None;
829        }
830        PathBuf::new()
831    };
832    let candidates = if parent_module.as_os_str().is_empty() {
833        vec![PathBuf::from("lib.rs"), PathBuf::from("main.rs")]
834    } else {
835        vec![
836            parent_module.with_extension("rs"),
837            parent_module.join("mod.rs"),
838        ]
839    };
840    candidates
841        .iter()
842        .find_map(|c| files.iter().position(|(p, _)| p == c))
843}
844
845/// The scopes whose bindings a name used in module `i` can see: the module itself,
846/// then each ancestor reachable while every module below it glob-imports `super::*`.
847fn visible_scopes(i: usize, facts: &[FsImportFacts], parents: &[Option<usize>]) -> Vec<usize> {
848    let mut scopes = vec![i];
849    let mut cur = i;
850    loop {
851        if !facts[cur].glob_imports_super {
852            break;
853        }
854        let Some(parent) = parents[cur] else { break };
855        scopes.push(parent);
856        cur = parent;
857    }
858    scopes
859}
860
861/// How `name` resolves in module `m`'s namespace, walking [`visible_scopes`] with
862/// nearest binding winning — a closer non-`std::fs` `use` binding *or* locally
863/// declared type-namespace item shadows a farther `std::fs` binding, as in Rust.
864enum NameResolution {
865    StdFs,
866    Other,
867    Unbound,
868}
869
870fn resolve_name_in_module(
871    m: usize,
872    name: &str,
873    facts: &[FsImportFacts],
874    parents: &[Option<usize>],
875) -> NameResolution {
876    for s in visible_scopes(m, facts, parents) {
877        if facts[s].std_fs_bindings.contains(name) {
878            return NameResolution::StdFs;
879        }
880        if facts[s].use_bound_names.contains(name) || facts[s].declared_type_names.contains(name) {
881            return NameResolution::Other;
882        }
883    }
884    NameResolution::Unbound
885}
886
887/// Does a bare path root in file `i` resolve to `std::fs` through the bindings it
888/// can see? Candidates are the names any visible scope binds to `std::fs`; each is
889/// then resolved from `i` with nearest-binding-wins shadowing.
890fn resolves_bare_std_fs(i: usize, facts: &[FsImportFacts], parents: &[Option<usize>]) -> bool {
891    let scopes = visible_scopes(i, facts, parents);
892    let mut candidates: BTreeSet<&str> = BTreeSet::new();
893    for &s in &scopes {
894        candidates.extend(facts[s].std_fs_bindings.iter().map(String::as_str));
895    }
896    candidates.into_iter().any(|name| {
897        facts[i].bare_path_roots.contains(name)
898            && matches!(
899                resolve_name_in_module(i, name, facts, parents),
900                NameResolution::StdFs
901            )
902    })
903}
904
905/// Does a `super::`/`self::`/`crate::`-qualified path in file `i` reach a `std::fs`
906/// binding (#1016 review)? Unlike the bare-root case these need no glob import: the
907/// root picks the starting module directly (`super`-hops up the parent chain, `self`
908/// the file itself, `crate` the crate root), then each further segment either
909/// resolves in that module's namespace — `std::fs` flags, anything else stops — or
910/// descends into a child module file and continues. Inline `mod name { … }` blocks
911/// are not modelled (their `use` bindings live in the same file, which the text scan
912/// and bare-root resolution already cover).
913fn resolves_qualified_std_fs(
914    i: usize,
915    facts: &[FsImportFacts],
916    parents: &[Option<usize>],
917    files: &[(PathBuf, String)],
918) -> bool {
919    facts[i]
920        .qualified_paths
921        .iter()
922        .any(|chain| qualified_chain_reaches_std_fs(chain, i, facts, parents, files))
923}
924
925fn qualified_chain_reaches_std_fs(
926    chain: &[String],
927    i: usize,
928    facts: &[FsImportFacts],
929    parents: &[Option<usize>],
930    files: &[(PathBuf, String)],
931) -> bool {
932    let mut idx = 1;
933    let mut m = match chain[0].as_str() {
934        "self" => i,
935        "crate" => {
936            let root = files
937                .iter()
938                .position(|(p, _)| p == Path::new("lib.rs") || p == Path::new("main.rs"));
939            match root {
940                Some(root) => root,
941                None => return false,
942            }
943        }
944        "super" => {
945            let mut m = i;
946            idx = 0;
947            while idx < chain.len() && chain[idx] == "super" {
948                let Some(parent) = parents[m] else {
949                    return false;
950                };
951                m = parent;
952                idx += 1;
953            }
954            m
955        }
956        _ => return false,
957    };
958    while idx < chain.len() {
959        let seg = chain[idx].as_str();
960        // Resolve `seg` in `m`, nearest scope first. Within a scope, a child module
961        // file for `seg` is checked *before* the shadow set: a declared `mod seg;`
962        // lands `seg` in `declared_type_names`, but that declaration IS the child
963        // module — it's the path's next hop, not a shadow over it. (In valid Rust a
964        // module and another same-name type-namespace item can't coexist in one
965        // scope, so the ordering costs nothing.)
966        let mut next = None;
967        for s in visible_scopes(m, facts, parents) {
968            if facts[s].std_fs_bindings.contains(seg) {
969                return true;
970            }
971            if let Some(child) = child_module_file(s, seg, files) {
972                next = Some(child);
973                break;
974            }
975            if facts[s].use_bound_names.contains(seg) || facts[s].declared_type_names.contains(seg)
976            {
977                return false; // bound to something that is neither std::fs nor a module
978            }
979        }
980        let Some(child) = next else {
981            return false;
982        };
983        m = child;
984        idx += 1;
985    }
986    false
987}
988
989/// The file defining module `m`'s child module `seg`, if it exists as a file:
990/// `lib.rs` + `a` → `a.rs`/`a/mod.rs`, `a.rs` + `b` → `a/b.rs`/`a/b/mod.rs`,
991/// `a/mod.rs` + `b` → `a/b.rs`/`a/b/mod.rs`.
992fn child_module_file(m: usize, seg: &str, files: &[(PathBuf, String)]) -> Option<usize> {
993    let m_path = &files[m].0;
994    let module_dir: PathBuf = match m_path.file_stem().and_then(|s| s.to_str()) {
995        Some("mod") => m_path.parent().unwrap_or(Path::new("")).to_path_buf(),
996        Some("lib") | Some("main") if m_path.parent().is_none_or(|p| p.as_os_str().is_empty()) => {
997            PathBuf::new()
998        }
999        _ => m_path.with_extension(""),
1000    };
1001    let candidates = [
1002        module_dir.join(format!("{seg}.rs")),
1003        module_dir.join(seg).join("mod.rs"),
1004    ];
1005    candidates
1006        .iter()
1007        .find_map(|c| files.iter().position(|(p, _)| p == c))
1008}
1009
1010/// Every `#[cfg(test)] mod <ident> { ... }` block in `lines`, as inclusive
1011/// `(start_line, end_line)` line-index ranges — every occurrence, not just a single
1012/// trailing block. A file in this codebase can carry several test modules scattered
1013/// through it with production code between them — `bynk-emit/src/emitter/lower.rs` has
1014/// two, 1031 lines apart, and treating "everything after the first (or last)
1015/// `#[cfg(test)]`" as one cutoff silently misclassifies that intervening production
1016/// code as test-scope (caught in review: it made `fs_below_driver`, a *gated* probe,
1017/// blind over that span, and inflated `test_density`'s ratio by up to 39%).
1018///
1019/// A block's end is found by real brace-depth counting via [`brace_delta`], not a
1020/// "first column-0 `}`" shortcut: an earlier version of this fix tried exactly that
1021/// shortcut (reasoning that rustfmt always dedents a closing brace back to column 0)
1022/// and it broke on files like `bynk-ide/src/sequence.rs`, whose test module embeds
1023/// multi-line `.bynk`/TypeScript fixture source as string literals — source that
1024/// itself contains a column-0 `}` closing a top-level construct *inside the string*,
1025/// which the shortcut mistook for the end of the Rust `mod` block, truncating it by
1026/// hundreds of lines. `brace_delta` skips characters inside Rust string/char literals
1027/// and comments, so embedded fixture text can't be mistaken for real Rust braces.
1028///
1029/// Only matches a brace-opening `mod` line — `#[cfg(test)] mod foo;` (an external-file
1030/// declaration, not an inline scope) does not open a range.
1031fn test_mod_ranges(lines: &[&str]) -> Vec<(usize, usize)> {
1032    let mut ranges = Vec::new();
1033    let mut i = 0;
1034    while i < lines.len() {
1035        if lines[i].trim() == "#[cfg(test)]"
1036            && let Some(off) = lines[i + 1..].iter().position(|l| !l.trim().is_empty())
1037        {
1038            let mod_line = i + 1 + off;
1039            let t = lines[mod_line].trim();
1040            if t.starts_with("mod ") && t.ends_with('{') {
1041                let mut depth = 0i32;
1042                let mut state = BraceScanState::Normal;
1043                let mut started = false;
1044                let mut end = lines.len() - 1;
1045                for (j, line) in lines[mod_line..].iter().enumerate() {
1046                    let (delta, new_state) = brace_delta(line, state);
1047                    state = new_state;
1048                    depth += delta;
1049                    if depth != 0 {
1050                        started = true;
1051                    }
1052                    if started && depth == 0 {
1053                        end = mod_line + j;
1054                        break;
1055                    }
1056                }
1057                ranges.push((mod_line, end));
1058                i = end + 1;
1059                continue;
1060            }
1061        }
1062        i += 1;
1063    }
1064    ranges
1065}
1066
1067fn in_test_range(line_idx: usize, ranges: &[(usize, usize)]) -> bool {
1068    ranges
1069        .iter()
1070        .any(|(start, end)| line_idx >= *start && line_idx <= *end)
1071}
1072
1073/// Scanner state carried across lines for [`brace_delta`]: whether the cursor is
1074/// inside a string literal, a raw string (with its `#`-count), or a block comment
1075/// (with nesting depth — Rust block comments nest).
1076#[derive(Clone, Copy, PartialEq)]
1077enum BraceScanState {
1078    Normal,
1079    InString,
1080    InRawString(u8),
1081    InBlockComment(u32),
1082}
1083
1084/// The net `{`/`}` depth change in `line`, skipping characters inside Rust string/char
1085/// literals, raw strings, and line/block comments — a naive per-character brace count
1086/// breaks the moment a line contains a fixture string like `"fn f() { \"{\" }"` or a
1087/// doc comment mentioning a brace. Returns the depth delta and the state to carry into
1088/// the next line (a string or block comment can span line boundaries).
1089fn brace_delta(line: &str, mut state: BraceScanState) -> (i32, BraceScanState) {
1090    let mut delta = 0i32;
1091    let chars: Vec<char> = line.chars().collect();
1092    let mut i = 0;
1093    while i < chars.len() {
1094        match state {
1095            BraceScanState::Normal => {
1096                if chars[i] == '/' && chars.get(i + 1) == Some(&'/') {
1097                    break; // rest of the line is a line comment
1098                }
1099                if chars[i] == '/' && chars.get(i + 1) == Some(&'*') {
1100                    state = BraceScanState::InBlockComment(1);
1101                    i += 2;
1102                    continue;
1103                }
1104                if chars[i] == '"' {
1105                    state = BraceScanState::InString;
1106                    i += 1;
1107                    continue;
1108                }
1109                if chars[i] == 'r' && matches!(chars.get(i + 1), Some('"') | Some('#')) {
1110                    let mut j = i + 1;
1111                    let mut hashes = 0u8;
1112                    while chars.get(j) == Some(&'#') {
1113                        hashes += 1;
1114                        j += 1;
1115                    }
1116                    if chars.get(j) == Some(&'"') {
1117                        state = BraceScanState::InRawString(hashes);
1118                        i = j + 1;
1119                        continue;
1120                    }
1121                }
1122                if chars[i] == '\'' {
1123                    // A `'\x'`/`'\\'`-style escaped char literal, or a plain `'x'` —
1124                    // skip past it so its contents can't be mistaken for braces.
1125                    // Anything else (no closing `'` within a couple of chars) is a
1126                    // lifetime, which owns no closing quote to skip.
1127                    if chars.get(i + 1) == Some(&'\\') {
1128                        let mut j = i + 2;
1129                        while j < chars.len() && chars[j] != '\'' {
1130                            j += 1;
1131                        }
1132                        i = (j + 1).min(chars.len());
1133                        continue;
1134                    } else if chars.get(i + 2) == Some(&'\'') {
1135                        i += 3;
1136                        continue;
1137                    }
1138                }
1139                match chars[i] {
1140                    '{' => delta += 1,
1141                    '}' => delta -= 1,
1142                    _ => {}
1143                }
1144                i += 1;
1145            }
1146            BraceScanState::InString => {
1147                if chars[i] == '\\' {
1148                    i += 2;
1149                    continue;
1150                }
1151                if chars[i] == '"' {
1152                    state = BraceScanState::Normal;
1153                }
1154                i += 1;
1155            }
1156            BraceScanState::InRawString(hashes) => {
1157                if chars[i] == '"' {
1158                    let mut j = i + 1;
1159                    let mut h = 0u8;
1160                    while chars.get(j) == Some(&'#') && h < hashes {
1161                        h += 1;
1162                        j += 1;
1163                    }
1164                    if h == hashes {
1165                        state = BraceScanState::Normal;
1166                        i = j;
1167                        continue;
1168                    }
1169                }
1170                i += 1;
1171            }
1172            BraceScanState::InBlockComment(depth) => {
1173                if chars[i] == '/' && chars.get(i + 1) == Some(&'*') {
1174                    state = BraceScanState::InBlockComment(depth + 1);
1175                    i += 2;
1176                    continue;
1177                }
1178                if chars[i] == '*' && chars.get(i + 1) == Some(&'/') {
1179                    state = if depth <= 1 {
1180                        BraceScanState::Normal
1181                    } else {
1182                        BraceScanState::InBlockComment(depth - 1)
1183                    };
1184                    i += 2;
1185                    continue;
1186                }
1187                i += 1;
1188            }
1189        }
1190    }
1191    (delta, state)
1192}
1193
1194// --- Gated probe 3: options_sources --------------------------------------
1195
1196/// R2.3. `CompileOptions` (in `bynk-emit/src/project.rs`) has a `sources` field.
1197fn options_sources(root: &Path) -> Probe {
1198    let src = std::fs::read_to_string(root.join("bynk-emit/src/project.rs")).unwrap_or_default();
1199    let present = struct_body(&src, "CompileOptions").is_some_and(|body| body.contains("sources"));
1200    Probe {
1201        name: "options_sources",
1202        gated: true,
1203        reads: if present {
1204            "present".to_string()
1205        } else {
1206            "absent".to_string()
1207        },
1208    }
1209}
1210
1211/// The `{ ... }` body text of `struct <name>` in `src`, brace-matched from the struct's
1212/// own opening brace to its close.
1213fn struct_body<'a>(src: &'a str, name: &str) -> Option<&'a str> {
1214    let needle = format!("struct {name}");
1215    let start = src.find(&needle)?;
1216    let open = start + src[start..].find('{')?;
1217    let mut depth = 0i32;
1218    for (offset, ch) in src[open..].char_indices() {
1219        match ch {
1220            '{' => depth += 1,
1221            '}' => {
1222                depth -= 1;
1223                if depth == 0 {
1224                    return Some(&src[open..open + offset + 1]);
1225                }
1226            }
1227            _ => {}
1228        }
1229    }
1230    None
1231}
1232
1233// --- Gated probe 4: hoist_sinks -------------------------------------------
1234
1235/// R6.2. Live (non-comment) occurrences of the sink-passing signature
1236/// `stmts: &mut Vec<String>` in `bynk-emit`. Tier B (T2.1) deletes it entirely.
1237fn hoist_sinks(root: &Path) -> Probe {
1238    let dir = root.join("bynk-emit/src");
1239    let needle = "stmts: &mut Vec<String>";
1240    let mut count = 0usize;
1241    for (_, contents) in rust_files(&dir) {
1242        for line in contents.lines() {
1243            if !is_line_comment(line) && line.contains(needle) {
1244                count += 1;
1245            }
1246        }
1247    }
1248    Probe {
1249        name: "hoist_sinks",
1250        gated: true,
1251        reads: count.to_string(),
1252    }
1253}
1254
1255// --- Gated probe 5: span_keyed_maps ---------------------------------------
1256
1257/// R2.4. Whole-repo occurrences of `HashMap<Span` (comments included — the phase-3
1258/// migration target is every mention, not just live call sites), **excluding
1259/// `xtask` itself**: this probe's own doc comment and source both name the search
1260/// string, which would otherwise self-count every time this file is touched — the
1261/// same self-reference hazard flagged for the dead-identifier probes below, caught
1262/// here by running the probe against itself before committing the first table.
1263fn span_keyed_maps(root: &Path) -> Probe {
1264    let count = count_repo_wide(root, "HashMap<Span", &["xtask"]);
1265    Probe {
1266        name: "span_keyed_maps",
1267        gated: true,
1268        reads: count.to_string(),
1269    }
1270}
1271
1272fn count_repo_wide(root: &Path, needle: &str, exclude_crates: &[&str]) -> usize {
1273    let mut total = 0usize;
1274    for entry in top_level_crate_dirs(root) {
1275        if exclude_crates
1276            .iter()
1277            .any(|c| entry.file_name().is_some_and(|n| n == *c))
1278        {
1279            continue;
1280        }
1281        for (_, contents) in rust_files(&entry.join("src")) {
1282            total += contents.matches(needle).count();
1283        }
1284    }
1285    total
1286}
1287
1288/// Every workspace member crate directory (anything at the repo root with a
1289/// `Cargo.toml` and a `src/` dir), excluding `target` and non-crate directories.
1290fn top_level_crate_dirs(root: &Path) -> Vec<PathBuf> {
1291    let mut out = Vec::new();
1292    let Ok(entries) = std::fs::read_dir(root) else {
1293        return out;
1294    };
1295    for entry in entries.flatten() {
1296        let path = entry.path();
1297        if path.is_dir() && path.join("Cargo.toml").is_file() && path.join("src").is_dir() {
1298            out.push(path);
1299        }
1300    }
1301    out.sort();
1302    out
1303}
1304
1305// --- Gated probe 6: emit_diagnostics --------------------------------------
1306
1307/// R3.5. `bynk.*` string literals in `bynk-emit`/`bynk-check` source, cross-referenced
1308/// against `bynk_syntax::diagnostics::REGISTRY` — not pattern-matched. A literal not in
1309/// `REGISTRY` is a commons/namespace path (e.g. `bynk.locale`, the compiled first-party
1310/// source module name), not a diagnostic code, and must not inflate the count (#999
1311/// Decision A: this cross-reference is what makes the exclusion correct by
1312/// construction rather than a second hand-maintained list).
1313fn emit_diagnostics(root: &Path) -> Probe {
1314    let registry: BTreeSet<&str> = bynk_syntax::diagnostics::REGISTRY
1315        .iter()
1316        .map(|d| d.code)
1317        .collect();
1318    let mut parts = Vec::new();
1319    for (label, dir) in [
1320        ("bynk-emit", "bynk-emit/src"),
1321        ("bynk-check", "bynk-check/src"),
1322    ] {
1323        let mut naive: BTreeSet<String> = BTreeSet::new();
1324        for (_, contents) in rust_files(&root.join(dir)) {
1325            for lit in bynk_dotted_literals(&contents) {
1326                naive.insert(lit.to_string());
1327            }
1328        }
1329        let true_count = naive
1330            .iter()
1331            .filter(|l| registry.contains(l.as_str()))
1332            .count();
1333        parts.push(format!("{label}={true_count}/{}", naive.len()));
1334    }
1335    Probe {
1336        name: "emit_diagnostics",
1337        gated: true,
1338        reads: format!("{} (true/naive)", parts.join(", ")),
1339    }
1340}
1341
1342// --- Gated probe 7: ide_emit_edge -----------------------------------------
1343
1344/// R10.2. `bynk-ide` → `bynk-emit` in the manifest (`bynk-emit.workspace = true` or an
1345/// equivalent path/version dependency line).
1346fn ide_emit_edge(root: &Path) -> Probe {
1347    let manifest = std::fs::read_to_string(root.join("bynk-ide/Cargo.toml")).unwrap_or_default();
1348    let present = manifest
1349        .lines()
1350        .any(|l| l.trim_start().starts_with("bynk-emit"));
1351    Probe {
1352        name: "ide_emit_edge",
1353        gated: true,
1354        reads: if present {
1355            "present".to_string()
1356        } else {
1357            "absent".to_string()
1358        },
1359    }
1360}
1361
1362// --- Gated probe 8: ast_importers -----------------------------------------
1363
1364/// #1176: `bynk-emit::ir`'s own two files — named exactly, not by path prefix, the same
1365/// permanent-carve-out discipline [`NAMED_FS_EXCEPTIONS`] and [`emit_diagnostics`]'s
1366/// registry cross-reference already use. An `Ast → Ir` lowering pass importing
1367/// `bynk_syntax::ast` is that pass's entire job, not the AST-walking this track is
1368/// closing (phase 6's own P6.9 correction, #1167 — see the retired `the-ir.md`'s
1369/// closing summary, `design/archive/retired-tracks.md`) — but `project.rs` also
1370/// imports `bynk_syntax::ast` today (`EmitProjectCtx` holding `ActorDecl`/`AgentDecl`
1371/// fields directly), and that *is* exactly the still-open R6.13 defect this probe
1372/// tracks (P6.6: "closes the emitter reading AST declarations directly"). A
1373/// path-prefix rule scoped to `emitter/**` would exclude that file right along with
1374/// `ir/`'s legitimate ones, silently undercounting real remaining work — see
1375/// [`is_named_ast_importer`].
1376///
1377/// #1184 review: this exclusion is necessary but not sufficient for R6.13. `bynk-ir`
1378/// still embeds AST types directly in IR struct fields (`TypeShape::Refined`'s
1379/// `BaseType`/`Refinement`, ADR 0366) rather than IR-native equivalents — an emitter
1380/// reading such a field touches the AST without ever spelling `bynk_syntax::ast`
1381/// itself, so it is invisible to this probe by construction. `ast_importers` reading
1382/// its retired floor (5 — `design/archive/retired-tracks.md`'s own closing summary
1383/// has the per-file argument) proves no *remaining* file outside these two and the
1384/// five-file rendering subtree imports the AST module directly; it does not by
1385/// itself prove every `bynk-ir` field is AST-free.
1386///
1387/// #1187's own closing scoping pass adds one more, on different grounds than the
1388/// `ir.rs`/`ir/lower.rs` pair above: `project/tests_emit.rs` was deliberately *not*
1389/// added alongside `project.rs` when this list was first cut (the
1390/// `ast_importer_exclusion_is_named_not_prefixed` test below used to assert exactly
1391/// that) — #1187's own scoping pass found new evidence changing that: its test/suite
1392/// case bodies call `emitter::lower_block_to_async_body`/`lower_test_case_body`/
1393/// `lower_integration_case_body` directly (the Q7-settled body-rendering pass —
1394/// `emitter/lower.rs` keeps hand-writing TypeScript source text after phase 6's
1395/// cutover, the printer that would change that is phase 7's), and
1396/// its own `driver_param_ty`/`strip_effect_httpresult` read a handler's *declared*
1397/// param/return `TypeRef` with no corresponding `TyId` available at that call site
1398/// (the same caller-reads-callee's-raw-declared-shape pattern #661 established for
1399/// cross-context codec generation). Both are the Q7/printer kind of unreachable, not
1400/// the "still open, real work" kind the original exclusion list deliberately left this
1401/// file out of — the correction is new evidence, not a reversal of that reasoning.
1402///
1403/// Review of #1210: `emitter.rs`/`emitter/lower.rs` themselves were considered for
1404/// this same exclusion and **rejected** — Q7 settles that these files' *body-rendering*
1405/// surface stays AST-parameter-driven, but both files also hold live, currently
1406/// untouched AST-*declaration* reads with no such gate: `emitter.rs`'s own
1407/// `CommonsItem::Service`/`svc.protocol` walk (consumed-event-root collection) and
1408/// `emitter/lower.rs`'s own `cap_op_param_names` (`CommonsItem::Capability`/`c.ops`)
1409/// were exactly the P6.2/P6.6-class conversions phase 6's own slice decomposition
1410/// still listed as in scope at the time, not body-rendering. Excluding either file
1411/// would have hidden that real, fixable surface from this probe the same way a
1412/// path-prefix rule would — the harm the named-not-prefixed discipline above exists
1413/// to prevent, just at file granularity instead of directory granularity. (Both
1414/// converted their own reachable surface later, without joining this list — phase 6's
1415/// closing summary, `design/archive/retired-tracks.md`, has the account.)
1416///
1417/// P6.33 (phase 6's own §6a.D re-settling, 19 August 2026): `emitter/serialisation.rs`
1418/// joins the list, on grounds distinct from every entry above — not Q7 body-rendering,
1419/// not test-only reach, but a phase boundary. Unlike `emitter.rs`/`emitter/lower.rs`,
1420/// this file holds no `CommonsItem`-declaration-read surface at all (confirmed:
1421/// `grep -c bynk_syntax::ast` finds only its one `use` line and its `#[cfg(test)]`
1422/// module) — its entire AST surface, ~120 sites, *is* the `TypeRef`-driven JSON/wire
1423/// codec renderer (`emit_record_codec`/`emit_sum_codec`/`serialise_expr`/
1424/// `deserialise_expr`/`ts_inner_type` and siblings). Rendering a checker type as TS
1425/// codec source text is the same class of question Q7 already settled belongs to the
1426/// eventual printer (phase 7, `bynk-ts`) — this file has no `use crate::ir` at all, so
1427/// nothing here has been resisting an available IR-native alternative; none exists. The
1428/// re-settling found no clean way to shrink this file's AST surface further without
1429/// building printer infrastructure phase 6's own scope already excluded.
1430///
1431/// P6.49 (phase 6's own §6b, 19 August 2026): `project.rs` — R6.13's own still-open
1432/// declaration-read surface named at the top of this doc block, above — cleared
1433/// **without joining this list**. Nine slices (P6.42–P6.49) relocated its remaining
1434/// declaration reads to the `bynk-check`/`bynk-project` crates that already own the
1435/// data (`SourceUnit::name()`, `own_contract_hashes`, `discover_event_subscribers`,
1436/// `combined_types_for_unit_info`, two owner-side accessors,
1437/// `lower_event_subscriber_shapes_ir`, `walk_unit_table_bodies`) or re-exported a type
1438/// from a `bynk-check` module whose own public API was already parameterised by it
1439/// (`TypeDecl`/`FnDecl`/`Visibility` from `project_model`, `ActorDecl` from `actors` —
1440/// the P6.27 `ExprId` precedent, applied four more times). This is the evidence this
1441/// exclusion list's own entries above are real, earned exclusions and not a standing
1442/// habit: the harder file cleared its own way, on its own schedule, with zero new
1443/// entries here.
1444///
1445/// **P6.58/P6.59, 19 August 2026: phase 6 (`the-ir.md`, spine #1137) retired at this
1446/// probe reading 5, not 0.** The floor is exactly `bynk-emit/src/emitter{,/**}` —
1447/// `emitter.rs`, `emitter/emit.rs`, `emitter/lower.rs`, `emitter/workers.rs`,
1448/// `emitter/workers_entry.rs` — the TypeScript-rendering subtree phase 7's own printer
1449/// inherits; each file's own structural reason, and the full slice history behind
1450/// every correction this doc block narrates, live in `design/archive/retired-tracks.md`
1451/// now that `the-ir.md` itself is gone. This exclusion list does **not** grow to
1452/// reach that floor — the floor is a fact about `AST_IMPORTER_EXCEPTIONS`'s own
1453/// four entries staying exactly these four, not a fifth argument for adding to them.
1454/// The probe itself stays gated, unchanged, reading 5: a regression ratchet phase 7
1455/// inherits and drives down as it builds the printer this floor's own residue names.
1456///
1457/// **Arc D, P7.12 (crate carve): `ir.rs`/`ir/lower.rs` drop out of this list
1458/// entirely — not because they stopped importing the AST (unchanged, still
1459/// do), but because they left `bynk-emit/src` altogether, carved into the new
1460/// `bynk-ir`/`bynk-lower` crates ADR 0332 deferred and ADR 0385 triggered.**
1461/// This probe was never scoped to those crates (`ast_importer_files` walks
1462/// `bynk-emit/src` only), so the pair is simply outside its universe now,
1463/// the same way a file moving to `bynk-check`/`bynk-project` already leaves
1464/// silently rather than needing its own exclusion-list removal step. Two
1465/// named exclusions remain.
1466const AST_IMPORTER_EXCEPTIONS: &[&str] = &["project/tests_emit.rs", "emitter/serialisation.rs"];
1467
1468/// Is `rel_path` (relative to `bynk-emit/src`) one of [`AST_IMPORTER_EXCEPTIONS`]?
1469fn is_named_ast_importer(rel_path: &Path) -> bool {
1470    let rel = rel_path.to_string_lossy().replace('\\', "/");
1471    AST_IMPORTER_EXCEPTIONS.contains(&rel.as_str())
1472}
1473
1474/// Is `contents` a module (not a nested block) that glob-imports its parent —
1475/// i.e. does it carry a top-level (column-0) `use super::*;`? Rust's own privacy
1476/// rule makes a parent module's private `use` visible to descendant modules, so a
1477/// file matching this can expose `bynk_syntax::ast` names it never spells itself
1478/// (P6.26 review, #1259) — deliberately column-0 only, so a `use super::*;`
1479/// *inside* a nested `#[cfg(test)] mod tests { .. }` block (glob-importing its own
1480/// immediately-enclosing module, not the grandparent file on disk) doesn't
1481/// false-positive this check.
1482fn has_module_level_super_glob(contents: &str) -> bool {
1483    contents.lines().any(|line| line == "use super::*;")
1484}
1485
1486/// For `rel_path` = `<dir>/<file>.rs`, does the sibling module file `<dir>.rs`
1487/// (the parent module a top-level `use super::*;` in `rel_path` would inherit
1488/// from) itself contain `bynk_syntax::ast`? `None` if `rel_path` has no such
1489/// parent (a file directly under `bynk-emit/src`, e.g. `emitter.rs` itself).
1490fn super_glob_parent_imports_ast(dir: &Path, rel_path: &Path) -> Option<bool> {
1491    let parent_dir = rel_path.parent()?;
1492    if parent_dir.as_os_str().is_empty() {
1493        return None;
1494    }
1495    let parent_file = dir.join(parent_dir).with_extension("rs");
1496    Some(
1497        std::fs::read_to_string(&parent_file)
1498            .is_ok_and(|contents| contents.contains("bynk_syntax::ast")),
1499    )
1500}
1501
1502/// The files [`ast_importers`] counts: `bynk-emit/src` files whose contents match
1503/// `bynk_syntax::ast` **or** that inherit it from an AST-importing parent through a
1504/// top-level `use super::*;` (P6.26 review, #1259 — a file that stops spelling the
1505/// AST module directly by deleting its own explicit import, while a live `use
1506/// super::*;` still channels a still-AST-importing parent's names in, must stay
1507/// counted; otherwise a future partial conversion could silently drop this probe
1508/// without the underlying AST dependency actually being gone), excluding
1509/// [`AST_IMPORTER_EXCEPTIONS`]. Split out from [`ast_importers`] so a test can
1510/// assert on the actual survivor set, not just its length (#1184 review).
1511fn ast_importer_files(root: &Path) -> Vec<PathBuf> {
1512    let dir = root.join("bynk-emit/src");
1513    rust_files(&dir)
1514        .into_iter()
1515        .filter(|(path, contents)| {
1516            contents.contains("bynk_syntax::ast")
1517                || (has_module_level_super_glob(contents)
1518                    && super_glob_parent_imports_ast(&dir, path.strip_prefix(&dir).unwrap_or(path))
1519                        .unwrap_or(false))
1520        })
1521        .filter(|(path, _)| !is_named_ast_importer(path.strip_prefix(&dir).unwrap_or(path)))
1522        .map(|(path, _)| path)
1523        .collect()
1524}
1525
1526/// R6.13. Files in `bynk-emit/src` that import `bynk_syntax::ast`, excluding
1527/// [`AST_IMPORTER_EXCEPTIONS`] — phase 6's own remaining AST import surface (retired,
1528/// spine #1137; `design/archive/retired-tracks.md` has the closing summary). #1176:
1529/// the unexcluded, crate-wide count could never reach 0 while `bynk-emit::ir`'s
1530/// lowering pass exists at all; this exclusion is what let the probe track phase 6's
1531/// real completion criterion instead of a floor its own IR module structurally could
1532/// not clear. Gated at 5, phase 6's own retired floor, for phase 7 to drive down.
1533fn ast_importers(root: &Path) -> Probe {
1534    Probe {
1535        name: "ast_importers",
1536        gated: true,
1537        reads: ast_importer_files(root).len().to_string(),
1538    }
1539}
1540
1541// --- Gated probe 9: emit_abi_shapes ---------------------------------------
1542
1543/// ADR 0310 D1's four emit-ABI shapes, as they surface as import names in the vendored
1544/// bindings — the `Result`/`Option` tag layout plus `JsonError`, `Uuid`, `FetchError`.
1545const EMIT_ABI: &[&str] = &[
1546    "Result",
1547    "Option",
1548    "Ok",
1549    "Err",
1550    "Some",
1551    "None",
1552    "JsonError",
1553    "Uuid",
1554    "FetchError",
1555];
1556
1557/// The capability interfaces a vendored binding legitimately imports to implement what
1558/// it declares — governed by language-stability rules, not ADR 0310's codegen-freeze
1559/// concern. See [`emit_abi_shapes`] and #999 Decision E for the two-list rationale.
1560const CAPABILITY_SURFACE: &[&str] = &[
1561    "Clock",
1562    "Fetch",
1563    "Idempotency",
1564    "Locale",
1565    "Logger",
1566    "Random",
1567    "Secrets",
1568    "Request",
1569    "Response",
1570    "LocaleTag",
1571    "Kv",
1572    "KVNamespace",
1573];
1574
1575/// Is `ident` one of ADR 0310's enumerated emit-ABI shapes, or part of the capability
1576/// surface a binding is required to import? If neither, it's a leak `emit_abi_shapes`
1577/// flags — this is the single predicate both the probe and its tests use, so a test
1578/// asserting "no leak" can't silently pass against a list the test itself redefined.
1579fn is_enumerated_emit_abi_or_capability_surface(ident: &str) -> bool {
1580    EMIT_ABI.contains(&ident) || CAPABILITY_SURFACE.contains(&ident)
1581}
1582
1583/// ADR 0310's probe (#999 Decision E). The vendored first-party bindings under
1584/// `bynk-check/src/firstparty/bindings/` must reference only [`EMIT_ABI`]'s nine names.
1585///
1586/// This does NOT count every non-enumerated import: a binding legitimately imports the
1587/// [`CAPABILITY_SURFACE`] interfaces it implements — that surface is governed by
1588/// language-stability rules, not ADR 0310's codegen-freeze concern, and a probe that
1589/// flagged it would read non-zero on every binding by construction. See #999 Decision
1590/// E for the two-list rationale and its falsifier.
1591fn emit_abi_shapes(root: &Path) -> Probe {
1592    let dir = root.join("bynk-check/src/firstparty/bindings");
1593    let mut leaks: Vec<String> = Vec::new();
1594    let Ok(entries) = std::fs::read_dir(&dir) else {
1595        return Probe {
1596            name: "emit_abi_shapes",
1597            gated: true,
1598            reads: "bindings directory not found".to_string(),
1599        };
1600    };
1601    let mut files: Vec<_> = entries.flatten().map(|e| e.path()).collect();
1602    files.sort();
1603    for path in files {
1604        if path.extension().is_none_or(|e| e != "ts") {
1605            continue;
1606        }
1607        let Ok(contents) = std::fs::read_to_string(&path) else {
1608            continue;
1609        };
1610        let name = path.file_name().unwrap().to_string_lossy().to_string();
1611        for ident in ts_named_imports_from_runtime_modules(&contents) {
1612            if !is_enumerated_emit_abi_or_capability_surface(&ident) {
1613                leaks.push(format!("{name}:{ident}"));
1614            }
1615        }
1616    }
1617    Probe {
1618        name: "emit_abi_shapes",
1619        gated: true,
1620        reads: format!("{} ({})", leaks.len(), leaks.join(", ")),
1621    }
1622}
1623
1624// --- Gated probe 10: ts_writes ---------------------------------------------
1625
1626/// Files under `bynk-emit/src` that contain `write!`/`writeln!`/`format!` calls but
1627/// produce no TypeScript at all — excluded from both [`ts_writes`] and [`ts_any`], each
1628/// argued individually the same way [`AST_IMPORTER_EXCEPTIONS`] is, not assumed from a
1629/// path prefix: `emitter/wrangler.rs` writes `wrangler.toml`; `emitter/secrets.rs`
1630/// writes `bynk-secrets.json`; `emitter/contracts.rs` writes `bynk-contracts.json`;
1631/// `testkit.rs` builds a `.bynk` source fixture — a compiler *input* for tests, not
1632/// output. P7.3 (#1303): `emitter/toml_doc.rs` writes `wrangler.toml` text too —
1633/// `emitter/wrangler.rs`'s own writes moved here when it stopped building the TOML text
1634/// directly and started building a typed `TomlDocument` for this module to print — same
1635/// rationale, same exclusion.
1636///
1637/// (`ir/lower.rs` — Rust-internal `String` values stored on `Ir*` struct fields during
1638/// the checker→IR lowering pass, never emitted syntax — was excluded here for the same
1639/// reason until Arc D's P7.12 crate carve moved it to `bynk-lower` entirely, outside
1640/// this probe's own `bynk-emit/src` universe; no exclusion needed for a file this probe
1641/// no longer walks. `emitter/source_map.rs`, which wrote source-map JSON, is the same
1642/// shape one carve earlier — P7.5 (#1308) relocated it to `bynk-ts/src/source_map.rs`
1643/// in full; its own #1561 removal from this list.)
1644const TS_WRITES_EXCLUDED_FILES: &[&str] = &[
1645    "emitter/wrangler.rs",
1646    "emitter/toml_doc.rs",
1647    "emitter/secrets.rs",
1648    "emitter/contracts.rs",
1649    "testkit.rs",
1650];
1651
1652/// Is `rel_path` (relative to `bynk-emit/src`) one of [`TS_WRITES_EXCLUDED_FILES`]?
1653fn is_ts_writes_excluded_file(rel_path: &Path) -> bool {
1654    let rel = rel_path.to_string_lossy().replace('\\', "/");
1655    TS_WRITES_EXCLUDED_FILES.contains(&rel.as_str())
1656}
1657
1658/// True if `line` builds a filesystem path via `format!` rather than TypeScript text —
1659/// the `PathBuf::from(format!(...))`/`.join(format!(...))`/`.with_file_name(format!(...))`
1660/// idiom [`ts_writes`] excludes at line granularity, not by file, because the files it
1661/// appears in (`project.rs`, `project/tests_emit.rs`) are otherwise genuinely
1662/// TypeScript-producing.
1663///
1664/// **`.with_file_name(format!` found and added by Arc F's own item-4 investigation
1665/// (#1457):** `project.rs`'s `sibling_path` (`output_path.with_file_name(format!(
1666/// "{name}.{suffix}"))`) builds a sibling filesystem path the same way the two idioms
1667/// above do, but spelled with `.with_file_name(` — the prior substring match didn't
1668/// catch it, over-counting `ts_writes` by this one site.
1669fn is_path_construction_line(line: &str) -> bool {
1670    line.contains("PathBuf::from(format!")
1671        || line.contains(".join(format!")
1672        || line.contains(".with_file_name(format!")
1673}
1674
1675/// Relativises every path in [`rust_files`]'s output against `dir`, so [`ts_writes`]
1676/// and [`ts_any`]'s counting logic ([`ts_writes_violations`], [`ts_any_violations`])
1677/// takes the same `&[(PathBuf, String)]` shape [`production_std_fs_files`] does — an
1678/// in-memory file list a test can construct directly, per review of #1297 (a first cut
1679/// of these two probes took `root: &Path` and did its own walk, so nothing but the
1680/// drift gate actually exercised the exclusion logic; deleting a `continue` left every
1681/// test green).
1682fn rust_files_relative(dir: &Path) -> Vec<(PathBuf, String)> {
1683    rust_files(dir)
1684        .into_iter()
1685        .map(|(path, contents)| {
1686            let rel = path.strip_prefix(dir).unwrap_or(&path).to_path_buf();
1687            (rel, contents)
1688        })
1689        .collect()
1690}
1691
1692/// [`ts_writes`]'s counting logic, over an explicit `(relative path, contents)` list —
1693/// see [`rust_files_relative`] for why this isn't `root: &Path`.
1694///
1695/// **A real mistake this slice's own grounding found and fixed, not carried forward:**
1696/// an earlier survey (during phase 7's own track-opening research) characterised
1697/// `project/tests_emit.rs`'s 128 such sites as excludable "test-assertion strings" — the
1698/// same mischaracterisation `semantics-in-the-checker.md`'s own settling review caught
1699/// and corrected for a *different* probe (`emit_diagnostics`) on this same file: it is
1700/// `process_tests`/`process_integration_tests`, real production TypeScript-emission
1701/// code, not fixture noise, and none of its 128 sites fall inside its own single
1702/// `#[cfg(test)] mod tests { .. }` block. All 128 count here, less the one line that
1703/// genuinely builds a file path ([`is_path_construction_line`]).
1704///
1705/// **Known, accepted gap:** `project/tests_emit.rs`'s
1706/// `target_name: format!("integration · {suite}")` builds a human-readable struct-field
1707/// label, not TypeScript text, and matches neither exclusion rule. A text-level scanner
1708/// has no cheap way to catch one field-name-specific site without a bespoke rule for it
1709/// alone — accepted as a one-site over-count, the same "known remaining gaps, out of
1710/// reach for a text-level scanner" discipline [`production_std_fs_files`] already
1711/// documents for a different probe.
1712fn ts_writes_violations(files: &[(PathBuf, String)]) -> usize {
1713    let mut count = 0usize;
1714    for (rel, contents) in files {
1715        if is_ts_writes_excluded_file(rel) {
1716            continue;
1717        }
1718        let lines: Vec<&str> = contents.lines().collect();
1719        let ranges = test_mod_ranges(&lines);
1720        for (i, line) in lines.iter().enumerate() {
1721            if in_test_range(i, &ranges) || is_line_comment(line) || is_path_construction_line(line)
1722            {
1723                continue;
1724            }
1725            if line.contains("write!") || line.contains("writeln!") || line.contains("format!") {
1726                count += 1;
1727            }
1728        }
1729    }
1730    count
1731}
1732
1733/// The trajectory's own phase-7 probe (`design/bynk-compiler-trajectory.md` §3):
1734/// "TypeScript-producing `write!` outside a printer". Never measured before this slice
1735/// (P7.0, #1296; see phase 7's own closing summary, `design/archive/retired-tracks.md`,
1736/// for the full retirement argument) — `bynk-ts` did not exist yet at measurement time,
1737/// so "outside a printer" reduced then to "in `bynk-emit`, outside a `Verbatim`
1738/// construction"; the `Verbatim` half of that exclusion was vacuous until P7.5 built the
1739/// type.
1740///
1741/// **Not "zero/closure"-shaped like this module's other twelve gated probes, and gated
1742/// anyway — a deliberate choice, not an inherited one.** The reading started at 1641 and
1743/// converged, over dozens of slices, to phase 7's own argued retirement floor, **809**
1744/// (ADR 0409; the full bucket-by-bucket accounting is in phase 7's own closing summary,
1745/// `design/archive/retired-tracks.md`) — never the literal 0 first proposed, nor a small
1746/// fixed number the way `ast_importers`/`emit_abi_shapes` are. Stays gated after
1747/// retirement, not deleted — a regression ratchet like `ast_importers` (floor 5) already
1748/// is: it moves on any `bynk-emit` PR that adds or removes a single `write!`/`writeln!`/
1749/// `format!` line anywhere in the crate — the same volatility #999 Decision D cites for
1750/// *not* gating `wildcard_arms` (311, ungated for exactly this reason) — but a floor this
1751/// track spent dozens of slices earning is worth catching a silent regression against,
1752/// the same trade `ast_importers` already made successfully across phase 6's 59 slices.
1753/// The churn cost is real and accepted, not overlooked: see ADR 0389/ADR 0390 for the
1754/// argument in full (review of #1297).
1755///
1756/// Counts `bynk-emit/src/**/*.rs` lines — excluding comments, `#[cfg(test)]` test-module
1757/// ranges, [`TS_WRITES_EXCLUDED_FILES`], and [`is_path_construction_line`] matches —
1758/// containing `write!`, `writeln!` or `format!`. See [`ts_writes_violations`] for the
1759/// counting logic itself.
1760fn ts_writes(root: &Path) -> Probe {
1761    let dir = root.join("bynk-emit/src");
1762    Probe {
1763        name: "ts_writes",
1764        gated: true,
1765        reads: ts_writes_violations(&rust_files_relative(&dir)).to_string(),
1766    }
1767}
1768
1769// --- Gated probe 11: ts_any -------------------------------------------------
1770
1771/// True if `line` (not a comment) violates R7.1's `TsType::Any` prohibition: an
1772/// `as any` cast, a bare `: any` type annotation, or `any` in generic type-argument
1773/// position (`Array<any>`, `Record<string, any[]>`, `Promise<any>`).
1774///
1775/// Six patterns, not `as any` alone, following three rounds of the same finding.
1776/// Round one (Q3, phase 7's own settling pass) found `as any` alone
1777/// under-counts R7.1 and added bare `: any`. Round two (review of #1297) found *that*
1778/// still under-counts: `bynk-emit/src/emitter/lower.rs`'s `joinOn`/`leftJoin`/`groupBy`
1779/// emit `const __h: Record<string, any[]> = {}` — `, any[]` contains neither `as any`
1780/// nor `: any`, so three live, production, TypeScript-emitting sites read as clean
1781/// under the round-one predicate. Widened to also match `<any`, `any>` and `any[]` —
1782/// each checked against the live tree for false positives (no non-`any`-typed English
1783/// word starts with `any` immediately after `<` or ends in `any` immediately before
1784/// `>`/`[]` anywhere in `bynk-emit/src` today) rather than assumed safe. Round three
1785/// (review of #1322) found a fourth spelling: once a site builds a real `bynk_ts::
1786/// TsType` node instead of writing TypeScript text directly, an emitted `any` no
1787/// longer appears as Rust-source `as any`/`: any` at all — `workers.rs`'s own
1788/// `TsType::named("any")` calls (#1321) emit the identical `payload as any`/
1789/// `let __who: any` text as before, byte-for-byte, but the *Rust spelling* that
1790/// produces it no longer matches any of the five text patterns above, so the probe
1791/// silently uncounted three real, still-live R7.1 residuals. Every later Arc C slice
1792/// converting an `any`-emitting `writeln!`/`format!` site the same way would keep
1793/// deflating this count the same way, so the fix generalises rather than special-
1794/// cases these three lines: match the construction spelling itself
1795/// (`named("any"`), not just raw emitted text.
1796///
1797/// Split out from [`ts_any_violations`] so a test can exercise the predicate directly,
1798/// without file I/O.
1799fn line_violates_ts_any(line: &str) -> bool {
1800    !is_line_comment(line)
1801        && (line.contains("as any")
1802            || line.contains(": any")
1803            || line.contains("<any")
1804            || line.contains("any>")
1805            || line.contains("any[]")
1806            || line.contains("named(\"any\""))
1807}
1808
1809/// [`ts_any`]'s counting logic, over an explicit `(relative path, contents)` list — see
1810/// [`rust_files_relative`] for why this isn't `root: &Path`.
1811fn ts_any_violations(files: &[(PathBuf, String)]) -> usize {
1812    let mut count = 0usize;
1813    for (rel, contents) in files {
1814        if is_ts_writes_excluded_file(rel) {
1815            continue;
1816        }
1817        let lines: Vec<&str> = contents.lines().collect();
1818        let ranges = test_mod_ranges(&lines);
1819        for (i, line) in lines.iter().enumerate() {
1820            if in_test_range(i, &ranges) {
1821                continue;
1822            }
1823            if line_violates_ts_any(line) {
1824                count += 1;
1825            }
1826        }
1827    }
1828    count
1829}
1830
1831/// Reference rule R7.1 (`design/bynk-greenfield-compiler.md` Part 7) — "the tree
1832/// contains no ... `TsType::Any`". Gated for the same reason [`ts_writes`] is (see its
1833/// own doc comment): started at 55 (not the settling review's estimated ~24 — ADR
1834/// 0390), converged over several slices to phase 7's own argued retirement floor, **26**
1835/// (ADR 0404; six already-argued families, none newly tractable — full accounting in
1836/// phase 7's own closing summary, `design/archive/retired-tracks.md`), and stays gated
1837/// after retirement as a regression ratchet, the same "I removed an `Any`" CI-checkable
1838/// claim every slice needed.
1839///
1840/// Counts `bynk-emit/src/**/*.rs` lines — excluding `#[cfg(test)]` test-module ranges
1841/// and [`TS_WRITES_EXCLUDED_FILES`] (the same files [`ts_writes`] excludes for producing
1842/// no TypeScript at all; an `any`-typed value there isn't R7.1's business either) —
1843/// matching [`line_violates_ts_any`]. Hand-written runtime `.ts` files under
1844/// `bynk-emit/runtime/` are out of scope by construction: [`rust_files`] only walks
1845/// `.rs` files, and R7.1 governs the emitted *tree*, not the hand-written runtime R7.7
1846/// separately covers.
1847fn ts_any(root: &Path) -> Probe {
1848    let dir = root.join("bynk-emit/src");
1849    Probe {
1850        name: "ts_any",
1851        gated: true,
1852        reads: ts_any_violations(&rust_files_relative(&dir)).to_string(),
1853    }
1854}
1855
1856// --- Gated probe 12: verbatim_origins ---------------------------------------
1857
1858/// P7.5 (#1307): distinct `bynk_ts::VerbatimOrigin` variants named in
1859/// `bynk-emit/src` — how many *families* of residual, not-yet-converted
1860/// emission remain, not their size (`verbatim_sites`, below, is the size).
1861/// Retired at an **argued floor**, named file-by-file the way `ast_importers`'s
1862/// floor of 5 was: **1** (ADR 0410) — only `NotYetConverted` has a live
1863/// production reference, at the same two sites `verbatim_sites`'s own floor
1864/// names permanent; full accounting in phase 7's own closing summary,
1865/// `design/archive/retired-tracks.md`. Read **0** at this slice's own
1866/// landing (`bynk-emit` built no `Verbatim` content yet, #1307's Decision C
1867/// — Arc C's own first slice is what gave this probe something to count);
1868/// stays gated after retirement as a regression ratchet.
1869///
1870/// Line-scans for `VerbatimOrigin::<Variant>` and counts distinct variant
1871/// names referenced, the same needle-scan shape [`hoist_sinks`] uses. A
1872/// known, accepted gap (review of #1308, finding 6): a bare `use
1873/// bynk_ts::VerbatimOrigin::Contracts;` followed by unqualified `Contracts`
1874/// elsewhere would undercount, since the needle is the qualified path. Not
1875/// worth a real-parser fix for an *argued-floor* probe (unlike
1876/// `verbatim_sites`'s own floor of exactly 0) — `bynk-emit`'s own existing
1877/// call-site style always qualifies (`TsStmt::verbatim(VerbatimOrigin::X,
1878/// …)`), so this is a theoretical undercount, not an observed one.
1879fn verbatim_origins(root: &Path) -> Probe {
1880    let dir = root.join("bynk-emit/src");
1881    Probe {
1882        name: "verbatim_origins",
1883        gated: true,
1884        reads: verbatim_origins_violations(&rust_files_relative(&dir)).to_string(),
1885    }
1886}
1887
1888/// [`verbatim_origins`]'s counting logic, over an explicit `(relative path,
1889/// contents)` list — see [`rust_files_relative`] for why this isn't `root:
1890/// &Path`. Excludes `#[cfg(test)]` ranges the same way [`ts_any_violations`]
1891/// does (review of #1308, finding 6): without this, one `bynk-emit` unit
1892/// test constructing a `VerbatimOrigin` for its own fixture pins this probe
1893/// above its argued floor permanently, for a reason that has nothing to do
1894/// with residual production emission.
1895fn verbatim_origins_violations(files: &[(PathBuf, String)]) -> usize {
1896    let needle = "VerbatimOrigin::";
1897    let mut variants: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
1898    for (_, contents) in files {
1899        let lines: Vec<&str> = contents.lines().collect();
1900        let ranges = test_mod_ranges(&lines);
1901        for (i, line) in lines.iter().enumerate() {
1902            if in_test_range(i, &ranges) || is_line_comment(line) {
1903                continue;
1904            }
1905            let mut rest = *line;
1906            while let Some(idx) = rest.find(needle) {
1907                let after = &rest[idx + needle.len()..];
1908                let name: String = after
1909                    .chars()
1910                    .take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
1911                    .collect();
1912                rest = &after[name.len()..];
1913                if !name.is_empty() {
1914                    variants.insert(name);
1915                }
1916            }
1917        }
1918    }
1919    variants.len()
1920}
1921
1922// --- Gated probe 13: verbatim_sites -----------------------------------------
1923
1924/// P7.5 (#1307): distinct `TsStmt::verbatim(...)`/`TsExpr::VerbatimExpr(...)`
1925/// construction call sites in `bynk-emit/src`, line-scanned the same way
1926/// [`hoist_sinks`] counts `stmts: &mut Vec<String>` occurrences. Every call
1927/// site converting to a real tree node is what Arc C's own per-file slices
1928/// were actually for — `verbatim_origins` alone can't distinguish "3
1929/// variants, 12 residual call sites" from "3 variants, 900 residual call
1930/// sites, two files never decomposed"; this is what closes that gap. Retired
1931/// at an **argued floor** for the `TsStmt` half, not the flat 0 first
1932/// proposed: **2** (ADR 0399/ADR 0407, confirmed unchanged by the #1486
1933/// capstone) — `project.rs`'s adapter-binding copy loop (a foreign,
1934/// user-authored TypeScript payload) and its `runtime.ts` staging (a
1935/// committed npm build artifact), neither ever generated by `bynk-emit`;
1936/// full accounting in phase 7's own closing summary, `design/archive/
1937/// retired-tracks.md`.
1938///
1939/// #1539 widens the scan to the `TsExpr` half of the same escape hatch
1940/// (`TsExpr::VerbatimExpr`, closing the untagged-`Ident` gap the review
1941/// found) and moves the floor to **11** (the 2 permanent `TsStmt` sites plus
1942/// 9 residual `TsExpr` construction sites in `emit.rs` — a generic-typed
1943/// callee `Call`/`New` has no `type_args` field, a nested `As`-under-`As`
1944/// chain the printer's own operand-parenthesisation rule would mis-wrap, a
1945/// block-bodied ICU IIFE, and a `pred_condition_and_message`-style message
1946/// that a second `TsLit::Str` escaping pass would corrupt — see
1947/// `TsExpr::VerbatimExpr`'s own doc for the full list). Unlike the `TsStmt`
1948/// pair, these 9 are not argued-permanent the same way: each converts to a
1949/// real node the day `bynk-ts` gains the matching type-algebra piece
1950/// (`type_args`, a parenthesisation fix, …), so this half of the floor is
1951/// expected to keep shrinking, tracked here rather than assumed fixed. Read
1952/// **0** at this slice's own landing, same reason `verbatim_origins` did;
1953/// stays gated after retirement as a regression ratchet.
1954fn verbatim_sites(root: &Path) -> Probe {
1955    let dir = root.join("bynk-emit/src");
1956    Probe {
1957        name: "verbatim_sites",
1958        gated: true,
1959        reads: verbatim_sites_violations(&rust_files_relative(&dir)).to_string(),
1960    }
1961}
1962
1963/// [`verbatim_sites`]'s counting logic, over an explicit `(relative path,
1964/// contents)` list — see [`rust_files_relative`] for why this isn't `root:
1965/// &Path`. Excludes `#[cfg(test)]` ranges the same way [`ts_any_violations`]
1966/// does (review of #1308, finding 6): `verbatim_sites` is documented as
1967/// retiring at 0, so a residual construction site inside a test fixture
1968/// would pin it above zero permanently for a reason that has nothing to do
1969/// with production emission conversion.
1970fn verbatim_sites_violations(files: &[(PathBuf, String)]) -> usize {
1971    let needles = ["TsStmt::verbatim(", "TsExpr::VerbatimExpr("];
1972    let mut count = 0usize;
1973    for (_, contents) in files {
1974        let lines: Vec<&str> = contents.lines().collect();
1975        let ranges = test_mod_ranges(&lines);
1976        for (i, line) in lines.iter().enumerate() {
1977            if in_test_range(i, &ranges) || is_line_comment(line) {
1978                continue;
1979            }
1980            if needles.iter().any(|needle| line.contains(needle)) {
1981                count += 1;
1982            }
1983        }
1984    }
1985    count
1986}
1987
1988/// Named identifiers imported from the compiler-generated firstparty/runtime relative
1989/// modules (`./bynk.js`, `./runtime.js`, `./bynk/locale/types.js`, `./cloudflare.js`,
1990/// or their `../` forms) — `import type { A, B }`/`import { A, B }` braces, stripping
1991/// `type ` markers and `X as Y` aliases (keeping the imported name, not the local one,
1992/// since the allowlists are about what's referenced, not what it's called locally).
1993fn ts_named_imports_from_runtime_modules(src: &str) -> Vec<String> {
1994    let mut out = Vec::new();
1995    for line in src.lines() {
1996        let line = line.trim();
1997        if !line.starts_with("import") {
1998            continue;
1999        }
2000        let is_runtime_module = ["\"./bynk.js\"", "\"./runtime.js\"", "\"../runtime.js\""]
2001            .iter()
2002            .any(|m| line.ends_with(&format!("from {m};")))
2003            || line.contains("bynk/locale/types.js")
2004            || line.contains("cloudflare.js");
2005        if !is_runtime_module {
2006            continue;
2007        }
2008        let Some(open) = line.find('{') else { continue };
2009        let Some(close) = line.find('}') else {
2010            continue;
2011        };
2012        for part in line[open + 1..close].split(',') {
2013            let part = part.trim().trim_start_matches("type ").trim();
2014            if part.is_empty() {
2015                continue;
2016            }
2017            let imported = part.split(" as ").next().unwrap_or(part).trim();
2018            out.push(imported.to_string());
2019        }
2020    }
2021    out
2022}
2023
2024// --- Gated probe 14: incremental_query_types --------------------------------
2025
2026/// Phase 8's own completion criterion (`design/bynk-compiler-trajectory.md` §3,
2027/// "keystroke-to-diagnostic latency by query level"), settled by #1509 (Q5, ADR 0414;
2028/// `design/tracks/incrementality.md` §5) as a one-time **existence** proof, not a count
2029/// trending toward a floor the way every other gated probe in this module is shaped —
2030/// R3.13/R3.14 describe a property to construct, not a defect to exhaust, so a
2031/// shrinking count would be the wrong shape regardless of how it was tuned.
2032///
2033/// **Re-settled by #1537 (2 September 2026), after the 30 August post-restructuring
2034/// review found the probe could not tell adoption from existence.** Phase 8 built all
2035/// four R3.13 levels; only the file level (P8.4's shared parse cache) and the unit
2036/// level's *proof* (P8.2's stability test over `UnitSignature`) had a consumer. The
2037/// definition level (`Body(DefId)`/`TypeOf(DefId)`, 816 lines) and the project level
2038/// (`ProjectGraph`, 174 lines) were reachable only from their own tests, with no
2039/// scheduler to call them and — per R3.15 and #1523 — no trigger yet for one. Both
2040/// were deleted rather than left "available but unwired" (P5), the same decision the
2041/// IR cutover (#1542) reached for phase 6's expression IR. This probe now certifies
2042/// the decision, in both directions:
2043///
2044/// 1. **Unit level** — `struct UnitSignature` exists as real code in `bynk-check`
2045///    (P8.1, ADR 0412). It is the R3.14 firewall's own specification, and the one
2046///    phase 8 artefact #1523's trigger presupposes; it stays as a *proof*, not a
2047///    production path (its only reader is clause 3's test), argued in #1537's ADR.
2048/// 2. **Shared cache** — the file-level parse cache has migrated off
2049///    `PROJECT_UNIT_CACHE` (`bynk-ide/src/completion.rs`) onto one shared,
2050///    `bynk-project`-owned cache (P8.4, ADR 0413). Checked two ways: the old static
2051///    gone from `bynk-ide/src`, *and* some cache-shaped `static`/`struct` present in
2052///    `bynk-project/src` — absence alone would read "migrated" for a bare deletion.
2053/// 3. **Stability test** — some `#[test]` under `bynk-check/tests/` proves
2054///    `UnitSignature`'s stability under a body edit (P8.2): any test name containing
2055///    both `unit_signature` and `stab`.
2056/// 4. **Definition and project levels absent** — no `struct ProjectGraph`, and no
2057///    `DefId`-keyed `fn body(`/`fn type_of(`, in **any** workspace crate's `src/`
2058///    ([`workspace_crate_src_files`], the same walk `unconsumed_ir_items` uses, minus
2059///    `xtask` itself, whose source spells the needles) — not just the two crates
2060///    phase 8 landed them in, since R3.13's own table
2061///    assigns `DefId` bindings to a `bynk-resolve` crate that does not exist yet and
2062///    a rebuild might put them there (review of #1582). `checker.rs`'s own
2063///    pre-existing per-expression `type_of`, which has no `DefId` parameter, does
2064///    not count — the false positive #1510's first run caught, now with the opposite
2065///    consequence. This clause is what makes the probe a gate on #1537 rather than a
2066///    memorial: re-adding either level changes the committed reading and fails the
2067///    currency test, so it needs a consumer and a re-settling — the trigger
2068///    R3.15/#1523 names. **What the gate does not see, stated rather than asserted
2069///    away:** a `DefId` parameter wrapped onto the line after `fn body(` (the
2070///    same-line rule [`defid_query_fn_present`] documents), or a query function under
2071///    any other name. Text-level, like every probe in this file.
2072///
2073/// Every clause is a static read of the tree (never a nested build or test run — see
2074/// [`unit_signature_present`]'s own doc comment for why a "does the stability test
2075/// *pass*" clause was deliberately rejected, #1510's own review-shaped framing).
2076fn incremental_query_types(root: &Path) -> Probe {
2077    let check_src = rust_files(&root.join("bynk-check/src"));
2078    let project_src = rust_files(&root.join("bynk-project/src"));
2079    let ide_src = rust_files(&root.join("bynk-ide/src"));
2080    let check_tests = rust_files(&root.join("bynk-check/tests"));
2081
2082    let unit_present = unit_signature_present(&check_src);
2083    let cache_migrated = shared_cache_migrated(&ide_src, &project_src);
2084    let test_present = stability_test_present(&check_tests);
2085    // Every workspace crate but this one: the harness's own source spells the
2086    // needles it scans for (in these very functions and their tests), so it
2087    // would read as a re-add of both levels on every run.
2088    let workspace_src: Vec<(PathBuf, String)> = workspace_crate_src_files(root)
2089        .into_iter()
2090        .filter(|(krate, _, _)| krate != "xtask")
2091        .map(|(_, path, contents)| (path, contents))
2092        .collect();
2093    let readded = deleted_levels_present(&workspace_src);
2094
2095    let reads = format!(
2096        "unit_signature {}; shared_cache {}; stability_test {}; definition/project levels {}",
2097        if unit_present { "present" } else { "absent" },
2098        if cache_migrated {
2099            "migrated"
2100        } else {
2101            "not migrated (PROJECT_UNIT_CACHE still bynk-ide-local)"
2102        },
2103        if test_present { "present" } else { "absent" },
2104        if readded.is_empty() {
2105            "absent (deleted by #1537)".to_string()
2106        } else {
2107            format!(
2108                "re-added ({}) — need a consumer and a re-settling of #1537",
2109                readded.join(", ")
2110            )
2111        },
2112    );
2113    Probe {
2114        name: "incremental_query_types",
2115        gated: true,
2116        reads,
2117    }
2118}
2119
2120/// Clause 1 of [`incremental_query_types`]: does `struct UnitSignature` exist as real
2121/// code (not a comment or doc prose) in `bynk-check`? The same "grep for the real
2122/// identifier, not the doc claim" discipline `design/tracks/incrementality.md` §1 used
2123/// to measure this reading as zero at settling. Deliberately *not* a "does P8.2's
2124/// fixture pass" check: every gated probe here is a static read of the tree, computed
2125/// from inside `xtask/tests/greenfield_status.rs`'s own `#[test]`; shelling out to
2126/// `cargo test` from inside a running `cargo test` is the identical nested-invocation
2127/// cost [`wildcard_arms`] (the one probe that shells out, and stays trend-only for
2128/// exactly this reason) avoids.
2129fn unit_signature_present(check_src: &[(PathBuf, String)]) -> bool {
2130    any_real_code_line(check_src, "struct UnitSignature")
2131}
2132
2133/// Clause 4 of [`incremental_query_types`]: which of the two levels #1537 deleted are
2134/// back anywhere in `src` — `ProjectGraph` as a real struct, or a `DefId`-keyed
2135/// `body`/`type_of` query function ([`defid_query_fn_present`]). Empty is the
2136/// committed reading; any entry changes the table and fails the currency gate, which
2137/// is the point.
2138fn deleted_levels_present(src: &[(PathBuf, String)]) -> Vec<&'static str> {
2139    let mut found = Vec::new();
2140    if any_real_code_line(src, "struct ProjectGraph") {
2141        found.push("ProjectGraph");
2142    }
2143    if defid_query_fn_present(src, "fn body(") {
2144        found.push("Body");
2145    }
2146    if defid_query_fn_present(src, "fn type_of(") {
2147        found.push("TypeOf");
2148    }
2149    found
2150}
2151
2152/// A `fn_needle`-matching signature line that *also* names `DefId` on the same line —
2153/// not just `fn_needle` alone. **A real, empirically-confirmed false positive this
2154/// slice's own first run caught, not a hypothetical:** `bynk-check/src/checker.rs`
2155/// already has a `pub(crate) fn type_of(expr: &Expr, expected: Option<TyId>, ctx: &mut
2156/// Ctx) -> Option<TyId>` — real, pre-existing, ordinary per-expression type-inference
2157/// plumbing that predates this whole track and has nothing to do with R3.13's
2158/// `DefId`-keyed query — a naive `fn type_of(` scan reads this as `TypeOf` already
2159/// existing on the very first run, before P8.5 does any work at all. Requiring
2160/// `DefId` on the same signature line is a real, if narrow, precision fix: it correctly
2161/// reads false against `checker.rs`'s own `type_of` today, and correctly flips true
2162/// once P8.5 lands a real `DefId`-keyed function, whatever it ends up calling it, as
2163/// long as the parameter appears on the `fn` line itself (a wrapped multi-line
2164/// signature would need widening this scan window — not needed for any function in
2165/// the tree today).
2166fn defid_query_fn_present(files: &[(PathBuf, String)], fn_needle: &str) -> bool {
2167    files.iter().any(|(_, contents)| {
2168        contents.lines().any(|line| {
2169            !is_line_comment(line) && line.contains(fn_needle) && line.contains("DefId")
2170        })
2171    })
2172}
2173
2174/// Whether the file-level parse cache has migrated off `bynk-ide`'s own
2175/// `PROJECT_UNIT_CACHE` onto some shared cache in `bynk-project` — see
2176/// [`incremental_query_types`]'s own doc comment (clause 2) for why *both* halves are
2177/// checked: absence from `bynk-ide` alone cannot distinguish a real migration from a
2178/// bare rename or deletion with nothing shared put in its place. The needle is
2179/// anchored on the `static` declaration line (`static PROJECT_UNIT_CACHE`), not a bare
2180/// substring, so `PROJECT_UNIT_CACHE_CAP` (a real, unrelated `const` in
2181/// `bynk-ide/src/completion.rs`) can't hold this false on its own.
2182fn shared_cache_migrated(ide_src: &[(PathBuf, String)], project_src: &[(PathBuf, String)]) -> bool {
2183    !any_real_code_line(ide_src, "static PROJECT_UNIT_CACHE")
2184        && cache_shaped_item_present(project_src)
2185}
2186
2187/// Whether `bynk-project/src` has a `static`/`struct` item whose name mentions "cache"
2188/// (case-insensitive) — the only crate-boundary-checkable proxy for "some shared cache
2189/// now lives where P8.4 is meant to put it," until that slice pins the real identifier
2190/// down. Deliberately loose, the same "exact name not yet proposed" reasoning
2191/// [`stability_test_present`] already uses.
2192fn cache_shaped_item_present(project_src: &[(PathBuf, String)]) -> bool {
2193    project_src.iter().any(|(_, contents)| {
2194        contents.lines().any(|line| {
2195            if is_line_comment(line) {
2196                return false;
2197            }
2198            let trimmed = line.trim_start();
2199            let is_item = trimmed.starts_with("static ")
2200                || trimmed.starts_with("pub static ")
2201                || trimmed.starts_with("struct ")
2202                || trimmed.starts_with("pub struct ");
2203            is_item && line.to_lowercase().contains("cache")
2204        })
2205    })
2206}
2207
2208/// Whether any `#[test]` fn under `bynk-check/tests/` looks like P8.2's own
2209/// body-edit-stability property test — see [`incremental_query_types`]'s own doc
2210/// comment (clause 3) for why the name match (`unit_signature` + `stab`) is
2211/// deliberately loose. The `#[test]` attribute itself is *not* loose: it must be on the
2212/// matching `fn` line or on a contiguous run of attribute lines directly above it, so a
2213/// same-named non-test helper (a fixture builder, say) can't satisfy this clause.
2214fn stability_test_present(check_tests: &[(PathBuf, String)]) -> bool {
2215    check_tests.iter().any(|(_, contents)| {
2216        let lines: Vec<&str> = contents.lines().collect();
2217        lines.iter().enumerate().any(|(i, line)| {
2218            if is_line_comment(line) {
2219                return false;
2220            }
2221            let lower = line.to_lowercase();
2222            let name_matches =
2223                lower.contains("fn ") && lower.contains("unit_signature") && lower.contains("stab");
2224            if !name_matches {
2225                return false;
2226            }
2227            line.contains("#[test]")
2228                || lines[..i]
2229                    .iter()
2230                    .rev()
2231                    .take_while(|l| l.trim_start().starts_with('#'))
2232                    .any(|l| l.trim() == "#[test]")
2233        })
2234    })
2235}
2236
2237/// Whether any line in `files` (excluding comments) contains `needle` — the shared
2238/// existence-check primitive [`unit_signature_present`]/[`deleted_levels_present`]/
2239/// [`shared_cache_migrated`] all use.
2240fn any_real_code_line(files: &[(PathBuf, String)], needle: &str) -> bool {
2241    files.iter().any(|(_, contents)| {
2242        contents
2243            .lines()
2244            .any(|line| !is_line_comment(line) && line.contains(needle))
2245    })
2246}
2247
2248// --- Gated probe 15: unconsumed_ir_items ------------------------------------
2249
2250/// Slice D3 of #1542 (`design/archive/retired-tracks.md`, the IR cutover's own
2251/// closing summary): the adoption probe the 30 August 2026 post-restructuring
2252/// review (`design/reviews/2026-08-30-post-restructuring-review.md`, Part 5 §8)
2253/// asked for — for each `pub` item in `bynk-ir/src` and `bynk-lower/src`, does
2254/// a production call site exist outside the owning crate and outside a test
2255/// module? That review found phase 6 had shipped an expression IR nothing
2256/// consumed (fifteen `bynk-lower` entry points, twenty-one `bynk-ir` types),
2257/// invisible to every existing gate because each of them certifies that a name
2258/// *exists* in a directory, not that anything *reads* it. Scoped to the two IR
2259/// crates because their entire purpose is to be consumed elsewhere: a `pub`
2260/// item in either with no reader in another crate is, by construction, either
2261/// dead or a second path waiting to be wired in — the P5 failure
2262/// (`bynk-greenfield-compiler.md`) both phases 6 and 8 reproduced.
2263///
2264/// Reads **0** at its own landing, by construction: Slices D0–D2 deleted every
2265/// unconsumed item (D1 also demoted the two crate-internal helpers,
2266/// `lower_fn_sig_ir_from_types`/`lower_op_sig_ir_from_commons`, that would
2267/// otherwise have read as 2). Gated as a ratchet: a new `pub` item in either
2268/// crate with no consumer moves it off zero and fails
2269/// `greenfield_status_table_is_current`, so the "available but unwired" state
2270/// cannot land silently again. The reading names the offending items so the
2271/// failure is actionable, not just a count.
2272///
2273/// **What counts as a consumer.** A non-comment line, outside any `#[cfg(test)]
2274/// mod` range ([`test_mod_ranges`]), in a workspace crate's `src/` that is
2275/// **neither owner crate**, containing the item's name as a whole word. The
2276/// two owners do not vouch for each other, on purpose (review of this slice's
2277/// own PR, #1581): run against pre-D0 `main` with only the owning crate
2278/// excluded, every one of phase 6's twenty-one unconsumed `bynk-ir` types
2279/// would have read as consumed, because `bynk-lower`'s own unconsumed
2280/// constructors named them — the probe would have missed half the surface it
2281/// was built to see. With both excluded, its first honest run read 5
2282/// (`IndexIr` and the four `MUTATING_*_OPS` tables, read only from
2283/// `bynk-lower`), resolved by inlining the alias and moving the tables beside
2284/// their one reader rather than arguing a floor. Text-level, like every probe
2285/// in this file — a name that happens to be shared with an unrelated item in
2286/// another crate would read as consumed (a false negative for the ratchet,
2287/// never a false positive that blocks a PR), accepted the same way
2288/// [`ts_writes`]'s own known over-count is. Items are `pub` at column zero
2289/// only — `fn`, `struct`, `enum`, `type`, `const`, `static`, `trait`, `union`,
2290/// with any `async`/`unsafe`/`const`/`extern` qualifier on a `fn` — see
2291/// [`column_zero_pub_item_name`]: `pub(crate)` is by definition not offered
2292/// to another crate, a `pub` item nested inside an `impl` block is reachable
2293/// only through its owner (which is what gets counted), and `pub use`/`pub
2294/// mod` re-export rather than declare.
2295fn unconsumed_ir_items(root: &Path) -> Probe {
2296    let owners = ["bynk-ir", "bynk-lower"];
2297    let mut owner_files = Vec::new();
2298    for owner in owners {
2299        for (path, contents) in rust_files_relative(&root.join(owner).join("src")) {
2300            owner_files.push((owner.to_string(), path, contents));
2301        }
2302    }
2303    let consumer_files = workspace_crate_src_files(root);
2304    let unconsumed = unconsumed_pub_items(&owners, &owner_files, &consumer_files);
2305    let reads = if unconsumed.is_empty() {
2306        "0".to_string()
2307    } else {
2308        format!("{} ({})", unconsumed.len(), unconsumed.join(", "))
2309    };
2310    Probe {
2311        name: "unconsumed_ir_items",
2312        gated: true,
2313        reads,
2314    }
2315}
2316
2317/// Every `.rs` file under `<crate>/src` for every workspace crate — each entry
2318/// tagged with its crate directory name so [`unconsumed_pub_items`] can exclude
2319/// an item's own crate. A workspace crate is any immediate child of `root` with
2320/// both a `Cargo.toml` and a `src/`, the same shape every `members` entry in the
2321/// root manifest has; reading the manifest itself would add a TOML parse for no
2322/// gain in precision.
2323fn workspace_crate_src_files(root: &Path) -> Vec<(String, PathBuf, String)> {
2324    let mut out = Vec::new();
2325    let Ok(entries) = std::fs::read_dir(root) else {
2326        return out;
2327    };
2328    let mut crates: Vec<PathBuf> = entries
2329        .flatten()
2330        .map(|e| e.path())
2331        .filter(|p| p.join("Cargo.toml").is_file() && p.join("src").is_dir())
2332        .collect();
2333    crates.sort();
2334    for krate in crates {
2335        let name = krate
2336            .file_name()
2337            .map(|n| n.to_string_lossy().into_owned())
2338            .unwrap_or_default();
2339        for (path, contents) in rust_files_relative(&krate.join("src")) {
2340            out.push((name.clone(), path, contents));
2341        }
2342    }
2343    out
2344}
2345
2346/// [`unconsumed_ir_items`]'s counting logic over explicit `(crate, relative
2347/// path, contents)` lists — see [`rust_files_relative`] for why this isn't
2348/// `root: &Path`. Returns `crate::item` for every column-zero `pub` item in
2349/// `owner_files` that no non-comment, non-test line in a file belonging to a
2350/// crate outside `owners` names as a whole word; sorted and deduplicated (a
2351/// name declared in two files of one crate is one item, not two), so the
2352/// reading is stable across runs and across a crate being split into modules.
2353///
2354/// Each consumer file is split and its `#[cfg(test)]` ranges computed once,
2355/// up front, not once per candidate item (review of #1581): this runs under
2356/// `cargo test --workspace` on every Rust-touching PR, and the failure path —
2357/// an item with no consumer — is exactly the one that scans every file.
2358fn unconsumed_pub_items(
2359    owners: &[&str],
2360    owner_files: &[(String, PathBuf, String)],
2361    consumer_files: &[(String, PathBuf, String)],
2362) -> Vec<String> {
2363    let mut items: Vec<(String, String)> = Vec::new();
2364    for (krate, _, contents) in owner_files {
2365        let lines: Vec<&str> = contents.lines().collect();
2366        let ranges = test_mod_ranges(&lines);
2367        for (i, line) in lines.iter().enumerate() {
2368            if in_test_range(i, &ranges) {
2369                continue;
2370            }
2371            if let Some(name) = column_zero_pub_item_name(line) {
2372                items.push((krate.clone(), name.to_string()));
2373            }
2374        }
2375    }
2376    items.sort();
2377    items.dedup();
2378
2379    // Production, non-owner lines only — preprocessed once.
2380    let consumer_lines: Vec<&str> = consumer_files
2381        .iter()
2382        .filter(|(krate, _, _)| !owners.contains(&krate.as_str()))
2383        .flat_map(|(_, _, contents)| {
2384            let lines: Vec<&str> = contents.lines().collect();
2385            let ranges = test_mod_ranges(&lines);
2386            lines
2387                .iter()
2388                .enumerate()
2389                .filter(|(i, line)| !in_test_range(*i, &ranges) && !is_line_comment(line))
2390                .map(|(_, line)| *line)
2391                .collect::<Vec<_>>()
2392        })
2393        .collect();
2394
2395    items
2396        .iter()
2397        .filter(|(_, name)| !consumer_lines.iter().any(|line| contains_word(line, name)))
2398        .map(|(owner, name)| format!("{owner}::{name}"))
2399        .collect()
2400}
2401
2402/// The name of a column-zero `pub` item declaration, if `line` is one: `pub`
2403/// followed by `fn`, `struct`, `enum`, `type`, `const`, `static`, `trait` or
2404/// `union`, where a `fn` may carry `async`/`unsafe`/`const`/`extern "…"`
2405/// qualifiers in any order. `pub(crate)`/`pub(super)` do not qualify (they
2406/// are not offered to other crates), and neither does anything indented (a
2407/// method or associated item, reachable only through its owner), nor `pub
2408/// use`/`pub mod` (re-exports and module declarations, not items).
2409fn column_zero_pub_item_name(line: &str) -> Option<&str> {
2410    let mut rest = line.strip_prefix("pub ")?;
2411    // `pub const fn` / `pub async unsafe fn` / `pub unsafe extern "C" fn` …:
2412    // peel qualifiers until the item keyword is exposed. A bare `pub const X`
2413    // is a constant, not a qualifier, so `const` only peels when a `fn`
2414    // (possibly behind further qualifiers) follows it.
2415    loop {
2416        if let Some(r) = rest
2417            .strip_prefix("async ")
2418            .or_else(|| rest.strip_prefix("unsafe "))
2419        {
2420            rest = r;
2421            continue;
2422        }
2423        if let Some(r) = rest.strip_prefix("extern ") {
2424            // `extern "C" fn` — skip the ABI string if present.
2425            let r = r.trim_start();
2426            let r = if let Some(after_quote) = r.strip_prefix('"') {
2427                after_quote
2428                    .find('"')
2429                    .map(|q| after_quote[q + 1..].trim_start())
2430                    .unwrap_or(r)
2431            } else {
2432                r
2433            };
2434            rest = r;
2435            continue;
2436        }
2437        if let Some(r) = rest.strip_prefix("const ")
2438            && (r.starts_with("fn ")
2439                || r.starts_with("async ")
2440                || r.starts_with("unsafe ")
2441                || r.starts_with("extern "))
2442        {
2443            rest = r;
2444            continue;
2445        }
2446        break;
2447    }
2448    let rest = [
2449        "fn ", "struct ", "enum ", "type ", "const ", "static ", "trait ", "union ",
2450    ]
2451    .iter()
2452    .find_map(|kw| rest.strip_prefix(kw))?;
2453    let end = rest
2454        .find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
2455        .unwrap_or(rest.len());
2456    (end > 0).then(|| &rest[..end])
2457}
2458
2459/// Does `line` contain `word` as a whole identifier — not as a prefix or
2460/// suffix of a longer one (`IrExpr` inside `IrExprKind` must not count)?
2461fn contains_word(line: &str, word: &str) -> bool {
2462    let bytes = line.as_bytes();
2463    let mut from = 0;
2464    while let Some(pos) = line[from..].find(word) {
2465        let start = from + pos;
2466        let end = start + word.len();
2467        let before_ok = start == 0 || !is_ident_byte(bytes[start - 1]);
2468        let after_ok = end == bytes.len() || !is_ident_byte(bytes[end]);
2469        if before_ok && after_ok {
2470            return true;
2471        }
2472        from = start + 1;
2473    }
2474    false
2475}
2476
2477fn is_ident_byte(b: u8) -> bool {
2478    b.is_ascii_alphanumeric() || b == b'_'
2479}
2480
2481// --- Reported probe 1: wildcard_arms --------------------------------------
2482
2483/// R2.12. `clippy::wildcard_enum_match_arm` diagnostics, forced on via `-W` so the
2484/// count is real from day one and doesn't wait on `workspace_lints`/T0.3 (#999 Decision
2485/// C — delegating to clippy's own type-aware pass, rather than a hand-rolled scan for
2486/// "compiler-owned enum", so the probe and the enforcement mechanism can never
2487/// disagree). A count, not a boolean — moves on nearly every match statement anyone
2488/// writes, so it is reported, not gated (#999 Decision D).
2489fn wildcard_arms(root: &Path) -> Probe {
2490    let reads = match run_clippy_wildcard_scan(root) {
2491        Ok(n) => n.to_string(),
2492        Err(e) => format!("error running clippy: {e}"),
2493    };
2494    Probe {
2495        name: "wildcard_arms",
2496        gated: false,
2497        reads,
2498    }
2499}
2500
2501/// Runs clippy with the lint forced on and parses the NDJSON output properly —
2502/// **not** a substring count. A single `wildcard_enum_match_arm` diagnostic's JSON
2503/// repeats the lint name several times (the `code` field, the human-readable message,
2504/// the `#[warn(...)]` note, and the `rendered` field duplicating the whole thing as
2505/// text), so `stdout.matches("wildcard_enum_match_arm").count()` overcounts by roughly
2506/// 3x — caught by cross-checking this probe's own first run against a real JSON parse
2507/// (296 real diagnostics, not the naive scan's 888).
2508///
2509/// Checks the process exit status: a forced `-W` (not `-D`) never fails the build on
2510/// account of the lint itself, so a non-zero exit means clippy genuinely could not run
2511/// (a compile error elsewhere, a missing toolchain component, offline with no cached
2512/// index) — in which case stdout carries no `compiler-message` lines and a silent
2513/// success would report a false, and indistinguishable, `0`. This probe is reported,
2514/// not gated, precisely so an honest "couldn't measure" surfaces loudly here rather
2515/// than being read as "closed."
2516fn run_clippy_wildcard_scan(root: &Path) -> std::io::Result<usize> {
2517    let output = Command::new("cargo")
2518        .args([
2519            "clippy",
2520            "--workspace",
2521            "--message-format=json",
2522            "--",
2523            "-W",
2524            "clippy::wildcard_enum_match_arm",
2525        ])
2526        .current_dir(root)
2527        .output()?;
2528    if !output.status.success() {
2529        return Err(std::io::Error::other(format!(
2530            "cargo clippy exited with {}: {}",
2531            output.status,
2532            String::from_utf8_lossy(&output.stderr).trim()
2533        )));
2534    }
2535    let stdout = String::from_utf8_lossy(&output.stdout);
2536    let mut count = 0usize;
2537    for line in stdout.lines() {
2538        let Ok(value) = serde_json::from_str::<serde_json::Value>(line) else {
2539            continue;
2540        };
2541        if value.get("reason").and_then(|r| r.as_str()) != Some("compiler-message") {
2542            continue;
2543        }
2544        let code = value.pointer("/message/code/code").and_then(|c| c.as_str());
2545        if code == Some("clippy::wildcard_enum_match_arm") {
2546            count += 1;
2547        }
2548    }
2549    Ok(count)
2550}
2551
2552// --- Reported probe 2: keep_in_sync ---------------------------------------
2553
2554/// P2 (trend only). Comments across the workspace containing "in sync", "mirrors",
2555/// "parity", or "must match" — each one names a rule the compiler cannot teach itself
2556/// and must be taught in review, every time.
2557fn keep_in_sync(root: &Path) -> Probe {
2558    let phrases = ["in sync", "mirrors", "parity", "must match"];
2559    let mut count = 0usize;
2560    for dir in top_level_crate_dirs(root) {
2561        for (_, contents) in rust_files(&dir.join("src")) {
2562            for line in contents.lines() {
2563                if is_line_comment(line) {
2564                    let lower = line.to_lowercase();
2565                    if phrases.iter().any(|p| lower.contains(p)) {
2566                        count += 1;
2567                    }
2568                }
2569            }
2570        }
2571    }
2572    Probe {
2573        name: "keep_in_sync",
2574        gated: false,
2575        reads: count.to_string(),
2576    }
2577}
2578
2579// --- Reported probe 3: test_density ---------------------------------------
2580
2581/// R11.1, and §3.4's phase-3 trigger. Per crate: (lines inside `#[test]` fn bodies,
2582/// plus lines inside `#[cfg(test)] mod` blocks outside those fns) ÷ (non-blank,
2583/// non-comment lines under that crate's `src/`) — #999 Decision F's definition,
2584/// written down precisely because an undefined "ratio" is exactly the ambiguity that
2585/// produced the track doc §9's four-row ambiguity.
2586fn test_density(root: &Path) -> Probe {
2587    let mut parts = Vec::new();
2588    for dir in top_level_crate_dirs(root) {
2589        let name = dir.file_name().unwrap().to_string_lossy().to_string();
2590        let src_dir = dir.join("src");
2591        let mut test_lines = 0usize;
2592        let mut code_lines = 0usize;
2593        for (_, contents) in rust_files(&src_dir) {
2594            let lines: Vec<&str> = contents.lines().collect();
2595            let ranges = test_mod_ranges(&lines);
2596            for (i, line) in lines.iter().enumerate() {
2597                let is_blank_or_comment = line.trim().is_empty() || is_line_comment(line);
2598                if !is_blank_or_comment {
2599                    code_lines += 1;
2600                }
2601                if in_test_range(i, &ranges) && !is_blank_or_comment {
2602                    test_lines += 1;
2603                }
2604            }
2605        }
2606        if code_lines > 0 {
2607            let ratio = 100.0 * test_lines as f64 / code_lines as f64;
2608            parts.push(format!("{name}={ratio:.1}%"));
2609        }
2610    }
2611    Probe {
2612        name: "test_density",
2613        gated: false,
2614        reads: parts.join(", "),
2615    }
2616}
2617
2618// --- diagnostic_coverage ---------------------------------------------------
2619
2620/// #1662 (track #1648, G2). Registry codes that some test **asserts**, out of all
2621/// `bynk_syntax::diagnostics::REGISTRY` codes, and the count no test asserts.
2622///
2623/// Static, so it runs inside this harness: the review that set the baseline
2624/// (2026-10-01, #1647 Part 4) instrumented `CompileError::new` across a full
2625/// `cargo test --workspace`, which this harness cannot afford. Asserted is a
2626/// subset of produced (a passing test that names a code saw it), so driving the
2627/// unasserted count to the argued floor meets "every reachable code is produced
2628/// by a test" a fortiori.
2629///
2630/// A code counts as asserted when it appears in:
2631/// - line 1 of a negative fixture's `expected_error.txt` (line 2 is a message
2632///   substring and may quote other codes);
2633/// - any other non-`.bynk` file under a crate's `tests/` directory (test
2634///   sources, expected-diagnostics files, JSON goldens). `.bynk` sources are
2635///   skipped, because their comments often name the code they provoke, and Rust
2636///   sources are read with their `//` comments removed, for the same reason;
2637/// - a `#[cfg(test)] mod` block in a crate's `src/`, comments removed;
2638/// - a blessed diagnostic transcript, `site/src/diagnostics/*.txt`.
2639///
2640/// Gated at the argued floor of **4** (#1662 Decision B). Each of the four is
2641/// emitted where no compiler test can reach it:
2642/// - `bynk.deploy.contract_skew`: `bynk deploy`, against a live deployment's
2643///   lock;
2644/// - `bynk.project.read_failed`: only when a host's file overlay omits a
2645///   discovered file, which neither the CLI nor the LSP does;
2646/// - `bynk.target.vendor_conflict`: needs platform-native capabilities from two
2647///   platforms, and only Cloudflare ships any today (the decision function is
2648///   unit-tested);
2649/// - `bynk.wasm.strip_failed`: stripping the compiler's own emitted TypeScript,
2650///   which fails only on an emitter bug.
2651///
2652/// `cargo xtask greenfield-status --list-unasserted` prints the codes.
2653pub fn unasserted_codes(root: &Path) -> Vec<&'static str> {
2654    let registry: BTreeSet<&'static str> = bynk_syntax::diagnostics::REGISTRY
2655        .iter()
2656        .map(|d| d.code)
2657        .collect();
2658    let mut asserted: BTreeSet<String> = BTreeSet::new();
2659    let mut note = |text: &str| {
2660        for code in registry_tokens(text) {
2661            asserted.insert(code);
2662        }
2663    };
2664    for krate in top_level_crate_dirs(root) {
2665        for (path, contents) in text_files(&krate.join("tests")) {
2666            if path.extension().is_some_and(|e| e == "bynk") {
2667                continue;
2668            }
2669            if path.file_name().is_some_and(|n| n == "expected_error.txt") {
2670                note(
2671                    contents
2672                        .lines()
2673                        .find(|l| !l.trim().is_empty())
2674                        .unwrap_or(""),
2675                );
2676            } else if path.extension().is_some_and(|e| e == "rs") {
2677                note(&strip_line_comments(&contents));
2678            } else {
2679                note(&contents);
2680            }
2681        }
2682        for (_, contents) in rust_files(&krate.join("src")) {
2683            let lines: Vec<&str> = contents.lines().collect();
2684            for (start, end) in test_mod_ranges(&lines) {
2685                note(&strip_line_comments(&lines[start..=end].join("\n")));
2686            }
2687        }
2688    }
2689    for (path, contents) in text_files(&root.join("site/src/diagnostics")) {
2690        if path.extension().is_some_and(|e| e == "txt") {
2691            note(&contents);
2692        }
2693    }
2694    registry
2695        .into_iter()
2696        .filter(|c| !asserted.contains(*c))
2697        .collect()
2698}
2699
2700/// `src` with every `//` comment (including `///` and `//!` doc comments)
2701/// removed, so a code named only in prose does not count as asserted. A `//`
2702/// inside a string literal is kept; block comments are rare enough in this
2703/// workspace to leave.
2704fn strip_line_comments(src: &str) -> String {
2705    src.lines()
2706        .map(|line| {
2707            let bytes = line.as_bytes();
2708            let (mut in_str, mut escaped) = (false, false);
2709            for (i, &b) in bytes.iter().enumerate() {
2710                if escaped {
2711                    escaped = false;
2712                } else if b == b'\\' && in_str {
2713                    escaped = true;
2714                } else if b == b'"' {
2715                    in_str = !in_str;
2716                } else if b == b'/' && !in_str && bytes.get(i + 1) == Some(&b'/') {
2717                    return &line[..i];
2718                }
2719            }
2720            line
2721        })
2722        .collect::<Vec<_>>()
2723        .join("\n")
2724}
2725
2726/// Every `bynk.<family>.<name>` token in `text` — the shape of a registry code,
2727/// whether quoted (Rust source) or bare (fixtures, transcripts).
2728fn registry_tokens(text: &str) -> Vec<String> {
2729    let bytes = text.as_bytes();
2730    let ident = |b: u8| b.is_ascii_alphanumeric() || b == b'_' || b == b'.';
2731    let mut out = Vec::new();
2732    let mut i = 0;
2733    while let Some(rel) = text[i..].find("bynk.") {
2734        let start = i + rel;
2735        let mut end = start;
2736        while end < bytes.len() && ident(bytes[end]) {
2737            end += 1;
2738        }
2739        if start == 0 || !ident(bytes[start - 1]) {
2740            out.push(text[start..end].trim_end_matches('.').to_string());
2741        }
2742        i = end.max(start + 1);
2743    }
2744    out
2745}
2746
2747/// Every readable UTF-8 file under `dir`, recursively, as `(path, contents)`.
2748fn text_files(dir: &Path) -> Vec<(PathBuf, String)> {
2749    let mut out = Vec::new();
2750    let mut stack = vec![dir.to_path_buf()];
2751    while let Some(d) = stack.pop() {
2752        let Ok(entries) = std::fs::read_dir(&d) else {
2753            continue;
2754        };
2755        for entry in entries.flatten() {
2756            let path = entry.path();
2757            if path.is_dir() {
2758                stack.push(path);
2759            } else if let Ok(contents) = std::fs::read_to_string(&path) {
2760                out.push((path, contents));
2761            }
2762        }
2763    }
2764    out
2765}
2766
2767fn diagnostic_coverage(root: &Path) -> Probe {
2768    let total = bynk_syntax::diagnostics::REGISTRY.len();
2769    let unasserted = unasserted_codes(root).len();
2770    Probe {
2771        name: "diagnostic_coverage",
2772        gated: true,
2773        reads: format!(
2774            "unasserted={unasserted} (asserted {}/{total})",
2775            total - unasserted
2776        ),
2777    }
2778}
2779
2780// --- Reported probe 4: fixture_kinds --------------------------------------
2781
2782/// R11.2. Fixture directories under `bynkc/tests` using each assertion granularity —
2783/// `expected_contains.txt` / `expected_absent.txt` / `expected_diagnostics.txt` — set
2784/// against the older, coarser `expected_error.txt` (category-string) convention.
2785///
2786/// #1660 (runtime-semantics track §3.5) added two counts:
2787/// - `warnings` (`expected_warnings.txt`, a positive fixture's pinned warnings), which
2788///   this probe had never counted;
2789/// - `run` (`expected_run.txt`), the positive fixtures whose `suite`s
2790///   `bynkc/tests/behaviour_fixtures.rs` actually *runs*, the one granularity that
2791///   asserts runtime behaviour rather than emitted text or diagnostics.
2792fn fixture_kinds(root: &Path) -> Probe {
2793    let tests_dir = root.join("bynkc/tests");
2794    let contains = count_files_named(&tests_dir, "expected_contains.txt");
2795    let absent = count_files_named(&tests_dir, "expected_absent.txt");
2796    let diagnostics = count_files_named(&tests_dir, "expected_diagnostics.txt");
2797    let error = count_files_named(&tests_dir, "expected_error.txt");
2798    let warnings = count_files_named(&tests_dir, "expected_warnings.txt");
2799    let run = count_files_named(&tests_dir, "expected_run.txt");
2800    Probe {
2801        name: "fixture_kinds",
2802        gated: false,
2803        reads: format!(
2804            "contains={contains}, absent={absent}, diagnostics={diagnostics}, error={error}, warnings={warnings}, run={run}"
2805        ),
2806    }
2807}
2808
2809fn count_files_named(dir: &Path, filename: &str) -> usize {
2810    let mut count = 0usize;
2811    count_files_named_walk(dir, filename, &mut count);
2812    count
2813}
2814
2815fn count_files_named_walk(dir: &Path, filename: &str, count: &mut usize) {
2816    let Ok(entries) = std::fs::read_dir(dir) else {
2817        return;
2818    };
2819    for entry in entries.flatten() {
2820        let path = entry.path();
2821        if path.is_dir() {
2822            count_files_named_walk(&path, filename, count);
2823        } else if path.file_name().is_some_and(|n| n == filename) {
2824            *count += 1;
2825        }
2826    }
2827}
2828
2829// --- Reported probe 5: keystroke_latency ------------------------------------
2830
2831/// Phase 8's own trend-only probe (`design/bynk-compiler-trajectory.md` §3,
2832/// "keystroke-to-diagnostic latency by query level") — settled (Q3/Q5, ADR 0414;
2833/// `design/tracks/incrementality.md` §5) as staying **"not measured" for this whole
2834/// phase's lifetime**: R3.15's scheduler decision defers whole (no memo table, salsa or
2835/// otherwise, ships in phase 8), and the literal latency number presupposes query
2836/// levels attributing latency to — levels [`incremental_query_types`] itself proves
2837/// exist, but attributing real latency to them needs a scheduler this phase
2838/// deliberately does not build. Added now, not deferred to whenever a scheduler
2839/// exists, so the trajectory's own §3.0 baseline table carries a live, CI-computed row
2840/// instead of a static doc claim — the same "instrument even a number that won't move
2841/// yet" precedent `test_density`/`fixture_kinds` already set for this module.
2842fn keystroke_latency(_root: &Path) -> Probe {
2843    Probe {
2844        name: "keystroke_latency",
2845        gated: false,
2846        reads: "not measured — no scheduler exists yet (R3.15, deferred whole this phase)"
2847            .to_string(),
2848    }
2849}
2850
2851// --- Rendering + diffing ---------------------------------------------------
2852
2853/// The committed table: a plain Markdown table, probe name → gated?/reads, plus a
2854/// pointer to the rule ledger `stamp::apply` writes (#1001).
2855pub fn render_table(report: &Report) -> String {
2856    let mut out = String::new();
2857    out.push_str("<!-- GENERATED FILE — do not edit by hand.\n");
2858    out.push_str("     Source: cargo xtask greenfield-status (xtask/src/greenfield_status.rs).\n");
2859    out.push_str("     Regenerate with: cargo xtask greenfield-status --apply -->\n\n");
2860    out.push_str("# Greenfield status\n\n");
2861    out.push_str(
2862        "Track slice T0.0 (#999); `ts_writes`/`ts_any` added by P7.0 (#1296); \
2863         `verbatim_origins`/`verbatim_sites` added by P7.5 (#1307); \
2864         `incremental_query_types`/`keystroke_latency` added by P8.0 (#1510); \
2865         `unconsumed_ir_items` added by Slice D3 of the IR cutover (#1542); \
2866         `diagnostic_coverage` added by #1662. Sixteen \
2867         probes are gated — a disagreement between this file and a fresh run fails \
2868         `greenfield_status_table_is_current` (`xtask/tests/greenfield_status.rs`). \
2869         Five are trend probes, reported only.\n\n",
2870    );
2871    out.push_str("| Probe | Gated | Reads |\n|---|---|---|\n");
2872    for probe in &report.probes {
2873        let _ = writeln!(
2874            out,
2875            "| `{}` | {} | {} |",
2876            probe.name,
2877            if probe.gated { "yes" } else { "no (trend)" },
2878            probe.reads
2879        );
2880    }
2881
2882    out.push_str("\n## Rules closed\n\n");
2883    // A static, unconditional link — not a count, and not even an existence
2884    // check. A first draft read `design/greenfield-status-rules.md` here to
2885    // report a row count, but nothing regenerates *this* file when `stamp`
2886    // writes the ledger (`stamp.yml` never runs `greenfield-status --apply`,
2887    // and the gating test only diffs the nine probes) — so a count or an
2888    // exists/doesn't-exist message would silently go stale the moment the
2889    // first `closes_rule` landed, which is exactly the drift this section
2890    // exists to avoid, not invite (#1001 review). Static text can't go stale;
2891    // the ledger is one click away either way.
2892    out.push_str(
2893        "See [`design/greenfield-status-rules.md`](greenfield-status-rules.md) for rule ids \
2894         closed so far (written by `cargo xtask stamp --apply` at merge; may not exist yet if \
2895         no increment has cited `closes_rule`).\n",
2896    );
2897    out
2898}
2899
2900/// Every gated probe whose live reading disagrees with the committed table's, as
2901/// `(probe name, committed, live)`. Trend probes are never compared, and never
2902/// computed here — this only runs the sixteen gated probes, so checking currency never
2903/// pays for `wildcard_arms`'s workspace-wide clippy pass. For a caller that has already
2904/// run the full report (e.g. to print it), use [`gated_disagreements_in`] instead so the
2905/// sixteen gated probes aren't computed a second time.
2906pub fn gated_disagreements(root: &Path) -> Vec<(String, String, String)> {
2907    gated_disagreements_in(&run_gated(root), root)
2908}
2909
2910/// Like [`gated_disagreements`], but diffs `probes` (typically a [`Report`]'s
2911/// `.probes`, already computed) instead of re-running the gated probes.
2912pub fn gated_disagreements_in(probes: &[Probe], root: &Path) -> Vec<(String, String, String)> {
2913    let committed = std::fs::read_to_string(table_path(root)).unwrap_or_default();
2914    let mut out = Vec::new();
2915    for probe in probes.iter().filter(|p| p.gated) {
2916        let row_prefix = format!("| `{}` | yes | ", probe.name);
2917        let committed_reads = committed
2918            .lines()
2919            .find(|l| l.starts_with(&row_prefix))
2920            .and_then(|l| l.strip_prefix(&row_prefix))
2921            .and_then(|l| l.strip_suffix(" |"))
2922            .unwrap_or("<row missing>");
2923        if committed_reads != probe.reads {
2924            out.push((
2925                probe.name.to_string(),
2926                committed_reads.to_string(),
2927                probe.reads.clone(),
2928            ));
2929        }
2930    }
2931    out
2932}
2933
2934#[cfg(test)]
2935mod tests {
2936    use super::*;
2937
2938    // --- emit_diagnostics (#999 Decision A) ---------------------------------
2939
2940    /// A standalone `"bynk.foo"` literal is found — the ordinary case.
2941    #[test]
2942    fn bynk_dotted_literals_finds_standalone_literal() {
2943        let src = r#"code("bynk.check.something", "a message")"#;
2944        assert_eq!(bynk_dotted_literals(src), vec!["bynk.check.something"]);
2945    }
2946
2947    /// The bug this slice found in its own first draft: a longer message that merely
2948    /// *starts* with "bynk." must not be truncated into a fake code literal. Regression
2949    /// test for `bynk.map itself uses bynk.list, so list must be injected too: {paths:?}`
2950    /// (`bynk-emit/src/project.rs`), which an earlier, less careful version of this scan
2951    /// wrongly counted as the literal `"bynk.map"`.
2952    #[test]
2953    fn bynk_dotted_literals_ignores_prefix_of_a_longer_message() {
2954        let src = r#"assert!(cond, "bynk.map itself uses bynk.list, so list must be injected too: {paths:?}");"#;
2955        assert!(bynk_dotted_literals(src).is_empty());
2956    }
2957
2958    /// Regression test for the other half of the same bug: a `\`-continued string
2959    /// literal (`"bynk.emit.unresolved_cross_context_signature: no signature for \`,
2960    /// continued on the next source line) is one string, not a diagnostic-code literal,
2961    /// even though its first segment matches the identifier charset — because the
2962    /// character after the run is `:`, never a closing quote, on either line.
2963    #[test]
2964    fn bynk_dotted_literals_ignores_a_line_continued_message() {
2965        let src =
2966            "\"bynk.emit.unresolved_cross_context_signature: no signature for \\\n     the rest\"";
2967        assert!(bynk_dotted_literals(src).is_empty());
2968    }
2969
2970    /// The whole point of Decision A: cross-referencing the real registry, not a
2971    /// hand-maintained exclusion list, correctly separates a real diagnostic code from
2972    /// a commons/namespace path that merely looks like one.
2973    #[test]
2974    fn emit_diagnostics_cross_references_the_real_registry() {
2975        let registry: BTreeSet<&str> = bynk_syntax::diagnostics::REGISTRY
2976            .iter()
2977            .map(|d| d.code)
2978            .collect();
2979        // A code this registry is known to carry (bynk-syntax/src/diagnostics.rs).
2980        assert!(registry.contains("bynk.parse.expected_expression"));
2981        // A commons/namespace path, not a diagnostic code — #999's own verified survey.
2982        assert!(!registry.contains("bynk.locale"));
2983    }
2984
2985    // --- ast_importers (#1176) ------------------------------------------------
2986
2987    /// The exclusion is named, not prefixed: `project/tests_emit.rs` is the
2988    /// Q7-settled `Ir → String` half that keeps hand-writing TypeScript by calling
2989    /// straight into `emitter.rs`'s own body-rendering, and
2990    /// keeps reading a handler's declared param/return `TypeRef` with no `TyId`
2991    /// available at that call site — but `project.rs` (which also imports
2992    /// `bynk_syntax::ast`, via `EmitProjectCtx`) must stay counted, and so, per
2993    /// review of #1210, must `emitter.rs`/`emitter/lower.rs` themselves: both still
2994    /// hold live AST-*declaration* reads (`emitter.rs`'s `CommonsItem::Service`/
2995    /// `svc.protocol` walk, `emitter/lower.rs`'s `cap_op_param_names`) that are the
2996    /// still-open R6.13 defect this probe tracks, not the Q7 kind — excluding either
2997    /// file would hide that real work the same way a path-prefix rule would. A
2998    /// path-prefix rule (e.g. "only `emitter/**` counts") would have excluded
2999    /// `project.rs` right along with the legitimate ones, silently undercounting
3000    /// real work. (`ir.rs`/`ir/lower.rs`, the lowering pass's own former `Ast → Ir`
3001    /// exclusion, left this list at Arc D's P7.12 crate carve — they left
3002    /// `bynk-emit/src` entirely, not merely this list.)
3003    #[test]
3004    fn ast_importer_exclusion_is_named_not_prefixed() {
3005        assert!(is_named_ast_importer(Path::new("project/tests_emit.rs")));
3006        assert!(is_named_ast_importer(Path::new("emitter/serialisation.rs")));
3007        assert!(!is_named_ast_importer(Path::new("project.rs")));
3008        assert!(!is_named_ast_importer(Path::new("emitter.rs")));
3009        assert!(!is_named_ast_importer(Path::new("emitter/lower.rs")));
3010        assert!(!is_named_ast_importer(Path::new("emitter/workers.rs")));
3011        assert!(!is_named_ast_importer(Path::new("ir.rs")));
3012        assert!(!is_named_ast_importer(Path::new("ir/lower.rs")));
3013    }
3014
3015    /// #1184 review: an `AST_IMPORTER_EXCEPTIONS` entry going stale (renamed or split,
3016    /// e.g. `ir/lower.rs` becoming `ir/lower/mod.rs`) must fail loud here, not surface
3017    /// as a silent `ast_importers` regression in `greenfield_status_table_is_current` —
3018    /// mirrors [`file_is_named_fs_floor`]'s own "fail loud, not quiet" discipline.
3019    #[test]
3020    fn ast_importer_exceptions_still_exist_and_still_import_the_ast() {
3021        let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
3022            .join("..")
3023            .join("bynk-emit/src");
3024        for rel in AST_IMPORTER_EXCEPTIONS {
3025            let contents = std::fs::read_to_string(dir.join(rel)).unwrap_or_else(|e| {
3026                panic!("AST_IMPORTER_EXCEPTIONS entry {rel:?} does not exist: {e}")
3027            });
3028            assert!(
3029                contents.contains("bynk_syntax::ast"),
3030                "AST_IMPORTER_EXCEPTIONS entry {rel:?} no longer imports bynk_syntax::ast \
3031                 — it excludes nothing and should be removed"
3032            );
3033        }
3034    }
3035
3036    /// #1184 review, extended by #1187's own closing scoping pass (and narrowed by
3037    /// review of #1210, which found `emitter.rs`/`emitter/lower.rs` still hold live,
3038    /// in-scope AST-declaration reads and must stay counted) and by P6.33's own
3039    /// re-settling (`emitter/serialisation.rs`, a phase boundary rather than a
3040    /// declaration-read exemption): exercises the real filter over the live tree, not
3041    /// just the pure predicate — the survivor set the PR's own named-vs-prefix
3042    /// argument depends on: the named exclusions drop out
3043    /// (`project/tests_emit.rs`'s Q7-settled `Ir → String` case and
3044    /// `emitter/serialisation.rs`'s phase-7 codec renderer), while `emitter.rs`/
3045    /// `emitter/lower.rs`/`emitter/workers.rs` do not. `ir.rs`/`ir/lower.rs` (the
3046    /// lowering pass's own former `Ast → Ir` pair, excluded here until Arc D's
3047    /// P7.12 crate carve) are asserted absent below for a different reason now:
3048    /// they left `bynk-emit/src` entirely, so `ast_importer_files` never walks
3049    /// them at all, named exclusion or not.
3050    ///
3051    /// P6.49 (phase 6's own §6b): `project.rs` and `project/diagnostics.rs`
3052    /// join the *excluded* side of this assertion — the opposite of what this test
3053    /// checked before. `project.rs` cleared without joining
3054    /// [`AST_IMPORTER_EXCEPTIONS`]: nine slices (P6.42–P6.49) either relocated its
3055    /// remaining declaration reads to the `bynk-check`/`bynk-project` crates that
3056    /// already own the data, or re-exported a type from a `bynk-check` module whose
3057    /// own public API was already parameterised by it (the P6.27 `ExprId` precedent,
3058    /// applied to `TypeDecl`/`FnDecl`/`Visibility`/`ActorDecl`) — real, verified
3059    /// movement, not a probe exemption. `project/diagnostics.rs` rides on it, per the
3060    /// same super-glob rule this file's own regression guard below pins.
3061    #[test]
3062    fn ast_importers_excludes_the_named_pairs_and_project_rs() {
3063        let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
3064        let dir = root.join("bynk-emit/src");
3065        let counted: BTreeSet<String> = ast_importer_files(&root)
3066            .into_iter()
3067            .map(|path| {
3068                path.strip_prefix(&dir)
3069                    .unwrap_or(&path)
3070                    .to_string_lossy()
3071                    .replace('\\', "/")
3072            })
3073            .collect();
3074        assert!(!counted.contains("ir.rs"), "moved to bynk-ir at P7.12");
3075        assert!(
3076            !counted.contains("ir/lower.rs"),
3077            "moved to bynk-lower at P7.12"
3078        );
3079        assert!(!counted.contains("project/tests_emit.rs"));
3080        assert!(!counted.contains("emitter/serialisation.rs"));
3081        assert!(!counted.contains("project.rs"));
3082        assert!(!counted.contains("project/diagnostics.rs"));
3083        assert!(counted.contains("emitter.rs"));
3084        assert!(counted.contains("emitter/lower.rs"));
3085        assert!(counted.contains("emitter/workers.rs"));
3086    }
3087
3088    /// P6.26 review (#1259): a module-level `use super::*;` is a real inheritance
3089    /// channel (Rust's own privacy rule makes a parent's private `use` visible to
3090    /// descendants) — must be detected — but a `use super::*;` nested inside a
3091    /// `#[cfg(test)] mod tests { .. }` block glob-imports its own *immediately
3092    /// enclosing* module, not the grandparent file on disk, and must not
3093    /// false-positive.
3094    #[test]
3095    fn module_level_super_glob_detection_ignores_nested_test_mod() {
3096        assert!(has_module_level_super_glob(
3097            "use std::fmt;\nuse super::*;\n"
3098        ));
3099        assert!(!has_module_level_super_glob(
3100            "fn f() {}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n}\n"
3101        ));
3102    }
3103
3104    /// P6.26 review (#1259): pins the real scenario the review found —
3105    /// `emitter/emit.rs` and `emitter/lower.rs` both carry a live, module-level
3106    /// `use super::*;` inheriting from `emitter.rs`, which itself still imports
3107    /// `bynk_syntax::ast` directly. Regression guard: if a future slice deletes
3108    /// either child's own explicit AST import while this inheritance channel and
3109    /// the parent's own AST dependency both remain, [`ast_importer_files`] must
3110    /// keep counting it rather than silently dropping the probe.
3111    #[test]
3112    fn super_glob_children_of_an_ast_importing_parent_are_detected() {
3113        let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
3114        let dir = root.join("bynk-emit/src");
3115        for rel in ["emitter/emit.rs", "emitter/lower.rs"] {
3116            let contents = std::fs::read_to_string(dir.join(rel))
3117                .unwrap_or_else(|e| panic!("{rel:?} does not exist: {e}"));
3118            assert!(
3119                has_module_level_super_glob(&contents),
3120                "{rel:?} no longer carries a module-level `use super::*;` — this \
3121                 regression guard (and the false-zero hazard it pins) no longer applies \
3122                 and may be deleted"
3123            );
3124            assert_eq!(
3125                super_glob_parent_imports_ast(&dir, Path::new(rel)),
3126                Some(true),
3127                "{rel:?}'s parent (`emitter.rs`) no longer imports bynk_syntax::ast — \
3128                 update this guard's expectation"
3129            );
3130        }
3131        // A file directly under `bynk-emit/src` (no directory component) has no
3132        // `use super::*;` parent to inherit from.
3133        assert_eq!(
3134            super_glob_parent_imports_ast(&dir, Path::new("emitter.rs")),
3135            None
3136        );
3137    }
3138
3139    // --- fs_below_driver / test_density (trailing `#[cfg(test)] mod tests {}`) ---
3140
3141    #[test]
3142    fn production_std_fs_usage_is_detected() {
3143        let src = "fn load(p: &Path) -> String {\n    std::fs::read_to_string(p).unwrap()\n}\n";
3144        assert!(has_production_std_fs(src));
3145    }
3146
3147    #[test]
3148    fn std_fs_inside_a_trailing_test_mod_is_not_production() {
3149        let src = "fn load(p: &Path) -> String {\n    String::new()\n}\n\n#[cfg(test)]\nmod tests {\n    #[test]\n    fn t() {\n        std::fs::write(\"x\", \"y\").unwrap();\n    }\n}\n";
3150        assert!(!has_production_std_fs(src));
3151    }
3152
3153    /// Regression test for the other real bug this slice found: `bynk-emit/src/lib.rs`
3154    /// has `#[cfg(test)] pub(crate) mod testkit;` — an external-file module
3155    /// *declaration* (ends in `;`), not an inline block. It must not be mistaken for a
3156    /// scope-opening `mod tests { ... }`, or the (genuinely production) code after it in
3157    /// the same file would be wrongly excluded.
3158    #[test]
3159    fn cfg_test_external_mod_declaration_does_not_open_a_test_region() {
3160        let src = "#[cfg(test)]\npub(crate) mod testkit;\n\nfn load(p: &Path) -> String {\n    std::fs::read_to_string(p).unwrap()\n}\n";
3161        assert!(has_production_std_fs(src));
3162    }
3163
3164    // --- unconsumed_ir_items (Slice D3 of #1542) ----------------------------
3165
3166    fn files(entries: &[(&str, &str, &str)]) -> Vec<(String, PathBuf, String)> {
3167        entries
3168            .iter()
3169            .map(|(k, p, c)| (k.to_string(), PathBuf::from(p), c.to_string()))
3170            .collect()
3171    }
3172
3173    /// The ordinary case: a `pub` item read from another crate's production
3174    /// code is consumed; one read from nowhere is not.
3175    #[test]
3176    fn unconsumed_pub_items_reports_only_items_with_no_other_crate_reader() {
3177        let owners = files(&[(
3178            "bynk-ir",
3179            "lib.rs",
3180            "pub struct Used;\npub struct Unused;\npub fn helper() {}\n",
3181        )]);
3182        let consumers = files(&[
3183            (
3184                "bynk-ir",
3185                "lib.rs",
3186                "pub struct Used;\npub struct Unused;\npub fn helper() {}\n",
3187            ),
3188            (
3189                "bynk-emit",
3190                "a.rs",
3191                "fn f(x: bynk_ir::Used) { helper(); }\n",
3192            ),
3193        ]);
3194        assert_eq!(
3195            unconsumed_pub_items(&["bynk-ir"], &owners, &consumers),
3196            vec!["bynk-ir::Unused"]
3197        );
3198    }
3199
3200    /// A mention in a comment, inside a `#[cfg(test)]` module, or in the
3201    /// owning crate itself is not a consumer.
3202    #[test]
3203    fn unconsumed_pub_items_ignores_comments_tests_and_the_owning_crate() {
3204        let owners = files(&[(
3205            "bynk-lower",
3206            "lib.rs",
3207            "pub fn lower_x() {}\npub fn lower_y() {}\npub fn lower_z() {}\n",
3208        )]);
3209        let consumers = files(&[
3210            (
3211                "bynk-lower",
3212                "lib.rs",
3213                "pub fn lower_x() {}\nfn own() { lower_x(); }\n",
3214            ),
3215            (
3216                "bynk-emit",
3217                "a.rs",
3218                "// lower_y() used to be called here\n/// and [`lower_y`] linked here\n",
3219            ),
3220            (
3221                "bynk-emit",
3222                "b.rs",
3223                "fn prod() {}\n\n#[cfg(test)]\nmod tests {\n    fn t() { lower_z(); }\n}\n",
3224            ),
3225        ]);
3226        assert_eq!(
3227            unconsumed_pub_items(&["bynk-lower"], &owners, &consumers),
3228            vec![
3229                "bynk-lower::lower_x",
3230                "bynk-lower::lower_y",
3231                "bynk-lower::lower_z"
3232            ]
3233        );
3234    }
3235
3236    /// Whole-word matching: `IrExpr` inside `IrExprKind` is not a read of
3237    /// `IrExpr`, and `pub(crate)`/indented items are not offered to other
3238    /// crates so are never counted either way.
3239    #[test]
3240    fn unconsumed_pub_items_matches_whole_words_and_skips_non_public_items() {
3241        let owners = files(&[(
3242            "bynk-ir",
3243            "lib.rs",
3244            "pub struct IrExpr;\npub enum IrExprKind {}\npub(crate) fn internal() {}\nimpl IrExpr {\n    pub fn method() {}\n}\n",
3245        )]);
3246        let consumers = files(&[("bynk-emit", "a.rs", "fn f(k: IrExprKind) {}\n")]);
3247        assert_eq!(
3248            unconsumed_pub_items(&["bynk-ir"], &owners, &consumers),
3249            vec!["bynk-ir::IrExpr"]
3250        );
3251    }
3252
3253    /// The two IR crates do not vouch for each other (review of #1581): a
3254    /// `bynk-ir` type read only from `bynk-lower` is unconsumed, and so is a
3255    /// `bynk-lower` helper read only from `bynk-ir` — pre-D0 `main`'s exact
3256    /// shape, where `bynk-lower`'s own unconsumed constructors named every
3257    /// unconsumed `bynk-ir` type.
3258    #[test]
3259    fn unconsumed_pub_items_does_not_let_owner_crates_vouch_for_each_other() {
3260        let owners = files(&[
3261            (
3262                "bynk-ir",
3263                "lib.rs",
3264                "pub struct IrExpr;\npub struct Shape;\n",
3265            ),
3266            (
3267                "bynk-lower",
3268                "lib.rs",
3269                "pub fn lower_expr_ir() -> IrExpr { IrExpr }\npub fn shape() -> Shape { Shape }\n",
3270            ),
3271        ]);
3272        let consumers = files(&[
3273            (
3274                "bynk-ir",
3275                "lib.rs",
3276                "pub struct IrExpr;\npub struct Shape;\n",
3277            ),
3278            (
3279                "bynk-lower",
3280                "lib.rs",
3281                "pub fn lower_expr_ir() -> IrExpr { IrExpr }\npub fn shape() -> Shape { Shape }\n",
3282            ),
3283            (
3284                "bynk-emit",
3285                "a.rs",
3286                "fn f() -> Shape { bynk_lower::shape() }\n",
3287            ),
3288        ]);
3289        assert_eq!(
3290            unconsumed_pub_items(&["bynk-ir", "bynk-lower"], &owners, &consumers),
3291            vec!["bynk-ir::IrExpr", "bynk-lower::lower_expr_ir"]
3292        );
3293    }
3294
3295    /// One item declared in two files of the same crate (a split module) is
3296    /// reported once, not twice.
3297    #[test]
3298    fn unconsumed_pub_items_deduplicates_a_name_declared_in_two_files() {
3299        let owners = files(&[
3300            ("bynk-lower", "a.rs", "pub fn twice() {}\n"),
3301            ("bynk-lower", "b.rs", "pub fn twice() {}\n"),
3302        ]);
3303        assert_eq!(
3304            unconsumed_pub_items(&["bynk-lower"], &owners, &[]),
3305            vec!["bynk-lower::twice"]
3306        );
3307    }
3308
3309    #[test]
3310    fn column_zero_pub_item_name_accepts_every_item_kind_and_fn_qualifier() {
3311        assert_eq!(column_zero_pub_item_name("pub fn f(x: i32) {}"), Some("f"));
3312        assert_eq!(column_zero_pub_item_name("pub struct S<'a> {"), Some("S"));
3313        assert_eq!(column_zero_pub_item_name("pub enum E {"), Some("E"));
3314        assert_eq!(
3315            column_zero_pub_item_name("pub type T = (u8, u8);"),
3316            Some("T")
3317        );
3318        assert_eq!(
3319            column_zero_pub_item_name("pub const C: &[&str] = &[];"),
3320            Some("C")
3321        );
3322        assert_eq!(
3323            column_zero_pub_item_name("pub static S: &[&str] = &[];"),
3324            Some("S")
3325        );
3326        assert_eq!(column_zero_pub_item_name("pub trait Tr {}"), Some("Tr"));
3327        assert_eq!(column_zero_pub_item_name("pub union U {"), Some("U"));
3328        // `fn` qualifiers, alone and stacked (review of #1581).
3329        assert_eq!(column_zero_pub_item_name("pub async fn a() {}"), Some("a"));
3330        assert_eq!(column_zero_pub_item_name("pub unsafe fn u() {}"), Some("u"));
3331        assert_eq!(column_zero_pub_item_name("pub const fn c() {}"), Some("c"));
3332        assert_eq!(
3333            column_zero_pub_item_name("pub extern \"C\" fn x() {}"),
3334            Some("x")
3335        );
3336        assert_eq!(
3337            column_zero_pub_item_name("pub const unsafe fn cu() {}"),
3338            Some("cu")
3339        );
3340        assert_eq!(
3341            column_zero_pub_item_name("pub unsafe extern \"C\" fn ue() {}"),
3342            Some("ue")
3343        );
3344        assert_eq!(
3345            column_zero_pub_item_name("pub async unsafe fn au() {}"),
3346            Some("au")
3347        );
3348        // Not items offered to another crate.
3349        assert_eq!(column_zero_pub_item_name("pub(crate) fn g() {}"), None);
3350        assert_eq!(column_zero_pub_item_name("pub(super) struct P;"), None);
3351        assert_eq!(column_zero_pub_item_name("    pub fn method() {}"), None);
3352        assert_eq!(column_zero_pub_item_name("pub use foo::Bar;"), None);
3353        assert_eq!(column_zero_pub_item_name("pub mod m;"), None);
3354        assert_eq!(column_zero_pub_item_name("pub impl Foo {}"), None);
3355    }
3356
3357    /// [`workspace_crate_src_files`] tags each file with its crate directory
3358    /// and only walks directories that are actually crates (a `Cargo.toml`
3359    /// *and* a `src/`), so a stray directory with one but not the other is
3360    /// neither an owner nor a consumer.
3361    #[test]
3362    fn workspace_crate_src_files_tags_files_by_crate_and_skips_non_crates() {
3363        let root = std::env::temp_dir().join(format!(
3364            "bynk-xtask-unconsumed-{}-{}",
3365            std::process::id(),
3366            std::time::SystemTime::now()
3367                .duration_since(std::time::UNIX_EPOCH)
3368                .map(|d| d.as_nanos())
3369                .unwrap_or(0)
3370        ));
3371        let mk = |rel: &str, contents: &str| {
3372            let p = root.join(rel);
3373            std::fs::create_dir_all(p.parent().unwrap()).unwrap();
3374            std::fs::write(p, contents).unwrap();
3375        };
3376        mk("alpha/Cargo.toml", "[package]\nname = \"alpha\"\n");
3377        mk("alpha/src/lib.rs", "pub fn a() {}\n");
3378        mk("alpha/src/inner/mod.rs", "pub fn b() {}\n");
3379        mk("beta/Cargo.toml", "[package]\nname = \"beta\"\n");
3380        mk("beta/src/lib.rs", "fn c() { alpha::a() }\n");
3381        mk("no-src/Cargo.toml", "[package]\nname = \"no-src\"\n");
3382        mk("no-manifest/src/lib.rs", "pub fn d() {}\n");
3383        let files = workspace_crate_src_files(&root);
3384        let _ = std::fs::remove_dir_all(&root);
3385        let mut tagged: Vec<(String, String)> = files
3386            .iter()
3387            .map(|(k, p, _)| (k.clone(), p.to_string_lossy().replace('\\', "/")))
3388            .collect();
3389        tagged.sort();
3390        assert_eq!(
3391            tagged,
3392            vec![
3393                ("alpha".to_string(), "inner/mod.rs".to_string()),
3394                ("alpha".to_string(), "lib.rs".to_string()),
3395                ("beta".to_string(), "lib.rs".to_string()),
3396            ]
3397        );
3398    }
3399
3400    /// Regression test for the bug caught in review: a file with **two** scattered
3401    /// `#[cfg(test)] mod ... { ... }` blocks, with real production code between them —
3402    /// exactly `bynk-emit/src/emitter/lower.rs`'s shape (two test modules, 1031
3403    /// production lines apart). A single "everything from the first/last `#[cfg(test)]`
3404    /// onward" cutoff would misclassify `lower_lambda` here as test-scope; the fix must
3405    /// close each block at its own boundary and resume production scanning after it.
3406    #[test]
3407    fn production_code_between_two_scattered_test_mods_is_detected() {
3408        let src = "\
3409#[cfg(test)]
3410mod decode_map_key_tests {
3411    #[test]
3412    fn t() {
3413        assert_eq!(1, 1);
3414    }
3415}
3416
3417fn lower_lambda(p: &Path) -> String {
3418    std::fs::read_to_string(p).unwrap()
3419}
3420
3421#[cfg(test)]
3422mod idempotency_scoping_tests {
3423    #[test]
3424    fn t2() {
3425        assert_eq!(2, 2);
3426    }
3427}
3428";
3429        assert!(has_production_std_fs(src));
3430    }
3431
3432    /// The same fixture's `test_mod_ranges` shape, checked directly: two disjoint
3433    /// ranges, not one span from the first block to the last.
3434    #[test]
3435    fn test_mod_ranges_finds_each_block_separately() {
3436        let src = "\
3437#[cfg(test)]
3438mod a {
3439    fn x() {}
3440}
3441
3442fn production() {}
3443
3444#[cfg(test)]
3445mod b {
3446    fn y() {}
3447}
3448";
3449        let lines: Vec<&str> = src.lines().collect();
3450        let ranges = test_mod_ranges(&lines);
3451        assert_eq!(
3452            ranges.len(),
3453            2,
3454            "expected two disjoint test-mod ranges: {ranges:?}"
3455        );
3456        // Line 5 (0-indexed) is `fn production() {}`, between the two blocks.
3457        assert!(
3458            !in_test_range(5, &ranges),
3459            "production() must not read as test-scope"
3460        );
3461    }
3462
3463    /// Regression test for the bug in the *fix* for the above: a column-0-`}`
3464    /// shortcut (tried and reverted during review) truncates a test module the moment
3465    /// its body embeds a multi-line fixture string containing a `}` flush against the
3466    /// left margin — exactly `bynk-ide/src/sequence.rs`'s shape, whose test mod embeds
3467    /// `.bynk` source fixtures. The real brace-depth scanner must see through the
3468    /// string and find the module's *actual* closing brace, hundreds of lines later.
3469    /// Uses a raw string for the outer fixture so the embedded `"..."` doesn't need
3470    /// escaping, and locates the real end by content rather than a hand-counted index
3471    /// — a hand-counted line number is exactly the kind of easy-to-miscount detail
3472    /// this codebase's own convention (verify, don't assume) warns against.
3473    #[test]
3474    fn test_mod_ranges_is_not_fooled_by_a_column_zero_brace_inside_a_string() {
3475        let src = r#"#[cfg(test)]
3476mod tests {
3477    const FIXTURE: &str = "
3478commons app.demo {
3479}
3480";
3481
3482    fn real_end_of_module() {}
3483}
3484"#;
3485        let lines: Vec<&str> = src.lines().collect();
3486        let ranges = test_mod_ranges(&lines);
3487        assert_eq!(ranges.len(), 1, "expected exactly one range: {ranges:?}");
3488        let (_, end) = ranges[0];
3489        // `str::lines()` drops the trailing newline, so the module's real closing
3490        // brace — the fixture's last line — is at `lines.len() - 1`. The string's
3491        // embedded `}` (an earlier line) must not be mistaken for it.
3492        assert_eq!(
3493            end,
3494            lines.len() - 1,
3495            "closed too early — mistook the string's `}}` for the module's: {ranges:?}"
3496        );
3497    }
3498
3499    // --- fs_below_driver: import resolution through `use super::*;` (#1013) ---
3500
3501    /// Run [`production_std_fs_files`] over an in-memory crate layout and name the
3502    /// flagged files, so each case reads as "these files, and only these".
3503    fn flagged(files: &[(&str, &str)]) -> Vec<String> {
3504        let owned: Vec<(PathBuf, String)> = files
3505            .iter()
3506            .map(|(p, s)| (PathBuf::from(p), (*s).to_string()))
3507            .collect();
3508        production_std_fs_files(&owned)
3509            .into_iter()
3510            .map(|i| files[i].0.to_string())
3511            .collect()
3512    }
3513
3514    /// The concrete #1013 instance, in miniature: `project.rs` has a module-level
3515    /// `use std::fs;` (ancestor-scoped, so visible to descendants), `discovery.rs`
3516    /// glob-imports it via `use super::*;` and calls bare `fs::read_to_string` —
3517    /// touching `std::fs` in production while never spelling it. The text scan alone
3518    /// reads only `project.rs`; the resolved probe must read both.
3519    #[test]
3520    fn bare_fs_reached_through_a_glob_imported_parent_is_flagged() {
3521        let files = [
3522            ("lib.rs", "mod project;\n"),
3523            ("project.rs", "use std::fs;\n\nmod discovery;\n"),
3524            (
3525                "project/discovery.rs",
3526                "use super::*;\n\nfn read_source(path: &std::path::Path) -> String {\n    fs::read_to_string(path).unwrap()\n}\n",
3527            ),
3528        ];
3529        assert!(
3530            !has_production_std_fs(files[2].1),
3531            "the text scan alone must miss it"
3532        );
3533        assert_eq!(flagged(&files), vec!["project.rs", "project/discovery.rs"]);
3534    }
3535
3536    /// Without `use super::*;` there is no path from the bare `fs::` to the parent's
3537    /// binding — the probe must not guess one into existence.
3538    #[test]
3539    fn bare_fs_without_a_glob_super_import_is_not_flagged() {
3540        let files = [
3541            ("lib.rs", "mod project;\n"),
3542            ("project.rs", "use std::fs;\n\nmod discovery;\n"),
3543            (
3544                "project/discovery.rs",
3545                "fn read_source(path: &std::path::Path) -> String {\n    fs::read_to_string(path).unwrap()\n}\n",
3546            ),
3547        ];
3548        assert_eq!(flagged(&files), vec!["project.rs"]);
3549    }
3550
3551    /// Glob chains re-reach ancestors transitively — grandparent binds `fs`, both
3552    /// hops glob-import `super::*` — and the `mod.rs` layout maps to the same module
3553    /// tree as the `name.rs` one. The middle file sees `fs` but never uses it, so
3554    /// only the leaf joins the (text-flagged) root.
3555    #[test]
3556    fn glob_super_resolution_is_transitive_across_mod_rs_parents() {
3557        let files = [
3558            ("lib.rs", "use std::fs;\n\nmod a;\n"),
3559            ("a/mod.rs", "use super::*;\n\nmod b;\n"),
3560            (
3561                "a/b.rs",
3562                "use super::*;\n\nfn walk() {\n    let _ = fs::read_dir(\".\");\n}\n",
3563            ),
3564        ];
3565        assert_eq!(flagged(&files), vec!["lib.rs", "a/b.rs"]);
3566    }
3567
3568    /// A break anywhere in the chain stops resolution: the middle module does not
3569    /// glob-import `super::*`, so the leaf's `use super::*;` reaches a module with no
3570    /// `fs` binding to offer.
3571    #[test]
3572    fn a_break_in_the_glob_chain_stops_resolution() {
3573        let files = [
3574            ("lib.rs", "use std::fs;\n\nmod a;\n"),
3575            ("a/mod.rs", "mod b;\n"),
3576            (
3577                "a/b.rs",
3578                "use super::*;\n\nfn walk() {\n    let _ = fs::read_dir(\".\");\n}\n",
3579            ),
3580        ];
3581        assert_eq!(flagged(&files), vec!["lib.rs"]);
3582    }
3583
3584    /// Nearest binding wins, as in Rust: the child re-binds `fs` to something that is
3585    /// not `std::fs`, so its bare `fs::` calls are that something's, not std's.
3586    #[test]
3587    fn a_local_non_std_binding_shadows_the_ancestors_std_fs() {
3588        let files = [
3589            ("lib.rs", "mod project;\n"),
3590            ("project.rs", "use std::fs;\n\nmod overlay;\nmod d;\n"),
3591            ("project/overlay.rs", "pub fn read(_p: &str) {}\n"),
3592            (
3593                "project/d.rs",
3594                "use super::*;\nuse crate::project::overlay as fs;\n\nfn f() {\n    let _ = fs::read(\"x\");\n}\n",
3595            ),
3596        ];
3597        assert_eq!(flagged(&files), vec!["project.rs"]);
3598    }
3599
3600    /// An aliased module binding resolves under its alias — the call site never
3601    /// contains the substring `fs::` at all.
3602    #[test]
3603    fn an_aliased_std_fs_binding_resolves_through_the_glob() {
3604        let files = [
3605            ("lib.rs", "mod p;\n"),
3606            ("p.rs", "use std::fs as stdfs;\n\nmod c;\n"),
3607            (
3608                "p/c.rs",
3609                "use super::*;\n\nfn f() {\n    stdfs::write(\"a\", \"b\").unwrap();\n}\n",
3610            ),
3611        ];
3612        assert_eq!(flagged(&files), vec!["p.rs", "p/c.rs"]);
3613    }
3614
3615    /// `use std::{fs, io};` binds `fs` without ever containing the substring
3616    /// `std::fs` — the same blind spot as #1013's, one file deep. Resolution applies
3617    /// in the file's own scope, no glob import required.
3618    #[test]
3619    fn a_group_imported_fs_binding_is_resolved_in_its_own_file() {
3620        let src = "use std::{fs, io};\n\nfn f() -> io::Result<()> {\n    fs::metadata(\"x\").map(|_| ())\n}\n";
3621        assert!(
3622            !has_production_std_fs(src),
3623            "the text scan alone must miss it"
3624        );
3625        let files = [("thing.rs", src)];
3626        assert_eq!(flagged(&files), vec!["thing.rs"]);
3627    }
3628
3629    /// The item-import shape #1013 scope-checked (zero current instances), at the
3630    /// granularity this probe can reach: an ancestor's `use std::fs::File;` used as a
3631    /// bare path root `File::open` in a glob-importing child resolves and flags. (A
3632    /// bare *call* of an imported fn — `read_to_string(p)`, no `::` — presents no
3633    /// path root and remains out of a text-level scanner's reach, per the doc.)
3634    #[test]
3635    fn an_item_import_under_std_fs_resolves_as_a_path_root() {
3636        let files = [
3637            ("lib.rs", "mod p;\n"),
3638            ("p.rs", "use std::fs::File;\n\nmod c;\n"),
3639            (
3640                "p/c.rs",
3641                "use super::*;\n\nfn f() {\n    let _ = File::open(\"x\");\n}\n",
3642            ),
3643        ];
3644        assert_eq!(flagged(&files), vec!["p.rs", "p/c.rs"]);
3645    }
3646
3647    /// A test module's `use super::*;` and tempdir `fs::` calls are test-scope — the
3648    /// `bynk-ide` files' shape (`architecture.rs`, `sequence.rs`), which must stay
3649    /// unflagged exactly as they were under the text-only scan.
3650    #[test]
3651    fn glob_and_bare_fs_inside_a_test_mod_stay_test_scope() {
3652        let files = [
3653            ("lib.rs", "use std::fs;\n\nmod w;\n"),
3654            (
3655                "w.rs",
3656                "fn production() {}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::fs;\n\n    #[test]\n    fn t() {\n        let _ = fs::read_dir(\".\");\n    }\n}\n",
3657            ),
3658        ];
3659        assert_eq!(flagged(&files), vec!["lib.rs"]);
3660    }
3661
3662    /// The module-tree mapping behind the resolution, checked directly: `name.rs` and
3663    /// `mod.rs` layouts, a preferred `a.rs` over `a/mod.rs`, and rootless roots.
3664    #[test]
3665    fn module_parent_maps_both_file_layouts() {
3666        let files: Vec<(PathBuf, String)> = ["lib.rs", "a.rs", "a/b.rs", "c/mod.rs", "c/d.rs"]
3667            .iter()
3668            .map(|p| (PathBuf::from(p), String::new()))
3669            .collect();
3670        let idx = |name: &str| {
3671            files
3672                .iter()
3673                .position(|(p, _)| p == Path::new(name))
3674                .unwrap()
3675        };
3676        assert_eq!(module_parent(Path::new("lib.rs"), &files), None);
3677        assert_eq!(
3678            module_parent(Path::new("a.rs"), &files),
3679            Some(idx("lib.rs"))
3680        );
3681        assert_eq!(
3682            module_parent(Path::new("a/b.rs"), &files),
3683            Some(idx("a.rs"))
3684        );
3685        assert_eq!(
3686            module_parent(Path::new("c/mod.rs"), &files),
3687            Some(idx("lib.rs"))
3688        );
3689        assert_eq!(
3690            module_parent(Path::new("c/d.rs"), &files),
3691            Some(idx("c/mod.rs"))
3692        );
3693    }
3694
3695    // --- fs_below_driver: #1016 review findings ------------------------------
3696
3697    /// Finding 1: a `super::`-qualified path needs no glob import — module privacy is
3698    /// ancestor-scoped, so `super::fs` names the parent's private `use std::fs;` from
3699    /// any child. One disambiguating edit away from `discovery.rs:39`'s bare call,
3700    /// and it must not drop the file out of the count.
3701    #[test]
3702    fn a_super_qualified_path_resolves_without_a_glob_import() {
3703        let files = [
3704            ("lib.rs", "mod project;\n"),
3705            ("project.rs", "use std::fs;\n\nmod discovery;\n"),
3706            (
3707                "project/discovery.rs",
3708                "fn read_source(path: &std::path::Path) -> String {\n    super::fs::read_to_string(path).unwrap()\n}\n",
3709            ),
3710        ];
3711        assert_eq!(flagged(&files), vec!["project.rs", "project/discovery.rs"]);
3712    }
3713
3714    /// Finding 1, the `crate::`-rooted form: the walk descends the module tree from
3715    /// the crate root file by file, then resolves the leaf against that module's
3716    /// bindings — from anywhere in the crate, glob import or not.
3717    #[test]
3718    fn a_crate_qualified_path_resolves_through_the_module_tree() {
3719        let files = [
3720            ("lib.rs", "mod other;\nmod project;\n"),
3721            (
3722                "other.rs",
3723                "fn f() {\n    let _ = crate::project::fs::read_dir(\".\");\n}\n",
3724            ),
3725            ("project.rs", "use std::fs;\n"),
3726        ];
3727        assert_eq!(flagged(&files), vec!["other.rs", "project.rs"]);
3728    }
3729
3730    /// Finding 1, stacked hops: `super::super::` climbs two parents (through a
3731    /// glob-free middle module — qualified paths don't need the glob chain).
3732    #[test]
3733    fn stacked_super_hops_climb_the_parent_chain() {
3734        let files = [
3735            ("lib.rs", "use std::fs;\n\nmod a;\n"),
3736            ("a/mod.rs", "mod b;\n"),
3737            (
3738                "a/b.rs",
3739                "fn f() {\n    let _ = super::super::fs::read_dir(\".\");\n}\n",
3740            ),
3741        ];
3742        assert_eq!(flagged(&files), vec!["lib.rs", "a/b.rs"]);
3743    }
3744
3745    /// Finding 1, `self::` composed with the glob chain: `self::fs` resolves in the
3746    /// file's own namespace, which includes what its `use super::*;` pulled in.
3747    #[test]
3748    fn a_self_qualified_path_resolves_through_the_files_own_glob_chain() {
3749        let files = [
3750            ("lib.rs", "mod p;\n"),
3751            ("p.rs", "use std::fs;\n\nmod c;\n"),
3752            (
3753                "p/c.rs",
3754                "use super::*;\n\nfn f() {\n    let _ = self::fs::read_dir(\".\");\n}\n",
3755            ),
3756        ];
3757        assert_eq!(flagged(&files), vec!["p.rs", "p/c.rs"]);
3758    }
3759
3760    /// Finding 1's negatives: a qualified path to a name the parent binds to
3761    /// something other than `std::fs` stops at that binding, and a path through a
3762    /// module that doesn't exist resolves nowhere.
3763    #[test]
3764    fn a_qualified_path_to_a_non_std_binding_or_missing_module_is_not_flagged() {
3765        let files = [
3766            ("lib.rs", "mod overlay;\nmod p;\n"),
3767            ("overlay.rs", "pub fn read_dir(_p: &str) {}\n"),
3768            ("p.rs", "use crate::overlay as fs;\n\nmod d;\n"),
3769            (
3770                "p/d.rs",
3771                "fn f() {\n    let _ = super::fs::read_dir(\".\");\n    let _ = crate::missing::fs::read_dir(\".\");\n}\n",
3772            ),
3773        ];
3774        assert_eq!(flagged(&files), Vec::<String>::new());
3775    }
3776
3777    /// Finding 2: a locally-declared type-namespace item beats a glob-imported name
3778    /// in real Rust — a child with its own `mod fs;` calling `fs::…` is calling its
3779    /// own submodule, not the ancestor's `std::fs`.
3780    #[test]
3781    fn a_locally_declared_module_shadows_the_ancestors_std_fs() {
3782        let files = [
3783            ("lib.rs", "mod p;\n"),
3784            ("p.rs", "use std::fs;\n\nmod c;\n"),
3785            (
3786                "p/c.rs",
3787                "use super::*;\n\nmod fs;\n\nfn f() {\n    let _ = fs::read_dir(\".\");\n}\n",
3788            ),
3789            ("p/c/fs.rs", "pub fn read_dir(_p: &str) {}\n"),
3790        ];
3791        assert_eq!(flagged(&files), vec!["p.rs"]);
3792    }
3793
3794    /// Finding 3: a trailing `//` comment on a `use` line must not sever the edge —
3795    /// neither the glob (`use super::*; // …`) nor the binding (`use std::fs; // …`).
3796    #[test]
3797    fn a_trailing_comment_on_a_use_line_does_not_sever_resolution() {
3798        let files = [
3799            ("lib.rs", "mod p;\n"),
3800            (
3801                "p.rs",
3802                "use std::fs; // read_source's disk fallback\n\nmod c;\n",
3803            ),
3804            (
3805                "p/c.rs",
3806                "use super::*; // parent's fs, PathBuf\n\nfn f() {\n    let _ = fs::read_dir(\".\");\n}\n",
3807            ),
3808        ];
3809        assert_eq!(flagged(&files), vec!["p.rs", "p/c.rs"]);
3810    }
3811
3812    /// Finding 4: the nested-group + `::self` normalisation branches, pinned
3813    /// directly — `use std::{fs::{self, File}, io};` binds `fs` *and* `File` to
3814    /// `std::fs`, and `io` only to the shadow set. Getting `::self` wrong would
3815    /// silently under-count, which is exactly this probe's failure mode.
3816    #[test]
3817    fn a_nested_group_with_self_binds_the_module_and_its_items() {
3818        let facts = fs_import_facts("use std::{fs::{self, File}, io};\n");
3819        let bound: Vec<&str> = facts.std_fs_bindings.iter().map(String::as_str).collect();
3820        assert_eq!(bound, vec!["File", "fs"]);
3821        assert!(facts.use_bound_names.contains("io"));
3822        assert!(!facts.std_fs_bindings.contains("io"));
3823    }
3824
3825    /// Finding 4, the children half of [`FsImportFacts`]' contract: a parent whose
3826    /// *only* `use std::fs;` lives in its `#[cfg(test)] mod` hands no binding to a
3827    /// glob-importing child — `bynk-ide/src/symbols.rs`' shape, latent until it
3828    /// grows a submodule.
3829    #[test]
3830    fn a_parents_test_mod_use_std_fs_does_not_reach_its_children() {
3831        let files = [
3832            ("lib.rs", "mod p;\n"),
3833            (
3834                "p.rs",
3835                "mod c;\n\nfn production() {}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n\n    #[test]\n    fn t() {\n        let _ = fs::read_dir(\".\");\n    }\n}\n",
3836            ),
3837            (
3838                "p/c.rs",
3839                "use super::*;\n\nfn f() {\n    let _ = fs::read_dir(\".\");\n}\n",
3840            ),
3841        ];
3842        assert_eq!(flagged(&files), Vec::<String>::new());
3843    }
3844
3845    // --- fs_below_driver: named-floor classification (#1104) -----------------
3846
3847    #[test]
3848    fn fn_name_on_line_strips_modifiers() {
3849        assert_eq!(fn_name_on_line("fn foo() {"), Some("foo".to_string()));
3850        assert_eq!(
3851            fn_name_on_line("pub(crate) fn read_adapter_binding("),
3852            Some("read_adapter_binding".to_string())
3853        );
3854        assert_eq!(
3855            fn_name_on_line("pub async unsafe fn go() {"),
3856            Some("go".to_string())
3857        );
3858    }
3859
3860    #[test]
3861    fn fn_name_on_line_ignores_non_fn_lines() {
3862        assert_eq!(fn_name_on_line("    let f = foo();"), None);
3863        assert_eq!(fn_name_on_line("/// calls fn bar somewhere"), None);
3864    }
3865
3866    /// A signature whose `{` arrives lines after the `fn` line — `read_adapter_binding`'s
3867    /// own real shape — must still resolve to the correct body range: `started` can't
3868    /// flip true on the parameter list, which has no braces of its own.
3869    #[test]
3870    fn production_fn_ranges_handles_a_wrapped_signature() {
3871        let src = "pub(crate) fn read_adapter_binding(\n    path: &Path,\n) -> std::io::Result<String> {\n    fs::read_to_string(path)\n}\n";
3872        let lines: Vec<&str> = src.lines().collect();
3873        let ranges = production_fn_ranges(&lines, &[]);
3874        assert_eq!(ranges.len(), 1);
3875        let (name, start, end) = &ranges[0];
3876        assert_eq!(name, "read_adapter_binding");
3877        assert_eq!(*start, 0);
3878        assert_eq!(*end, lines.len() - 1);
3879        assert_eq!(
3880            enclosing_fn(3, &ranges),
3881            Some("read_adapter_binding".to_string())
3882        );
3883    }
3884
3885    /// #1561: [`NAMED_FS_EXCEPTIONS`] itself is empty (P4.0 moved every file it used to
3886    /// name out of `bynk-emit` entirely — see the const's own doc comment), so the tests
3887    /// below that exercise a *match* use these synthetic tuples instead of real,
3888    /// currently-empty production data — same crate as the real #1104 shape, but a
3889    /// file/fn pair invented for this test and never a real production exception.
3890    const SYNTHETIC_EXCEPTIONS: &[(&str, &str, &str)] = &[
3891        (
3892            "bynk-emit",
3893            "project/synthetic_example.rs",
3894            "synthetic_named_fn_a",
3895        ),
3896        (
3897            "bynk-emit",
3898            "project/synthetic_example.rs",
3899            "synthetic_named_fn_b",
3900        ),
3901    ];
3902
3903    /// Build the `facts`/`parents` vectors [`file_is_named_fs_floor`] now takes as
3904    /// caller-supplied arguments, the same way [`fs_below_driver`] does, so each test
3905    /// below reads as "classify this file" rather than repeating the setup.
3906    fn classify(
3907        krate: &str,
3908        files: &[(PathBuf, String)],
3909        i: usize,
3910        exceptions: &[(&str, &str, &str)],
3911    ) -> bool {
3912        let facts: Vec<FsImportFacts> = files.iter().map(|(_, s)| fs_import_facts(s)).collect();
3913        let parents: Vec<Option<usize>> =
3914            files.iter().map(|(p, _)| module_parent(p, files)).collect();
3915        file_is_named_fs_floor(krate, files, &facts, &parents, i, exceptions)
3916    }
3917
3918    /// The concrete #1104 shape, in miniature (#1561: against [`SYNTHETIC_EXCEPTIONS`],
3919    /// `NAMED_FS_EXCEPTIONS` itself being empty): `project.rs`'s bare `use std::fs;` (no
3920    /// enclosing fn — never itself a violation) plus `synthetic_example.rs`'s two
3921    /// named-exception functions. The whole file must read as a named floor, not
3922    /// residual.
3923    #[test]
3924    fn file_is_named_fs_floor_true_for_a_file_of_only_named_exceptions() {
3925        let files = [
3926            (
3927                PathBuf::from("project.rs"),
3928                "use std::fs;\n\nmod synthetic_example;\n".to_string(),
3929            ),
3930            (
3931                PathBuf::from("project/synthetic_example.rs"),
3932                "use super::*;\n\npub(crate) fn synthetic_named_fn_a() {\n    let _ = fs::read_dir(\".\");\n}\n\npub(crate) fn synthetic_named_fn_b(path: &Path) -> std::io::Result<String> {\n    fs::read_to_string(path)\n}\n".to_string(),
3933            ),
3934        ];
3935        assert!(classify("bynk-emit", &files, 1, SYNTHETIC_EXCEPTIONS));
3936    }
3937
3938    /// A new, unlisted fn touching `std::fs` in the *same file* as two named exceptions
3939    /// must flip the whole file to residual — no partial credit, since "named floor"
3940    /// must mean every touch is accounted for, not most of them.
3941    #[test]
3942    fn file_is_named_fs_floor_false_when_an_unnamed_fn_also_touches_fs() {
3943        let files = [
3944            (
3945                PathBuf::from("project.rs"),
3946                "use std::fs;\n\nmod synthetic_example;\n".to_string(),
3947            ),
3948            (
3949                PathBuf::from("project/synthetic_example.rs"),
3950                "use super::*;\n\npub(crate) fn synthetic_named_fn_a() {\n    let _ = fs::read_dir(\".\");\n}\n\nfn some_new_helper() {\n    let _ = fs::write(\"x\", \"y\");\n}\n".to_string(),
3951            ),
3952        ];
3953        assert!(!classify("bynk-emit", &files, 1, SYNTHETIC_EXCEPTIONS));
3954    }
3955
3956    /// A file whose only production-scope touch is a bare `use std::fs;` import — no
3957    /// enclosing fn at all — is trivially a named floor: the import performs no I/O by
3958    /// itself, and the descendant it enables is checked (and named) separately. No
3959    /// named exceptions needed at all here — nothing to match against.
3960    #[test]
3961    fn file_is_named_fs_floor_true_for_an_import_only_file() {
3962        let files = [(
3963            PathBuf::from("project.rs"),
3964            "use std::fs;\n\nmod discovery;\n".to_string(),
3965        )];
3966        assert!(classify("bynk-emit", &files, 0, &[]));
3967    }
3968
3969    /// The same synthetic-example shape under the wrong crate label must not read as a
3970    /// floor — [`NAMED_FS_EXCEPTIONS`] is keyed on `(crate, file, fn)`, not `(file, fn)`
3971    /// alone, so a same-named file/fn pair in a different crate isn't accidentally
3972    /// covered.
3973    #[test]
3974    fn file_is_named_fs_floor_false_under_the_wrong_crate() {
3975        let files = [
3976            (
3977                PathBuf::from("project.rs"),
3978                "use std::fs;\n\nmod synthetic_example;\n".to_string(),
3979            ),
3980            (
3981                PathBuf::from("project/synthetic_example.rs"),
3982                "use super::*;\n\npub(crate) fn synthetic_named_fn_a() {\n    let _ = fs::read_dir(\".\");\n}\n".to_string(),
3983            ),
3984        ];
3985        assert!(!classify("bynk-ide", &files, 1, SYNTHETIC_EXCEPTIONS));
3986    }
3987
3988    /// Review finding (#1106): a module-scope `std::fs` touch that isn't an import
3989    /// declaration — a `static` initialiser doing real I/O — has no enclosing fn either,
3990    /// but is a genuine R2.3 violation and must not be waved through as a floor just
3991    /// because it sits outside every known fn range.
3992    #[test]
3993    fn file_is_named_fs_floor_false_for_a_module_scope_static_that_reads() {
3994        let files = [(
3995            PathBuf::from("project.rs"),
3996            "use std::fs;\n\nstatic ROOT: once_cell::sync::Lazy<String> = once_cell::sync::Lazy::new(|| fs::read_to_string(\"x\").unwrap());\n"
3997                .to_string(),
3998        )];
3999        assert!(!classify("bynk-emit", &files, 0, &[]));
4000    }
4001
4002    /// Same review finding, the [`fn_name_on_line`] half: an `extern "C" fn` (a modifier
4003    /// combination the parser doesn't strip) produces no [`production_fn_ranges`] entry
4004    /// at all, so its whole body would fall into the "no enclosing fn" branch. It must
4005    /// still read as residual, not floor, once it touches `std::fs`.
4006    #[test]
4007    fn file_is_named_fs_floor_false_for_an_unparsed_extern_fn_body() {
4008        let files = [(
4009            PathBuf::from("project.rs"),
4010            "use std::fs;\n\nextern \"C\" fn callback() {\n    let _ = fs::read_dir(\".\");\n}\n"
4011                .to_string(),
4012        )];
4013        assert!(!classify("bynk-emit", &files, 0, &[]));
4014    }
4015
4016    /// #1587: [`NAMED_FS_EXCEPTIONS`] is empty today (#1561 cleared the three entries
4017    /// that outlived their files' P4.0 move out of `bynk-emit` for weeks, unnoticed,
4018    /// while `fs_below_driver`'s `0 named floor` reading stayed vacuously "healthy"),
4019    /// so this loop is vacuous now — but guards whatever named exception is decided
4020    /// next the same way [`ts_writes_excluded_files_still_exist`] and
4021    /// [`ast_importer_exceptions_still_exist_and_still_import_the_ast`] guard their own
4022    /// lists: a future entry going stale must fail loud here, not silently surface as
4023    /// a falsely-healthy `fs_below_driver` count. The tuple's third field is checked
4024    /// too (review of #1591), not just discarded — [`file_is_named_fs_floor`] matches
4025    /// on the full `(crate, file, fn)` key, so a refactor that keeps the file but
4026    /// renames or deletes the named function would otherwise leave a silently dead
4027    /// entry, the same recurrence shape #1561's three entries took.
4028    #[test]
4029    fn named_fs_exceptions_still_exist() {
4030        let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
4031        for (krate, rel, fn_name) in NAMED_FS_EXCEPTIONS {
4032            let path = root.join(krate).join("src").join(rel);
4033            let contents = std::fs::read_to_string(&path).unwrap_or_else(|e| {
4034                panic!("NAMED_FS_EXCEPTIONS entry {krate:?}/{rel:?} does not exist: {e}")
4035            });
4036            assert!(
4037                contents.contains(fn_name),
4038                "NAMED_FS_EXCEPTIONS entry {krate:?}/{rel:?}/{fn_name:?} no longer names \
4039                 a function in that file — it excludes nothing and should be removed"
4040            );
4041        }
4042    }
4043
4044    // --- emit_abi_shapes (#999 Decision E) ----------------------------------
4045
4046    /// A binding's ordinary capability-interface imports, and the emit-ABI tag-layout
4047    /// names, must not be flagged — the exact failure mode Decision E rebuilt the probe
4048    /// to avoid (the original single-allowlist definition read 29-33 here, not 1).
4049    ///
4050    /// Exercises the real production allowlists via [`is_enumerated_emit_abi_or_capability_surface`]
4051    /// — not a local re-declaration. A test with its own copy of `EMIT_ABI` would still
4052    /// pass if the real one lost an entry (e.g. deleting `Uuid` from the production
4053    /// list), proving nothing about the probe it claims to cover.
4054    #[test]
4055    fn emit_abi_shapes_does_not_flag_capability_or_tag_layout_imports() {
4056        let src = "import type { Clock, Fetch, Locale } from \"./bynk.js\";\n\
4057                    import { FetchError, Uuid } from \"./bynk.js\";\n\
4058                    import { Err, None, Ok, Some, type Option, type Result } from \"./runtime.js\";\n";
4059        let imports = ts_named_imports_from_runtime_modules(src);
4060        let leaks: Vec<&String> = imports
4061            .iter()
4062            .filter(|i| !is_enumerated_emit_abi_or_capability_surface(i))
4063            .collect();
4064        assert!(leaks.is_empty(), "unexpected leaks: {leaks:?}");
4065    }
4066
4067    /// The falsifier from #999 Decision E, checked directly: deleting an entry from the
4068    /// real production allowlist must be detectable by *some* test — this one flags
4069    /// `Uuid` as a leak the moment it's removed from [`EMIT_ABI`], which the test above
4070    /// (using the real const) would also start failing on.
4071    #[test]
4072    fn is_enumerated_checks_the_real_production_allowlist() {
4073        assert!(is_enumerated_emit_abi_or_capability_surface("Uuid"));
4074        assert!(is_enumerated_emit_abi_or_capability_surface("LocaleTag"));
4075        assert!(!is_enumerated_emit_abi_or_capability_surface(
4076            "negotiateLocale"
4077        ));
4078    }
4079
4080    /// The real, current-tree finding this probe exists to surface: `negotiateLocale`,
4081    /// a plain value helper from `./runtime.js` alongside the tag-layout constructors,
4082    /// is neither an enumerated emit-ABI shape nor a capability-interface import.
4083    #[test]
4084    fn emit_abi_shapes_flags_a_non_enumerated_runtime_helper() {
4085        let src = "import { Err, None, Ok, Some, negotiateLocale, type Option, type Result } from \"./runtime.js\";\n";
4086        let imports = ts_named_imports_from_runtime_modules(src);
4087        assert!(imports.contains(&"negotiateLocale".to_string()));
4088    }
4089
4090    /// `FetchError` is `import type` in one binding and a plain value import in
4091    /// another (`FetchError.Timeout`) — Decision E's rejected type-vs-value
4092    /// discriminator. Confirms the extractor treats both forms as the same identifier,
4093    /// so the allowlist check doesn't depend on which form a given file happens to use.
4094    #[test]
4095    fn ts_import_extraction_ignores_type_only_vs_value_distinction() {
4096        let type_only = "import type { FetchError } from \"./bynk.js\";\n";
4097        let value = "import { FetchError, Uuid } from \"./bynk.js\";\n";
4098        assert_eq!(
4099            ts_named_imports_from_runtime_modules(type_only),
4100            vec!["FetchError".to_string()]
4101        );
4102        assert!(ts_named_imports_from_runtime_modules(value).contains(&"FetchError".to_string()));
4103    }
4104
4105    // --- options_sources -----------------------------------------------------
4106
4107    #[test]
4108    fn struct_body_finds_a_field_by_name() {
4109        let src = "struct Foo {\n    pub sources: Option<HashMap<PathBuf, String>>,\n    pub other: bool,\n}\n";
4110        let body = struct_body(src, "Foo").expect("struct body found");
4111        assert!(body.contains("sources"));
4112    }
4113
4114    #[test]
4115    fn struct_body_does_not_match_an_unrelated_struct() {
4116        let src =
4117            "struct Bar {\n    pub sources: bool,\n}\n\nstruct Foo {\n    pub other: bool,\n}\n";
4118        let body = struct_body(src, "Foo").expect("struct body found");
4119        assert!(!body.contains("sources"));
4120    }
4121
4122    // --- render_table's "Rules closed" section (#1001) ------------------------
4123
4124    fn empty_report() -> Report {
4125        Report { probes: Vec::new() }
4126    }
4127
4128    /// The section is static text — no count, no existence check — precisely
4129    /// because nothing regenerates `design/greenfield-status.md` when `stamp`
4130    /// writes the ledger, so a computed count would silently go stale the
4131    /// moment the first `closes_rule` landed (the drift a first draft of this
4132    /// section introduced, caught in #1001's review). This test pins "static"
4133    /// as the actual behaviour, not just the intent in a comment.
4134    #[test]
4135    fn render_table_rules_closed_section_is_static_regardless_of_the_tree() {
4136        let out = render_table(&empty_report());
4137        assert!(out.contains("greenfield-status-rules.md"), "{out}");
4138        assert!(
4139            out.contains("may not exist yet"),
4140            "the wording must not claim to know whether the ledger exists: {out}"
4141        );
4142    }
4143
4144    // --- ts_writes / ts_any (P7.0, #1296; testability + widening, review of #1297) --
4145
4146    /// Run [`ts_writes_violations`] over an in-memory file list — mirrors
4147    /// [`flagged`]'s own role for `production_std_fs_files`.
4148    fn ts_writes_over(files: &[(&str, &str)]) -> usize {
4149        let owned: Vec<(PathBuf, String)> = files
4150            .iter()
4151            .map(|(p, s)| (PathBuf::from(p), (*s).to_string()))
4152            .collect();
4153        ts_writes_violations(&owned)
4154    }
4155
4156    /// Run [`ts_any_violations`] over an in-memory file list.
4157    fn ts_any_over(files: &[(&str, &str)]) -> usize {
4158        let owned: Vec<(PathBuf, String)> = files
4159            .iter()
4160            .map(|(p, s)| (PathBuf::from(p), (*s).to_string()))
4161            .collect();
4162        ts_any_violations(&owned)
4163    }
4164
4165    /// Run [`verbatim_origins_violations`] over an in-memory file list.
4166    fn verbatim_origins_over(files: &[(&str, &str)]) -> usize {
4167        let owned: Vec<(PathBuf, String)> = files
4168            .iter()
4169            .map(|(p, s)| (PathBuf::from(p), (*s).to_string()))
4170            .collect();
4171        verbatim_origins_violations(&owned)
4172    }
4173
4174    /// Run [`verbatim_sites_violations`] over an in-memory file list.
4175    fn verbatim_sites_over(files: &[(&str, &str)]) -> usize {
4176        let owned: Vec<(PathBuf, String)> = files
4177            .iter()
4178            .map(|(p, s)| (PathBuf::from(p), (*s).to_string()))
4179            .collect();
4180        verbatim_sites_violations(&owned)
4181    }
4182
4183    #[test]
4184    fn ts_writes_excluded_files_are_recognised() {
4185        assert!(is_ts_writes_excluded_file(Path::new("emitter/wrangler.rs")));
4186        assert!(is_ts_writes_excluded_file(Path::new("emitter/secrets.rs")));
4187        assert!(is_ts_writes_excluded_file(Path::new(
4188            "emitter/contracts.rs"
4189        )));
4190        assert!(is_ts_writes_excluded_file(Path::new("testkit.rs")));
4191        // Name proximity to a file that used to be excluded must not false-positive:
4192        // `emitter/lower.rs` (the emitter's own lowering pass) is genuinely
4193        // TS-producing and must stay counted — unlike `ir/lower.rs` (the checker→IR
4194        // pass), which isn't a name-proximity risk at all any more: it left
4195        // `bynk-emit/src` entirely at Arc D's P7.12 crate carve. `emitter/source_map.rs`
4196        // is the same shape (#1561): it left for `bynk-ts/src/source_map.rs` at P7.5
4197        // (#1308), so it's no longer excluded either — there's nothing left to exclude.
4198        assert!(!is_ts_writes_excluded_file(Path::new("emitter/lower.rs")));
4199        assert!(!is_ts_writes_excluded_file(Path::new("ir/lower.rs")));
4200        assert!(!is_ts_writes_excluded_file(Path::new(
4201            "emitter/source_map.rs"
4202        )));
4203        assert!(!is_ts_writes_excluded_file(Path::new("emitter.rs")));
4204        assert!(!is_ts_writes_excluded_file(Path::new("project.rs")));
4205        assert!(!is_ts_writes_excluded_file(Path::new(
4206            "project/tests_emit.rs"
4207        )));
4208    }
4209
4210    /// #1587: the exact failure #1561 fixed for [`NAMED_FS_EXCEPTIONS`] (an entry
4211    /// outliving its file by weeks while the gated probe kept reading a vacuous,
4212    /// falsely-healthy number) applies just as well to [`TS_WRITES_EXCLUDED_FILES`] —
4213    /// nothing previously caught a stale entry here either. Must fail loud, not surface
4214    /// as a silent `ts_writes`/`ts_any` regression in `greenfield_status_table_is_current`
4215    /// — mirrors [`ast_importer_exceptions_still_exist_and_still_import_the_ast`]'s own
4216    /// discipline for the sibling list: existence alone isn't enough (review of #1591)
4217    /// — a file that survives but stops containing any `write!`/`writeln!`/`format!`
4218    /// site would excludes nothing, and removing it would change no probe count, so
4219    /// [`ts_writes_violations`] must actually see a nonzero count over each entry's real
4220    /// content. Scored under a dummy, non-excluded path so the exclusion filter itself
4221    /// doesn't short-circuit the check.
4222    #[test]
4223    fn ts_writes_excluded_files_still_exist() {
4224        let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
4225            .join("..")
4226            .join("bynk-emit/src");
4227        for rel in TS_WRITES_EXCLUDED_FILES {
4228            let contents = std::fs::read_to_string(dir.join(rel)).unwrap_or_else(|e| {
4229                panic!("TS_WRITES_EXCLUDED_FILES entry {rel:?} does not exist: {e}")
4230            });
4231            let dummy = [(PathBuf::from("__not_excluded__.rs"), contents)];
4232            assert!(
4233                ts_writes_violations(&dummy) > 0,
4234                "TS_WRITES_EXCLUDED_FILES entry {rel:?} no longer contains a \
4235                 write!/writeln!/format! call outside a test module — it excludes \
4236                 nothing and should be removed"
4237            );
4238        }
4239    }
4240
4241    /// Regression for a real mistake this slice's own grounding found: an earlier
4242    /// survey during phase 7's own track-opening research treated
4243    /// `project/tests_emit.rs` as excludable "test-assertion" noise. It is real
4244    /// production code (`process_tests`/`process_integration_tests`) per
4245    /// `semantics-in-the-checker.md`'s own settling finding for a different probe on
4246    /// the same file. Exercises the real probe, not just the predicate (review of
4247    /// #1297 — a first cut of this test called `is_ts_writes_excluded_file` directly,
4248    /// which can't catch a bug in [`ts_writes_violations`]'s own use of it).
4249    #[test]
4250    fn ts_writes_does_not_exclude_tests_emit_rs_wholesale() {
4251        let count = ts_writes_over(&[(
4252            "project/tests_emit.rs",
4253            "fn process_tests() {\n    let _ = format!(\"const x = 1;\");\n    let _ = writeln!(out, \"const y = 2;\");\n}\n",
4254        )]);
4255        assert_eq!(
4256            count, 2,
4257            "tests_emit.rs's own production emission code must be counted, not excluded wholesale"
4258        );
4259    }
4260
4261    #[test]
4262    fn is_path_construction_line_catches_the_idiom_not_ordinary_format_calls() {
4263        assert!(is_path_construction_line(
4264            "let p = PathBuf::from(format!(\"workers/{dashes}/index.ts\"));"
4265        ));
4266        assert!(is_path_construction_line(
4267            "root.join(format!(\"tests/integration_{sanitized}.test.ts\"))"
4268        ));
4269        assert!(is_path_construction_line(
4270            "output_path.with_file_name(format!(\"{name}.{suffix}\"))"
4271        ));
4272        // An ordinary TS-producing `format!` call, no path construction, must not be
4273        // excluded by this idiom.
4274        assert!(!is_path_construction_line(
4275            "writeln!(out, \"{}\", format!(\"const {name} = 1;\"))"
4276        ));
4277    }
4278
4279    #[test]
4280    fn line_violates_ts_any_catches_the_cast_and_the_bare_annotation() {
4281        assert!(line_violates_ts_any("let x = (value as any).field;"));
4282        assert!(line_violates_ts_any("format!(\"{}: any\", name)"));
4283        assert!(line_violates_ts_any(
4284            "\"(seq: any[]) => ({ns} as any).drive(seq)\""
4285        ));
4286        assert!(!line_violates_ts_any("let x: unknown = value;"));
4287    }
4288
4289    /// Regression for review of #1297, finding 1: `any` in generic type-argument
4290    /// position (`Record<string, any[]>`, the live `emitter/lower.rs`
4291    /// `joinOn`/`leftJoin`/`groupBy` shape) contains neither `as any` nor `: any` and
4292    /// was silently uncounted by the round-one predicate.
4293    #[test]
4294    fn line_violates_ts_any_catches_generic_position_any() {
4295        assert!(line_violates_ts_any(
4296            "\"{{ const __h: Record<string, any[]> = {{}}; ...}}\""
4297        ));
4298        assert!(line_violates_ts_any("\"Array<any>\""));
4299        assert!(line_violates_ts_any("\"Promise<any>\""));
4300        // Must not regress the round-one patterns while widening.
4301        assert!(line_violates_ts_any("(value as any).field"));
4302        assert!(line_violates_ts_any("(e: any) => {}"));
4303    }
4304
4305    /// Regression for review of #1322, finding 2: once a site builds a real
4306    /// `bynk_ts::TsType` node instead of writing TypeScript text directly, the
4307    /// emitted `any` no longer appears as Rust-source `as any`/`: any` — the round-
4308    /// one/round-two patterns above all match *emitted-text* spellings, none of
4309    /// which appear in `TsType::named("any")`. `workers.rs`'s own three real sites
4310    /// (#1321) were silently uncounted until this pattern was added.
4311    #[test]
4312    fn line_violates_ts_any_catches_the_named_any_construction_spelling() {
4313        assert!(line_violates_ts_any(
4314            "    let mut args = vec![as_expr(ident(\"payload\"), TsType::named(\"any\"))];"
4315        ));
4316        assert!(line_violates_ts_any(
4317            "        Some(TsType::named(\"any\")),"
4318        ));
4319        // Must not regress the round-one/round-two patterns while widening.
4320        assert!(line_violates_ts_any("(value as any).field"));
4321        assert!(line_violates_ts_any("\"Array<any>\""));
4322    }
4323
4324    /// A comment mentioning either pattern in prose — the same self-reference-shaped
4325    /// hazard [`bynk_dotted_literals`]'s own regression tests guard against for a
4326    /// different probe — must not count.
4327    #[test]
4328    fn line_violates_ts_any_ignores_comments() {
4329        assert!(!line_violates_ts_any(
4330            "/// lowering machinery, same as any other subexpression."
4331        ));
4332        assert!(!line_violates_ts_any(
4333            "// TODO: stop emitting `: any` here once bynk-ts exists"
4334        ));
4335    }
4336
4337    /// `#[cfg(test)]`-gated write!-family calls (a file's own unit tests constructing a
4338    /// fixture string) must not count toward either probe — mirrors
4339    /// [`has_production_std_fs`]'s own test-range exclusion for a different probe.
4340    /// Exercises the real probes end to end, not a re-implementation of their loop
4341    /// (review of #1297, finding 2): deleting either probe's `in_test_range` guard, its
4342    /// `is_ts_writes_excluded_file` `continue`, or (for `ts_writes`) its
4343    /// `is_path_construction_line` `continue` now fails one of these tests.
4344    #[test]
4345    fn ts_writes_and_ts_any_exclude_cfg_test_ranges() {
4346        let src = "fn production() {\n    let _ = format!(\"const x = 1;\");\n}\n\n\
4347                    #[cfg(test)]\nmod tests {\n    #[test]\n    fn t() {\n        \
4348                    let _ = format!(\"(v as any)\");\n    }\n}\n";
4349        assert_eq!(
4350            ts_writes_over(&[("emitter.rs", src)]),
4351            1,
4352            "only the production format! call counts"
4353        );
4354        assert_eq!(
4355            ts_any_over(&[("emitter.rs", src)]),
4356            0,
4357            "the test-only `as any` site must be excluded"
4358        );
4359    }
4360
4361    /// Exercises the real probes' file-exclusion `continue`, not just the predicate:
4362    /// a whole file on [`TS_WRITES_EXCLUDED_FILES`] must contribute 0 to either count
4363    /// even when its content would otherwise match both.
4364    #[test]
4365    fn ts_writes_and_ts_any_exclude_named_non_ts_files_end_to_end() {
4366        let files = [(
4367            "emitter/wrangler.rs",
4368            "fn write_toml(out: &mut String) {\n    let _ = writeln!(out, \"name = {v}\");\n    let __x: any = 1;\n}\n",
4369        )];
4370        assert_eq!(ts_writes_over(&files), 0);
4371        assert_eq!(ts_any_over(&files), 0);
4372    }
4373
4374    /// Exercises the real probes' [`is_path_construction_line`] `continue` end to end,
4375    /// not just the predicate in isolation.
4376    #[test]
4377    fn ts_writes_excludes_path_construction_end_to_end() {
4378        let files = [(
4379            "project.rs",
4380            "fn out_path(dashes: &str) -> PathBuf {\n    PathBuf::from(format!(\"workers/{dashes}/index.ts\"))\n}\n\nfn emit(out: &mut String) {\n    let _ = writeln!(out, \"export const x = 1;\");\n}\n",
4381        )];
4382        assert_eq!(
4383            ts_writes_over(&files),
4384            1,
4385            "the path-construction line must not count; the genuine emission line must"
4386        );
4387    }
4388
4389    #[test]
4390    fn verbatim_origins_counts_distinct_variants_not_construction_sites() {
4391        let files = [(
4392            "emitter/contracts.rs",
4393            "fn a() { TsStmt::verbatim(VerbatimOrigin::Contracts, \"x\", None) }\nfn b() { TsStmt::verbatim(VerbatimOrigin::Contracts, \"y\", None) }\nfn c() { TsStmt::verbatim(VerbatimOrigin::Secrets, \"z\", None) }\n",
4394        )];
4395        // Three construction sites, but only two distinct origins.
4396        assert_eq!(verbatim_origins_over(&files), 2);
4397        assert_eq!(verbatim_sites_over(&files), 3);
4398    }
4399
4400    #[test]
4401    fn verbatim_origins_and_sites_ignore_comments() {
4402        let files = [(
4403            "emitter/contracts.rs",
4404            "// TsStmt::verbatim(VerbatimOrigin::Contracts, \"x\", None)\n/// Mentions VerbatimOrigin::Secrets in prose.\n",
4405        )];
4406        assert_eq!(verbatim_origins_over(&files), 0);
4407        assert_eq!(verbatim_sites_over(&files), 0);
4408    }
4409
4410    #[test]
4411    fn verbatim_origins_and_sites_read_zero_over_an_empty_tree() {
4412        let files: [(&str, &str); 0] = [];
4413        assert_eq!(verbatim_origins_over(&files), 0);
4414        assert_eq!(verbatim_sites_over(&files), 0);
4415    }
4416
4417    /// Review of #1308, finding 6: without stripping `#[cfg(test)]` ranges,
4418    /// a single `bynk-emit` unit test fixture constructing a `TsStmt::
4419    /// verbatim(...)` for its own coverage would pin `verbatim_sites` above
4420    /// its documented 0 floor permanently, for a reason unrelated to
4421    /// residual production emission.
4422    #[test]
4423    fn verbatim_origins_and_sites_exclude_cfg_test_ranges() {
4424        let src = "fn production() {\n    TsStmt::verbatim(VerbatimOrigin::Contracts, \"x\", None);\n}\n\n\
4425                    #[cfg(test)]\nmod tests {\n    #[test]\n    fn t() {\n        \
4426                    TsStmt::verbatim(VerbatimOrigin::Secrets, \"y\", None);\n    }\n}\n";
4427        assert_eq!(
4428            verbatim_origins_over(&[("emitter/contracts.rs", src)]),
4429            1,
4430            "only the production-code origin counts"
4431        );
4432        assert_eq!(
4433            verbatim_sites_over(&[("emitter/contracts.rs", src)]),
4434            1,
4435            "the test-only construction site must be excluded"
4436        );
4437    }
4438
4439    /// #1539: `verbatim_sites` widened from a `TsStmt::verbatim(`-only scan
4440    /// to also count `TsExpr::VerbatimExpr(` construction sites — the same
4441    /// escape hatch, now closed at the expression level too. Pins that both
4442    /// needles are counted (and both still respect the comment/`#[cfg(test)]`
4443    /// exclusions the sibling tests above already establish for the
4444    /// `TsStmt` half).
4445    #[test]
4446    fn verbatim_sites_counts_the_expr_level_needle_too() {
4447        let src = "fn f() {\n    let x = TsExpr::VerbatimExpr(\"a\".to_string(), VerbatimOrigin::Emit);\n    let y = TsStmt::verbatim(VerbatimOrigin::Emit, \"b\", None);\n}\n";
4448        assert_eq!(
4449            verbatim_sites_over(&[("emitter/emit.rs", src)]),
4450            2,
4451            "one TsExpr::VerbatimExpr( site plus one TsStmt::verbatim( site"
4452        );
4453        assert_eq!(
4454            verbatim_origins_over(&[("emitter/emit.rs", src)]),
4455            1,
4456            "both reference the same VerbatimOrigin::Emit variant"
4457        );
4458    }
4459
4460    // --- incremental_query_types (P8.0, #1510; re-settled by #1537) ----------
4461
4462    fn owned(files: &[(&str, &str)]) -> Vec<(PathBuf, String)> {
4463        files
4464            .iter()
4465            .map(|(p, s)| (PathBuf::from(p), (*s).to_string()))
4466            .collect()
4467    }
4468
4469    #[test]
4470    fn unit_signature_present_recognises_the_real_struct_and_ignores_a_comment() {
4471        assert!(unit_signature_present(&owned(&[(
4472            "unit_signature.rs",
4473            "pub struct UnitSignature {\n    types: HashMap<String, Arc<TypeDecl>>,\n}\n",
4474        )])));
4475        assert!(!unit_signature_present(&owned(&[(
4476            "lib.rs",
4477            "// TODO: build a struct UnitSignature here eventually\n",
4478        )])));
4479        assert!(!unit_signature_present(&[]));
4480    }
4481
4482    /// The committed reading after #1537: neither deleted level is back.
4483    #[test]
4484    fn deleted_levels_present_is_empty_when_neither_level_exists() {
4485        assert!(deleted_levels_present(&[]).is_empty());
4486    }
4487
4488    /// Re-adding `ProjectGraph` anywhere flips the reading — the whole workspace is
4489    /// scanned (review of #1582) because P8.3 (ADR 0415) already landed it in a
4490    /// different crate from the one first assumed, and R3.13's table names a third
4491    /// (`bynk-resolve`) that does not exist yet.
4492    #[test]
4493    fn deleted_levels_present_sees_project_graph_wherever_it_lands() {
4494        let graph = "pub struct ProjectGraph {\n    units: HashMap<UnitId, Unit>,\n}\n";
4495        assert_eq!(
4496            deleted_levels_present(&owned(&[("project_graph.rs", graph)])),
4497            vec!["ProjectGraph"]
4498        );
4499        assert_eq!(
4500            deleted_levels_present(&owned(&[("resolve/graph.rs", graph)])),
4501            vec!["ProjectGraph"]
4502        );
4503        assert!(
4504            deleted_levels_present(&owned(&[(
4505                "lib.rs",
4506                "// a struct ProjectGraph used to live here\n"
4507            )]))
4508            .is_empty()
4509        );
4510    }
4511
4512    /// **The empirically-confirmed false positive #1510's own first run caught** (see
4513    /// [`defid_query_fn_present`]): `bynk-check/src/checker.rs` has an ordinary,
4514    /// pre-existing `fn type_of(expr: &Expr, ..)` with no `DefId` anywhere in its
4515    /// signature. After #1537 the direction of the mistake reverses — it would now
4516    /// read as the deleted level having been *re-added* — so it is pinned against the
4517    /// real function's own signature text here too.
4518    #[test]
4519    fn deleted_levels_present_does_not_count_checkers_pre_existing_type_of() {
4520        let found = deleted_levels_present(&owned(&[(
4521            "checker.rs",
4522            "pub(crate) fn type_of(expr: &Expr, expected: Option<TyId>, ctx: &mut Ctx) -> Option<TyId> {\n",
4523        )]));
4524        assert!(
4525            found.is_empty(),
4526            "checker.rs's own type_of has no DefId parameter and must not count: {found:?}"
4527        );
4528    }
4529
4530    #[test]
4531    fn deleted_levels_present_sees_a_real_defid_keyed_body_and_type_of() {
4532        let queries = "pub fn body(id: DefId) -> Body {\n    todo!()\n}\n\npub fn type_of(id: DefId) -> TypeOf {\n    todo!()\n}\n";
4533        assert_eq!(
4534            deleted_levels_present(&owned(&[("queries.rs", queries)])),
4535            vec!["Body", "TypeOf"]
4536        );
4537    }
4538
4539    #[test]
4540    fn shared_cache_migrated_is_false_while_project_unit_cache_still_exists() {
4541        let ide_src: Vec<(PathBuf, String)> = vec![(
4542            PathBuf::from("completion.rs"),
4543            "static PROJECT_UNIT_CACHE: LazyLock<Mutex<HashMap<PathBuf, CachedUnit>>> = ..;"
4544                .to_string(),
4545        )];
4546        let project_src: Vec<(PathBuf, String)> = vec![(
4547            PathBuf::from("cache.rs"),
4548            "pub struct SharedUnitCache { .. }".to_string(),
4549        )];
4550        assert!(!shared_cache_migrated(&ide_src, &project_src));
4551    }
4552
4553    #[test]
4554    fn shared_cache_migrated_is_true_once_project_unit_cache_is_gone_and_a_shared_cache_lands() {
4555        let ide_src: Vec<(PathBuf, String)> = vec![(
4556            PathBuf::from("completion.rs"),
4557            "fn cached_project_unit(path: &Path, content: &str) -> Option<Arc<SourceUnit>> { .. }"
4558                .to_string(),
4559        )];
4560        let project_src: Vec<(PathBuf, String)> = vec![(
4561            PathBuf::from("cache.rs"),
4562            "pub struct SharedUnitCache { units: HashMap<FileId, Arc<SourceUnit>> }".to_string(),
4563        )];
4564        assert!(shared_cache_migrated(&ide_src, &project_src));
4565    }
4566
4567    /// **The real hole finding 1 caught**: absence of `PROJECT_UNIT_CACHE` from
4568    /// `bynk-ide` alone used to read "migrated" even when nothing shared replaced it —
4569    /// a rename or deletion with no cache anywhere in `bynk-project` satisfied the old
4570    /// clause. Now requires a cache-shaped item to actually land in
4571    /// `bynk-project/src` too.
4572    #[test]
4573    fn shared_cache_migrated_is_false_when_project_unit_cache_is_gone_but_nothing_shared_replaces_it()
4574     {
4575        let ide_src: Vec<(PathBuf, String)> = vec![(
4576            PathBuf::from("completion.rs"),
4577            "fn cached_project_unit(path: &Path, content: &str) -> Option<Arc<SourceUnit>> { .. }"
4578                .to_string(),
4579        )];
4580        assert!(!shared_cache_migrated(&ide_src, &[]));
4581    }
4582
4583    /// The needle is anchored on `static PROJECT_UNIT_CACHE`, not a bare substring —
4584    /// `PROJECT_UNIT_CACHE_CAP` (a real, unrelated `const` in
4585    /// `bynk-ide/src/completion.rs`) must not hold this false on its own.
4586    #[test]
4587    fn shared_cache_migrated_is_not_confused_by_project_unit_cache_cap() {
4588        let ide_src: Vec<(PathBuf, String)> = vec![(
4589            PathBuf::from("completion.rs"),
4590            "const PROJECT_UNIT_CACHE_CAP: usize = 4096;".to_string(),
4591        )];
4592        let project_src: Vec<(PathBuf, String)> = vec![(
4593            PathBuf::from("cache.rs"),
4594            "pub struct SharedUnitCache { .. }".to_string(),
4595        )];
4596        assert!(shared_cache_migrated(&ide_src, &project_src));
4597    }
4598
4599    #[test]
4600    fn stability_test_present_recognises_a_matching_test_name() {
4601        let check_tests: Vec<(PathBuf, String)> = vec![(
4602            PathBuf::from("unit_signature.rs"),
4603            "#[test]\nfn unit_signature_is_stable_under_a_body_edit() { .. }\n".to_string(),
4604        )];
4605        assert!(stability_test_present(&check_tests));
4606    }
4607
4608    #[test]
4609    fn stability_test_present_recognises_test_attribute_separated_by_other_attributes() {
4610        let check_tests: Vec<(PathBuf, String)> = vec![(
4611            PathBuf::from("unit_signature.rs"),
4612            "#[test]\n#[should_panic]\nfn unit_signature_panics_when_stability_is_violated() { .. }\n"
4613                .to_string(),
4614        )];
4615        assert!(stability_test_present(&check_tests));
4616    }
4617
4618    #[test]
4619    fn stability_test_present_is_false_for_an_unrelated_test() {
4620        let check_tests: Vec<(PathBuf, String)> = vec![(
4621            PathBuf::from("differential_analysis.rs"),
4622            "#[test]\nfn new_entry_point_matches_analyse_project_with() { .. }\n".to_string(),
4623        )];
4624        assert!(!stability_test_present(&check_tests));
4625    }
4626
4627    #[test]
4628    fn stability_test_present_ignores_a_comment_mentioning_it() {
4629        let check_tests: Vec<(PathBuf, String)> = vec![(
4630            PathBuf::from("lib.rs"),
4631            "// TODO: add a unit_signature stability test (P8.2)\n".to_string(),
4632        )];
4633        assert!(!stability_test_present(&check_tests));
4634    }
4635
4636    /// **The real hole finding 3 caught**: the old match required only
4637    /// `fn `+`unit_signature`+`stab` on one line, with no check for an actual
4638    /// `#[test]` attribute — a plain, non-test helper used to satisfy the clause with
4639    /// no passing test in existence.
4640    #[test]
4641    fn stability_test_present_is_false_for_a_non_test_helper_with_a_matching_name() {
4642        let check_tests: Vec<(PathBuf, String)> = vec![(
4643            PathBuf::from("unit_signature.rs"),
4644            "fn unit_signature_stability_fixture(edit: &Edit) -> UnitSignature { .. }\n"
4645                .to_string(),
4646        )];
4647        assert!(!stability_test_present(&check_tests));
4648    }
4649}