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

nordisk/nornir

src/code_room/units.rs

//! **The build board**: every compile unit of a workspace read, merged across
//! its members, with a live state — what a Dwarves front page draws as a tree
//! (members → units) while a full read runs.
//!
//! * PLAN: per member, `cargo check --unit-graph` of its tree (the dumper runs
//!   it before each commit's compile; a new commit re-plans only that member).
//!   Units merge on ONE key — package (a path source named relative to the
//!   workspace's clones, so `../skade` from nornir and skade itself are one
//!   package), target, mode, features, profile, platform. Features are never
//!   unified: another feature set is another unit. `shared_by` = the members
//!   whose plan holds the unit.
//! * STATE, per unit: `queued` (its members' builds have not started),
//!   `waiting` (its member is compiling, a dependency is not done),
//!   `compiling` (its member is compiling, every dependency is done),
//!   `reading` (compiled this build, the release is not written yet), `done`,
//!   `failed`. Cargo's own messages drive it (`compiler-artifact`, `fresh`
//!   included; `build-script-executed`; error diagnostics).
//!
//! One board per workspace lake, in memory: a restart starts empty and the
//! next read fills it. Read it with `Coderoom.Units` ([`answer`]).

use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::Instant;

use serde_json::{Value, json};

/// A unit's live state.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum UnitState {
    Queued,
    Waiting,
    Compiling,
    Reading,
    Done,
    Failed,
}

impl UnitState {
    pub fn as_str(self) -> &'static str {
        match self {
            UnitState::Queued => "queued",
            UnitState::Waiting => "waiting",
            UnitState::Compiling => "compiling",
            UnitState::Reading => "reading",
            UnitState::Done => "done",
            UnitState::Failed => "failed",
        }
    }
}

/// Where a member's turn is.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Phase {
    Queued,
    Planning,
    Compiling,
    Reading,
    Landed,
    Failed,
}

impl Phase {
    pub fn as_str(self) -> &'static str {
        match self {
            Phase::Queued => "queued",
            Phase::Planning => "planning",
            Phase::Compiling => "compiling",
            Phase::Reading => "reading",
            Phase::Landed => "landed",
            Phase::Failed => "failed",
        }
    }
}

#[derive(Debug, Clone)]
struct Unit {
    key: String,
    package: String,
    target: String,
    mode: String,
    features: Vec<String>,
    test: bool,
    deps: BTreeSet<usize>,
    members: BTreeSet<String>,
    /// Compiled (or found fresh) — by which member's build, when it was this
    /// run's (`None` = before).
    done: Option<Option<String>>,
    failed: bool,
}

#[derive(Debug, Clone)]
struct MemberRow {
    phase: Phase,
    sha: String,
    since: Instant,
    units: BTreeSet<usize>,
    /// The rustflags its builds get, before and after the linker policy.
    flags: Option<mimir_ingest::RepoFlags>,
}

/// The board of one workspace.
#[derive(Debug, Default)]
pub struct Board {
    /// `file://<clones>/` of the workspace: path packages are named from here.
    clones: String,
    units: Vec<Unit>,
    by_key: HashMap<String, usize>,
    /// (package, target, mode, features) → units (a lib and its test harness
    /// share all four).
    by_base: HashMap<String, Vec<usize>>,
    members: BTreeMap<String, MemberRow>,
    /// (member, head) whose plan was asked for in this process ([`Board::try_plan`]).
    tried: BTreeSet<(String, String)>,
}

/// The process's boards, one per workspace lake (by its clones dir).
static BOARDS: Mutex<BTreeMap<PathBuf, Arc<Mutex<Board>>>> = Mutex::new(BTreeMap::new());

/// The board of the workspace whose consumer clones live in `clones`.
pub fn board(clones: &Path) -> Arc<Mutex<Board>> {
    let mut all = BOARDS.lock().unwrap_or_else(|e| e.into_inner());
    all.entry(clones.to_path_buf())
        .or_insert_with(|| Arc::new(Mutex::new(Board { clones: format!("file://{}/", clones.display()), ..Default::default() })))
        .clone()
}

impl Board {
    /// A package id with a path source under the clones named relative to
    /// them (`path+ws:skade/skade#0.7.0`): one name whichever member's tree
    /// points at it.
    fn package(&self, pkg_id: &str) -> String {
        pkg_id.replacen(&self.clones, "ws:", 1)
    }

    fn base(package: &str, target: &str, mode: &str, features: &[String]) -> String {
        format!("{package}|{target}|{mode}|{}", features.join(","))
    }

