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

nordisk/nornir

crates/mimir-room/tests/room/api.rs

//! The reader API over the `shop` log (see `shop.rs`): attributes and spans,
//! name lookup, bounded and mask-seeded walks, paths, edge export, quotient
//! graphs, symbols, repeated keys, the bottleneck semiring, serialisable
//! evidence, the sync door and the typed "not consumed" error — every one
//! with a known answer.

use mimir_ingest::{EventLog, PayloadSpec, Specs};
use mimir_room::{AnswerState, AttrValue, EdgeSel, GroupBy, MaskSpec, NameMatch, NameQuery, Room, Scope, ScopeError, Span, Weights};
use mimir_schema::{EdgeKind, EffectiveVisibility, Level, Provenance};

use super::common::{open_log, serial};
use super::shop::*;

fn o(r: &Room, d: &str) -> u64 {
    r.ordinal(&m(d)).unwrap_or_else(|| panic!("{d} is not in the room"))
}

/// Readable paths of `ords`, in the order given.
fn paths(r: &Room, ords: &[u64]) -> Vec<String> {
    ords.iter().map(|x| r.display_path(*x)).collect()
}

fn sorted(mut v: Vec<String>) -> Vec<String> {
    v.sort();
    v
}

fn s(v: &[&str]) -> Vec<String> {
    v.iter().map(|x| x.to_string()).collect()
}

async fn current(log: &EventLog) -> Room {
    Room::at(log, &Scope::current()).await.unwrap()
}

/// ONE runtime and ONE `shop` log for every read-only test here: writing the
/// log (git commits + the consumer) is most of a test's wall clock, and no
/// test below writes to it. The log lives as long as the runtime it was
/// opened on.
static RT: std::sync::LazyLock<tokio::runtime::Runtime> =
    std::sync::LazyLock::new(|| tokio::runtime::Builder::new_multi_thread().worker_threads(2).enable_all().build().unwrap());
static SHOP_LOG: tokio::sync::OnceCell<(tempfile::TempDir, EventLog)> = tokio::sync::OnceCell::const_new();

async fn shared() -> &'static EventLog {
    &SHOP_LOG
        .get_or_init(|| async {
            let tmp = tempfile::tempdir().unwrap();
            let log = shop_log(tmp.path()).await;
            (tmp, log)
        })
        .await
        .1
}

