src/viz/bloodstream.rs
//! 🩸 **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));
}
}