facett-core 0.1.15

facett — visual kernel: render a node/edge Scene into egui (wgpu fast path to come)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
//! **ROOT LAW #0 — the rayon-free law**, and the machinery that can actually
//! report RED on it. Shared so korp and facett hold the *same* line from *one*
//! writer (LAW 5) instead of two drifting copies of a grep.
//!
//! # Why this module exists
//!
//! `znippy-zoomies/tests/rayon_free_law.rs` has guarded the law for the codec
//! tree for months. korp and facett had **no gate at all**: a `rayon = "1"` in
//! `facett-map/Cargo.toml` would have passed every test in both repos.
//!
//! # The hard part: which rayon?
//!
//! A naive lockfile grep is worse than no gate, because it is red on day one and
//! stays red forever. Measured on 2026-08-03, `Cargo.lock` in **both** repos
//! contains `rayon 1.12.0` — pulled in like this:
//!
//! ```text
//! rayon ← av-scenechange ← rav1e ← ravif ← image ← dify ← egui_kittest
//!       ← nornir-robotui "snapshot"  [dev-dependencies]  ← korp / facett-*
//! ```
//!
//! Nobody chose that. It is the AVIF encoder inside the image crate that the
//! screenshot-diff crate inside the robot-UI harness happens to enable. LAW 3
//! forbids **us** using rayon; it does not forbid a third-party image decoder in
//! the fourth ring from using it in a test harness. A gate that goes red on that
//! edge gets switched off within a week, and a gate that ignores the whole axis
//! cannot see the day it stops being test-only.
//!
//! So this module separates **three** things and says which one it tripped on:
//!
//! 1. [`scan_source`] — **our own code** calling rayon. Always red.
//! 2. [`scan_manifests`] — **our own manifests** declaring rayon, in any
//!    dependency table. Always red.
//! 3. [`shipped_rayon_tree`] — a **normal (non-dev, non-build) dependency
//!    edge** that reaches rayon, i.e. rayon linked into something we ship.
//!    Red. [`unshipped_rayon_chain`] finds the dev/build edge and is reported as
//!    *context*, never as a failure.
//!
//! Measured 2026-08-03 with `cargo tree -e normal -i rayon --target all`:
//! "nothing to print" in korp and in facett. Check 3 is therefore **green
//! today** and still able to go red — which is the only kind of green worth
//! having (LAW 2).
//!
//! It is one feature flag away from red, and that is the point of keeping it:
//! `image` **is** already a normal dependency of korp (via `eframe`→`arboard`,
//! and via `facett-about`), just without its `avif` feature. Turning `avif` on
//! anywhere in the graph puts `rayon` in the shipped korp binary. Under
//! `--all-features` that is exactly what happens, measured on the same day —
//! which is why [`rayon_tree_all_features_chain`] exists: check 3 demonstrates its own
//! red on **every run** instead of asserting that it still could.
//!
//! # Which instrument decides which axis
//!
//! Check 3 reads `cargo tree`, **not** `cargo metadata`. The first cut of this
//! module decided it from the metadata resolve, and pointed at korp that resolve
//! reported a violation — `korp -> image -> rayon` — that does not exist:
//! `cargo metadata` unifies dev-dependency features into one graph, so `image`
//! appears with `avif` on and its `ravif → rayon` edge looks normal. The
//! dependency-KIND walk was right; the FEATURE resolution under it was not, and
//! it errs towards inventing violations. `cargo metadata` is kept for kind
//! attribution and for the dev-route context chain, where over-approximating
//! costs nothing. See [`overapproximated_normal_chain`].
//!
//! # Self-honesty
//!
//! The forbidden tokens below are built with [`concat!`] so this file does not
//! contain them literally. The scanner therefore covers itself — there is no
//! path carve-out for the guard, which is how a guard quietly stops guarding.

use std::collections::VecDeque;
use std::path::{Path, PathBuf};
use std::process::Command;

/// One offending line, located.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Hit {
    /// Absolute path of the file the hit is in.
    pub file: PathBuf,
    /// 1-based line number.
    pub line: usize,
    /// The offending token that matched.
    pub token: String,
    /// The source line, trimmed (comments already stripped).
    pub text: String,
}

impl std::fmt::Display for Hit {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}: `{}` — {}", self.file.display(), self.line, self.token, self.text)
    }
}

/// Tokens that can only appear when rayon is actually being **used**. Method
/// calls are matched with their leading `.` and trailing `(` so an unrelated
/// identifier such as `par_sorted` is not a false positive.
///
/// Written through [`concat!`] so the constant does not match itself — this file
/// is scanned like every other (see the module docs).
fn rayon_call_tokens() -> Vec<String> {
    [
        concat!("use ", "rayon"),
        concat!("extern crate ", "rayon"),
        concat!("rayon", "::"),
        concat!(".", "par_iter("),
        concat!(".", "into_par_iter("),
        concat!(".", "par_iter_mut("),
        concat!(".", "par_bridge("),
        concat!(".", "par_chunks("),
        concat!(".", "par_chunks_mut("),
        concat!(".", "par_sort("),
        concat!(".", "par_sort_by("),
        concat!(".", "par_sort_unstable("),
        concat!(".", "par_extend("),
        concat!("Parallel", "Iterator"),
        concat!("IntoParallel", "Iterator"),
        concat!("ThreadPool", "Builder"),
    ]
    .iter()
    .map(|s| (*s).to_string())
    .collect()
}