    /// A member's turn is coming (a full read queues every member first).
    pub fn queue(&mut self, member: &str) {
        let row = self.members.entry(member.to_string()).or_insert_with(|| MemberRow { phase: Phase::Queued, sha: String::new(), since: Instant::now(), units: BTreeSet::new(), flags: None });
        if row.phase != Phase::Queued {
            row.phase = Phase::Queued;
            row.since = Instant::now();
        }
    }

    pub fn phase(&mut self, member: &str, phase: Phase, sha: &str) {
        let row = self.members.entry(member.to_string()).or_insert_with(|| MemberRow { phase, sha: sha.to_string(), since: Instant::now(), units: BTreeSet::new(), flags: None });
        row.phase = phase;
        row.since = Instant::now();
        if !sha.is_empty() {
            row.sha = sha.to_string();
        }
        if phase == Phase::Landed {
            // the member's whole plan is in the log now: what it compiled,
            // and every unit cargo's messages did not name apart (two units
            // of one package, target and features under other dependencies)
            let mine: Vec<usize> = row.units.iter().copied().collect();
            for i in mine {
                let u = &mut self.units[i];
                match &u.done {
                    Some(Some(by)) if by == member => u.done = Some(None),
                    None if !u.failed => u.done = Some(None),
                    _ => {}
                }
            }
        }
    }

    /// Has `member` a plan on the board?
    pub fn has_plan(&self, member: &str) -> bool {
        self.members.get(member).is_some_and(|r| !r.units.is_empty())
    }

    /// A plan of `member` at `sha` is being asked for (whether cargo gives it or not).
    pub fn try_plan(&mut self, member: &str, sha: &str) {
        self.tried.insert((member.to_string(), sha.to_string()));
    }

    /// Was a plan of `member` at `sha` asked for in this process?
    pub fn tried(&self, member: &str, sha: &str) -> bool {
        self.tried.contains(&(member.to_string(), sha.to_string()))
    }

    /// `member` is caught up at `sha`: every unit of its plan is in the log
    /// (done, unless a build said it failed), its phase `landed`.
    pub fn settle(&mut self, member: &str, sha: &str) {
        if let Some(r) = self.members.get(member) {
            for &i in &r.units {
                let u = &mut self.units[i];
                if !u.failed && u.done.is_none() {
                    u.done = Some(None);
                }
            }
        }
        self.phase(member, Phase::Landed, sha);
    }

    /// Merge `member`'s unit graph (`cargo … --unit-graph` JSON) into the
    /// board: its units replace what the member planned before.
    pub fn plan(&mut self, member: &str, graph: &Value) {
        self.plan_with(member, graph, None);
    }

