Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

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));
    }
}