/// The crate name the manifest scan looks for as a dependency key.
fn rayon_dep_key() -> String {
    concat!("ray", "on").to_string()
}

/// Strip a `//`-to-end-of-line comment so only live code is scanned. The repos
/// are full of prose *about* the law (`// never rayon here`); commentary is not
/// a violation of it.
fn strip_line_comment(line: &str) -> &str {
    match line.find("//") {
        Some(i) => &line[..i],
        None => line,
    }
}

/// Walk up from `start` to the outermost directory whose `Cargo.toml` declares a
/// `[workspace]`. Handles both shapes in this tree: facett's virtual root and
/// korp's `[workspace]`-plus-`[package]` root.
pub fn workspace_root(start: &Path) -> PathBuf {
    let mut found = start.to_path_buf();
    let mut cursor = Some(start);
    while let Some(dir) = cursor {
        let manifest = dir.join("Cargo.toml");
        if std::fs::read_to_string(&manifest)
            .map(|text| text.lines().any(|l| l.trim() == "[workspace]"))
            .unwrap_or(false)
        {
            found = dir.to_path_buf();
        }
        cursor = dir.parent();
    }
    found
}

/// Every `Cargo.toml` this workspace owns: the root, plus one per member listed
/// in the root `members = [...]` array, plus one per `exclude = [...]` leaf.
///
/// The excluded leaves are ours too. korp's root manifest excludes
/// `crates/korp-ontology`, `crates/korp-embed` and `crates/korp-demo` because
/// they carry their own empty `[workspace]` tables — and korp *depends* on
/// `korp-demo` by path, so a `rayon = "1"` in that leaf ships. Reading only the
/// members would have left first-party manifests and first-party `src/`
/// unscanned while the count still looked healthy.
pub fn workspace_manifests(root: &Path) -> Vec<PathBuf> {
    let mut out = vec![root.join("Cargo.toml")];
    for member in first_party_dirs(root) {
        let m = root.join(&member).join("Cargo.toml");
        if m.exists() {
            out.push(m);
        }
    }
    out
}

/// The `members = [...]` entries of the root manifest. Tolerates the array being
/// written on one line or spread over many.
pub fn workspace_members(root: &Path) -> Vec<String> {
    manifest_string_array(root, "members")
}

/// The `exclude = [...]` entries of the root manifest — self-rooting leaves that
/// live in this repo and are therefore first-party even though cargo does not
/// call them members.
pub fn workspace_excludes(root: &Path) -> Vec<String> {
    manifest_string_array(root, "exclude")
}

/// Members and excluded leaves together: every directory in this repo that holds
/// a package of ours.
pub fn first_party_dirs(root: &Path) -> Vec<String> {
    let mut out = workspace_members(root);
    for e in workspace_excludes(root) {
        if !out.contains(&e) {
            out.push(e);
        }
    }
    out
}

/// Read a `key = [ "a", "b" ]` array out of the root manifest's `[workspace]`
/// table.
///
/// Two things this does that the first version did not, both of them defects
/// that were found by pointing the guard at korp:
///
/// 1. **It anchors on a LINE, not on the first occurrence of the word.** korp's
///    root manifest says "are NOT pulled in as members" in its header comment
///    three lines above `[workspace]`. A bare `text.find("members")` landed
///    there, then took `[workspace]`'s own brackets as the array and returned
///    `["workspace"]` — one member that does not exist and none of the real
///    ones. The manifest scan read 1 file where it should read 2.
/// 2. **It stays inside `[workspace]`.** `exclude` is also a `[package]` key
///    (packaging excludes), and korp's root manifest carries both tables.
///
/// Comments are `#` here, not `//` — this is TOML, and an entry may be followed
/// by a trailing `# why`.
fn manifest_string_array(root: &Path, key: &str) -> Vec<String> {
    let Ok(text) = std::fs::read_to_string(root.join("Cargo.toml")) else {
        return Vec::new();
    };
    let mut cursor = 0usize;
    let mut start = None;
    let mut in_workspace = false;
    for line in text.split_inclusive('\n') {
        let trimmed = line.trim_start();
        if trimmed.starts_with('[') {
            in_workspace = trimmed.starts_with("[workspace]");
        } else if in_workspace {
            if let Some(rest) = trimmed.strip_prefix(key) {
                if rest.trim_start().starts_with('=') {
                    start = Some(cursor + (line.len() - trimmed.len()));
                    break;
                }
            }
        }
        cursor += line.len();
    }
    let Some(start) = start else { return Vec::new() };
    // Strip `#`-to-end-of-line on every line BEFORE locating the brackets, so a
    // trailing `"a", # the first one` cannot swallow the rest of the array. The
    // first cut of this function stripped per comma-separated piece instead, and
    // its own new test caught it eating `"b"`.
    let rest: String = text[start..]
        .lines()
        .map(|l| l.split('#').next().unwrap_or(""))
        .collect::<Vec<_>>()
        .join("\n");
    let Some(open) = rest.find('[') else { return Vec::new() };
    let Some(close) = rest[open..].find(']') else { return Vec::new() };
    rest[open + 1..open + close]
        .split(',')
        .map(|s| s.trim().trim_matches('"').to_string())
        .filter(|s| !s.is_empty())
        .collect()
}

