nornir 0.5.3

Companion to cargo: dependency tracking, release gating, deploy, benchmarks, and documentation assembly. Project-agnostic.
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
//! 🩸 **Bloodstream** β€” the pure (no-egui) data core behind the organic
//! *circulatory-system* view of a nornir workspace (the "blodomlopp").
//!
//! The metaphor, made concrete as data:
//!
//!   * **organs / limbs = crates** β€” every workspace member is an [`Organ`]
//!     placed at an anatomical body coordinate (heart, lungs, liver, the four
//!     limbs…), sized by how many other members depend on it (a busy hub is a
//!     bigger organ);
//!   * **arteries / veins = dependency edges** β€” every cross-repo
//!     [`CrossRepoEdge`] becomes a [`Vessel`] between two organs;
//!   * **blood = the call-chain coursing through them** β€” a scalar `flow_phase`
//!     (advanced by an injected clock, FC-7) drives corpuscles along every
//!     vessel; the renderer reads it, the state dump reports it.
//!
//! **Recursive drill-down (the deep part).** Expanding an organ pulls that
//! crate's **entire transitive dependency closure that is NOT a workspace
//! member** β€” the real crates.io deps-of-deps β€” straight out of a `Cargo.lock`
//! ([`external_closure`]), lazily, and hangs them off the organ as **capillary**
//! nodes in concentric rings by recursion depth. This is live resolver data (the
//! same `[[package]]` graph `cargo` writes and [`crate::deepscan`] scans), never
//! synthetic.
//!
//! Kept egui-free (like [`super::depgraph_layout`]) so the layout, the closure
//! walk, and [`Organism::state_json`] are all inject-and-assert testable and fold
//! straight into the viz `NORNIR_VIZ_STATE` dump (LAW #6).

use std::collections::{BTreeMap, BTreeSet, VecDeque};

use crate::warehouse::dep_graph::CrossRepoEdge;

/// One external (non-workspace) transitive dependency reached by drilling into an
/// organ β€” a crates.io crate pulled from the lockfile closure.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtDep {
    /// Crate name (`serde`, `tokio`, …).
    pub name: String,
    /// Resolved version from the lockfile (`1.0.219`).
    pub version: String,
    /// Recursion depth from the expanded organ: a *direct* dependency is `1`, its
    /// own dependency `2`, and so on β€” the concentric-ring index for the render.
    pub depth: usize,
}

/// A raw `[[package]]` entry parsed out of a `Cargo.lock`.
#[derive(Debug, Clone)]
struct LockPkg {
    source: Option<String>,
    /// The raw `dependencies = [ "name", "name ver", … ]` strings.
    deps: Vec<String>,
}

/// Parse a `Cargo.lock` into `(name, version) β†’ package` plus a `name β†’ versions`
/// index for resolving unversioned dependency references.
fn parse_lock(
    lock_toml: &str,
) -> (
    BTreeMap<(String, String), LockPkg>,
    BTreeMap<String, Vec<String>>,
) {
    let mut by_nv: BTreeMap<(String, String), LockPkg> = BTreeMap::new();
    let mut versions: BTreeMap<String, Vec<String>> = BTreeMap::new();
    let doc: toml::Value = match toml::from_str(lock_toml) {
        Ok(d) => d,
        Err(_) => return (by_nv, versions),
    };
    let Some(pkgs) = doc.get("package").and_then(|p| p.as_array()) else {
        return (by_nv, versions);
    };
    for pkg in pkgs {
        let (Some(name), Some(version)) = (
            pkg.get("name").and_then(|v| v.as_str()),
            pkg.get("version").and_then(|v| v.as_str()),
        ) else {
            continue;
        };
        let source = pkg.get("source").and_then(|s| s.as_str()).map(str::to_string);
        let deps = pkg
            .get("dependencies")
            .and_then(|d| d.as_array())
            .map(|a| a.iter().filter_map(|d| d.as_str().map(str::to_string)).collect())
            .unwrap_or_default();
        versions.entry(name.to_string()).or_default().push(version.to_string());
        by_nv.insert((name.to_string(), version.to_string()), LockPkg { source, deps });
    }
    (by_nv, versions)
}