    /// [`Board::plan`] for a build that gets `flags` (they are in every
    /// unit's key: cargo hashes rustflags into each unit).
    pub fn plan_with(&mut self, member: &str, graph: &Value, flags: Option<&mimir_ingest::RepoFlags>) {
        let units = graph["units"].as_array().map(Vec::as_slice).unwrap_or(&[]);
        let flag_text = flags.map(|f| f.normalized.join(" ")).unwrap_or_default();
        // a lib and its test harness: same package, target, mode, features;
        // the harness is the one with more dependencies (the dev-deps)
        let mut base_of: Vec<String> = Vec::with_capacity(units.len());
        let mut groups: HashMap<String, Vec<usize>> = HashMap::new();
        for (i, u) in units.iter().enumerate() {
            let package = self.package(u["pkg_id"].as_str().unwrap_or(""));
            let target = format!("{}:{}", u["target"]["kind"].as_array().and_then(|k| k.first()).and_then(Value::as_str).unwrap_or("?"), u["target"]["name"].as_str().unwrap_or("?"));
            let mut features: Vec<String> = u["features"].as_array().map(|f| f.iter().filter_map(Value::as_str).map(str::to_string).collect()).unwrap_or_default();
            features.sort();
            let base = Self::base(&package, &target, u["mode"].as_str().unwrap_or(""), &features);
            groups.entry(base.clone()).or_default().push(i);
            base_of.push(base);
        }
        let ndeps = |i: usize| units[i]["dependencies"].as_array().map_or(0, Vec::len);
        let tests: Vec<bool> = (0..units.len())
            .map(|i| {
                let group = &groups[&base_of[i]];
                group.len() > 1 && group.iter().all(|&j| j == i || ndeps(j) < ndeps(i))
            })
            .collect();
        // a unit's OWN key — package, target, mode, features, harness, the
        // whole profile, platform, the build's flags — and its FULL key: its
        // own plus the full keys of its dependencies, as cargo's unit hash
        // mixes them in (two builds share a compiled unit only when both agree)
        let own: Vec<String> = units
            .iter()
            .enumerate()
            .map(|(i, u)| {
                let profile = serde_json::to_string(&u["profile"]).unwrap_or_default();
                let platform = u["platform"].as_str().unwrap_or("host");
                format!("{}|{}|{profile}|{platform}|{flag_text}", base_of[i], if tests[i] { "test" } else { "" })
            })
            .collect();
        let deps_of = |i: usize| -> Vec<usize> { units[i]["dependencies"].as_array().map(|d| d.iter().filter_map(|x| x["index"].as_u64().map(|j| j as usize)).filter(|&j| j < units.len()).collect()).unwrap_or_default() };
        let mut full: Vec<Option<String>> = vec![None; units.len()];
        for root in 0..units.len() {
            // iterative post-order: a unit's key once its dependencies' are known
            let mut stack = vec![(root, false)];
            while let Some((i, expanded)) = stack.pop() {
                if full[i].is_some() {
                    continue;
                }
                if !expanded {
                    stack.push((i, true));
                    for j in deps_of(i) {
                        if full[j].is_none() {
                            stack.push((j, false));
                        }
                    }
                    continue;
                }
                let mut dep_keys: Vec<&str> = deps_of(i).into_iter().filter_map(|j| full[j].as_deref()).collect();
                dep_keys.sort_unstable();
                // an in-process key: two 64-bit SipHashes (seeded apart) side by side
                let mut out = String::with_capacity(32);
                for seed in [0u8, 1] {
                    use std::hash::{Hash, Hasher};
                    let mut h = std::collections::hash_map::DefaultHasher::new();
                    seed.hash(&mut h);
                    own[i].hash(&mut h);
                    dep_keys.hash(&mut h);
                    out.push_str(&format!("{:016x}", h.finish()));
                }
                full[i] = Some(out);
            }
        }
        let mut local: Vec<usize> = Vec::with_capacity(units.len());
        for (i, _u) in units.iter().enumerate() {
            let test = tests[i];
            let key = format!("{}|{}", base_of[i], full[i].as_deref().unwrap_or(""));
            let idx = match self.by_key.get(&key) {
                Some(&k) => k,
                None => {
                    let mut parts = base_of[i].splitn(4, '|');
                    let (package, target, mode) = (parts.next().unwrap_or("").to_string(), parts.next().unwrap_or("").to_string(), parts.next().unwrap_or("").to_string());
                    let features = parts.next().filter(|f| !f.is_empty()).map(|f| f.split(',').map(str::to_string).collect()).unwrap_or_default();
                    let k = self.units.len();
                    self.units.push(Unit { key: key.clone(), package, target, mode, features, test, deps: BTreeSet::new(), members: BTreeSet::new(), done: None, failed: false });
                    self.by_key.insert(key, k);
                    self.by_base.entry(base_of[i].clone()).or_default().push(k);
                    k
                }
            };
            local.push(idx);
        }
        for (i, u) in units.iter().enumerate() {
            for d in u["dependencies"].as_array().map(Vec::as_slice).unwrap_or(&[]) {
                if let Some(j) = d["index"].as_u64().and_then(|j| local.get(j as usize)) {
                    self.units[local[i]].deps.insert(*j);
                }
            }
        }
        // the member's units are exactly this plan's
        let row = self.members.entry(member.to_string()).or_insert_with(|| MemberRow { phase: Phase::Planning, sha: String::new(), since: Instant::now(), units: BTreeSet::new(), flags: None });
        let old = std::mem::take(&mut row.units);
        for i in old {
            self.units[i].members.remove(member);
        }
        let row = self.members.get_mut(member).expect("inserted above");
        row.units = local.iter().copied().collect();
        if let Some(f) = flags {
            row.flags = Some(f.clone());
        }
        for &i in &local {
            self.units[i].members.insert(member.to_string());
        }
    }