/// Run a test body on the shared runtime, one at a time (the room cache is
/// per process).
fn on_shop<F: Future<Output = ()>>(body: impl FnOnce(&'static EventLog) -> F) {
    RT.block_on(async {
        let _g = serial().await;
        body(shared().await).await
    })
}

fn lines(v: &[Span<'_>]) -> Vec<(String, u32)> {
    v.iter().map(|s| (s.file.to_string(), s.start_line)).collect()
}

// ---------------------------------------------------------------- R1 + R2

#[test]
fn a_node_carries_its_payload_its_path_and_its_span() {
    on_shop(|log| async move {
    let r = current(log).await;

    let add = r.node(o(&r, ADD));
    assert_eq!((add.name, add.kind, add.level, add.krate, add.version), (Some("add"), Some("method"), Some(Level::Item), Some("shop"), Some("0.1.0")));
    assert_eq!(add.parent, Some(o(&r, IMPL)));
    assert_eq!((add.file, add.start_line, add.end_line), (Some("src/cart.rs"), Some(9), Some(14)));
    assert!(!add.is_test && add.defined && add.cfg.is_none());
    assert_eq!(r.display_path(add.ordinal), "shop::cart::Cart::add", "the impl block reads as its self type");
    assert_eq!(r.display_path(o(&r, IMPL)), "shop::cart::Cart");
    assert_eq!(r.display_path(o(&r, ADD_ITEM)), "shop::cart::Cart::add::item");
    assert_eq!(r.display_path(o(&r, CRATE)), "shop");

    let t = r.node(o(&r, ADDS_UP));
    assert!(t.is_test);
    assert_eq!(r.display_path(t.ordinal), "shop::cart::tests::adds_up");
    assert_eq!(r.node(o(&r, CHARGE)).cfg, Some("feature = \"stripe\""));
    assert_eq!(r.node(o(&r, ITEMS)).ty, Some("Vec<Item>"));
    assert_eq!(r.node(o(&r, ITEMS)).level, Some(Level::Part));

    // a reference stub: its module is not in the room, the moniker is read instead
    let send = r.node(r.ordinal(&send()).unwrap());
    assert!(send.stub_only && send.parent.is_none() && send.file.is_none(), "{send:?}");
    assert_eq!(r.display_path(send.ordinal), "stripe::Client::send");
    // a binary-only node has no name chain and no moniker of ours: itself
    assert_eq!(r.display_path(r.ordinal("extern write").unwrap()), "write");

    // by column name: the typed ones, and honestly nothing for a column no producer writes
    assert_eq!(r.attr(add.ordinal, "name"), Some(AttrValue::Str("add")));
    assert_eq!(r.attr(add.ordinal, "parent").and_then(|v| v.as_str().map(str::to_string)), Some(m(IMPL)));
    assert_eq!(r.attr(t.ordinal, "is_test"), Some(AttrValue::Bool(true)));
    // what the producer says about the API surface: nominal and effective visibility, docs
    assert_eq!((add.visibility, add.effective_visibility, add.has_doc, add.doc), (Some("pub"), Some(EffectiveVisibility::Direct), Some(false), None));
    let charge = r.node(o(&r, CHARGE));
    assert_eq!((charge.visibility, charge.effective_visibility), (Some("pub"), Some(EffectiveVisibility::Internal)), "`pub` in a private module is not API");
    let co = r.node(o(&r, CHECKOUT));
    assert_eq!((co.has_doc, co.doc), (Some(true), Some("Check a cart out and charge it.")));
    assert_eq!(r.node(o(&r, IMPL)).visibility, None, "an impl block has none");
    assert_eq!(r.attr(add.ordinal, "visibility"), Some(AttrValue::Str("pub")), "the same column, by name");
    assert!(!r.has_attr("arity") && r.attr(add.ordinal, "arity").is_none(), "a column no producer writes is honestly absent");
    // the attributes rustc has on the item: `#[ignore]` only means something on a test
    assert_eq!((r.attr(t.ordinal, "ignored"), r.attr(add.ordinal, "ignored")), (Some(AttrValue::Bool(false)), None));
    assert_eq!(r.attr(add.ordinal, "doc_hidden"), Some(AttrValue::Bool(false)));
    assert!(r.has_attr("cfg") && r.attr(add.ordinal, "cfg").is_none(), "the column exists, this node has no gate");

    assert_eq!(r.module_of(add.ordinal), Some(o(&r, CART)));
    assert_eq!(r.module_of(o(&r, CHECKOUT)), Some(o(&r, CRATE)), "an item at the root: the crate");
    assert_eq!(r.module_of(send.ordinal), None);
    assert_eq!(r.impl_self(o(&r, IMPL)), Some(o(&r, CART_T)));

    let st = r.stats();
    assert_eq!((st.tests, st.files, st.symbols, st.orphan_spans), (2, 4, 4, 0));
    assert_eq!(st.spans, 16 + 3 + 2, "lib 16 + harness 3 + web 2 node spans");
    assert_eq!(st.sites, 10 + 4 + 1, "lib 10 + harness 4 new (charge→audit is the lib's) + web 1");
    assert_eq!(st.extra_columns, s(&["visibility", "effective_visibility", "doc", "has_doc", "ignored", "doc_hidden", "link_name"]));
    assert!(st.unread_columns.is_empty(), "{st:?}");
    })
}

#[test]
fn spans_and_sites_follow_the_seq_and_never_touch_identity() {
    on_shop(|log| async move {
    let at = |n: i64| Scope::repo(SHOP, Some(n));
    let (r1, r2) = (Room::at(log, &at(1)).await.unwrap(), Room::at(log, &at(2)).await.unwrap());
    let sp = |r: &Room, d: &str| r.span(o(r, d)).map(|s| (s.repo.to_string(), s.file.to_string(), s.start_line, s.end_line));
    assert_eq!(sp(&r1, CHARGE), Some((SHOP.into(), "src/pay.rs".into(), 5, 20)));
    assert_eq!(sp(&r2, CHARGE), Some((SHOP.into(), "src/pay.rs".into(), 7, 22)), "seq 2 moved it");
    assert_eq!(sp(&r2, AUDIT), Some((SHOP.into(), "src/pay.rs".into(), 22, 30)));
    assert_eq!(r2.spans_of(o(&r2, CHARGE)).len(), 1, "the old position is closed, not kept beside the new");
    let d = r1.diff(&r2).unwrap().value;
    assert!(d.added_nodes.is_empty() && d.changed.is_empty() && d.edges.is_empty(), "moved code is spans only: {d:?}");

    use EdgeKind::*;
    let pay = |l: u32| ("src/pay.rs".to_string(), l);
    let lib = |l: u32| ("src/lib.rs".to_string(), l);
    assert_eq!(lines(&r1.sites(o(&r1, CHECKOUT), o(&r1, CHARGE), Calls)), vec![lib(14), lib(15), lib(18)]);
    assert_eq!(lines(&r1.sites(o(&r1, CHARGE), o(&r1, AUDIT), Calls)), vec![pay(11), pay(17)]);
    assert_eq!(lines(&r2.sites(o(&r2, CHARGE), o(&r2, AUDIT), Calls)), vec![pay(13), pay(19)]);
    assert_eq!(lines(&r1.sites(o(&r1, CHECKOUT), o(&r1, CART_T), UsesType)), vec![lib(11)]);
    assert!(r1.sites(o(&r1, CHECKOUT), o(&r1, CART_T), Calls).is_empty(), "sites are per kind");
    assert!(r1.sites(o(&r1, CRATE), o(&r1, CART), Contains).is_empty(), "CONTAINS has no positioned site");
    // the count column: positioned or not; two units carrying one edge do not add up
    assert_eq!(r1.site_count(o(&r1, CHECKOUT), o(&r1, CHARGE), Calls).unwrap(), Some(3));
    assert_eq!(r1.site_count(o(&r1, CHARGE), o(&r1, AUDIT), Calls).unwrap(), Some(2), "lib and lib+test both carry it with 2 sites: 2, not 4");
    assert_eq!(r1.site_count(o(&r1, CRATE), o(&r1, CART), Contains).unwrap(), Some(1));
    assert_eq!(r1.site_count(o(&r1, AUDIT), o(&r1, CHARGE), Calls).unwrap(), None, "no such edge");

    // file → nodes, in line order; the same path in two repos stays apart
    let r = current(log).await;
    assert_eq!(
        paths(&r, &r.nodes_in_file(None, "src/pay.rs")),
        s(&["shop::pay", "shop::pay::charge", "shop::pay::audit", "shop::pay::open", "shop::pay::charges_once", "shop::pay::libc_write"])
    );
    assert_eq!(paths(&r, &r.nodes_in_file(Some(WEB), "src/lib.rs")), s(&["web", "web::render"]));
    assert_eq!(paths(&r, &r.nodes_in_file(Some(SHOP), "src/lib.rs")), s(&["shop", "shop::checkout"]));
    assert_eq!(r.nodes_in_file(None, "src/lib.rs").len(), 4);
    assert!(r.nodes_in_file(None, "src/nope.rs").is_empty());
    let files: Vec<(String, String, u64)> = r.files().iter().map(|f| (f.repo.to_string(), f.path.to_string(), f.nodes)).collect();
    assert_eq!(
        files,
        vec![(SHOP.into(), "src/cart.rs".into(), 11), (SHOP.into(), "src/lib.rs".into(), 2), (SHOP.into(), "src/pay.rs".into(), 6), (WEB.into(), "src/lib.rs".into(), 2)]
    );
    let m = r.mask(&MaskSpec { files: vec!["src/lib.rs".into()], node_kinds: vec!["fn".into()], ..Default::default() }).unwrap();
    assert_eq!(sorted(paths(&r, m.value.ordinals())), s(&["shop::checkout", "web::render"]));
    })
}

/// What an UNCOMMITTED tree would change is the ingest's dry delta (nothing
/// is written); its touched nodes are the seeds of the dependents walk — the
/// release doctor's question, asked here of the log and the room directly.
#[test]
fn a_dry_delta_of_an_uncommitted_tree_seeds_the_dependents_walk() {
    on_shop(|log| async move {
        let r = current(log).await;
        // the tree as it was consumed: nothing would change
        let same = log.dry_delta(SHOP, &shop_tree("0.1.0", |_| true)).await.unwrap();
        assert!(same.is_semantically_empty() && same.spans.is_empty(), "{same:?}");
        // `checkout` is gone from the tree: web still calls it. The delta
        // speaks in STABLE ids; the walk resolves them to the room's
        // (versioned) nodes.
        let gone = m(CHECKOUT);
        let stable_checkout = mimir_schema::moniker::stable(&gone).into_owned();
        let d = log.dry_delta(SHOP, &shop_tree("0.1.0", |n| n.id != gone)).await.unwrap();
        assert_eq!((d.nodes.removed.clone(), d.nodes.added.len(), d.nodes.changed.len()), (vec![stable_checkout.clone()], 0, 0));
        let walk = r.dependents(&d.touched_nodes(), None).unwrap();
        assert_eq!(paths(&r, &walk.value.reached), s(&["web::render"]));
        // a version bump keeps every identity: the payloads re-emit as
        // CHANGED on the same stable ids. Only a COLLIDING key (`helper`,
        // whose identity hangs on its payload hash) moves between dup
        // identities.
        let bumped = log.dry_delta(SHOP, &shop_tree("0.2.0", |_| true)).await.unwrap();
        let dup_only = |v: &[String]| v.iter().all(|k| k.contains("\u{1f}#"));
        assert!(dup_only(&bumped.nodes.removed) && dup_only(&bumped.nodes.added), "{bumped:?}");
        assert!(bumped.nodes.changed.contains(&stable_checkout), "{bumped:?}");
        assert!(bumped.edges.removed.is_empty() && bumped.edges.added.is_empty(), "{bumped:?}");
        assert_eq!(log.offset(SHOP).await.unwrap().unwrap().seq, 2, "a dry delta moves no offset");
    })
}

/// A column a producer adds later (here `arity`) is readable by
/// name the moment the log's schema has it — no change in the room. That is
/// how `visibility`, the doc columns and `ignored` / `doc_hidden` arrived.
#[tokio::test(flavor = "current_thread")]
async fn a_column_added_by_a_producer_is_readable_by_name() {
    let _g = serial().await;
    let tmp = tempfile::tempdir().unwrap();
    let rows = extras_rows();
    let batch = nodes_with_extras(&rows);
    let mut specs = Specs::mimir().unwrap();
    specs.nodes = PayloadSpec::with_log_owned(batch.schema(), &["id"], &["repo", "valid_from_sha", "valid_to_sha", "file", "start_line", "end_line"]).unwrap();
    let wh = skade::open(tmp.path().join("lake")).await.unwrap();
    let log = EventLog::open(wh, specs).await.unwrap();
    let dump = tiny_dump(batch);
    let log = consume_dumps(log, tmp.path(), "v", vec![dump]).await;
    let r = current(&log).await;
    assert_eq!(r.stats().extra_columns, s(&["visibility", "effective_visibility", "doc", "has_doc", "ignored", "doc_hidden", "link_name", "arity"]));
    assert!(r.has_attr("ignored") && r.has_attr("arity"));
    assert!(r.attr_names().contains(&"arity"));
    let id = |d: &str| r.ordinal(&v(d)).unwrap();
    assert_eq!(r.attr(id("slow_test()."), "ignored"), Some(AttrValue::Bool(true)));
    assert_eq!(r.attr(id("api()."), "ignored"), None, "not a test: no such notion");
    assert_eq!(r.attr(id("api()."), "arity"), Some(AttrValue::Int(2)));
    assert_eq!(r.attr(id("inner()."), "arity"), Some(AttrValue::Int(0)), "0 is a value, not null");
    assert_eq!(r.attr(id("crate/"), "ignored"), None, "null for this node");
    assert_eq!(r.attr(id("api()."), "name"), Some(AttrValue::Str("api")));
}

/// mimir-schema 2 in the room: the API surface (visibility, docs) as masks,
/// cargo features as nodes, GATED_BY with its `negated` / `partial` flags,
/// and the items the configuration compiled OUT.
#[tokio::test(flavor = "current_thread")]
async fn the_api_surface_and_the_feature_gates() {
    let _g = serial().await;
    let tmp = tempfile::tempdir().unwrap();
    let log = consume_dumps(open_log(tmp.path()).await, tmp.path(), "v", vec![tiny_commit()]).await;
    let r = current(&log).await;
    use EdgeKind::*;
    let mask = |spec: MaskSpec| sorted(paths(&r, r.mask(&spec).unwrap().value.ordinals()));
    assert_eq!(mask(MaskSpec { public_api: Some(true), ..Default::default() }), s(&["v::api"]));
    assert_eq!(mask(MaskSpec { public_api: Some(false), ..Default::default() }), s(&["v::inner", "v::slow_test"]), "the stub and the feature have no effective visibility");
    assert_eq!(mask(MaskSpec { has_doc: Some(false), is_test: Some(false), ..Default::default() }), s(&["v::inner"]));
    assert_eq!(mask(MaskSpec::kinds(&["feature"])), s(&["v::fast"]));

    // what a feature gates: the item itself, a part of one, and — negated — what it switches OFF
    let fast = v_fast();
    let gated = r.dependents_by(&[fast.as_str()], &[GatedBy], Some(1), None).unwrap();
    assert_eq!(sorted(paths(&r, &gated.value.reached)), s(&["v::api", "v::inner", "v::legacy"]));
    assert!(gated.evidence.is_complete(), "{}", gated.evidence);
    let e = r.edges(&EdgeSel::kinds(&[GatedBy])).unwrap().value;
    let flags: Vec<(String, bool, bool)> = e.edges.iter().map(|x| (r.display_path(x.from), x.negated, x.partial)).collect();
    assert_eq!(sorted(flags.iter().map(|f| format!("{} negated={} partial={}", f.0, f.1, f.2)).collect()), s(&[
        "v::api negated=false partial=false",
        "v::inner negated=false partial=true",
        "v::legacy negated=true partial=false",
    ]));
    assert_eq!(lines(&r.sites(r.ordinal(&v("api().")).unwrap(), r.ordinal(&fast).unwrap(), GatedBy)), vec![("src/lib.rs".to_string(), 3)]);
    // an item rustc never saw: a stub with what the SOURCE says, never a resolved item
    let legacy = r.node(r.ordinal(&v_legacy()).unwrap());
    assert_eq!((legacy.kind, legacy.cfg, legacy.ty, legacy.visibility, legacy.effective_visibility), (Some("cfg_gated_out"), Some("not(feature = \"fast\")"), Some("fn"), Some("pub"), None));
    assert_eq!((legacy.file, legacy.start_line), (Some("src/lib.rs"), Some(16)));
    // the build's own selection
    let root = v("crate/");
    assert_eq!(paths(&r, &r.reach(&[root.as_str()], &[Enables], None, None).unwrap().value.reached), s(&["v::fast"]));
    // a kind the scope has no edge of names the two possible reasons
    let weak = r.reach(&[root.as_str()], &[EnablesWeak], None, None).unwrap();
    assert!(weak.evidence.missing_input("ENABLES_WEAK edges")[0].why.contains("mimir-schema 2"), "{}", weak.evidence);
}

// ---------------------------------------------------------------- R3

#[test]
fn names_paths_and_symbols_find_their_nodes() {
    on_shop(|log| async move {
    let r = current(log).await;
    let find = |q: NameQuery| sorted(paths(&r, &r.lookup(&q).unwrap().value));
    let q = |t: &str, mode| NameQuery::new(t).mode(mode);

    assert_eq!(find(q("open", NameMatch::Name)), s(&["shop::cart::open", "shop::pay::open"]));
    assert_eq!(find(q("pay::open", NameMatch::Path)), s(&["shop::pay::open"]));
    assert_eq!(find(q("Cart::add", NameMatch::Path)), s(&["shop::cart::Cart::add"]));
    assert_eq!(find(q("shop::cart::Cart::total", NameMatch::Path)), s(&["shop::cart::Cart::total"]));
    assert_eq!(find(q("open", NameMatch::Path)), s(&["shop::cart::open", "shop::pay::open"]), "one segment = the name");
    assert!(find(q("art::open", NameMatch::Path)).is_empty(), "a suffix matches on segment borders only");
    assert!(find(q("cart::add", NameMatch::Path)).is_empty(), "add is Cart's, not the module's");
    // a stub is found by the path its moniker spells
    assert_eq!(find(q("Client::send", NameMatch::Path)), s(&["stripe::Client::send"]));
    // auto: the moniker itself, a linker symbol, then the path
    let auto = |t: &str| {
        let a = r.lookup(&NameQuery::new(t)).unwrap();
        (paths(&r, &a.value), a.evidence.notes.iter().find(|n| n.starts_with("matched as")).cloned().unwrap_or_default())
    };
    assert_eq!(auto(&m(TOTAL)), (s(&["shop::cart::Cart::total"]), "matched as: moniker".into()));
    assert_eq!(auto("_Rcharge"), (s(&["shop::pay::charge"]), "matched as: linker symbol".into()));
    assert_eq!(auto("pay::audit"), (s(&["shop::pay::audit"]), "matched as: path suffix".into()));
    assert_eq!(find(q("shop::checkout", NameMatch::Symbol)), s(&["shop::checkout"]), "the demangled form");
    assert_eq!(find(q("_Rcharge_closure0", NameMatch::Symbol)), s(&["shop::pay::charge"]), "a closure's symbol is its item's");
    assert_eq!(find(q("CHARGE", NameMatch::Substring)), s(&["shop::pay::charge", "shop::pay::charges_once"]));
    // filters
    assert_eq!(find(q("open", NameMatch::Name).kinds(&["method"])), Vec::<String>::new());
    assert_eq!(find(NameQuery { defined_only: true, ..q("checkout", NameMatch::Name) }), s(&["shop::checkout"]));
    let web_only = r.mask(&MaskSpec { repos: vec![WEB.into()], ..Default::default() }).unwrap().value;
    assert!(find(NameQuery { within: Some(web_only), ..q("checkout", NameMatch::Name) }).is_empty(), "the definition is shop's row");
    let capped = r.lookup(&q("open", NameMatch::Name).limit(1)).unwrap();
    assert_eq!(capped.value.len(), 1);
    assert_eq!(capped.evidence.capped.map(|c| (c.shown, c.total)), Some((1, 2)));
    // nothing by that name: an empty FACT, said so
    let none = r.lookup(&q("nope", NameMatch::Name)).unwrap();
    assert!(none.value.is_empty());
    assert_eq!(none.evidence.state, AnswerState::EmptyFact, "{}", none.evidence);
    // a symbol asked of a room without a binary stream: the gap, not "no such symbol"
    let web = Room::at(log, &Scope::repo(WEB, None)).await.unwrap();
    let gap = web.lookup(&q("_Rcharge", NameMatch::Symbol)).unwrap();
    assert!(gap.value.is_empty() && !gap.evidence.missing_input("linked symbols").is_empty(), "{}", gap.evidence);
    assert_eq!(gap.evidence.state, AnswerState::EmptyGap);
    })
}

// ---------------------------------------------------------------- R4 + R5 + R6

#[test]
fn bounded_and_mask_seeded_walks_and_hop_layers() {
    on_shop(|log| async move {
    let r = current(log).await;
    use EdgeKind::*;
    let audit = m(AUDIT);
    // direct callers only
    let one = r.dependents_by(&[audit.as_str()], &[Calls], Some(1), None).unwrap();
    assert_eq!(paths(&r, &one.value.reached), s(&["shop::pay::charge"]));
    let two = r.dependents_by(&[audit.as_str()], &[Calls], Some(2), None).unwrap();
    assert_eq!(sorted(paths(&r, &two.value.reached)), s(&["shop::checkout", "shop::pay::charge", "shop::pay::charges_once"]));
    let all = r.dependents_by(&[audit.as_str()], &[Calls], None, None).unwrap();
    assert_eq!(sorted(paths(&r, &all.value.reached)), s(&["shop::checkout", "shop::pay::charge", "shop::pay::charges_once", "web::render"]), "across repos");

    // seeds as a mask: everything the tests reach
    let tests = r.mask(&MaskSpec { is_test: Some(true), ..Default::default() }).unwrap().value;
    assert_eq!(sorted(paths(&r, tests.ordinals())), s(&["shop::cart::tests::adds_up", "shop::pay::charges_once"]));
    let reached = r.reach(&tests, &[Calls], None, None).unwrap();
    assert_eq!(
        sorted(paths(&r, &reached.value.reached)),
        s(&["shop::cart::Cart::add", "shop::cart::Cart::total", "shop::cart::open", "shop::pay::audit", "shop::pay::charge", "shop::pay::libc_write", "stripe::Client::send"])
    );
    assert_eq!(reached.value.seeds.len(), 2);
    // seeds as ordinals: the same walk as by moniker
    let by_ord = r.reach(&[o(&r, CHECKOUT)], &[Calls], Some(1), None).unwrap();
    let checkout = m(CHECKOUT);
    let by_name = r.reach(&[checkout.as_str()], &[Calls], Some(1), None).unwrap();
    assert_eq!(by_ord.value, by_name.value);
    assert_eq!(sorted(paths(&r, &by_ord.value.reached)), s(&["shop::cart::Cart::total", "shop::pay::charge"]));
    // an ordinal of another room is said, not silently dropped
    let far = r.reach(&[9_999u64], &[Calls], None, None).unwrap();
    assert!(far.value.seeds.is_empty() && !far.evidence.missing_input("seed").is_empty(), "{}", far.evidence);
    // mask set algebra
    let fns = r.mask(&MaskSpec::kinds(&["fn"])).unwrap().value;
    assert_eq!(fns.intersect(&tests).len(), 2);
    assert_eq!(fns.minus(&tests).len(), fns.len() - 2);
    assert_eq!(tests.union(&r.mask_of(&[o(&r, CHECKOUT)])).len(), 3);
    let gated = r.mask(&MaskSpec { with_cfg: true, ..Default::default() }).unwrap().value;
    assert_eq!(sorted(paths(&r, gated.ordinals())), s(&["shop::cart::tests", "shop::pay::charge"]));
    // the undocumented API: nameable from outside (direct or re-exported), no doc comment
    let undoc = r.mask(&MaskSpec { public_api: Some(true), has_doc: Some(false), node_kinds: vec!["fn".into(), "method".into()], ..Default::default() }).unwrap().value;
    assert_eq!(sorted(paths(&r, undoc.ordinals())), s(&["shop::cart::Cart::add", "shop::cart::open"]), "charge is `pub` but internal; total and checkout are documented");
    let mods = r.mask(&MaskSpec { levels: vec![Level::Module], ..Default::default() }).unwrap().value;
    assert_eq!(sorted(paths(&r, mods.ordinals())), s(&["shop::cart", "shop::cart::tests", "shop::pay"]));

    // hop layers from an entry point
    let layers = r.hops_from(&[checkout.as_str()], &[Calls], None, None).unwrap();
    let got: Vec<(String, u64)> = layers.value.iter().map(|(n, h)| (r.display_path(*n), *h)).collect();
    let mut want = vec![
        ("shop::cart::Cart::total".to_string(), 1),
        ("shop::pay::charge".into(), 1),
        ("shop::cart::open".into(), 2),
        ("shop::pay::audit".into(), 2),
        ("stripe::Client::send".into(), 2),
        ("shop::pay::libc_write".into(), 3),
    ];
    let mut got_sorted = got.clone();
    got_sorted.sort_by(|a, b| a.1.cmp(&b.1).then(a.0.cmp(&b.0)));
    want.sort_by(|a, b| a.1.cmp(&b.1).then(a.0.cmp(&b.0)));
    assert_eq!(got_sorted, want);
    assert!(got.windows(2).all(|w| w[0].1 <= w[1].1), "by hops: {got:?}");
    let two = r.hops_from(&[checkout.as_str()], &[Calls], Some(1), None).unwrap();
    assert_eq!(two.value.len(), 2);
    })
}

#[test]
fn a_shortest_path_names_the_kind_of_every_hop() {
    on_shop(|log| async move {
    let r = current(log).await;
    use EdgeKind::*;
    let (render, write, checkout) = (render(), m(WRITE), m(CHECKOUT));
    let p = r.path(&[render.as_str()], &[write.as_str()], &[Calls], None, None).unwrap();
    let found = p.value.expect("render reaches libc_write");
    assert_eq!(paths(&r, &found.nodes), s(&["web::render", "shop::checkout", "shop::pay::charge", "shop::pay::audit", "shop::pay::libc_write"]));
    assert_eq!(found.kinds, vec![Calls; 4]);
    assert_eq!(found.hops(), 4);
    assert_eq!(p.evidence.state, AnswerState::Complete, "{}", p.evidence);

    // two kinds: each hop says which one it walked
    let imp = m(IMPL);
    let open = m(CART_OPEN);
    let mixed = r.path(&[imp.as_str()], &[open.as_str()], &[Calls, Contains], None, None).unwrap().value.unwrap();
    assert_eq!(paths(&r, &mixed.nodes), s(&["shop::cart::Cart", "shop::cart::Cart::total", "shop::cart::open"]));
    assert_eq!(mixed.kinds, vec![Contains, Calls]);
    // the SHORTEST: crate → pay → libc_write (2 CONTAINS), not the 3-hop call chain from checkout
    let root = m(CRATE);
    let short = r.path(&[root.as_str()], &[write.as_str()], &[Calls, Contains], None, None).unwrap().value.unwrap();
    assert_eq!(paths(&r, &short.nodes), s(&["shop", "shop::pay", "shop::pay::libc_write"]));
    assert_eq!(short.kinds, vec![Contains, Contains]);
    // sets on both ends: the nearest pair
    let tests = r.mask(&MaskSpec { is_test: Some(true), ..Default::default() }).unwrap().value;
    let ffi = r.mask(&MaskSpec::kinds(&["ffi"])).unwrap().value;
    let near = r.path(&tests, &ffi, &[Calls], None, None).unwrap().value.unwrap();
    assert_eq!(paths(&r, &near.nodes), s(&["shop::pay::charges_once", "shop::pay::charge", "shop::pay::audit", "shop::pay::libc_write"]));
    // a node in both sets: zero hops
    let zero = r.path(&[checkout.as_str()], &[checkout.as_str()], &[Calls], None, None).unwrap().value.unwrap();
    assert_eq!((zero.nodes.len(), zero.hops()), (1, 0));

    // no path: against the direction, past the hop bound, outside the mask
    let back = r.path(&[write.as_str()], &[checkout.as_str()], &[Calls], None, None).unwrap();
    assert!(back.value.is_none());
    assert_eq!(back.evidence.state, AnswerState::EmptyFact, "edges are there, the path is not: {}", back.evidence);
    assert!(r.path(&[render.as_str()], &[write.as_str()], &[Calls], Some(3), None).unwrap().value.is_none(), "4 hops needed");
    assert!(r.path(&[render.as_str()], &[write.as_str()], &[Calls], Some(4), None).unwrap().value.is_some());
    let shop_only = r.mask(&MaskSpec { crates: vec!["shop".into()], ..Default::default() }).unwrap().value;
    let masked = r.path(&[render.as_str()], &[write.as_str()], &[Calls], None, Some(&shop_only)).unwrap();
    assert!(masked.value.is_none() && !masked.evidence.missing_input("seed").is_empty(), "render lies outside the mask: {}", masked.evidence);
    // an end the room does not know: the gap is named
    let unknown = r.path(&["nope"], &[write.as_str()], &[Calls], None, None).unwrap();
    assert!(unknown.value.is_none());
    assert_eq!(unknown.evidence.state, AnswerState::EmptyGap);
    // a kind nobody produces
    let gated = r.path(&[render.as_str()], &[write.as_str()], &[GatedBy], None, None).unwrap();
    assert!(gated.value.is_none() && !gated.evidence.missing_input("GATED_BY edges").is_empty());
    })
}

// ---------------------------------------------------------------- R7 + R8

#[test]
fn edges_come_out_with_their_site_counts() {
    on_shop(|log| async move {
    let r = current(log).await;
    use EdgeKind::*;
    let all = r.edges(&EdgeSel::kinds(&[Calls])).unwrap();
    assert_eq!((all.value.total, all.value.edges.len()), (10, 10), "lib 6 + harness 3 + web 1 (charge→audit once)");
    assert_eq!(all.value.sites, 1 + 3 + 1 + 2 + 1 + 1 + 2 + 1 + 1 + 1);
    assert_eq!(all.evidence.kinds, vec![(Calls, 10)]);
    let row = |f: &str, t: &str| all.value.edges.iter().find(|e| e.from == o(&r, f) && e.to == o(&r, t)).map(|e| e.count);
    assert_eq!(row(CHECKOUT, CHARGE), Some(3));
    assert_eq!(row(CHARGE, AUDIT), Some(2));
    assert_eq!(row(ADDS_UP, ADD), Some(2));
    assert_eq!(row(TOTAL, CART_OPEN), Some(1));

    let top = r.edges(&EdgeSel { heaviest_first: true, ..EdgeSel::kinds(&[Calls]).cap(1) }).unwrap();
    assert_eq!(top.value.edges.len(), 1);
    assert_eq!((top.value.edges[0].from, top.value.edges[0].to, top.value.edges[0].count), (o(&r, CHECKOUT), o(&r, CHARGE), 3));
    assert_eq!((top.value.total, top.value.sites), (10, 14), "the cap cuts the list, not the totals");
    assert_eq!(top.evidence.capped.map(|c| (c.shown, c.total)), Some((1, 10)));

    let tests = r.mask(&MaskSpec { is_test: Some(true), ..Default::default() }).unwrap().value;
    let from_tests = r.edges(&EdgeSel { from: Some(&tests), ..EdgeSel::kinds(&[Calls, Tests]) }).unwrap();
    assert_eq!(from_tests.value.total, 6, "3 CALLS + 3 TESTS");
    let pay = r.mask(&MaskSpec { files: vec!["src/pay.rs".into()], ..Default::default() }).unwrap().value;
    let inside = r.edges(&EdgeSel::kinds(&[Calls]).inside(&pay)).unwrap();
    let got: Vec<(String, String, u32)> = inside.value.edges.iter().map(|e| (r.display_path(e.from), r.display_path(e.to), e.count)).collect();
    let mut got = got;
    got.sort();
    assert_eq!(
        got,
        vec![
            ("shop::pay::audit".into(), "shop::pay::libc_write".into(), 1),
            ("shop::pay::charge".into(), "shop::pay::audit".into(), 2),
            ("shop::pay::charges_once".into(), "shop::pay::charge".into(), 1)
        ]
    );
    let none = r.edges(&EdgeSel::kinds(&[TouchesTable])).unwrap();
    assert_eq!(none.value.total, 0);
    assert_eq!(none.evidence.state, AnswerState::EmptyGap, "no producer: {}", none.evidence);
    })
}

#[test]
fn the_quotient_graph_by_crate_module_repo_and_file() {
    on_shop(|log| async move {
    let r = current(log).await;
    use EdgeKind::*;
    let edges = |q: &mimir_room::Quotient| -> Vec<(String, String, u64, u64)> {
        q.edges.iter().map(|e| (q.groups[e.from].key.clone(), q.groups[e.to].key.clone(), e.edges, e.sites)).collect()
    };
    let group = |q: &mimir_room::Quotient, key: &str| q.groups.iter().find(|g| g.key == key).cloned().unwrap_or_else(|| panic!("no group {key}"));

    // ---- by crate
    let q = r.quotient(GroupBy::Crate, &[Calls], None).unwrap().value;
    assert_eq!(q.groups.iter().map(|g| g.key.as_str()).collect::<Vec<_>>(), vec!["extern@0", "shop@0.1.0", "stripe@2.0.0", "web@0.2.0"]);
    assert_eq!(edges(&q), vec![("shop@0.1.0".into(), "stripe@2.0.0".into(), 1, 1), ("web@0.2.0".into(), "shop@0.1.0".into(), 1, 1)]);
    let shop = group(&q, "shop@0.1.0");
    assert_eq!((shop.label.as_str(), shop.nodes, shop.internal, shop.fan_out, shop.fan_in, shop.files), ("shop", 19, 8, 1, 1, 3));
    assert_eq!(shop.node, Some(o(&r, CRATE)), "the crate group knows its crate node");
    assert_eq!(shop.node_kinds["fn"], 8);
    assert_eq!(shop.node_kinds["method"], 2);
    assert_eq!(q.ungrouped, 0);

    // ---- by module: the board's nodes
    let q = r.quotient(GroupBy::Module, &[Calls], None).unwrap();
    assert_eq!(
        q.value.groups.iter().map(|g| (g.key.as_str(), g.nodes)).collect::<Vec<_>>(),
        vec![("shop", 2), ("shop::cart", 9), ("shop::cart::tests", 2), ("shop::pay", 6), ("stripe", 1), ("web", 2)]
    );
    assert_eq!(
        edges(&q.value),
        vec![
            ("shop".into(), "shop::cart".into(), 1, 1),
            ("shop".into(), "shop::pay".into(), 1, 3),
            ("shop::cart::tests".into(), "shop::cart".into(), 2, 3),
            ("web".into(), "shop".into(), 1, 1),
        ]
    );
    let pay = group(&q.value, "shop::pay");
    assert_eq!((pay.label.as_str(), pay.internal, pay.fan_in, pay.fan_out, pay.node), ("pay", 3, 1, 0, Some(o(&r, PAY))));
    assert_eq!(q.value.ungrouped, 2, "the stripe stub's module is not in the room; `extern write` has no parent");
    assert!(q.evidence.notes.iter().any(|n| n.contains("2 node(s) belong to no group")), "{}", q.evidence);
    // several kinds at once, per kind inside an edge
    let q2 = r.quotient(GroupBy::Module, &[Calls, UsesType, Tests], None).unwrap().value;
    let e = q2.edges.iter().find(|e| q2.groups[e.from].key == "shop::cart::tests" && q2.groups[e.to].key == "shop::cart").unwrap();
    assert_eq!((e.edges, e.kinds.get(&Calls), e.kinds.get(&Tests)), (4, Some(&2), Some(&2)));
    let e = q2.edges.iter().find(|e| q2.groups[e.from].key == "shop" && q2.groups[e.to].key == "shop::cart").unwrap();
    assert_eq!((e.edges, e.kinds.get(&Calls), e.kinds.get(&UsesType)), (2, Some(&1), Some(&1)));

    // ---- by repo
    let q = r.quotient(GroupBy::Repo, &[Calls], None).unwrap().value;
    assert_eq!(q.groups.iter().map(|g| (g.key.as_str(), g.nodes)).collect::<Vec<_>>(), vec![(SHOP, 22), (WEB, 2)]);
    assert_eq!(edges(&q), vec![(WEB.into(), SHOP.into(), 1, 1)]);
    // ---- by file
    let q = r.quotient(GroupBy::File, &[Calls], None).unwrap().value;
    assert_eq!(q.groups.iter().map(|g| (g.key.as_str(), g.label.as_str())).collect::<Vec<_>>(), vec![
        ("shop:src/cart.rs", "cart.rs"),
        ("shop:src/lib.rs", "lib.rs"),
        ("shop:src/pay.rs", "pay.rs"),
        ("web:src/lib.rs", "lib.rs")
    ]);
    assert_eq!(
        edges(&q),
        vec![
            ("shop:src/lib.rs".into(), "shop:src/cart.rs".into(), 1, 1),
            ("shop:src/lib.rs".into(), "shop:src/pay.rs".into(), 1, 3),
            ("web:src/lib.rs".into(), "shop:src/lib.rs".into(), 1, 1),
        ]
    );
    assert_eq!(group(&q, "shop:src/cart.rs").internal, 3, "total→open, adds_up→add, adds_up→total: inside one file");
    // ---- inside a mask: only its nodes form groups
    let shop_only = r.mask(&MaskSpec { repos: vec![SHOP.into()], defined_only: true, ..Default::default() }).unwrap().value;
    let q = r.quotient(GroupBy::Module, &[Calls], Some(&shop_only)).unwrap().value;
    assert!(q.groups.iter().all(|g| g.key.starts_with("shop")), "{:?}", q.groups.iter().map(|g| &g.key).collect::<Vec<_>>());
    assert_eq!(q.edges.len(), 3);
    })
}

// ---------------------------------------------------------------- R9 + R10 + R11

#[test]
fn linked_symbols_with_sizes_and_the_repeated_keys() {
    on_shop(|log| async move {
    let r = current(log).await;
    let charge = r.symbols_of(o(&r, CHARGE));
    assert_eq!(
        charge.iter().map(|s| (s.symbol, s.role, s.size, s.artifact, s.section, s.demangled)).collect::<Vec<_>>(),
        vec![
            ("_Rcharge", Some("item"), Some(300), ARTIFACT, Some(".text"), Some("shop::pay::charge")),
            ("_Rcharge_closure0", Some("closure"), Some(40), ARTIFACT, Some(".text"), None)
        ]
    );
    assert_eq!(r.linked_bytes(o(&r, CHARGE)), 340);
    assert_eq!(r.linked_bytes(o(&r, AUDIT)), 0, "not linked");
    assert_eq!(r.node(o(&r, CHARGE)).provenance, Some(Provenance::Both));
    let all = r.symbols(None, None).unwrap();
    assert_eq!(all.value.len(), 4);
    let import = all.value.iter().find(|s| s.symbol == "write").unwrap();
    assert_eq!((import.role, import.size, import.binary), (Some("import"), None, Some("shop")));
    let fns = r.mask(&MaskSpec::kinds(&["fn"])).unwrap().value;
    assert_eq!(r.symbols(Some(&fns), None).unwrap().value.len(), 3, "checkout + charge ×2");
    let cut = r.symbols(None, Some(1)).unwrap();
    assert_eq!((cut.value.len(), cut.evidence.capped.map(|c| c.total)), (1, Some(4)));
    // a repo without a binary stream: the gap is named
    let web = Room::at(log, &Scope::repo(WEB, None)).await.unwrap();
    let none = web.symbols(None, None).unwrap();
    assert!(none.value.is_empty() && !none.evidence.missing_input("binary stream").is_empty(), "{}", none.evidence);

    // ---- repeated keys
    let d = r.duplicate_keys(None).unwrap();
    let got: Vec<(String, &str, u32, u32, u32, Vec<String>)> =
        d.value.iter().map(|k| (r.display_path(k.node), k.rows_of, k.rows, k.distinct_payloads, k.key_dup_rows, k.owners.clone())).collect();
    assert_eq!(
        sorted(got.iter().map(|g| g.0.clone()).collect()),
        s(&["shop::cart::helper", "shop::pay::audit"]),
        "add / total / charge are described twice too, identically: not listed. {got:?}"
    );
    // owners surface as the STABLE unit names (the events own by them)
    let helper = got.iter().find(|g| g.0 == "shop::cart::helper").unwrap();
    assert_eq!((helper.1, helper.2, helper.3, helper.4, helper.5.clone()), ("definition", 2, 2, 2, s(&["shop:lib"])), "ONE unit repeated the key");
    let audit = got.iter().find(|g| g.0 == "shop::pay::audit").unwrap();
    assert_eq!((audit.1, audit.2, audit.3, audit.4, audit.5.clone()), ("definition", 2, 2, 0, s(&["shop:lib", "shop:lib+test"])), "two units disagree");
    assert_eq!(r.stats().duplicate_keys, 2);
    // the ledger counted the same producer collision
    assert_eq!(log.runs(SHOP).await.unwrap()[0].key_collisions, 1);
    let web_dups = web.duplicate_keys(None).unwrap();
    assert!(web_dups.value.is_empty());
    assert_eq!(web_dups.evidence.state, AnswerState::EmptyFact);
    })
}

#[test]
fn the_bottleneck_walk_is_the_best_weakest_hop() {
    on_shop(|log| async move {
    let r = current(log).await;
    use EdgeKind::*;
    let send = r.ordinal(&send()).unwrap();
    let w = Weights { default: 10, set: vec![(o(&r, CHARGE), 4), (o(&r, AUDIT), 7), (send, 9), (o(&r, WRITE), 8)] };
    let named = |v: &[(u64, u64)]| {
        let mut x: Vec<(String, u64)> = v.iter().map(|(n, t)| (r.display_path(*n), *t)).collect();
        x.sort();
        x
    };
    let checkout = m(CHECKOUT);
    let b = r.bottleneck(&[checkout.as_str()], &[Calls], &w, None).unwrap();
    assert_eq!(
        named(&b.value),
        vec![
            ("shop::cart::Cart::total".into(), 10),
            ("shop::cart::open".into(), 10),
            ("shop::checkout".into(), 10),
            ("shop::pay::audit".into(), 4),
            ("shop::pay::charge".into(), 4),
            ("shop::pay::libc_write".into(), 4),
            ("stripe::Client::send".into(), 4),
        ],
        "everything behind charge (4) is capped at 4, however clean it is itself"
    );
    // a second entry: audit seeds itself at 7, and its callee rises with it (max over paths)
    let audit = m(AUDIT);
    let two = r.bottleneck(&[checkout.as_str(), audit.as_str()], &[Calls], &w, None).unwrap();
    let v = named(&two.value);
    assert_eq!(v.iter().find(|x| x.0 == "shop::pay::audit").unwrap().1, 7);
    assert_eq!(v.iter().find(|x| x.0 == "shop::pay::libc_write").unwrap().1, 7, "min(7, 8) beats the 4 through charge");
    assert_eq!(v.iter().find(|x| x.0 == "stripe::Client::send").unwrap().1, 4);
    // a node nobody reaches is absent — no provenance is not a weight
    assert!(!b.value.iter().any(|(n, _)| *n == o(&r, PAY_OPEN)));
    // no edge of the kind: the seeds keep their own weight, and the gap is said
    let none = r.bottleneck(&[o(&r, CHARGE)], &[TouchesTable], &w, None).unwrap();
    assert_eq!(none.value, vec![(o(&r, CHARGE), 4)]);
    assert_eq!(none.evidence.state, AnswerState::Partial, "{}", none.evidence);

    // worst value on any path FROM a node
    let worst = r.worst_on_path(&[Calls], &[(o(&r, WRITE), 5), (send, 9)], None).unwrap();
    assert_eq!(
        named(&worst.value),
        vec![
            ("shop::checkout".into(), 9),
            ("shop::pay::audit".into(), 5),
            ("shop::pay::charge".into(), 9),
            ("shop::pay::charges_once".into(), 9),
            ("shop::pay::libc_write".into(), 5),
            ("stripe::Client::send".into(), 9),
            ("web::render".into(), 9),
        ]
    );
    })
}

// ---------------------------------------------------------------- R12 + R14 + R15

#[test]
fn evidence_travels_as_json_and_back() {
    on_shop(|log| async move {
    let r = current(log).await;
    let checkout = m(CHECKOUT);
    let a = r.reach(&[checkout.as_str(), "nope"], &[EdgeKind::Calls, EdgeKind::GatedBy], None, None).unwrap();
    let j = serde_json::to_value(&a.evidence).unwrap();
    assert_eq!(j["state"], "partial", "a value, with a kind and a seed missing");
    assert_eq!(j["kinds"], serde_json::json!([{"kind": "CALLS", "edges": 10}, {"kind": "GATED_BY", "edges": 0}]));
    assert!(j["missing"].as_array().unwrap().iter().any(|m| m["input"] == "GATED_BY edges"));
    assert!(j["missing"].as_array().unwrap().iter().any(|m| m["input"] == "seed" && m["why"].as_str().unwrap().contains("nope")));
    assert_eq!(j["inputs"][0]["table"], "code_node_events");
    assert!(j.get("capped").is_none(), "absent unless a cap cut the answer");
    let back: mimir_room::Evidence = serde_json::from_value(j).unwrap();
    assert_eq!(back, a.evidence);
    // the whole answer, and the values a front door prints
    let whole = serde_json::to_value(&a).unwrap();
    assert_eq!(whole["value"]["seeds"].as_array().unwrap().len(), 1);
    let node = serde_json::to_value(r.node(o(&r, CHARGE))).unwrap();
    assert_eq!((node["name"].as_str(), node["level"].as_str(), node["provenance"].as_str(), node["crate"].as_str()), (Some("charge"), Some("item"), Some("both"), Some("shop")));
    assert_eq!(node["file"], "src/pay.rs");
    let stats = serde_json::to_value(r.stats()).unwrap();
    assert_eq!(stats["edges"]["CALLS"], 10);
    assert_eq!(stats["provenance"]["both"], 2);
    let p = r.path(&[checkout.as_str()], &[o(&r, AUDIT)], &[EdgeKind::Calls], None, None).unwrap();
    assert_eq!(serde_json::to_value(&p.value).unwrap()["kinds"], serde_json::json!(["CALLS", "CALLS"]));
    let q = serde_json::to_value(r.quotient(GroupBy::Crate, &[EdgeKind::Calls], None).unwrap().value).unwrap();
    assert_eq!(q["by"], "crate");
    assert_eq!(q["edges"][0]["kinds"]["CALLS"], 1);
    // every state has its word
    for (state, word) in [(AnswerState::Complete, "complete"), (AnswerState::Partial, "partial"), (AnswerState::EmptyFact, "empty_fact"), (AnswerState::EmptyGap, "empty_gap")] {
        assert_eq!(serde_json::to_value(state).unwrap(), word);
        assert_eq!(state.as_str(), word);
    }
    })
}

#[test]
fn the_build_phases_are_named_for_the_log() {
    on_shop(|log| async move {
    let r = current(log).await;
    let note = r.stats().build.phase_note();
    for part in ["fold ", "index ", "sort ", "fill ", "symbols ", "provenance ", "matrices ", "spans ", " ms"] {
        assert!(note.contains(part), "`{note}` misses `{part}`");
    }
    })
}

#[test]
fn a_repo_that_was_never_consumed_is_a_typed_state() {
    on_shop(|log| async move {
        let err = Room::at(log, &Scope::repo("nobody", None)).await.err().expect("no offset");
        match err.downcast_ref::<ScopeError>() {
            Some(ScopeError::NotConsumed { repo, known }) => {
                assert_eq!(repo, "nobody");
                assert_eq!(known, &s(&[SHOP, WEB]));
            }
            other => panic!("expected NotConsumed, got {other:?} ({err:#})"),
        }
        // a seq the repo never had is another state, not "not consumed"
        let past = Room::at(log, &Scope::repo(SHOP, Some(9))).await.err().expect("past the offset");
        assert!(matches!(past.downcast_ref::<ScopeError>(), Some(ScopeError::PastOffset { seq: 9, offset: 2, .. })), "{past:#}");
        let neg = Room::at(log, &Scope::repo(SHOP, Some(-1))).await.err().expect("negative");
        assert!(matches!(neg.downcast_ref::<ScopeError>(), Some(ScopeError::NegativeSeq { .. })), "{neg:#}");
        // an empty log: every repo is unconsumed, and a scope of "all" is an empty room that says so
        let tmp2 = tempfile::tempdir().unwrap();
        let empty = open_log(tmp2.path()).await;
        let none = Room::at(&empty, &Scope::repo(SHOP, None)).await.err().expect("nothing consumed");
        assert!(matches!(none.downcast_ref::<ScopeError>(), Some(ScopeError::NotConsumed { known, .. }) if known.is_empty()), "{none:#}");
        let nothing = Room::at(&empty, &Scope::current()).await.unwrap();
        assert!(nothing.is_empty() && nothing.scope().repos.is_empty());
        let q = nothing.lookup(&NameQuery::new("x")).unwrap();
        assert!(!q.evidence.missing_input("repos").is_empty(), "{}", q.evidence);
    })
}

/// The sync door on the shared log: the same answers, the same cache.
#[test]
fn the_sync_door_gives_the_same_room_as_the_async_one() {
    on_shop(|log| async move {
        Room::clear_cache();
        // called here INSIDE a runtime context (block_on of the shared runtime)
        let opened = Room::open_blocking(log, &Scope::repo(SHOP, None)).unwrap();
        assert!(!opened.cached);
        assert_eq!(opened.room.display_path(o(&opened.room, ADD)), "shop::cart::Cart::add");
        let again = Room::open(log, &Scope::repo(SHOP, None)).await.unwrap();
        assert!(again.cached && again.room.same_snapshot(&opened.room), "one process cache behind both doors");
        let err = Room::at_blocking(log, &Scope::repo("nobody", None)).err().expect("no offset");
        assert!(matches!(err.downcast_ref::<ScopeError>(), Some(ScopeError::NotConsumed { .. })), "{err:#}");
    })
}

/// From plain sync code, no runtime on the thread at all.
#[test]
fn the_sync_door_opens_with_no_runtime_on_the_thread() {
    let log = RT.block_on(async {
        let _g = serial().await;
        shared().await
    });
    assert!(tokio::runtime::Handle::try_current().is_err(), "this thread has no runtime");
    let room = Room::at_blocking(log, &Scope::current()).unwrap();
    assert_eq!(room.scope().repos.len(), 2);
    assert_eq!(room.lookup(&NameQuery::new("Cart::total")).unwrap().value, vec![o(&room, TOTAL)]);
}

/// On a CURRENT-THREAD runtime — where blocking on the runtime itself would
/// panic ("cannot start a runtime from within a runtime") and blocking on
/// its handle would hang — with a log opened on that very runtime.
#[tokio::test(flavor = "current_thread")]
async fn the_sync_door_opens_on_a_current_thread_runtime() {
    let _g = serial().await;
    let tmp = tempfile::tempdir().unwrap();
    let log = super::shop::consume_dumps(open_log(tmp.path()).await, tmp.path(), "v", vec![tiny_commit()]).await;
    let room = Room::at_blocking(&log, &Scope::current()).unwrap();
    assert_eq!(room.len(), 6);
    assert_eq!(room.display_path(room.lookup(&NameQuery::new("api")).unwrap().value[0]), "v::api");
}

/// The room over the READ-FAST zone is the same room: materialize both
/// repos (its own lake — the shared one stays event-only for the other
/// tests), drop the cache, build again — the same nodes, edges, spans and
/// answers, now read as interval rows + tail instead of the event history.
#[test]
fn the_room_over_the_read_fast_zone_is_the_same_room() {
    RT.block_on(async {
        let _g = serial().await;
        let tmp = tempfile::tempdir().unwrap();
        let log = super::shop::shop_log(tmp.path()).await;
        let before = Room::at(&log, &Scope::current()).await.unwrap();
        let monikers = sorted((0..before.len() as u64).map(|o| before.moniker(o).to_string()).collect());
        for repo in [SHOP, WEB] {
            let r = log.materialize(repo).await.unwrap();
            assert!(!r.skipped && r.rows > 0, "{r:?}");
        }
        Room::clear_cache();
        let after = Room::at(&log, &Scope::current()).await.unwrap();
        assert_eq!(sorted((0..after.len() as u64).map(|o| after.moniker(o).to_string()).collect()), monikers, "the same nodes");
        let (b, a) = (before.stats(), after.stats());
        assert_eq!((a.nodes, &a.edges, &a.provenance, &a.node_kinds), (b.nodes, &b.edges, &b.provenance, &b.node_kinds));
        assert_eq!((a.spans, a.files, a.sites, a.endpoint_only), (b.spans, b.files, b.sites, b.endpoint_only));
        // an answer through the walk, as before
        assert_eq!(
            paths(&after, &after.dependents(&[m(CHECKOUT).as_str()][..], None).unwrap().value.reached),
            paths(&before, &before.dependents(&[m(CHECKOUT).as_str()][..], None).unwrap().value.reached)
        );
        // and time travel: the room as of seq 1 is the seq-1 room either way
        Room::clear_cache();
        let early = Room::at(&log, &Scope::repo(SHOP, Some(1))).await.unwrap();
        assert!(early.len() > 0 && early.len() < after.len(), "seq 1 is a smaller shop");
    })
}

/// **The commit window from the READ-FAST zone is the as-of rooms' diff.**
/// Four commits of `shop` without a binary stream: an item dropped and back,
/// a payload change, a visibility change, an item dropped while an edge still
/// names it (an endpoint on the newer side), a version bump that moves every
/// id — two units describe most items (the first among equals wins) and the
/// dump repeats a key. For every window, every moniker the diff of the two
/// as-of rooms lists with a node row on a side is in the window, in the same
/// list, with the node the room holds; nothing else is.
#[test]
fn the_commit_window_is_the_diff_of_the_two_as_of_rooms() {
    RT.block_on(async {
        let _g = serial().await;
        let tmp = tempfile::tempdir().unwrap();
        let is = |n: &mimir_schema::NodeRow, name: &str| n.name == name && n.level == Level::Item;
        let commits = vec![
            shop_tree("0.1.0", |_| true),
            shop_tree("0.1.0", |n| {
                if is(n, "checkout") {
                    n.ty = Some("fn() -> u64".into());
                }
                if is(n, "total") {
                    n.effective_visibility = Some(EffectiveVisibility::Direct);
                }
                !(is(n, "open") && n.id.contains("cart"))
            }),
            shop_tree("0.1.0", |n| !is(n, "audit") && !is(n, "adds_up")),
            shop_tree("0.2.0", |n| !is(n, "charge")),
        ];
        let log = consume_dumps(open_log(tmp.path()).await, tmp.path(), SHOP, commits).await;
        assert!(Room::commit_window(&log, SHOP, 2).await.unwrap().is_none(), "no zone yet: the caller folds");
        log.materialize(SHOP).await.unwrap();
        let view = |r: &Room, o: u64| {
            let n = r.node(o);
            mimir_room::WindowNode {
                krate: n.krate.map(str::to_string),
                level: n.level,
                origin: n.origin,
                defined: n.defined,
                effective_visibility: r.attr(o, "effective_visibility").and_then(|v| v.as_str()).map(str::to_string),
            }
        };
        let mut seen = [0usize; 3];
        for seq in 2..=4 {
            let older = Room::build_uncached(&log, &Scope::repo(SHOP, Some(seq - 1))).await.unwrap();
            let newer = Room::build_uncached(&log, &Scope::repo(SHOP, Some(seq))).await.unwrap();
            let d = older.diff(&newer).unwrap().value;
            let rows = |r: &Room, m: &str| r.ordinal(m).is_some_and(|o| !r.node(o).endpoint_only);
            let pick = |ms: &[String], side: &Room| -> Vec<(String, mimir_room::WindowNode)> {
                ms.iter().filter(|m| rows(&older, m) || rows(&newer, m)).map(|m| (m.clone(), view(side, side.ordinal(m).unwrap()))).collect()
            };
            let want = (pick(&d.added_nodes, &newer), pick(&d.removed_nodes, &older), pick(&d.changed, &newer));
            let w = Room::commit_window(&log, SHOP, seq).await.unwrap().expect("inside the zone, no binary stream");
            assert_eq!((&w.added, &w.removed, &w.changed), (&want.0, &want.1, &want.2), "window {}→{seq}", seq - 1);
            seen[0] += w.added.len();
            seen[1] += w.removed.len();
            seen[2] += w.changed.len();
            assert!(w.changed.iter().chain(&w.removed).chain(&w.added).all(|(m, _)| older.ordinal(m).is_some() || newer.ordinal(m).is_some()));
        }
        assert!(seen.iter().all(|n| *n > 0), "every kind of change was exercised: {seen:?}");
    })
}