/// Every directory of ours that holds first-party Rust: each member's `src`,
/// `tests`, `benches` and `examples`, plus the root package's own if the root
/// manifest carries a `[package]`. `target/` is never walked.
pub fn source_roots(root: &Path) -> Vec<PathBuf> {
    const SUBDIRS: &[&str] = &["src", "tests", "benches", "examples"];
    let mut dirs: Vec<PathBuf> = Vec::new();
    let push_for = |base: PathBuf, dirs: &mut Vec<PathBuf>| {
        for sub in SUBDIRS {
            let p = base.join(sub);
            if p.is_dir() {
                dirs.push(p);
            }
        }
    };
    push_for(root.to_path_buf(), &mut dirs);
    for member in first_party_dirs(root) {
        push_for(root.join(member), &mut dirs);
    }
    dirs
}

fn rs_files(dir: &Path, out: &mut Vec<PathBuf>) {
    let Ok(rd) = std::fs::read_dir(dir) else { return };
    for entry in rd.flatten() {
        let path = entry.path();
        if path.is_dir() {
            if path.file_name().map(|n| n == "target").unwrap_or(false) {
                continue;
            }
            rs_files(&path, out);
        } else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
            out.push(path);
        }
    }
}

/// What a source scan found, plus how much it looked at — a scan that found
/// nothing because it read nothing is the hollow green this whole module is
/// about, so the count travels with the verdict.
#[derive(Debug, Clone, Default)]
pub struct Scan {
    /// Offending lines.
    pub hits: Vec<Hit>,
    /// How many files were actually read.
    pub scanned: usize,
}

/// Scan every first-party `.rs` file under `root` for live rayon usage.
pub fn scan_source(root: &Path) -> Scan {
    let tokens = rayon_call_tokens();
    let mut files = Vec::new();
    for dir in source_roots(root) {
        rs_files(&dir, &mut files);
    }
    let mut hits = Vec::new();
    for file in &files {
        let Ok(text) = std::fs::read_to_string(file) else { continue };
        for (i, raw) in text.lines().enumerate() {
            let code = strip_line_comment(raw);
            for tok in &tokens {
                if code.contains(tok.as_str()) {
                    hits.push(Hit {
                        file: file.clone(),
                        line: i + 1,
                        token: tok.clone(),
                        text: code.trim().to_string(),
                    });
                }
            }
        }
    }
    Scan { hits, scanned: files.len() }
}

/// Scan every first-party `Cargo.toml` for a `rayon` dependency key, in any
/// dependency table (`[dependencies]`, `[dev-dependencies]`,
/// `[build-dependencies]`, `[target.'…'.dependencies]`,
/// `[workspace.dependencies]`). Comments are stripped: a manifest may freely
/// explain *why* rayon is banned.
pub fn scan_manifests(root: &Path) -> Scan {
    let key = rayon_dep_key();
    let manifests = workspace_manifests(root);
    let mut hits = Vec::new();
    for manifest in &manifests {
        let Ok(text) = std::fs::read_to_string(manifest) else { continue };
        let mut in_deps = false;
        for (i, raw) in text.lines().enumerate() {
            let line = raw.trim();
            if line.starts_with('[') {
                in_deps = line.contains("dependencies]");
                continue;
            }
            if !in_deps {
                continue;
            }
            let code = strip_line_comment(line);
            let name = code.split('=').next().unwrap_or("").trim().trim_matches('"');
            if name == key {
                hits.push(Hit {
                    file: manifest.clone(),
                    line: i + 1,
                    token: key.clone(),
                    text: code.trim().to_string(),
                });
            }
        }
    }
    Scan { hits, scanned: manifests.len() }
}

/// Run `cargo metadata` for `root` and return the parsed document.
///
/// `--offline --locked` on purpose: the guard must never mutate `Cargo.lock`
/// (LAW 6) and must never reach the network from a test. A stale lock is a hard
/// error, not a skip — a guard that skips itself is the disease.
pub fn cargo_metadata(root: &Path) -> Result<serde_json::Value, String> {
    let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
    let out = Command::new(cargo)
        .args(["metadata", "--format-version", "1", "--offline", "--locked"])
        .current_dir(root)
        .output()
        .map_err(|e| format!("could not run `cargo metadata` in {}: {e}", root.display()))?;
    if !out.status.success() {
        let stderr = String::from_utf8_lossy(&out.stderr);
        return Err(format!(
            "`cargo metadata --offline --locked` failed in {} ({}):\n{}",
            root.display(),
            out.status,
            stderr.lines().take(8).collect::<Vec<_>>().join("\n"),
        ));
    }
    serde_json::from_slice(&out.stdout).map_err(|e| format!("cargo metadata is not JSON: {e}"))
}