    /// One line of cargo's JSON stream of `member`'s build.
    pub fn cargo_line(&mut self, member: &str, line: &str) {
        let Ok(msg) = serde_json::from_str::<Value>(line) else { return };
        let package = self.package(msg["package_id"].as_str().unwrap_or(""));
        let target = || format!("{}:{}", msg["target"]["kind"].as_array().and_then(|k| k.first()).and_then(Value::as_str).unwrap_or("?"), msg["target"]["name"].as_str().unwrap_or("?"));
        match msg["reason"].as_str() {
            Some("compiler-artifact") => {
                let target = target();
                let mut features: Vec<String> = msg["features"].as_array().map(|f| f.iter().filter_map(Value::as_str).map(str::to_string).collect()).unwrap_or_default();
                features.sort();
                let test = msg["profile"]["test"].as_bool() == Some(true);
                let fresh = msg["fresh"].as_bool() == Some(true);
                // a build script's compile is mode `build`; everything else of
                // a check is `check`
                let modes: &[&str] = if target.starts_with("custom-build:") { &["build"] } else { &["check", "build", "test"] };
                for mode in modes {
                    let base = Self::base(&package, &target, mode, &features);
                    let Some(cands) = self.by_base.get(&base) else { continue };
                    let mine = |i: &usize| self.units[*i].members.contains(member);
                    let pick = cands
                        .iter()
                        .copied()
                        .find(|i| mine(i) && self.units[*i].test == test && self.units[*i].done.is_none())
                        .or_else(|| cands.iter().copied().find(|i| mine(i) && self.units[*i].test == test))
                        .or_else(|| cands.iter().copied().find(|i| self.units[*i].test == test))
                        .or_else(|| cands.first().copied());
                    if let Some(i) = pick {
                        let u = &mut self.units[i];
                        u.failed = false;
                        if u.done.is_none() || !fresh {
                            u.done = Some(if fresh { None } else { Some(member.to_string()) });
                        }
                        break;
                    }
                }
            }
            Some("build-script-executed") => {
                for u in self.units.iter_mut().filter(|u| u.package == package && u.mode == "run-custom-build") {
                    u.done.get_or_insert(Some(member.to_string()));
                }
            }
            Some("compiler-message") if msg["message"]["level"].as_str() == Some("error") => {
                let target = target();
                for u in self.units.iter_mut().filter(|u| u.package == package && u.target == target && u.done.is_none()) {
                    u.failed = true;
                }
            }
            _ => {}
        }
    }

    fn state(&self, i: usize) -> UnitState {
        let u = &self.units[i];
        if u.failed {
            return UnitState::Failed;
        }
        match &u.done {
            Some(None) => return UnitState::Done,
            Some(Some(by)) => {
                return match self.members.get(by).map(|m| m.phase) {
                    Some(Phase::Landed) | None => UnitState::Done,
                    Some(Phase::Failed) => UnitState::Failed,
                    _ => UnitState::Reading,
                };
            }
            None => {}
        }
        let building = u.members.iter().any(|m| self.members.get(m).is_some_and(|r| r.phase == Phase::Compiling));
        if !building {
            return UnitState::Queued;
        }
        if u.deps.iter().all(|&d| self.units[d].done.is_some()) { UnitState::Compiling } else { UnitState::Waiting }
    }

    /// The planner's order: the members whose units most others share
    /// first (their builds compile the shared leaves), then by name.
    pub fn order(&self) -> Vec<String> {
        let mut m: Vec<(usize, &String)> = self.members.iter().map(|(name, r)| (self.shared_count(name).unwrap_or(0), name)).collect();
        m.sort_by(|a, b| b.0.cmp(&a.0).then(a.1.cmp(b.1)));
        m.into_iter().map(|(_, n)| n.clone()).collect()
    }

    /// How many of `member`'s planned units another member needs too
    /// (`None`: no plan for it).
    pub fn shared_count(&self, member: &str) -> Option<usize> {
        let r = self.members.get(member).filter(|r| !r.units.is_empty())?;
        Some(r.units.iter().filter(|&&i| self.units[i].members.len() > 1).count())
    }

    /// The PLAN in numbers: units summed over the members, unique by the full
    /// key, shared (needed by 2+ members), unique by package + target + mode
    /// + features alone (what the hand count gave), the top repeats, and
    /// every member's flags (configured → normalized).
    pub fn plan_json(&self) -> Value {
        let live: Vec<usize> = (0..self.units.len()).filter(|&i| !self.units[i].members.is_empty()).collect();
        let summed: usize = self.members.values().map(|r| r.units.len()).sum();
        let shared = live.iter().filter(|&&i| self.units[i].members.len() > 1).count();
        let semantic: BTreeSet<String> = live.iter().map(|&i| {
            let u = &self.units[i];
            format!("{}|{}|{}|{}|{}", u.package, u.target, u.mode, u.features.join(","), u.test)
        }).collect();
        let mut top: Vec<usize> = live.clone();
        top.sort_by(|a, b| self.units[*b].members.len().cmp(&self.units[*a].members.len()).then(self.units[*a].package.cmp(&self.units[*b].package)));
        let top_repeats: Vec<Value> = top
            .iter()
            .take(20)
            .map(|&i| {
                let u = &self.units[i];
                json!({ "package": u.package, "target": u.target, "mode": u.mode, "features": u.features, "shared_by": u.members.len() })
            })
            .collect();
        let members: Vec<Value> = self
            .members
            .iter()
            .map(|(m, r)| {
                let own = r.units.iter().filter(|&&i| self.units[i].members.len() == 1).count();
                json!({
                    "member": m, "units": r.units.len(), "own_units": own, "shared_units": r.units.len() - own,
                    "flags": r.flags.as_ref().map(|f| json!({ "configured": f.configured, "normalized": f.normalized, "changed": f.changed() })),
                })
            })
            .collect();
        json!({
            "summed_units": summed,
            "unique_units": live.len(),
            "shared_units": shared,
            "unique_by_package_features": semantic.len(),
            "repeat_compiles_saved": summed.saturating_sub(live.len()),
            "top_repeats": top_repeats,
            "members": members,
            "order": self.order(),
            "flags_changed": self.members.iter().filter(|(_, r)| r.flags.as_ref().is_some_and(mimir_ingest::RepoFlags::changed)).map(|(m, _)| m.clone()).collect::<Vec<_>>(),
        })
    }

