crates/mimir-room/tests/room/advance.rs
//! New events ADVANCE the kept room — the fold applies the lake's delta
//! instead of re-reading the log's history — and the advanced snapshot is
//! indistinguishable from a fresh full build (the oracle).
use mimir_ingest::Consumer;
use mimir_room::{Room, Scope};
use mimir_schema::{EdgeKind, Origin};
use super::common::*;
fn commit(src: &std::path::Path, n: usize) {
std::fs::write(src.join("src/lib.rs"), format!("// commit {n}\n")).unwrap();
nornir_git::gitio::commit_all(src, &format!("c{n}")).unwrap();
}
async fn run(log: mimir_ingest::EventLog, tmp: &std::path::Path, repo: &str, step: Step) -> mimir_ingest::EventLog {
let mut c = Consumer::boot(log, Scripted(vec![step])).await.unwrap();
let rep = c.run(&repo_spec(tmp, repo)).await.unwrap();
assert!(rep.iter().all(|r| r.status == "ok"), "{rep:?}");
c.into_parts().0
}
#[tokio::test(flavor = "current_thread")]
async fn new_events_advance_the_kept_room_and_match_a_fresh_build() {
let _g = serial().await;
Room::clear_cache();
Room::set_cache_capacity(16);
let tmp = tempfile::tempdir().unwrap();
let log = fixture(tmp.path()).await;
let first = Room::open(&log, &Scope::current()).await.unwrap();
assert!(!first.cached && !first.advanced, "the first build folds");
// new source events land: an edit of p1, a new fn, a new call
commit(&tmp.path().join(format!("src-{PLAIN}")), 2);
let step = Step {
owner: "p@0.1.0:lib",
nodes: vec![node("p p1", "fn", "p", Origin::Local, true), node("p p9", "fn", "p", Origin::Local, true)],
edges: vec![edge("p p9", "p p1", EdgeKind::Calls)],
binary: None,
ring: vec![],
};
let log = run(log, tmp.path(), PLAIN, step).await;
let adv = Room::open(&log, &Scope::current()).await.unwrap();
assert!(!adv.cached && adv.advanced, "the kept fold advances by the delta, it does not refold");
assert!(adv.room.ordinal("p p9").is_some(), "the new fn landed");
// the advanced room is the cache's: a re-open serves it
let again = Room::open(&log, &Scope::current()).await.unwrap();
assert!(again.cached && again.room.same_snapshot(&adv.room), "the advanced snapshot is the kept one");
// the oracle: a fresh full build of the same state must be identical
Room::clear_cache();
let full = Room::open(&log, &Scope::current()).await.unwrap();
assert!(!full.cached && !full.advanced);
let (a, b) = (serde_json::to_value(adv.room.stats()).unwrap(), serde_json::to_value(full.room.stats()).unwrap());
for k in ["nodes", "edges", "node_kinds", "provenance", "spans", "files", "sites", "symbols", "duplicate_keys", "endpoint_only", "repo_rows", "universe_rows", "unknown_kinds"] {
assert_eq!(a[k], b[k], "stats `{k}` differ between the advanced room and the fresh build");
}
assert_eq!(adv.room.ordinal("p p9"), full.room.ordinal("p p9"), "ordinals agree (same sorted names)");
fn reached(r: &Room) -> Vec<String> {
let d = r.dependents(&["p p1"], None).unwrap();
r.monikers(&d.value.reached).into_iter().map(str::to_string).collect()
}
assert_eq!(reached(&adv.room), reached(&full.room), "the walks agree");
}
/// The advanced snapshot and a from-scratch refold of the same lake state
/// must be indistinguishable: every stats law, and the ordinals.
fn assert_rooms_equal(a: &Room, b: &Room, what: &str) {
let (sa, sb) = (serde_json::to_value(a.stats()).unwrap(), serde_json::to_value(b.stats()).unwrap());
for k in ["nodes", "edges", "node_kinds", "provenance", "spans", "files", "sites", "symbols", "duplicate_keys", "endpoint_only", "repo_rows", "universe_rows", "unknown_kinds"] {
assert_eq!(sa[k], sb[k], "{what}: stats `{k}` differ from the fresh build");
}
}
/// The ring room ADVANCES: a consume that moves only the repos' source
/// streams (the dependency universe untouched) carries the kept universe
/// fold by the lake's delta, and the advanced snapshot equals a from-scratch
/// refold exactly — nodes, edges, spans, stats, and a walk through the ring.
#[tokio::test(flavor = "current_thread")]
async fn new_events_advance_the_kept_ring_room_and_match_a_fresh_build() {
let _g = serial().await;
Room::clear_cache();
Room::set_ring_keeps_fold(true);
Room::set_cache_capacity(16);
let tmp = tempfile::tempdir().unwrap();
let log = fixture(tmp.path()).await;
let scope = Scope::current().with_universe(true);
let first = Room::open(&log, &scope).await.unwrap();
assert!(!first.cached && !first.advanced, "the first ring build folds");
// new source events land, the dependency universe does not move
commit(&tmp.path().join(format!("src-{PLAIN}")), 2);
let step = Step {
owner: "p@0.1.0:lib",
nodes: vec![node("p p1", "fn", "p", Origin::Local, true), node("p p9", "fn", "p", Origin::Local, true)],
edges: vec![edge("p p9", "p p1", EdgeKind::Calls)],
binary: None,
ring: vec![],
};
let log = run(log, tmp.path(), PLAIN, step).await;
let adv = Room::open(&log, &scope).await.unwrap();
assert!(!adv.cached && adv.advanced, "the kept universe fold advances by the delta, it does not refold");
assert!(adv.room.ordinal("p p9").is_some(), "the new fn landed");
assert!(adv.room.ordinal("dep w").is_some(), "the ring rows carried");
// the advanced room is the cache's: a re-open serves it
let again = Room::open(&log, &scope).await.unwrap();
assert!(again.cached && again.room.same_snapshot(&adv.room), "the advanced snapshot is the kept one");
// the oracle: a fresh full build of the same state must be identical
Room::clear_cache();
let full = Room::open(&log, &scope).await.unwrap();
assert!(!full.cached && !full.advanced);
assert_rooms_equal(&adv.room, &full.room, "advanced ring room");
assert_eq!(adv.room.ordinal("p p9"), full.room.ordinal("p p9"), "ordinals agree (same sorted names)");
assert_eq!(adv.room.ordinal("dep w"), full.room.ordinal("dep w"), "ring ordinals agree");
fn reached(r: &Room) -> Vec<String> {
let d = r.dependents(&["dep w"], None).unwrap();
r.monikers(&d.value.reached).into_iter().map(str::to_string).collect()
}
assert_eq!(reached(&adv.room), reached(&full.room), "the walk through the ring agrees");
assert!(reached(&adv.room).iter().any(|m| m == "dep h"), "the ring edge dep h -> dep w stands");
}
/// The ring view is PINNED against count eviction: newer non-ring rooms
/// cycle through the cache while the kept universe fold STAYS, so the next
/// consume still ADVANCES it. (Live 2026-10-20: cap 4 — the panes' per-repo
/// rooms evicted the ring view between asks and every ring ask refolded the
/// whole dependency universe, 160-175 s each on dwarf.)
#[tokio::test(flavor = "current_thread")]
async fn the_ring_view_survives_newer_rooms_and_still_advances() {
let _g = serial().await;
Room::clear_cache();
Room::set_ring_keeps_fold(true);
Room::set_cache_capacity(2);
let tmp = tempfile::tempdir().unwrap();
let log = fixture(tmp.path()).await;
let scope = Scope::current().with_universe(true);
let first = Room::open(&log, &scope).await.unwrap();
assert!(!first.cached && !first.advanced, "the first ring build folds");
// more non-ring rooms than the cap: without the pin they evict the ring view
for s in [Scope::current(), Scope::repo(ROOM, None), Scope::repo(PLAIN, None)] {
Room::open(&log, &s).await.unwrap();
}
let again = Room::open(&log, &scope).await.unwrap();
assert!(again.cached && again.room.same_snapshot(&first.room), "the ring view is still served after the churn");
// a consume lands: the kept (pinned) universe fold advances
commit(&tmp.path().join(format!("src-{PLAIN}")), 2);
let step = Step {
owner: "p@0.1.0:lib",
nodes: vec![node("p p1", "fn", "p", Origin::Local, true), node("p p9", "fn", "p", Origin::Local, true)],
edges: vec![edge("p p9", "p p1", EdgeKind::Calls)],
binary: None,
ring: vec![],
};
let log = run(log, tmp.path(), PLAIN, step).await;
let adv = Room::open(&log, &scope).await.unwrap();
assert!(!adv.cached && adv.advanced, "the pinned universe fold advances after the churn");
// zero-loss: the advanced snapshot equals a from-scratch refold
Room::clear_cache();
let full = Room::open(&log, &scope).await.unwrap();
assert_rooms_equal(&adv.room, &full.room, "pinned ring advance");
Room::set_cache_capacity(16);
}
/// The dependency universe itself moved (a new ring crate was dumped: the
/// `universe_manifest` snapshot advanced): the kept fold must REFUSE to
/// carry and the room is refolded from scratch — holding the new ring rows.
#[tokio::test(flavor = "current_thread")]
async fn a_moved_dependency_universe_refuses_the_carry() {
let _g = serial().await;
Room::clear_cache();
Room::set_cache_capacity(16);
let tmp = tempfile::tempdir().unwrap();
let log = fixture(tmp.path()).await;
let scope = Scope::current().with_universe(true);
let first = Room::open(&log, &scope).await.unwrap();
assert!(!first.cached && !first.advanced);
assert!(first.room.ordinal("dep9 z").is_none(), "dep9 not dumped yet");
// the consume dumps a NEW ring crate and depends on it: the universe moved
commit(&tmp.path().join(format!("src-{PLAIN}")), 2);
let step = Step {
owner: "p@0.1.0:lib",
nodes: vec![node("p", "crate", "p", Origin::Local, true)],
edges: vec![edge("p", "dep9", EdgeKind::DependsOn)],
binary: None,
ring: vec![("dep9@1.0.0:lib", vec![node("dep9", "crate", "dep9", Origin::Library, true), node("dep9 z", "fn", "dep9", Origin::Library, true)], vec![])],
};
let log = run(log, tmp.path(), PLAIN, step).await;
let second = Room::open(&log, &scope).await.unwrap();
assert!(!second.cached && !second.advanced, "a moved universe manifest must force the full refold");
assert!(second.room.ordinal("dep9 z").is_some(), "the new ring crate's rows are in the refolded room");
// zero-loss either way: the refold equals a from-scratch build
Room::clear_cache();
let full = Room::open(&log, &scope).await.unwrap();
assert_rooms_equal(&second.room, &full.room, "refused ring carry");
}
/// The universe tables did not move, but the scope's dependency CLOSURE did:
/// a new local DEPENDS_ON edge pulls an already-dumped ring crate into the
/// room. The kept fold's ring scan never read that crate's rows, so the
/// carry must be refused and the refold holds them.
#[tokio::test(flavor = "current_thread")]
async fn a_grown_dependency_closure_refuses_the_carry() {
let _g = serial().await;
Room::clear_cache();
Room::set_cache_capacity(16);
let tmp = tempfile::tempdir().unwrap();
let log = fixture(tmp.path()).await;
// dump a ring crate NOTHING depends on yet (the manifest moves here)
commit(&tmp.path().join(format!("src-{PLAIN}")), 2);
let step = Step {
owner: "p@0.1.0:lib",
nodes: vec![node("p p1", "fn", "p", Origin::Local, true)],
edges: vec![],
binary: None,
ring: vec![("dep8@1.0.0:lib", vec![node("dep8", "crate", "dep8", Origin::Library, true), node("dep8 z", "fn", "dep8", Origin::Library, true)], vec![])],
};
let log = run(log, tmp.path(), PLAIN, step).await;
let scope = Scope::current().with_universe(true);
let first = Room::open(&log, &scope).await.unwrap();
assert!(!first.cached && !first.advanced);
assert!(first.room.ordinal("dep8 z").is_none(), "dumped but not depended on: outside the closure");
// the next commit DEPENDS_ON it: the manifest is unchanged, the closure grew
commit(&tmp.path().join(format!("src-{PLAIN}")), 3);
let step = Step {
owner: "p@0.1.0:lib",
nodes: vec![node("p", "crate", "p", Origin::Local, true)],
edges: vec![edge("p", "dep8", EdgeKind::DependsOn)],
binary: None,
ring: vec![],
};
let log = run(log, tmp.path(), PLAIN, step).await;
let second = Room::open(&log, &scope).await.unwrap();
assert!(!second.cached && !second.advanced, "a grown closure must force the full refold (the kept scan never read dep8)");
assert!(second.room.ordinal("dep8 z").is_some(), "the newly depended-on ring crate's rows are in the room");
// zero-loss either way: the refold equals a from-scratch build
Room::clear_cache();
let full = Room::open(&log, &scope).await.unwrap();
assert_rooms_equal(&second.room, &full.room, "refused ring carry (closure)");
}
/// The LIVE order — consume, then the post-consume compaction REWRITES the
/// window's appended files (stamped row-preserving replace), then the room
/// opens — must still ADVANCE: the delta reads the replace's merged files
/// through the kept track (re-seen rows are idempotent) instead of refusing.
/// (Live 2026-10-20: every real commit's window was swallowed this way —
/// 13:40:31 consume, 13:40:42 replace, 13:41:32 'the node stream's window
/// holds a commit a delta cannot express' — and every ring ask paid a
/// 140-200 s refold; the one advance that fired rode a +0/−0 consume whose
/// compaction had nothing to rewrite.)
#[tokio::test(flavor = "current_thread")]
async fn a_compaction_after_the_consume_still_advances() {
let _g = serial().await;
Room::clear_cache();
Room::set_ring_keeps_fold(true);
Room::set_cache_capacity(16);
let tmp = tempfile::tempdir().unwrap();
let log = fixture(tmp.path()).await;
let scope = Scope::current().with_universe(true);
let first = Room::open(&log, &scope).await.unwrap();
assert!(!first.cached && !first.advanced, "the first ring build folds");
let repo_first = Room::open(&log, &Scope::current()).await.unwrap();
assert!(!repo_first.cached && !repo_first.advanced);
// a commit that MODIFIES an existing fn, ADDS one and CLOSES others
// (p p2 and the k-a stub leave the dump), then the compaction rewrites
// the window's files BEFORE anyone reads them — the live order
commit(&tmp.path().join(format!("src-{PLAIN}")), 2);
let step = Step {
owner: "p@0.1.0:lib",
nodes: vec![node("p p1", "fn", "p", Origin::Local, true), node("p p9", "fn", "p", Origin::Local, true)],
edges: vec![edge("p p9", "p p1", EdgeKind::Calls)],
binary: None,
ring: vec![],
};
let log = run(log, tmp.path(), PLAIN, step).await;
let opts = mimir_ingest::CompactOptions { grace: std::time::Duration::ZERO, ..Default::default() };
let reports = log.compact(&opts).await.unwrap();
assert!(reports.iter().any(|r| r.snapshot.is_some()), "a replace landed over the window: {reports:?}");
let adv = Room::open(&log, &scope).await.unwrap();
assert!(!adv.cached && adv.advanced, "the ring fold advances THROUGH the replace that swallowed the window");
assert!(adv.room.ordinal("p p9").is_some(), "the swallowed commit's fn landed");
let repo_adv = Room::open(&log, &Scope::current()).await.unwrap();
assert!(!repo_adv.cached && repo_adv.advanced, "the repo fold advances through it too");
// the oracle: a fresh full build of the same state must be identical
Room::clear_cache();
let full = Room::open(&log, &scope).await.unwrap();
assert!(!full.cached && !full.advanced);
assert_rooms_equal(&adv.room, &full.room, "advance through the post-consume replace");
assert_eq!(adv.room.ordinal("p p9"), full.room.ordinal("p p9"), "ordinals agree");
assert_eq!(adv.room.ordinal("p p2"), full.room.ordinal("p p2"), "the closed fn agrees (gone from both)");
let full_repo = Room::open(&log, &Scope::current()).await.unwrap();
assert_rooms_equal(&repo_adv.room, &full_repo.room, "repo advance through the post-consume replace");
}
/// Wave item 8 — a lake compaction between consumes no longer forces a full
/// rebuild: the kept fold advances across the stamped row-preserving replace
/// (`Opened.advanced` stays true) and the room equals a fresh build.
#[tokio::test(flavor = "current_thread")]
async fn a_compaction_between_consumes_does_not_force_a_rebuild() {
let _g = serial().await;
Room::clear_cache();
Room::set_cache_capacity(16);
let tmp = tempfile::tempdir().unwrap();
let log = fixture(tmp.path()).await;
let first = Room::open(&log, &Scope::current()).await.unwrap();
assert!(!first.cached && !first.advanced, "the first build folds");
// the lake compacts: stamped replace per table, older snapshots expired
let opts = mimir_ingest::CompactOptions { grace: std::time::Duration::ZERO, ..Default::default() };
let reports = log.compact(&opts).await.unwrap();
assert!(reports.iter().any(|r| r.snapshot.is_some()), "a replace landed: {reports:?}");
// a release lands after the compaction
commit(&tmp.path().join(format!("src-{PLAIN}")), 2);
let step = Step {
owner: "p@0.1.0:lib",
nodes: vec![node("p p1", "fn", "p", Origin::Local, true), node("p p9", "fn", "p", Origin::Local, true)],
edges: vec![edge("p p9", "p p1", EdgeKind::Calls)],
binary: None,
ring: vec![],
};
let log = run(log, tmp.path(), PLAIN, step).await;
// THE POINT: the kept fold advances ACROSS the compaction, no refold
let adv = Room::open(&log, &Scope::current()).await.unwrap();
assert!(!adv.cached && adv.advanced, "the compaction must not force a full rebuild");
assert!(adv.room.ordinal("p p9").is_some(), "the post-compaction fn landed");
// the oracle: a fresh full build of the same state is identical
Room::clear_cache();
let full = Room::open(&log, &Scope::current()).await.unwrap();
assert!(!full.cached && !full.advanced);
let (a, b) = (serde_json::to_value(adv.room.stats()).unwrap(), serde_json::to_value(full.room.stats()).unwrap());
for k in ["nodes", "edges", "node_kinds", "provenance", "spans", "files", "sites", "symbols", "duplicate_keys", "endpoint_only", "repo_rows", "universe_rows", "unknown_kinds"] {
assert_eq!(a[k], b[k], "stats `{k}` differ between the advanced room and the fresh build");
}
assert_eq!(adv.room.ordinal("p p9"), full.room.ordinal("p p9"), "ordinals agree");
}