/// Package-id → package-name index from a `cargo metadata` document.
fn id_to_name(md: &serde_json::Value) -> std::collections::HashMap<String, String> {
    md["packages"]
        .as_array()
        .map(|ps| {
            ps.iter()
                .filter_map(|p| {
                    Some((p["id"].as_str()?.to_string(), p["name"].as_str()?.to_string()))
                })
                .collect()
        })
        .unwrap_or_default()
}

/// Which dependency kinds an edge carries. `cargo metadata` spells a normal
/// dependency as `kind: null`.
fn edge_kinds(dep: &serde_json::Value) -> Vec<String> {
    dep["dep_kinds"]
        .as_array()
        .map(|ks| {
            ks.iter()
                .map(|k| k["kind"].as_str().unwrap_or("normal").to_string())
                .collect()
        })
        .unwrap_or_default()
}

/// Breadth-first walk from the workspace members to `target_name`.
///
/// `require_non_normal` is what keeps the two answers from collapsing into one:
/// with it set, a chain only counts if it crosses at least one `dev` or `build`
/// edge, so "the dev-only route" cannot be silently answered with the shipped
/// route. The BFS state therefore carries that flag, and a package is re-visited
/// once per flag value.
///
/// Returns the shortest qualifying chain of package names, workspace member
/// first.
fn chain_to(
    md: &serde_json::Value,
    target_name: &str,
    require_non_normal: bool,
) -> Option<Vec<String>> {
    let names = id_to_name(md);
    let nodes: std::collections::HashMap<&str, &serde_json::Value> = md["resolve"]["nodes"]
        .as_array()?
        .iter()
        .filter_map(|n| Some((n["id"].as_str()?, n)))
        .collect();

    let members: Vec<&str> =
        md["workspace_members"].as_array()?.iter().filter_map(|m| m.as_str()).collect();

    let mut seen: std::collections::HashSet<(&str, bool)> = std::collections::HashSet::new();
    let mut queue: VecDeque<(&str, bool, Vec<String>)> = VecDeque::new();
    for m in members {
        if seen.insert((m, false)) {
            let label = names.get(m).cloned().unwrap_or_else(|| m.to_string());
            queue.push_back((m, false, vec![label]));
        }
    }

    while let Some((id, crossed, path)) = queue.pop_front() {
        if names.get(id).map(|n| n == target_name).unwrap_or(false)
            && path.len() > 1
            && (crossed || !require_non_normal)
        {
            return Some(path);
        }
        let Some(node) = nodes.get(id) else { continue };
        let Some(deps) = node["deps"].as_array() else { continue };
        for dep in deps {
            let kinds = edge_kinds(dep);
            // A single dependency can carry several kinds at once (normal AND
            // dev). Take the cheapest interpretation: if it is normal at all,
            // the shipped walk may use it without crossing.
            let has_normal = kinds.iter().any(|k| k == "normal");
            let has_other = kinds.iter().any(|k| k == "dev" || k == "build");
            let Some(pkg) = dep["pkg"].as_str() else { continue };

            // Which flag values this edge can hand on. The shipped walk only
            // ever follows a normal edge and never sets the flag, so it can
            // never answer with a dev route.
            let mut onward: Vec<bool> = Vec::new();
            if has_normal {
                onward.push(crossed);
            }
            if require_non_normal && has_other {
                onward.push(true);
            }

            for next_crossed in onward {
                if !seen.insert((pkg, next_crossed)) {
                    continue;
                }
                let mut next = path.clone();
                next.push(names.get(pkg).cloned().unwrap_or_else(|| pkg.to_string()));
                queue.push_back((pkg, next_crossed, next));
            }
        }
    }
    None
}

/// A **normal** dependency path from this workspace to rayon, as `cargo
/// metadata` sees it.
///
/// **Do not decide the shipped axis with this.** It over-approximates and it was
/// measured doing so: pointed at korp on 2026-08-03 it reported
/// `korp -> image -> rayon` while `cargo tree -e normal -i rayon --target all`
/// said "nothing to print". `cargo metadata`'s resolve unifies
/// dev-dependency features into one graph, so `image` shows up with `avif` on
/// and its `ravif -> rayon` edge looks normal. The dependency-KIND walk under
/// this function is right; the FEATURE resolution beneath it is not, and it errs
/// in the direction that invents violations. Kept for kind attribution and for
/// [`unshipped_rayon_chain`] (where over-approximating a *context* chain is
/// harmless); the verdict belongs to [`shipped_rayon_tree`].
pub fn overapproximated_normal_chain(md: &serde_json::Value) -> Option<Vec<String>> {
    chain_to(md, &rayon_dep_key(), false)
}

/// A path to rayon that crosses at least one **dev** or **build** edge: real,
/// known, and **not** a violation — third-party test-harness weight, nothing we
/// chose and nothing that ships. Reported as context so the gate can say which
/// of the two axes it tripped on, and used as the walker's own liveness check.
pub fn unshipped_rayon_chain(md: &serde_json::Value) -> Option<Vec<String>> {
    chain_to(md, &rayon_dep_key(), true)
}