    /// The board as JSON: members, units (all, or one member's), counts.
    pub fn to_json(&self, only: Option<&str>, with_units: bool) -> Value {
        let states: Vec<UnitState> = (0..self.units.len()).map(|i| self.state(i)).collect();
        let selected: Vec<usize> = match only {
            Some(m) => self.members.get(m).map(|r| r.units.iter().copied().collect()).unwrap_or_default(),
            None => (0..self.units.len()).filter(|&i| !self.units[i].members.is_empty()).collect(),
        };
        let mut counts: BTreeMap<&str, u64> = BTreeMap::new();
        for &i in &selected {
            *counts.entry(states[i].as_str()).or_default() += 1;
        }
        let members: Vec<Value> = self
            .members
            .iter()
            .filter(|(m, _)| only.is_none_or(|o| o == m.as_str()))
            .map(|(m, r)| {
                let done = r.units.iter().filter(|&&i| states[i] == UnitState::Done).count();
                let alone = r.units.iter().filter(|&&i| self.units[i].members.len() == 1).count();
                json!({ "member": m, "phase": r.phase.as_str(), "sha": r.sha, "for_ms": r.since.elapsed().as_millis() as u64, "units": r.units.len(), "done": done, "own_units": alone })
            })
            .collect();
        let summed: usize = self.members.values().map(|r| r.units.len()).sum();
        let mut out = json!({
            "members": members,
            "counts": counts,
            "distinct_units": selected.len(),
            "summed_units": summed,
            "state": if selected.is_empty() { "empty_fact" } else { "complete" },
        });
        if with_units {
            let pos: HashMap<usize, usize> = selected.iter().enumerate().map(|(p, &i)| (i, p)).collect();
            let units: Vec<Value> = selected
                .iter()
                .map(|&i| {
                    let u = &self.units[i];
                    json!({
                        "key": u.key, "package": u.package, "target": u.target, "mode": u.mode, "test": u.test, "features": u.features,
                        "members": u.members, "shared_by": u.members.len(), "state": states[i].as_str(),
                        "deps": u.deps.iter().filter_map(|d| pos.get(d)).collect::<Vec<_>>(),
                    })
                })
                .collect();
            out["units"] = Value::Array(units);
        }
        out
    }
}

/// `Coderoom.Units`: the build board of the workspace (`member` narrows to
/// one member; `summary: true` leaves the unit rows out).
pub fn answer(clones: &Path, p: &Value) -> Value {
    let b = board(clones);
    let b = b.lock().unwrap_or_else(|e| e.into_inner());
    let only = p.get("member").and_then(Value::as_str).filter(|m| !m.is_empty());
    let summary = p.get("summary").and_then(Value::as_bool).unwrap_or(false);
    let mut v = b.to_json(only, !summary);
    v["plan"] = b.plan_json();
    v["evidence"] = json!({
        "notes": [
            "one unit = package · target · mode · features · profile · platform, merged across the members (a path package named by its place under the workspace's clones); features are never unified: another feature set is another unit",
            "states from cargo's own messages during the consumer's builds; the board is this server process's — after a restart it fills again with the next read",
        ]
    });
    v
}

/// The ONE linker flag set every repo of a read is built with (owner
/// 2026-10-15: "normalize, but DON'T ignore clang"): clang driving mold, as
/// dwarves' own `.cargo/config.toml` says. `NORNIR_MIMIR_LINKER_FLAGS`
/// (space-separated) replaces it for a box with another linker.
pub const LINKER_FLAGS: &str = "-C linker=clang -C link-arg=--ld-path=/usr/bin/ld.mold";