/// Resolve a lockfile dependency reference (`"serde"` or `"serde 1.0.219"`) to a
/// concrete `(name, version)` key, disambiguating a bare name to its sole (or, if
/// several, first-sorted) version.
fn resolve_ref(
    dep: &str,
    versions: &BTreeMap<String, Vec<String>>,
) -> Option<(String, String)> {
    let mut it = dep.splitn(2, ' ');
    let name = it.next()?.to_string();
    if let Some(ver) = it.next() {
        return Some((name, ver.to_string()));
    }
    let vs = versions.get(&name)?;
    // Deterministic: smallest version string when a bare name is ambiguous.
    let mut sorted = vs.clone();
    sorted.sort();
    sorted.first().map(|v| (name.clone(), v.clone()))
}

/// **The recursive external closure.** Walk the `Cargo.lock` dependency graph
/// breadth-first from every `root` package (matched by crate name β€” the workspace
/// members) and return every **external** package reached (one carrying a
/// `source`, i.e. a crates.io / registry dep β€” path & workspace members have
/// none), tagged with its recursion `depth`. This is the deps-of-deps closure the
/// organic drill-down hangs off an organ.
///
/// * `roots` β€” the crate name(s) to drill from (usually one organ).
/// * `max_depth` β€” bound the recursion (concentric-ring budget); `0` β‡’ unbounded.
///
/// The walk descends through workspace members too (they bridge to more external
/// deps) but only *records* the source-carrying ones, so it is genuinely the
/// crate's transitive **crates.io** closure, not just its direct deps.
pub fn external_closure(lock_toml: &str, roots: &[&str], max_depth: usize) -> Vec<ExtDep> {
    let (by_nv, versions) = parse_lock(lock_toml);
    // Seed the frontier with every version of each named root package.
    let mut queue: VecDeque<((String, String), usize)> = VecDeque::new();
    let mut visited: BTreeSet<(String, String)> = BTreeSet::new();
    for r in roots {
        if let Some(vs) = versions.get(*r) {
            for v in vs {
                let key = ((*r).to_string(), v.clone());
                if visited.insert(key.clone()) {
                    queue.push_back((key, 0));
                }
            }
        }
    }
    // Shallowest sighting of each external crate name wins (BFS).
    let mut found: BTreeMap<String, ExtDep> = BTreeMap::new();
    while let Some((key, depth)) = queue.pop_front() {
        let Some(pkg) = by_nv.get(&key) else { continue };
        // Record an external (source-carrying, non-root) package the first time we
        // reach it.
        if depth > 0 && pkg.source.is_some() {
            found
                .entry(key.0.clone())
                .and_modify(|e| {
                    if depth < e.depth {
                        e.depth = depth;
                    }
                })
                .or_insert(ExtDep { name: key.0.clone(), version: key.1.clone(), depth });
        }
        if max_depth != 0 && depth >= max_depth {
            continue;
        }
        for dep in &pkg.deps {
            if let Some(next) = resolve_ref(dep, &versions) {
                if visited.insert(next.clone()) {
                    queue.push_back((next, depth + 1));
                }
            }
        }
    }
    let mut out: Vec<ExtDep> = found.into_values().collect();
    // Order: shallowest first, then alphabetical β€” stable for the render + tests.
    out.sort_by(|a, b| a.depth.cmp(&b.depth).then_with(|| a.name.cmp(&b.name)));
    out
}

/// The names of every **workspace member** in a `Cargo.lock` β€” every
/// `[[package]]` with **no** `source` (path/workspace deps). Used to seed the
/// organic body from nornir's own lockfile when there is no live warehouse
/// snapshot yet, so the view renders REAL crates instead of a demo.
pub fn workspace_members(lock_toml: &str) -> Vec<String> {
    let (by_nv, _) = parse_lock(lock_toml);
    let mut out: BTreeSet<String> = BTreeSet::new();
    for ((name, _), pkg) in &by_nv {
        if pkg.source.is_none() {
            out.insert(name.clone());
        }
    }
    out.into_iter().collect()
}