// ───────────────────────── the shipped verdict, from cargo tree ──────────────
//
// `cargo tree -e normal -i rayon --target all` is the command that was actually
// measured on 2026-08-03, and it is the one that resolves features the way a
// build does. Everything below reads ITS output. `cargo metadata` stays where it
// belongs: dependency-kind attribution and the dev-route context chain.

/// Run `cargo tree -i <crate> --target all -e <edges>` in `root` and hand back
/// stdout.
///
/// `--offline --locked` for the same reason [`cargo_metadata`] uses them: a
/// guard must never mutate `Cargo.lock` (LAW 6) and never reach the network. A
/// stale lock is a hard error, not a skip.
///
/// `extra` carries the feature selection — `["--all-features"]`,
/// `["--no-default-features", "--features", "robot-watch"]`, or nothing for the
/// default shape. It is a parameter and not a bool because **the default shape
/// is not the only shape that ships**: measured on oden 2026-08-03, korp is
/// clean at default features and at five of its six single-feature builds, and
/// `--no-default-features --features robot-watch` puts rayon on a NORMAL edge —
/// and that is the build `.nornir/ingest-tab-app-robot.md:27` tells people to
/// make. A gate that only ever asks about the default answer is blind to
/// exactly the binary the documentation recommends.
pub fn rayon_tree(root: &Path, edges: &str, extra: &[&str]) -> Result<String, String> {
    let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
    let mut cmd = Command::new(cargo);
    cmd.args(["tree", "-i", &rayon_dep_key(), "--target", "all", "-e", edges])
        .args(["--offline", "--locked"])
        .args(extra)
        .current_dir(root);
    let out = cmd
        .output()
        .map_err(|e| format!("could not run `cargo tree` in {}: {e}", root.display()))?;
    let stdout = String::from_utf8_lossy(&out.stdout).to_string();
    let stderr = String::from_utf8_lossy(&out.stderr).to_string();
    if !out.status.success() {
        return Err(format!(
            "`cargo tree -e {edges} -i rayon --target all {}` failed in {} ({}):\n{}",
            extra.join(" "),
            root.display(),
            out.status,
            stderr.lines().take(8).collect::<Vec<_>>().join("\n"),
        ));
    }
    // Exit code is not the answer here. `cargo tree` says "nothing to print" on
    // stderr and exits 0, and a command that failed to name a crate at all would
    // also exit 0 with empty stdout. The caller gets stderr folded in so
    // "nothing to print" is a fact it can read, not an absence it must guess at.
    Ok(if stdout.trim().is_empty() { stderr } else { stdout })
}

/// Did `cargo tree -i` find no path at all? True for the "nothing to print"
/// notice and for genuinely empty output.
pub fn tree_found_nothing(out: &str) -> bool {
    out.trim().is_empty() || out.contains("nothing to print")
}

/// The chain `cargo tree -i` drew, from the first package of OURS back up to
/// rayon.
///
/// `cargo tree -i` prints the inverted tree: rayon at depth 0, its dependents
/// below it. A package is ours when the path cargo printed in parentheses is
/// under `root`. Returns e.g.
/// `["korp", "eframe", "egui-winit", "arboard", "image", "ravif", "rav1e",
///   "av-scenechange", "rayon"]`.
///
/// `None` means the tree named nothing of ours — either it is empty (clean) or
/// the format changed, and the caller must not read those two as the same thing.
pub fn tree_chain_to_first_party(out: &str, root: &Path) -> Option<Vec<String>> {
    let here = format!("({}", root.display());
    let mut stack: Vec<String> = Vec::new();
    for line in out.lines() {
        // The tree prefix is drawn from these five characters, four per level.
        let prefix: String =
            line.chars().take_while(|c| matches!(c, '' | ' ' | '' | '' | '')).collect();
        let body = &line[prefix.len()..];
        if body.is_empty() || body.starts_with('[') {
            // `[dev-dependencies]` / `[build-dependencies]` markers carry no
            // package of their own.
            continue;
        }
        let depth = prefix.chars().count() / 4;
        let Some(name) = body.split_whitespace().next().map(str::to_string) else { continue };
        stack.truncate(depth);
        stack.push(name);
        if body.contains(&here) {
            let mut chain = stack.clone();
            chain.reverse();
            return Some(chain);
        }
    }
    None
}

/// The verdict: is rayon reachable from this workspace over **normal** edges?
///
/// `Ok(None)` is clean. `Ok(Some(chain))` is a LAW 3 violation on a shipped
/// edge.
pub fn shipped_rayon_tree(root: &Path) -> Result<Option<Vec<String>>, String> {
    shipped_rayon_tree_with(root, &[])
}