/// The workspace read's flag policy ([`LINKER_FLAGS`] or its override).
pub fn flag_policy() -> mimir_ingest::FlagPolicy {
    let text = std::env::var("NORNIR_MIMIR_LINKER_FLAGS").ok().filter(|v| !v.trim().is_empty()).unwrap_or_else(|| LINKER_FLAGS.to_string());
    mimir_ingest::FlagPolicy { linker: text.split_whitespace().map(str::to_string).collect() }
}

/// The board's sink for one member's dumper ([`mimir_ingest::DumpProgress`]).
pub struct Sink {
    pub board: Arc<Mutex<Board>>,
    pub member: String,
}

impl mimir_ingest::DumpProgress for Sink {
    fn planned(&self, graph: &Value, flags: Option<&mimir_ingest::RepoFlags>) {
        self.board.lock().unwrap_or_else(|e| e.into_inner()).plan_with(&self.member, graph, flags);
    }
    fn phase(&self, phase: &str, sha: &str) {
        let p = match phase {
            "planning" => Phase::Planning,
            "compiling" => Phase::Compiling,
            "reading" => Phase::Reading,
            "landed" => Phase::Landed,
            "failed" => Phase::Failed,
            _ => Phase::Queued,
        };
        self.board.lock().unwrap_or_else(|e| e.into_inner()).phase(&self.member, p, sha);
    }
    fn cargo_line(&self, line: &str) {
        self.board.lock().unwrap_or_else(|e| e.into_inner()).cargo_line(&self.member, line);
    }
}

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

    fn unit(pkg: &str, kind: &str, name: &str, mode: &str, features: &[&str], deps: &[usize]) -> Value {
        json!({
            "pkg_id": pkg, "target": { "kind": [kind], "name": name }, "profile": { "name": "dev" }, "platform": null,
            "mode": mode, "features": features, "dependencies": deps.iter().map(|d| json!({ "index": d })).collect::<Vec<_>>(),
        })
    }

    fn artifact(pkg: &str, kind: &str, name: &str, features: &[&str], test: bool, fresh: bool) -> String {
        json!({ "reason": "compiler-artifact", "package_id": pkg, "target": { "kind": [kind], "name": name }, "profile": { "test": test }, "features": features, "fresh": fresh }).to_string()
    }

    const SERDE: &str = "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.0";

    /// Two members of one workspace: a dependency with the SAME features is
    /// one unit shared by both; another feature set is another unit; a path
    /// package is one package whichever member's tree names it. States move
    /// with cargo's messages: queued → waiting/compiling → reading → done.
    #[test]
    fn units_merge_on_their_key_and_their_state_follows_cargo() {
        let mut b = Board { clones: "file:///ws/clones/".into(), ..Default::default() };
        let skade = "path+file:///ws/clones/skade/skade#0.7.0";
        let nornir = "path+file:///ws/clones/nornir#0.6.0";
        // skade: serde (derive), skade lib, skade lib's test harness
        b.plan("skade", &json!({ "units": [
            unit(SERDE, "lib", "serde", "check", &["derive"], &[]),
            unit(skade, "lib", "skade", "check", &[], &[0]),
            unit(skade, "lib", "skade", "check", &[], &[0, 1]),
        ]}));
        // nornir: serde (derive, the same unit) + serde (std: another), skade lib, nornir lib
        b.plan("nornir", &json!({ "units": [
            unit(SERDE, "lib", "serde", "check", &["derive"], &[]),
            unit(SERDE, "lib", "serde", "check", &["std"], &[]),
            unit(skade, "lib", "skade", "check", &[], &[0]),
            unit(nornir, "lib", "nornir", "check", &[], &[1, 2]),
        ]}));
        let v = b.to_json(None, true);
        assert_eq!((v["distinct_units"].as_u64(), v["summed_units"].as_u64()), (Some(5), Some(7)));
        let row = |v: &Value, key_part: &str, test: bool| v["units"].as_array().unwrap().iter().find(|u| u["key"].as_str().unwrap().contains(key_part) && u["test"] == test).cloned().unwrap();
        assert_eq!(row(&v, "serde@1.0.0|lib:serde|check|derive", false)["shared_by"], 2, "one serde+derive for both");
        assert_eq!(row(&v, "ws:skade/skade#0.7.0|lib:skade", false)["shared_by"], 2, "the path package is one package");
        assert_eq!(row(&v, "ws:skade/skade#0.7.0|lib:skade", true)["shared_by"], 1, "the harness is skade's alone");
        assert_eq!(v["counts"]["queued"], 5, "nothing started");

        b.queue("nornir");
        b.phase("skade", Phase::Compiling, "aaa");
        let v = b.to_json(None, true);
        assert_eq!(row(&v, "serde|check|derive", false)["state"], "compiling", "no dependency: compiling");
        assert_eq!(row(&v, "ws:skade/skade#0.7.0|lib:skade", false)["state"], "waiting", "serde is not done");
        b.cargo_line("skade", &artifact(SERDE, "lib", "serde", &["derive"], false, false));
        let v = b.to_json(None, true);
        assert_eq!(row(&v, "serde|check|derive", false)["state"], "reading", "compiled, not yet in the log");
        assert_eq!(row(&v, "ws:skade/skade#0.7.0|lib:skade", false)["state"], "compiling");
        b.cargo_line("skade", &artifact(skade, "lib", "skade", &[], false, false));
        b.cargo_line("skade", &artifact(skade, "lib", "skade", &[], true, false));
        b.phase("skade", Phase::Landed, "aaa");
        let v = b.to_json(Some("skade"), true);
        assert_eq!(v["counts"]["done"], 3, "{v}");
        // nornir's build: serde+derive and skade are fresh (the shared target)
        b.phase("nornir", Phase::Compiling, "bbb");
        b.cargo_line("nornir", &artifact(SERDE, "lib", "serde", &["derive"], false, true));
        b.cargo_line("nornir", &artifact(skade, "lib", "skade", &[], false, true));
        b.cargo_line("nornir", &json!({ "reason": "compiler-message", "package_id": nornir, "target": { "kind": ["lib"], "name": "nornir" }, "message": { "level": "error" } }).to_string());
        let v = b.to_json(Some("nornir"), true);
        assert_eq!(row(&v, "serde|check|std", false)["state"], "compiling");
        assert_eq!(row(&v, "nornir#0.6.0|lib:nornir", false)["state"], "failed");
        assert_eq!(v["counts"]["done"], 2, "{v}");
        let m = &v["members"][0];
        assert_eq!((m["member"].as_str(), m["phase"].as_str(), m["units"].as_u64(), m["own_units"].as_u64()), (Some("nornir"), Some("compiling"), Some(4), Some(2)));
    }

    /// A new commit re-plans only its member: units it no longer has are no
    /// longer its own.
    #[test]
    fn a_new_plan_replaces_the_members_units() {
        let mut b = Board { clones: "file:///ws/clones/".into(), ..Default::default() };
        b.plan("a", &json!({ "units": [unit(SERDE, "lib", "serde", "check", &[], &[])] }));
        b.plan("a", &json!({ "units": [unit(SERDE, "lib", "serde", "check", &["std"], &[])] }));
        let v = b.to_json(None, true);
        assert_eq!(v["distinct_units"], 1, "{v}");
        assert!(v["units"][0]["key"].as_str().unwrap().contains("|std|"));
    }

    /// The board is a surface of the door: answered on the request (no room
    /// is opened), empty until a build ran in this process.
    #[test]
    fn the_board_is_asked_through_the_door() {
        let ws = crate::code_room::reader::fixture::fresh_shop();
        let reach = crate::code_room::Reach::room_only(&ws.handle);
        let v = crate::code_room::reader::door("Coderoom.Units", &reach, "{}");
        assert_eq!((v["surface"].as_str(), v["state"].as_str()), (Some("Coderoom.Units"), Some("empty_fact")), "{v}");
        assert!(v["members"].as_array().is_some_and(|m| m.is_empty()) && v["units"].as_array().is_some());
        let b = board(&ws.handle.layout().clones());
        b.lock().unwrap().plan("shop", &json!({ "units": [unit(SERDE, "lib", "serde", "check", &[], &[])] }));
        let v = crate::code_room::reader::door("Coderoom.Units", &reach, r#"{"summary":true}"#);
        assert_eq!((v["state"].as_str(), v["distinct_units"].as_u64(), v.get("units").is_none()), (Some("complete"), Some(1), true), "{v}");
        assert!(!crate::code_room::reader::is_queued("Coderoom.Units"), "the board is cheap: answered at once");
    }

    /// The PLANNER's key is cargo's: a unit is shared only when its own key
    /// AND its dependencies' keys agree — `log` with the same features under
    /// a `serde` with other features is another unit — and the build's flags
    /// are in it (a repo with its own linker flags shares nothing until they
    /// are normalized). The plan says summed / unique / shared, by package +
    /// features alone, the top repeats, and the order (most shared first).
    #[test]
    fn the_plan_merges_on_the_full_key_with_dependencies_and_flags() {
        let flags = |n: &[&str]| mimir_ingest::RepoFlags { configured: vec![], normalized: n.iter().map(|s| s.to_string()).collect() };
        let clang = flags(&["-C", "linker=clang"]);
        const LOG: &str = "registry+https://github.com/rust-lang/crates.io-index#log@0.4.0";
        let graph = |serde_features: &[&str], name: &str| json!({ "units": [
            unit(SERDE, "lib", "serde", "check", serde_features, &[]),
            unit(LOG, "lib", "log", "check", &[], &[0]),
            unit(&format!("path+file:///ws/clones/{name}#0.1.0"), "lib", name, "check", &[], &[1]),
        ]});
        let mut b = Board { clones: "file:///ws/clones/".into(), ..Default::default() };
        b.plan_with("a", &graph(&["std"], "a"), Some(&clang));
        b.plan_with("b", &graph(&["std"], "b"), Some(&clang));
        b.plan_with("c", &graph(&["std", "derive"], "c"), Some(&clang));
        b.plan_with("d", &graph(&["std"], "d"), Some(&flags(&["-C", "linker=gcc"])));
        let p = b.plan_json();
        // serde(std)+log shared by a and b; c's serde differs → its log differs
        // too (the cascade); d's flags differ → all of d's units are its own
        assert_eq!((p["summed_units"].as_u64(), p["unique_units"].as_u64(), p["shared_units"].as_u64()), (Some(12), Some(10), Some(2)), "{p}");
        assert_eq!(p["unique_by_package_features"].as_u64(), Some(7), "serde×2 features, log, the four member libs: {p}");
        assert_eq!(p["top_repeats"][0]["shared_by"], 2);
        assert_eq!(p["order"][0], "a", "a and b share the most; then by name");
        let a = p["members"].as_array().unwrap().iter().find(|m| m["member"] == "a").unwrap();
        assert_eq!((a["units"].as_u64(), a["shared_units"].as_u64()), (Some(3), Some(2)));
        assert!(a["flags"]["changed"].as_bool().unwrap(), "{a}");
        assert_eq!(p["flags_changed"].as_array().unwrap().len(), 4);
    }

    /// A member that is CAUGHT UP is on the board with its whole plan, every
    /// unit done — the tree shows all members, not only those read since the
    /// server started (live dwarf 2026-10-16: 13 of 21 repos showed 0/0).
    #[test]
    fn a_caught_up_member_is_planned_and_settled() {
        let mut b = Board { clones: "file:///ws/clones/".into(), ..Default::default() };
        assert!(!b.has_plan("a"));
        b.plan("a", &json!({ "units": [unit(SERDE, "lib", "serde", "check", &[], &[]), unit("path+file:///ws/clones/a#0.1.0", "lib", "a", "check", &[], &[0])] }));
        assert!(b.has_plan("a"));
        b.settle("a", "abc");
        let v = b.to_json(Some("a"), false);
        assert_eq!((v["counts"]["done"].as_u64(), v["members"][0]["phase"].as_str(), v["members"][0]["units"].as_u64(), v["members"][0]["done"].as_u64()), (Some(2), Some("landed"), Some(2), Some(2)), "{v}");
    }

    /// A member that LANDED has every unit of its plan in the log — also a
    /// unit cargo's messages never named apart (live dwarf 2026-10-16: nornir
    /// landed with 103 units still queued/waiting: two units of one package,
    /// target and features — another dependency set — and the one artifact
    /// line marks only one of them).
    #[test]
    fn a_landed_member_has_every_unit_done() {
        let mut b = Board { clones: "file:///ws/clones/".into(), ..Default::default() };
        const LOG: &str = "registry+https://github.com/rust-lang/crates.io-index#log@0.4.0";
        b.plan("n", &json!({ "units": [
            unit(LOG, "lib", "log", "check", &[], &[]),
            unit(SERDE, "lib", "serde", "check", &[], &[]),
            unit(SERDE, "lib", "serde", "check", &[], &[0]),
        ]}));
        b.phase("n", Phase::Compiling, "s1");
        b.cargo_line("n", &artifact(LOG, "lib", "log", &[], false, false));
        b.cargo_line("n", &artifact(SERDE, "lib", "serde", &[], false, false));
        b.phase("n", Phase::Landed, "s1");
        let v = b.to_json(Some("n"), false);
        assert_eq!((v["counts"]["done"].as_u64(), v["members"][0]["done"].as_u64()), (Some(3), Some(3)), "{v}");
        // a unit a build said FAILED stays failed
        b.phase("n", Phase::Compiling, "s2");
        b.cargo_line("n", &json!({ "reason": "compiler-message", "package_id": LOG, "target": { "kind": ["lib"], "name": "log" }, "message": { "level": "error" } }).to_string());
        b.phase("n", Phase::Landed, "s2");
        let v = b.to_json(Some("n"), false);
        assert_eq!(v["counts"]["done"].as_u64(), Some(3), "a failure after done does not undo the log: {v}");
    }

}