/// Synthesize cross-member dependency edges from a `Cargo.lock`: for every
/// workspace member, the other workspace members it directly depends on. Lets the
/// bloodstream draw real arteries between organs even before a warehouse
/// `dep_graph_edges` snapshot exists.
pub fn member_edges(lock_toml: &str) -> Vec<CrossRepoEdge> {
    let (by_nv, versions) = parse_lock(lock_toml);
    let members: BTreeSet<String> = by_nv
        .iter()
        .filter(|(_, p)| p.source.is_none())
        .map(|((n, _), _)| n.clone())
        .collect();
    let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
    let mut out: Vec<CrossRepoEdge> = Vec::new();
    for ((name, _), pkg) in &by_nv {
        if pkg.source.is_some() || !members.contains(name) {
            continue;
        }
        for dep in &pkg.deps {
            if let Some((dn, _)) = resolve_ref(dep, &versions) {
                if dn != *name && members.contains(&dn) && seen.insert((name.clone(), dn.clone())) {
                    out.push(CrossRepoEdge::normal(
                        name.clone(),
                        dn.clone(),
                        BTreeSet::from([dn.clone()]),
                    ));
                }
            }
        }
    }
    out.sort_by(|a, b| a.from.cmp(&b.from).then_with(|| a.to.cmp(&b.to)));
    out
}

/// The anatomical slot an organ occupies β€” a fixed body position in normalised
/// coordinates (x∈[-1,1] leftβ†’right, y∈[-1,1] bottomβ†’top) plus a human label.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BodySlot {
    pub label: &'static str,
    xi: i32,
    yi: i32,
}

impl BodySlot {
    pub fn x(self) -> f32 {
        self.xi as f32 / 1000.0
    }
    pub fn y(self) -> f32 {
        self.yi as f32 / 1000.0
    }
}

/// The anatomy: the ordered body positions crates are seated into. The busiest
/// (most-depended-on) crate becomes the **heart** at the centre; the rest fill
/// outward through the vital organs and down the limbs. Beyond the table, extra
/// crates spiral out around the torso so no crate is ever left unplaced.
pub const ANATOMY: &[BodySlot] = &[
    BodySlot { label: "heart", xi: 0, yi: 150 },
    BodySlot { label: "brain", xi: 0, yi: 850 },
    BodySlot { label: "left lung", xi: -260, yi: 300 },
    BodySlot { label: "right lung", xi: 260, yi: 300 },
    BodySlot { label: "liver", xi: -180, yi: -20 },
    BodySlot { label: "stomach", xi: 190, yi: -30 },
    BodySlot { label: "left kidney", xi: -170, yi: -230 },
    BodySlot { label: "right kidney", xi: 170, yi: -230 },
    BodySlot { label: "spleen", xi: 60, yi: 60 },
    BodySlot { label: "gut", xi: 0, yi: -330 },
    BodySlot { label: "left shoulder", xi: -470, yi: 470 },
    BodySlot { label: "right shoulder", xi: 470, yi: 470 },
    BodySlot { label: "left hand", xi: -640, yi: 90 },
    BodySlot { label: "right hand", xi: 640, yi: 90 },
    BodySlot { label: "left knee", xi: -230, yi: -640 },
    BodySlot { label: "right knee", xi: 230, yi: -640 },
    BodySlot { label: "left foot", xi: -300, yi: -900 },
    BodySlot { label: "right foot", xi: 300, yi: -900 },
];

/// A crate seated in the body β€” one organ of the circulatory system.
#[derive(Debug, Clone)]
pub struct Organ {
    /// The crate / workspace-member name.
    pub crate_name: String,
    /// Anatomical label of the slot it occupies (`heart`, `left lung`, …).
    pub organ: String,
    /// Normalised body position (x∈[-1,1], y∈[-1,1]).
    pub x: f32,
    pub y: f32,
    /// How many other members depend on this one β€” drives the organ's size/pulse.
    pub in_degree: usize,
    /// Whether the operator has drilled into this organ (capillaries loaded).
    pub expanded: bool,
    /// The lazily-loaded external transitive closure (empty until expanded).
    pub external: Vec<ExtDep>,
}

impl Organ {
    /// The deepest recursion ring currently loaded (0 when not expanded / a leaf).
    pub fn max_depth(&self) -> usize {
        self.external.iter().map(|e| e.depth).max().unwrap_or(0)
    }
}