/// [`shipped_rayon_tree`] for ONE feature selection — the build shape is a
/// parameter, because rayon can sit behind a feature and in korp it does.
///
/// Pass `&[]` for the default shape, or e.g.
/// `&["--no-default-features", "--features", "server"]`.
pub fn shipped_rayon_tree_with(
    root: &Path,
    features: &[&str],
) -> Result<Option<Vec<String>>, String> {
    let out = rayon_tree(root, "normal", features)?;
    if tree_found_nothing(&out) {
        return Ok(None);
    }
    Ok(Some(tree_chain_to_first_party(&out, root).unwrap_or_else(|| {
        out.lines().map(str::trim).filter(|l| !l.is_empty()).map(str::to_string).collect()
    })))
}

/// The `--no-default-features --features <name>` argv for one feature.
pub fn only_feature(name: &str) -> Vec<String> {
    vec!["--no-default-features".into(), "--features".into(), name.into()]
}

/// The same instrument and the same parser, asked a question this tree is
/// **known** to answer yes to: reach rayon over edges of *any* kind.
///
/// Both repos really do carry rayon in `Cargo.lock`, through
/// `nornir-robotui`'s snapshot harness (`egui_kittest → dify → image → ravif →
/// rav1e → av-scenechange`), and every edge of that is a `[dev-dependencies]`
/// edge. So this call must always come back with a chain that ends at one of
/// OUR packages — and [`shipped_rayon_tree`] is the identical command with the
/// single `-e` flag narrowed to `normal`.
///
/// What it proves: cargo ran, rayon is in the graph, the parser read real
/// cargo output, and the walk reaches this workspace. What it does **not**
/// prove: that a *normal* edge would be detected — that is what
/// `the_tree_parser_reads_a_real_violation_chain_all_the_way_to_us` is for,
/// which is proven red against a captured violation. Stated plainly because a
/// liveness check dressed up as a red proof is the disease this module is about.
///
/// A guard whose clean answer is `None` cannot tell "clean" from "the command
/// stopped working". This makes it say which, on every run.
pub fn rayon_tree_liveness_chain(root: &Path) -> Result<Option<Vec<String>>, String> {
    let out = rayon_tree(root, "normal,dev,build", &[])?;
    if tree_found_nothing(&out) {
        return Ok(None);
    }
    Ok(tree_chain_to_first_party(&out, root))
}

/// The `-e normal` query under `--all-features`, which in **korp** really does
/// produce a shipped violation: `--all-features` turns on `image`'s `avif` and
/// `ravif → rav1e → av-scenechange → rayon` becomes a normal edge reaching korp
/// itself. Measured on oden 2026-08-03 — the same command that says "nothing to
/// print" at default features prints that chain.
///
/// This is the strongest available proof for check 3, because the failing query
/// is byte-for-byte the query the verdict uses. It is **not** universal:
/// measured the same day, facett answers "nothing to print" even under
/// `--all-features`, because `--all-features` applies to workspace members only
/// and no facett member turns `avif` on. Callers that cannot get a chain here
/// must fall back to [`rayon_tree_liveness_chain`], not skip the axis.
pub fn rayon_tree_all_features_chain(root: &Path) -> Result<Option<Vec<String>>, String> {
    let out = rayon_tree(root, "normal", &["--all-features"])?;
    if tree_found_nothing(&out) {
        return Ok(None);
    }
    Ok(tree_chain_to_first_party(&out, root))
}

/// The feature set cargo resolves for `pkg` when the **whole workspace** is
/// selected — exactly what `cargo test --workspace` compiles with.
///
/// This asks cargo rather than re-implementing feature unification, because a
/// hand-rolled resolver that quietly disagrees with cargo is a guard that
/// reports on a build nobody runs.
pub fn workspace_features(md: &serde_json::Value, pkg: &str) -> Option<Vec<String>> {
    let names = id_to_name(md);
    let mut out: Vec<String> = Vec::new();
    let mut found = false;
    for node in md["resolve"]["nodes"].as_array()? {
        let id = node["id"].as_str()?;
        if names.get(id).map(|n| n != pkg).unwrap_or(true) {
            continue;
        }
        found = true;
        if let Some(fs) = node["features"].as_array() {
            out.extend(fs.iter().filter_map(|f| f.as_str().map(str::to_string)));
        }
    }
    if !found {
        return None;
    }
    out.sort();
    out.dedup();
    Some(out)
}

/// A test file whose crate-level `#![cfg(feature = "…")]` silences it.
#[derive(Debug, Clone)]
pub struct GatedTestFile {
    /// The file.
    pub file: PathBuf,
    /// The feature its crate-level `cfg` demands.
    pub feature: String,
    /// How many `#[test]`/`#[tokio::test]` functions it holds — the number that
    /// disappears into `0 tests, 0 benchmarks` when the feature is off.
    pub tests: usize,
}