/// A dependency edge drawn as an artery/vein between two organs.
#[derive(Debug, Clone)]
pub struct Vessel {
    pub from: String,
    pub to: String,
    /// The crate(s) justifying the edge (the "blood type" carried).
    pub via: Vec<String>,
}

/// The whole workspace as one organism β€” organs (crates) wired by vessels
/// (dependency edges), plus the animated blood `flow_phase` and the drill-down
/// state. The pure model the renderer paints and `state_json` reports.
#[derive(Debug, Clone, Default)]
pub struct Organism {
    pub organs: Vec<Organ>,
    pub vessels: Vec<Vessel>,
    /// Blood-flow phase in [0,1) β€” a pure function of the injected clock, wrapped.
    pub flow_phase: f32,
    /// `true` once built from REAL data (live snapshot or nornir's own lockfile),
    /// as opposed to an empty organism.
    pub from_data: bool,
    /// The selected organ (crate name), if any β€” its subtree is lit.
    pub selected: Option<String>,
}

impl Organism {
    /// Seat a workspace's crates + edges into the body. The most-depended-on crate
    /// takes the heart; the rest fill [`ANATOMY`] in descending in-degree order,
    /// spiralling around the torso once the named slots run out.
    pub fn build(crates: &[String], edges: &[CrossRepoEdge]) -> Self {
        // in-degree = how many members depend on this crate (a hub β†’ a big organ).
        let mut in_deg: BTreeMap<&str, usize> = crates.iter().map(|c| (c.as_str(), 0)).collect();
        for e in edges {
            if let Some(d) = in_deg.get_mut(e.to.as_str()) {
                *d += 1;
            }
        }
        // Seat by descending in-degree, then name β€” deterministic + hub-at-heart.
        let mut ordered: Vec<&String> = crates.iter().collect();
        ordered.sort_by(|a, b| {
            in_deg
                .get(b.as_str())
                .cmp(&in_deg.get(a.as_str()))
                .then_with(|| a.cmp(b))
        });
        let organs: Vec<Organ> = ordered
            .into_iter()
            .enumerate()
            .map(|(i, name)| {
                let (organ, x, y) = slot_position(i);
                Organ {
                    crate_name: name.clone(),
                    organ,
                    x,
                    y,
                    in_degree: in_deg.get(name.as_str()).copied().unwrap_or(0),
                    expanded: false,
                    external: Vec::new(),
                }
            })
            .collect();
        let present: BTreeSet<&str> = crates.iter().map(String::as_str).collect();
        let vessels: Vec<Vessel> = edges
            .iter()
            .filter(|e| present.contains(e.from.as_str()) && present.contains(e.to.as_str()))
            .map(|e| Vessel {
                from: e.from.clone(),
                to: e.to.clone(),
                via: e.via.iter().cloned().collect(),
            })
            .collect();
        // EMIT-DOCTRINE (Β§selftest): bloodstream's egui-free DATA CORE β€” ok ⇔ the
        // body was seated from REAL workspace crates (organs present); an empty
        // organism (no members) is the RED no-data state the render falls back on.
        #[cfg(feature = "testmatrix")]
        crate::selftest::emit(
            "viz/bloodstream (data core)",
            "organism_seated",
            !organs.is_empty(),
            &format!("{} organ(s), {} vessel(s)", organs.len(), vessels.len()),
        );
        Self {
            organs,
            vessels,
            flow_phase: 0.0,
            from_data: !crates.is_empty(),
            selected: None,
        }
    }

    /// Advance the blood by an injected clock reading (seconds). Pure: the phase is
    /// `fract(seconds Β· RATE)`, so identical clock β‡’ identical frame (FC-7).
    pub fn set_clock(&mut self, seconds: f64) {
        const RATE: f64 = 0.35; // full artery traversals per second
        self.flow_phase = (seconds * RATE).rem_euclid(1.0) as f32;
    }

    /// Look up an organ by crate name.
    pub fn organ(&self, crate_name: &str) -> Option<&Organ> {
        self.organs.iter().find(|o| o.crate_name == crate_name)
    }