/// Find every `tests/*.rs` under `crate_dir` gated behind a crate-level
/// `#![cfg(feature = "…")]`.
///
/// A gated file whose feature is off compiles to an empty binary that prints
/// `0 tests, 0 benchmarks` and exits 0 — indistinguishable from a passing file.
/// That is the exact shape of a green nobody has seen red.
pub fn gated_test_files(crate_dir: &Path) -> Vec<GatedTestFile> {
    let mut out = Vec::new();
    let Ok(rd) = std::fs::read_dir(crate_dir.join("tests")) else { return out };
    for entry in rd.flatten() {
        let path = entry.path();
        if path.extension().and_then(|e| e.to_str()) != Some("rs") {
            continue;
        }
        let Ok(text) = std::fs::read_to_string(&path) else { continue };
        let Some(feature) = text.lines().find_map(crate_level_cfg_feature) else { continue };
        let tests = text
            .lines()
            .filter(|l| {
                let t = l.trim();
                t.starts_with("#[test]") || t.starts_with("#[tokio::test")
            })
            .count();
        out.push(GatedTestFile { file: path, feature, tests });
    }
    out.sort_by(|a, b| a.file.cmp(&b.file));
    out
}

/// `#![cfg(feature = "x")]` → `Some("x")`. Inner attribute only: a per-`fn`
/// `#[cfg(...)]` silences one test, a crate-level one silences the whole file.
fn crate_level_cfg_feature(line: &str) -> Option<String> {
    let t = line.trim();
    let rest = t.strip_prefix("#![cfg(feature")?;
    let rest = rest.trim_start().strip_prefix('=')?.trim_start();
    let rest = rest.strip_prefix('"')?;
    let end = rest.find('"')?;
    Some(rest[..end].to_string())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn comment_stripping_keeps_code_and_drops_prose() {
        assert_eq!(strip_line_comment("let x = 1; // rayon::scope is banned").trim(), "let x = 1;");
        assert_eq!(strip_line_comment("//! never rayon").trim(), "");
        assert_eq!(strip_line_comment("let y = 2;").trim(), "let y = 2;");
    }

    #[test]
    fn the_forbidden_tokens_are_not_spelled_literally_in_this_file() {
        // The scanner covers its own source (no path carve-out), so the tokens
        // must be assembled rather than written out — otherwise this module
        // trips the law it enforces, and someone "fixes" it by adding an
        // exemption that then hides real hits.
        let me = include_str!("law.rs");
        for tok in rayon_call_tokens() {
            let literal_uses = me
                .lines()
                .filter(|l| !strip_line_comment(l).trim().is_empty())
                .filter(|l| strip_line_comment(l).contains(&tok))
                .count();
            assert_eq!(literal_uses, 0, "`{tok}` appears literally in live code in law.rs");
        }
    }

    #[test]
    fn a_crate_level_gate_is_read_and_a_per_fn_one_is_not() {
        assert_eq!(crate_level_cfg_feature("#![cfg(feature = \"wgpu\")]"), Some("wgpu".into()));
        assert_eq!(crate_level_cfg_feature("  #![cfg(feature=\"gpu\")]  "), Some("gpu".into()));
        assert_eq!(crate_level_cfg_feature("#[cfg(feature = \"wgpu\")]"), None);
        assert_eq!(crate_level_cfg_feature("fn main() {}"), None);
    }

    /// The `guard-7(a)` case, verbatim in shape: korp's root manifest names
    /// "members" in a header comment three lines above `[workspace]`. The old
    /// `text.find("members")` landed in that comment, took `[workspace]`'s own
    /// brackets as the array and returned `["workspace"]` — so korp's manifest
    /// scan read 1 file where it should read 2, and the liveness assert in the
    /// gate refused the green it could not stand behind.
    #[test]
    fn the_word_members_in_a_header_comment_is_not_the_members_key() {
        let dir = scratch("header-comment");
        std::fs::write(
            dir.join("Cargo.toml"),
            "# korp is the workspace ROOT. The `crates/*` leaves are NOT pulled in\n\
             # as members — they are self-rooting.\n\
             [workspace]\n\
             resolver = \"3\"\n\
             members = [\"xtask\"]\n\
             exclude = [\"crates/korp-ontology\", \"crates/korp-demo\"]\n\
             \n\
             [package]\n\
             name = \"korp\"\n\
             exclude = [\"docs/*\"]\n",
        )
        .unwrap();
        assert_eq!(workspace_members(&dir), vec!["xtask".to_string()]);
        // `exclude` must come from `[workspace]`, not from `[package]`.
        assert_eq!(
            workspace_excludes(&dir),
            vec!["crates/korp-ontology".to_string(), "crates/korp-demo".to_string()]
        );
        // Both, because korp *depends on* `crates/korp-demo` by path: a
        // `rayon = "1"` there ships.
        assert_eq!(
            first_party_dirs(&dir),
            vec![
                "xtask".to_string(),
                "crates/korp-ontology".to_string(),
                "crates/korp-demo".to_string()
            ]
        );
        std::fs::remove_dir_all(&dir).ok();
    }

    #[test]
    fn a_trailing_toml_comment_inside_the_array_is_not_a_member() {
        let dir = scratch("array-comment");
        std::fs::write(
            dir.join("Cargo.toml"),
            "[workspace]\nmembers = [\n  \"a\", # the first one\n  \"b\",\n]\n",
        )
        .unwrap();
        assert_eq!(workspace_members(&dir), vec!["a".to_string(), "b".to_string()]);
        std::fs::remove_dir_all(&dir).ok();
    }

    fn scratch(tag: &str) -> PathBuf {
        let dir = std::env::temp_dir()
            .join(format!("facett-law-{tag}-{}-{:?}", std::process::id(), std::thread::current().id()));
        std::fs::create_dir_all(&dir).unwrap();
        dir
    }

    /// The complete, verbatim stdout of
    /// `cargo tree -e normal -i rayon --target all --all-features --offline
    /// --locked`, run in korp on oden 2026-08-03. Not a hand-drawn tree: this is
    /// the shape the guard has to read, dedupe markers (`(*)`), branch points
    /// and all. A parser tested only against the clean case is a parser that has
    /// never been asked to find anything.
    const KORP_ALL_FEATURES_TREE: &str = "\
rayon v1.12.0
├── av-scenechange v0.14.1
│   └── rav1e v0.8.1
│       └── ravif v0.13.0
│           └── image v0.25.10
│               ├── arboard v3.6.1
│               │   └── egui-winit v0.35.0
│               │       └── eframe v0.35.0
│               │           ├── korp v0.1.0 (/home/rickard/scratch/lawfix/korp)
│               │           └── nornir-robotui v0.3.0
│               │               └── korp v0.1.0 (/home/rickard/scratch/lawfix/korp)
│               ├── dify v0.8.0
│               │   └── egui_kittest v0.35.0
│               │       └── nornir-robotui v0.3.0 (*)
│               ├── eframe v0.35.0 (*)
│               ├── egui_kittest v0.35.0 (*)
│               ├── facett-about v0.1.13 (/home/rickard/scratch/lawfix/facett/facett-about)
│               │   └── korp v0.1.0 (/home/rickard/scratch/lawfix/korp)
│               ├── korp v0.1.0 (/home/rickard/scratch/lawfix/korp)
│               ├── nornir-robotui v0.3.0 (*)
│               └── webp v0.3.1
│                   └── nornir-robotui v0.3.0 (*)
├── dify v0.8.0 (*)
├── image v0.25.10 (*)
├── maybe-rayon v0.1.1
│   └── rav1e v0.8.1 (*)
└── ravif v0.13.0 (*)
";

    #[test]
    fn the_tree_parser_reads_a_real_violation_chain_all_the_way_to_us() {
        assert!(!tree_found_nothing(KORP_ALL_FEATURES_TREE));

        // (1) A straight descent: rayon down to korp, first hit in the file.
        let korp = Path::new("/home/rickard/scratch/lawfix/korp");
        let chain = tree_chain_to_first_party(KORP_ALL_FEATURES_TREE, korp)
            .expect("the chain reaches korp and must be readable");
        assert_eq!(
            chain,
            vec![
                "korp",
                "eframe",
                "egui-winit",
                "arboard",
                "image",
                "ravif",
                "rav1e",
                "av-scenechange",
                "rayon"
            ]
        );

        // (2) The same capture read as FACETT's workspace. This is the case that
        // makes the test able to fail: `facett-about` sits at depth 5, but the
        // three lines before it descend to depth 7, so the walker must POP back
        // up. A depth arithmetic that never pops still answers (1) correctly —
        // every line in that path goes one level deeper — and answers this one
        // with the stale `dify/egui_kittest/nornir-robotui` branch still on the
        // stack. Measured: with the divisor wrong, (1) stayed green and this
        // assert went red.
        let facett = Path::new("/home/rickard/scratch/lawfix/facett");
        let via_facett = tree_chain_to_first_party(KORP_ALL_FEATURES_TREE, facett)
            .expect("facett-about is first-party to the facett workspace");
        assert_eq!(
            via_facett,
            vec!["facett-about", "image", "ravif", "rav1e", "av-scenechange", "rayon"]
        );

        // (3) …and it must not claim a chain for a workspace it is not looking at.
        assert_eq!(tree_chain_to_first_party(KORP_ALL_FEATURES_TREE, Path::new("/nowhere")), None);
    }

    #[test]
    fn nothing_to_print_is_the_clean_answer_and_empty_is_not_silently_the_same() {
        // The literal notice cargo emits (on stderr) when `-i` finds no path.
        let clean = "warning: nothing to print.\n\nTo find dependencies that require \
                     specific target platforms, try to use option `--target all` first.\n";
        assert!(tree_found_nothing(clean));
        assert!(tree_found_nothing("   \n"));
        assert!(!tree_found_nothing(KORP_ALL_FEATURES_TREE));
        // A clean tree names nothing of ours — the two answers must not collapse.
        assert_eq!(tree_chain_to_first_party(clean, Path::new("/home/rickard/scratch/lawfix/korp")), None);
    }

    #[test]
    fn members_parse_from_a_multi_line_array() {
        let dir = scratch("multiline");
        std::fs::write(
            dir.join("Cargo.toml"),
            "[workspace]\nmembers = [\n  \"a\",\n  \"b\",\n]\n",
        )
        .unwrap();
        assert_eq!(workspace_members(&dir), vec!["a".to_string(), "b".to_string()]);
        std::fs::remove_dir_all(&dir).ok();
    }
}