    /// **Drill in / out of an organ.** On expand, resolve the crate's external
    /// transitive closure from `lock_toml` ([`external_closure`]) and hang it off
    /// the organ as capillaries; on collapse, drop them. Returns the number of
    /// external deps now attached (0 if the organ is unknown or a true leaf).
    /// Lazy: the closure walk runs only on the expand transition.
    pub fn toggle_expand(&mut self, crate_name: &str, lock_toml: &str, max_depth: usize) -> usize {
        let Some(idx) = self.organs.iter().position(|o| o.crate_name == crate_name) else {
            return 0;
        };
        if self.organs[idx].expanded {
            self.organs[idx].expanded = false;
            self.organs[idx].external.clear();
            return 0;
        }
        let ext = external_closure(lock_toml, &[crate_name], max_depth);
        let n = ext.len();
        self.organs[idx].external = ext;
        self.organs[idx].expanded = true;
        n
    }

    /// Total capillaries loaded across every expanded organ.
    pub fn external_count(&self) -> usize {
        self.organs.iter().map(|o| o.external.len()).sum()
    }

    /// The introspection blob folded into the viz `state_json` (LAW #6): the whole
    /// drawn organism as DATA β€” organs (position, organ label, in-degree, expand
    /// state + the loaded external ring counts), vessels, the flow phase, and the
    /// selection β€” so a robot test reads exactly what the body paints (incl. the
    /// recursive external layer after a drill-down).
    pub fn state_json(&self) -> serde_json::Value {
        serde_json::json!({
            "from_data": self.from_data,
            "flow_phase": self.flow_phase,
            "selected": self.selected,
            "organ_count": self.organs.len(),
            "vessel_count": self.vessels.len(),
            "external_count": self.external_count(),
            "organs": self.organs.iter().map(|o| serde_json::json!({
                "crate": o.crate_name,
                "organ": o.organ,
                "x": o.x,
                "y": o.y,
                "in_degree": o.in_degree,
                "expanded": o.expanded,
                "external_count": o.external.len(),
                "external_max_depth": o.max_depth(),
                "external": o.external.iter().map(|e| serde_json::json!({
                    "name": e.name,
                    "version": e.version,
                    "depth": e.depth,
                })).collect::<Vec<_>>(),
            })).collect::<Vec<_>>(),
            "vessels": self.vessels.iter().map(|v| serde_json::json!({
                "from": v.from,
                "to": v.to,
                "via": v.via,
            })).collect::<Vec<_>>(),
        })
    }
}

/// The body position for the `i`-th seated crate: the named [`ANATOMY`] slots
/// first, then a deterministic spiral around the torso for any overflow so a
/// large workspace still lays out without collisions.
fn slot_position(i: usize) -> (String, f32, f32) {
    if let Some(s) = ANATOMY.get(i) {
        return (s.label.to_string(), s.x(), s.y());
    }
    // Overflow: golden-angle spiral around the torso centre.
    let k = (i - ANATOMY.len()) as f32;
    let angle = k * 2.399_963_2; // golden angle (radians)
    let r = 0.30 + 0.055 * k.sqrt();
    (
        format!("node {i}"),
        (r * angle.cos()).clamp(-0.98, 0.98),
        (r * angle.sin()).clamp(-0.98, 0.98),
    )
}

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

    /// A tiny synthetic lockfile: workspace member `app` (no source) β†’ external
    /// `alpha` β†’ external `beta`; plus an unrelated external `gamma`.
    const SYNTH_LOCK: &str = r#"
version = 3

[[package]]
name = "app"
version = "0.1.0"
dependencies = [
 "alpha",
 "sibling",
]

[[package]]
name = "sibling"
version = "0.1.0"
dependencies = []

[[package]]
name = "alpha"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaaa"
dependencies = [
 "beta 2.0.0",
]

[[package]]
name = "beta"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb"
dependencies = []

[[package]]
name = "gamma"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccc"
dependencies = []
"#;

    #[test]
    fn external_closure_is_recursive_and_excludes_workspace_members() {
        let ext = external_closure(SYNTH_LOCK, &["app"], 0);
        let names: Vec<&str> = ext.iter().map(|e| e.name.as_str()).collect();
        // alpha (direct) + beta (deps-of-deps) β€” the recursion reached depth 2.
        assert!(names.contains(&"alpha"), "direct external dep present: {names:?}");
        assert!(names.contains(&"beta"), "TRANSITIVE deps-of-deps reached: {names:?}");
        // `sibling` is a workspace member (no source) β†’ NOT an external dep.
        assert!(!names.contains(&"sibling"), "workspace member excluded: {names:?}");
        // `gamma` is external but unreachable from `app` β†’ not in the closure.
        assert!(!names.contains(&"gamma"), "unreachable crate excluded: {names:?}");
        // depths: alpha=1, beta=2 (genuinely recursive).
        assert_eq!(ext.iter().find(|e| e.name == "alpha").unwrap().depth, 1);
        assert_eq!(ext.iter().find(|e| e.name == "beta").unwrap().depth, 2);
        assert_eq!(ext.iter().map(|e| e.depth).max(), Some(2), "recursion went β‰₯2 deep");
    }

    #[test]
    fn max_depth_bounds_the_recursion() {
        let shallow = external_closure(SYNTH_LOCK, &["app"], 1);
        let names: Vec<&str> = shallow.iter().map(|e| e.name.as_str()).collect();
        assert!(names.contains(&"alpha"), "depth-1 dep kept");
        assert!(!names.contains(&"beta"), "depth-2 dep cut by max_depth=1: {names:?}");
    }

    #[test]
    fn workspace_members_and_member_edges_from_lock() {
        let mut members = workspace_members(SYNTH_LOCK);
        members.sort();
        assert_eq!(members, vec!["app".to_string(), "sibling".to_string()]);
        let edges = member_edges(SYNTH_LOCK);
        // app β†’ sibling is the only intra-member edge (alpha is external).
        assert_eq!(edges.len(), 1);
        assert_eq!((edges[0].from.as_str(), edges[0].to.as_str()), ("app", "sibling"));
    }

    #[test]
    fn build_seats_the_hub_at_the_heart() {
        // b is depended on by a and c β†’ highest in-degree β†’ heart (0,0.15).
        let crates = vec!["a".to_string(), "b".to_string(), "c".to_string()];
        let edges = vec![
            CrossRepoEdge::normal("a", "b", Default::default()),
            CrossRepoEdge::normal("c", "b", Default::default()),
        ];
        let org = Organism::build(&crates, &edges);
        assert!(org.from_data);
        assert_eq!(org.organs.len(), 3);
        assert_eq!(org.vessels.len(), 2);
        let heart = org.organ("b").unwrap();
        assert_eq!(heart.organ, "heart", "the most-depended-on crate is the heart");
        assert_eq!(heart.in_degree, 2);
    }

    #[test]
    fn toggle_expand_loads_then_drops_the_capillaries() {
        let crates = vec!["app".to_string()];
        let mut org = Organism::build(&crates, &[]);
        assert_eq!(org.external_count(), 0, "lazy: nothing loaded before expand");
        let n = org.toggle_expand("app", SYNTH_LOCK, 0);
        assert!(n >= 2, "expand loaded the recursive external layer: {n}");
        assert!(org.organ("app").unwrap().expanded);
        assert!(org.organ("app").unwrap().max_depth() >= 2, "capillaries reach β‰₯2 deep");
        // state_json carries the loaded external layer.
        let j = org.state_json();
        assert_eq!(j["external_count"], serde_json::json!(n));
        // collapse drops them.
        let n2 = org.toggle_expand("app", SYNTH_LOCK, 0);
        assert_eq!(n2, 0);
        assert_eq!(org.external_count(), 0, "collapse dropped the capillaries");
    }

    #[test]
    fn set_clock_is_pure_and_wraps() {
        let mut org = Organism::build(&["a".to_string()], &[]);
        org.set_clock(0.0);
        assert_eq!(org.flow_phase, 0.0);
        org.set_clock(1000.0);
        let a = org.flow_phase;
        org.set_clock(1000.0);
        assert_eq!(org.flow_phase, a, "identical clock β‡’ identical phase (FC-7)");
        assert!((0.0..1.0).contains(&org.flow_phase));
    }
}