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

nordisk/nornir

.nornir/mimir-v2-land.md

Mímir v2 — land (integration branch mimir-v2-land)

2026-10-13. Spec: workspace_nordisk/PLAN-MIMIR-V2-2026-10-12.md. Inventory of the removal: .nornir/mimir-v2-inventory.md (§8–§10 = what answers not_wired today).

This branch is the first tree where the old roads are gone AND the new engine is present. Nothing is wired yet: every surface in §6 still answers not_wired. No merge to main (plan §7: deletion + rebuild land as one).

input head what it brings
mimir-v2 (removal lane) 60ba98c roads a–e deleted (−34k lines), guard tests/no_second_parser.rs, every reader of the old tables answers not_wired, gates fail closed
origin/mimir-v2-room 067b787 crates/mimir-schema, mimir-driver (nightly-2026-10-05, excluded), mimir-ingest, mimir-binary, graphblas-sys, mimir-graph, mimir-room

Merge base c18e6d1 (nornir main).

1. Merge: conflicts and resolutions

git merge origin/mimir-v2-room on mimir-v2: 2 conflicted files, 3 hunks. Outside the new crate directories the room branch changed only Cargo.toml and src/coderoom.rs.

# file conflict resolution
1 Cargo.toml [workspace] removal: members unchanged, exclude = ["bench-corpus"] + its comment DELETED (bench-corpus/ is gone). room: 6 new members, exclude = ["bench-corpus", "crates/mimir-driver"] members = the removal's list + crates/mimir-schema, mimir-ingest, mimir-binary, graphblas-sys, mimir-graph, mimir-room. exclude = ["crates/mimir-driver"] ONLY — bench-corpus and its comment stay deleted. The driver comment is kept and names the guard.
2 src/coderoom.rs hunk 1 (~1 480 lines) removal DELETED the tarball scan (definition_extents, walk_extents, ingest, compat edge, what-breaks, snapshot-diff). room MODIFIED one line inside it (walk_extents: impl scope Type# → impl#[Type] via mimir_schema::moniker::impl_descriptor) the region stays DELETED. The room's edit was to the syn walker; with the walker gone there is nothing to carry. The RA-form impl scope lives on where it is written now: mimir-schema (formatter) and mimir-driver (the one producer).
3 src/coderoom.rs hunk 2 (tests, ~130 lines) removal DELETED the tests over definition_extents. room RENAMED/REWROTE one of them (an_impl_method_scope_is_rust_analyzers_impl_scope) stays DELETED — it tests the deleted walker. The moniker bytes are pinned by mimir-schema's own tests and by the driver's fixture (21/21 symbols byte-identical to rust-analyzer scip).

Auto-merged in src/coderoom.rs, and KEPT (the part of the room's change that touches surviving code): scip_descriptor → mimir_schema::moniker::descriptor_for_kind, scip_moniker → mimir_schema::moniker::moniker, the ItemExtent::scope doc (impl#[B]), and root Cargo.toml's mimir-schema dependency. One writer of moniker bytes (LAW 5).

Follow-ups of the resolution, in the merge commit:

Check that nothing deleted came back: the 62 paths git diff --diff-filter=D c18e6d1 mimir-v2 lists — none exists in the merged tree.

2. Guard (tests/no_second_parser.rs)

The guard iterates [workspace].members, so the six new member crates were covered the moment they became members. Made explicit and widened:

rule what was added
7 every crate directory on disk (crates/*, top-level dirs with a Cargo.toml) is a workspace member or THE excluded driver; [workspace].exclude is exactly ["crates/mimir-driver"]; the six stable Mímir v2 crates are members BY NAME (MIMIR_V2_MEMBERS) and among the scanned manifests. For member crates the source scan now reads src/, tests/, examples/, benches/ and build.rs (was src/ only).
8 crates/mimir-driver is the ONE user of compiler internals (COMPILER_INTERNALS_USER): it must really name rustc_private (no stale exemption), carry its own [workspace] table and a DATED nightly pin (nightly-YYYY-MM-DD); its manifest and sources are scanned for the forbidden crates like everyone else; no member source names rustc_private / extern crate rustc_*, no member manifest asks for it, no member pins a toolchain.

Not forbidden, noted: graphblas-sys has an OPTIONAL build-dependency bindgen (feature regenerate, off by default; bindings are checked in). bindgen parses C headers, not Rust; it pulls syn transitively the way any proc-macro crate does (plan §7.1: not ours).

Result and red-proof: §4.

3. What is NOT in this branch

4. Compile and test matrix

Box: dwarf (12 threads, load 25–95 during the runs — shared), stable rustc 1.98.1, siblings from ~/d-scip, Cargo.lock seeded from the room worktree, --offline. Logs: ~/d-scip/tmp/land-logs/. Removal baseline: the mimir-v2-L2 row in nornir-workspace.toml and the removal lane's logs (~/d-scip/tmp/mimirv2/).

step command removal lane (mimir-v2) land
default cargo check green green (11m28s cold). nornir lib 46 warnings, none in src/coderoom.rs — the only source file that differs from the removal tree
full-server cargo check --no-default-features --features full-server green green (nornir lib 15 warnings)
test targets cargo check --tests --keep-going RED, same as main: BenchRun lacks id / machine_facts in the fixtures, Toolchain lacks image / image_sha256 — bin nornir (test), cli_ui_parity, data_submit_rpcs, mcp_matrix, mcp_tools, release_gates, viz_surface RED in the SAME 7 targets with the same 7 × E0063, nothing else
whole workspace cargo check --workspace (the six crates were not there) green; no warning from any of the six new crates
guard cargo test --test no_second_parser 5 tests green 7 tests green
lib unit tests cargo test --lib 1046 passed / 0 failed at a9635e7; two later removal commits deleted more code with its tests 995 passed, 0 failed, 16 ignored (275 s). Same test set as the removal head 60ba98c by construction: git diff mimir-v2 -- src touches no test, and both trees hold 1 102 test attributes under src/ (a9635e7: 1 157)

New crates (cargo test -p … --no-fail-fast, one run, 466 s):

crate tests result
mimir-schema 10 integration green
mimir-ingest 13 unit + 28 integration (git walk, checkout, consumer, owners, binary, real driver) green
mimir-binary 14 unit + 1 fixture binary green
graphblas-sys none (links libgraphblas 7.4.0) builds
nornir-mimir-graph 20 ops + 1 proptest green
nornir-mimir-room 10 (182 s) green
mimir-driver — its own workspace, nightly-2026-10-05, cd crates/mimir-driver && cargo test 1 unit + 3 fixture dump + 2 pin green

Guard red-proof (the compiled guard binary re-run against the edited tree, each edit reverted, then green again — log guard-red.log):

edit test that went red
syn = "2" in crates/mimir-room/Cargo.toml [dependencies] no_manifest_in_the_workspace_declares_a_second_parser_or_graph_engine
use syn::visit::Visit; in crates/mimir-room/benches/zz_redproof.rs no_source_uses_syn_or_spawns_rust_analyzer
#![feature(rustc_private)] + extern crate rustc_middle; in the same file mimir_driver_is_the_one_user_of_compiler_internals
crates/zz-stray/Cargo.toml on disk, not a member every_crate_on_disk_is_a_member_or_the_one_excluded_driver

Not run here: --all-features (red on main and on the removal lane: gpu.rs wgpu API), and the root crate's integration tests (the removal lane runs those against its own head; this merge changes none of them except the guard).

5. libgraphblas-dev

graphblas-sys links the system SuiteSparse:GraphBLAS dynamically (cargo:rustc-link-lib=dylib=graphblas). Its build.rs only WARNS when the header is missing, so nothing fails until a LINK happens: cargo check stays green on a box without the library; cargo build / cargo test of anything that links mimir-graph / mimir-room fails at link time (-lgraphblas).

Today only --workspace (or -p) commands reach those crates, because the root crate does not depend on them yet.

gate / step command without libgraphblas-dev
release TEST gate — release::pipeline::run_cargo_test (src/release/pipeline.rs:650) cargo test --workspace --no-fail-fast -j 1 RED: link error in nornir-mimir-graph + nornir-mimir-room test binaries, mimir-room bin, graphblas-sys
build gate — edda nornir-build-thing buildgate.rs:961–963 cargo test --workspace --no-run --tests, cargo build --workspace --bins RED (same targets)
target check — edda nornir-testmatrix targetcheck.rs cargo build --workspace --tests --keep-going RED rows for the mimir-graph / mimir-room targets (cargo check --workspace --all-targets arm stays green)
test surface — edda nornir-testmatrix testsurface.rs:748 cargo test --workspace RED
CI .woodpecker.yml build-test cargo build --workspace, cargo test --workspace RED (apt list has no libgraphblas-dev)
edition gate — edda nornir-release-core edition.rs:333/375 cargo check --workspace green (no link; a cargo:warning)
stage rehearsal — release/stage.rs:628 cargo publish --workspace --no-verify no link, so not tripped by the library. Separate finding: it would now try to publish the five publishable mimir crates, and mimir-ingest depends on mimir-binary (publish = false) — NOT verified here; decide publish = false on all mimir crates until landing
edition gate, opt-in docs rehearsal — edition.rs:403 cargo doc --workspace --all-features trips on EVERY box without libclang-dev, library or not: --all-features switches on graphblas-sys/regenerate (bindgen at build time)

After lane A/B make the root crate depend on mimir-room, every gate that LINKS the root crate trips too (cargo test -p nornir … lines in CI, xtask feature-matrix builds, tests/atom_linkage.rs, the solo tests), and nornir-server / dwarves-full gain a RUNTIME dependency on libgraphblas.so.7 (+ libgomp).

Proposed fix (not implemented): a build requirement, not a feature flag — plus one narrow feature for linkage.

  1. GraphBLAS is THE engine (plan §4), not an optional extra. A flag that lets the workspace build without it creates a second "engine absent" state on every surface and a new matrix dimension — the shape §7 forbids. And a root-crate feature would not help the gates above anyway: they say --workspace, which builds every member regardless of root features.
  2. So: add libgraphblas-dev where builds link — .woodpecker.yml apt list (check the image's package is 7.x: graphblas-sys refuses another major), the nordisk-cargo-toolchain image / cargo-test.Containerfile (edda nornir-build-thing), README build prerequisites — and libgraphblas7 in every runtime image / appliance compose that ships nornir-server or dwarves-full (ops kits: both gunnar and holger).
  3. One preflight line instead of a linker wall: the gates that link (run_cargo_test, the build gate) probe for the library first and fail with SuiteSparse:GraphBLAS 7.x not found — apt-get install libgraphblas-dev.
  4. The ONE feature gate that buys something: the root crate's dependency on mimir-room is optional (code-room), ON in server / full-server / solo, OFF in the thin atoms (nornir-cli thin, nornir-ui, wasm). Thin clients ask the server over gRPC and must not link the engine (tests/atom_linkage.rs already audits that nornir-ui carries no server symbols). That gates LINKAGE of thin atoms; it is not a way to run a server without the engine.
  5. Take regenerate out of cargo features (an env var read by build.rs, or an xtask verb) so --all-features never runs bindgen.

Owner decision inside this: dynamic system library (above) vs a vendored static build of GraphBLAS for self-contained sold media (cmake, minutes of build, no package on the target).

6. Wiring map (for the next lanes — no code here)

6.1 The handle every lane uses

One log per workspace, one consumer writing it, rooms opened from it:

<registry_root>/<ws>/git/<member>/              server's clone (monitor::fetch_workspace) — KEPT
<registry_root>/<ws>/builds/mimir_v2/lake/      skade lake, namespace mimir_v2 (EventLog) — NEW
<registry_root>/<ws>/builds/mimir_v2/clones/<member>/   the consumer's own clone (RepoSpec.worktree)
<registry_root>/<ws>/builds/mimir_v2/target/<member>/   nightly target dir (DriverDumper.target_dir)
<registry_root>/<ws>/builds/mimir_v2/out/<member>/      driver IPC dumps (DriverDumper.out_root)
<registry_root>/<ws>/builds/mimir_v2/open-set.bin       open-set cache (Consumer::boot)

The lake is its OWN skade catalog, not builds/catalog.redb: redb is single-writer and IcebergWarehouse holds that file; plan §7.5 wants a new namespace nobody else reads. (Decision D1.)

6.2 Where the deleted poll loop + populate are replaced (lane A)

deleted at c18e6d1 replaced by
server_grpc.rs::spawn_server_poll_loop + its call in run_with_policy (the gap right after eprintln!("nornir-server: listening on {addr}"), now ~server_grpc.rs:5763) spawn the consumer loop at the same place, same shape (tokio interval, MissedTickBehavior::Skip, body on spawn_blocking)
server_grpc.rs::server_sweep per workspace: reg.list() → Mode::Monitored → nornir_poll_cadence due check (as before) → fetch_served (KEPT, server_grpc.rs:5492) → clone_events::record_sweep_report (KEPT) → for every member that is behind → Consumer::consume(&RepoSpec) inside ctx.work_gate
workspace_needs_build (current_snapshot empty) and monitor::knowledge_is_gone "behind on its offset": EventLog::offsets() has no row for the member, or offset.sha != MemberState.last_seen_sha. Boot, a wiped lake and "a week away" are the same path (plan §3)
republish_served / republish_served_locked → monitor::republish_with steps 1–9 Consumer::step (classify → checkout_changed → Dumper::dump → delta vs open set → ONE atomic release: events + offset + ledger). reg.set_snapshot(ws, …) gets the log's Stamp (or repo@seq list)
ctx.lens_chain.submit_feeds / submit (re-queue 13 lenses per republish) nothing — no result cache. Optional warm: Room::at(log, &Scope::current()) after a batch
monitor::PhaseJobs (job children per populate phase) one job row per (member, commit) from CommitReport + PhaseMs (needs I2). Job kinds live in edda (jera::jobs::kind): reuse WORKSPACE_REPUBLISH as the parent so no cross-repo change is needed
monitor::sync_once / monitor::spawn_poll_loop (CLI / fat mode) nornir workspace populate runs the same per-workspace consume inline
Workspaces.Register eager build (register, server_grpc.rs:4524, the block at ~4720–4740) and Workspaces.Fetch{force} (fetch, 4777, ~4809) call the same consume; the "populate": NOT_WIRED_NOTE job detail becomes the commit reports
Workspaces.Repopulate (repopulate, 4910) same wire type: purged + dropped = the v1 tables dropped once (plan §7.5, I6); generation = EventLog::identity() / stamp; queued = members behind; in_flight = member being consumed; refusal = driver not installed / lake not open
Ops.ScipIngest (scip_ingest, 5344) decision D4: delete the verb (edda nornir-wire proto change) or map it to "consume this member now"
status EventLog::offsets(), runs(repo), bin_runs(repo), Room::stats() — carried on EXISTING wires (Viz.Jobs ledger, the Repopulate report, workspace status JSON). A new RPC is an edda nornir-wire change: avoid in the first pass

Registry → repos → offsets:

Registry::list() → Workspace{name, mode, poll, members: Vec<MemberState{name, remote, git_ref, last_seen_sha}>}
  → RepoSpec { name:  member.name,
               source: <root>/<ws>/git/<name>      (local path; the fetch keeps auth + HTTPS in ONE place)
               branch: member.git_ref or "main",
               worktree: <root>/<ws>/builds/mimir_v2/clones/<name>,
               initial_offset: D2 }
  → offsets live in mimir_v2.consumer_offsets ONLY (never copied into the registry)

Non-graph feeds that rode the deleted populate and now have NO trigger (P7): they need a side-step on consume, or their lenses stay blind although nothing about them changed — git_heat_facts (knowledge::git_heat::scan_repo, today only release::pipeline::run_pipeline writes it), the sbom / vuln scan, the dep-graph snapshot, the tantivy index, embeddings (monitor::embed_workspace is kept, untriggered).

6.3 What the room API lacks

Read side (crates/mimir-room):

id missing who needs it
R1 node attributes: name, parent, file, is_test, ty, cfg — NodeView has moniker/kind/level/crate/version/repo/origin/provenance only (NODE_READ skips the rest) every reader that prints a symbol row; test inventory; file-keyed lenses
R2 spans: code_span_events is not folded at all — node span (file, start, end), edge sites (call-site lines), and file → nodes Knowledge rows, defined_in, villa sites, subclone / chunker / embeddings, funnel anchors
R3 name → node: exact / path suffix (a::b::c) / substring, kind filter, limit; and linker symbol → node. Room::ordinal takes an exact moniker only SymbolLookup, Callers/Callees by name, funnel retriever, Introspect / Dwarf
R4 bounded reverse walk: dependents_by has no max_hops (the private walk already takes one; forward reach exposes it) direct callers, villa, reachable_vuln
R5 seeds as a Mask / ordinals — reach / dependents* take &[&str] monikers only every "from this crate / these tests" walk
R6 a path A → B along kinds, and forward hop layers from seeds (min_hops_to goes TO a target set) CallPath, PathBetween, metro trace
R7 edge export (from, to, count) of a kind inside a mask, capped; the matrices are boolean, the count column is not read callgraph pane, edge-level reports
R8 quotient: collapse nodes by crate / module (CONTAINS ancestor) / repo / file → group×group edge counts + per-group node counts and degrees arch board, Viz.Knowledge bubbles, release deep edges, bench_gaps, churn complexity, world_impact
R9 the binary stream's symbol rows (size, section, role, artifact, demangled) — the room keeps one flag (linked) generic_cost, binary_drift
R10 duplicate keys: which nodes had key_dup / distinct payload hashes (the ledger counts them, the fold merges them) name_collision, symbol_identity
R11 semirings beyond boolean reach: (max, min) bottleneck over node weights; mimir-graph::ops has reach / min_hops / fan / similarity trust
R12 Answer / Evidence are not Serialize and have no machine state (complete / partial / empty-fact) every JSON front door (adapter otherwise hand-maps to feeds + evidence_note)
R13 handle model: Room::open(&EventLog, …), and the EventLog is OWNED by the Consumer; skade Warehouse is single-process and not Clone. No room can open while a consume runs (minutes), and no second process (CLI fat mode, MCP stdio) can read a lake the server holds ALL readers — the first thing lane A must settle (shared read handle + read-only open of a held lake, the IcebergWarehouse::open_read_only catalog-copy shape)
R14 sync entry: Room::open is async; viz / CLI / MCP call sites are sync viz panes, CLI
R15 typed "repo has no consumer offset" — Scope::resolve returns an anyhow string readers must answer not_consumed, not an internal error

Producer / schema gaps (a surface stays not_wired until its row here closes; none is a room API question):

id gap blocks
P1 CLOSED for visibility + docs by lane D (mimir-v2-wire-d, mimir-schema 2): code_nodes carries visibility (nominal, as rustc resolves it: pub / pub(crate) / pub(in crate::a::b) / private), effective_visibility (rustc's effective visibility: direct / reexported / reachable / reachable_impl_trait / internal), doc (first line) and has_doc; all four are payload (a change is one node event). CLOSED for attributes by lane E (mimir-schema 3): ignored (#[ignore] on a test fn; null on a node that is not a test) and doc_hidden (the item ITSELF carries #[doc(hidden)]; null where has_doc is null and on a cfg_gated_out stub) — nullable booleans, payload. STILL OPEN: a rendered signature (params are PART nodes with ty; the return type is not there), a doc-line count undoc_api, autonom surface, release doctor API-surface delta, Knowledge symbol rows (visibility), data_reach public entry points: the producer side is done, they read the columns once R1 exposes them. Test is_heavy = ignored (wired, §9); undoc_api skips doc_hidden (own attribute or a hidden module above it)
P2 CLOSED by lane D, for every cfg that gates CODE: feature nodes (kind feature, one per crate@version feature, defined_here = the manifest declares it); ENABLES (feature → feature / dependency crate / dependency feature, and crate → every feature it was COMPILED with) and ENABLES_WEAK (x?/f); GATED_BY item → feature with negated and partial. partial = false: the item's own #[cfg] — from rustc's CfgTrace for the items it kept, and from a token scan (mimir-driver/src/cfg_scan.rs, proc-macro2, allow-listed in the guard) for the items it compiled out, which are cfg_gated_out STUB nodes (name, file, line, predicate, written visibility; nothing resolved, no CALLS / USES_TYPE). partial = true: a cfg on a statement / expression / match arm / use / macro call (cfg_if!) or a cfg!(…) INSIDE the item (token scan, both configurations). LOWER BOUND that stays, with its size in every dump's stats (feature_cfg_mentions − feature_cfg_attributed − feature_cfg_partial): the predicate of cfg_attr (it switches an attribute, not code) is no edge; a cfg written by a macro with a $metavariable feature name is an edge only on the kept side; the token scan runs on the workspace's own crates (MIMIR_CFG_SCAN=all adds dependencies), so a dependency has own-gate edges of KEPT items only zombie_features: NeverBuilt = a declared feature with no ENABLES from a crate node (exact); gated code = GATED_BY sources (partial tells an item that exists only under the feature from one that merely contains gated code); a stub has no inbound edges by construction, so it is evidence for never-built, NEVER for unreached. Knowledge feature-gates: both configurations. Both read cfg / the edges once R1 and the lens are wired
P3 CLOSED by lane K (mimir-v2-wire-k, mimir-schema 4, §10): the driver writes TOUCHES_TABLE (fn → table node, access read / write / both, sites) where a table name meets a read / write of the storage engine; a runtime name is an edge to the one <unresolved> table node. STILL OPEN: calls through &dyn Warehouse made inside a crate that sees no impl (edda's release ledger and knowledge view) — they need the annotation on the trait (§10) data_reach, the table-lineage template, the board's Table nodes and the metro termini read it (§10); trust's access docking does not yet
P4 binary CALL edges have no producer (DWARF and LLVM call graphs deleted; mimir-binary writes symbols + INSTANCE_OF) binary_drift edge half; Introspect / Dwarf verbs can only answer "source CALLS between LINKED nodes"
P5 world ring callers, COMPATIBLE, semver_diffs: the coderoom ingest is deleted world_impact world half, coderoom compat / what-breaks
P6 operator / closure / fn-pointer calls are not recorded (lane 3 open item) every reachability answer is a LOWER bound — the evidence must say so
P7 non-graph feeds without a trigger (§6.2) change_risk, churn, anomaly, undoc_api, bench_gaps, weak_tests, reachable_vuln

Write side (crates/mimir-ingest):

id missing who needs it
I1 one Consumer owns ONE Dumper, and DriverDumper is per repo (repo, target_dir, out_root, pending, mirror). A workspace has N members on ONE log the consumer loop
I2 consume() reports only when it returns: no per-commit progress sink, no max_commits bound, no cancel job ledger rows, fairness between members, shutdown
I3 CLOSED (lane A: the dry-run delta; lane E: the release doctor asks for it, §9) release doctor semantic blast
I4 first offset: initial_offset: None replays from the ROOT commit. The field exists — the policy is decision D2 first registration of a big repo
I5 nothing probes cargo-mimir-dump + the nightly before the first commit Repopulate refusal, honest "driver not installed"
I6 dropping the v1 tables (plan §7.5) is not in ingest — it is an IcebergWarehouse verb Repopulate purged / dropped

6.4 Lane B — the lenses (src/mimir_lens)

Mimir.* RPCs, the MCP mimir_* tools and nornir mimir analyze all reach the same 18 lenses through mimir_lens::answer (+ subclone, MCP only). Wired on mimir-v2-wire-b (the two vector lenses on mimir-v2-wire-j; only data_reach still answers not_wired): the scoring / report halves are kept, their inputs are typed facts read from the code room by src/mimir_lens/room_feed (and the warehouse for the feeds that are not a graph). Every answer is the lens envelope with lens_meta.state = complete | partial | empty_fact | empty_gap, or a refusal state (not_wired, not_consumed, no_room, lake_unavailable, not_linked).

lens state kept half room feed (as wired) still missing — said in the answer
anomaly live whole (anomaly::analyze over change_series) none — git_heat_facts, every generation a file needs 3 scans for a baseline
villa live classify re-keyed to the resolved callee path + node kind, known_tools, report name lookup of the impure std / libc / nix items → edges(CALLS, to = them) → one finding per positioned site P6
zombie_features live Unreached / NeverBuilt split, ManifestFacts, features_in_cfg (cross-check of cfg strings against the edges) feature nodes; built = ENABLES from a crate node; GATED_BY with negated / partial from EdgeOut; inbound = CALLS + USES_TYPE sites; a cfg_gated_out stub is evidence for never-built only lower bounds of P2 (cfg_attr, $metavariable, dependencies = kept items' own gates), P6
change_risk live callers × churn × (1 − coverage) per file, a node-level reduce per callable: CALLS sites arriving, in the test-reachable set (reach(tests, [CALLS]) ∪ TESTS); cfg(test) code is test code coverage_fn (line coverage) when empty; semver_diffs (P5); P6
churn live band filter + hotspot rank functions per file as the complexity —
bench_gaps live hot-path rank, perf_hint, arm_covers_module, the gap per module: CALLS sites, files, test coverage bench_runs / bench_results when empty; P6
undoc_api live ranking by churn, authors callables with effective visibility direct / reexported, has_doc; trait-impl methods and cfg(test) code are no API —
weak_tests live weak::weak_tests whole (churn keyed (repo, test)) outcome name → the ONE #[test] node it is a path suffix of → reach → files → churn unmatched names are counted; P6
reachable_vuln live vuln_findings × sbom_components join, refuse / hold CALLS + USES_TYPE sites from the repo's own code into each crate@version direct use only (ring off); P6
generic_cost live bloat::classify, the tax fold + report mono_instance nodes, INSTANCE_OF, symbol sizes per artifact (Room::symbols) a repo without a binary stream
binary_drift live (node half) is_glue / is_runtime / is_derive_impl / suspicious_tag, findings, report per linked symbol: first party + explained by the source (provenance both, or an instance of a source item) P4: no binary call edges — the edge half is kept and not evaluated; attestation reads SYMBOLS ONLY
name_collision live report shape; conflation = edges into a MERGED id, leaf names are the labelled baseline Room::duplicate_keys over callables, CALLS edges out of own code P6
symbol_identity live class split + verdict: repeated key in one unit / one id in several files (ours) vs units that disagree Room::duplicate_keys over own definitions, spans —
trust live (max, min) semiring definition, local_trust, Bounds, ACT / HOLD / REFUSE, weakest hop Room::bottleneck from the entry surface over local-trust weights; weakest hop by Room::path through nodes at least as trusted the sensitive-access term is not applied (the edges exist since lane K; this lens does not read them yet); P6
world_impact live (workspace half) rank = callers + constellation dependents, breaking first Room::diff of a repo's last two consumer ordinals per crate; breaking = a removed public item; callers = Room::dependents grouped by repo; dep_graph_edges P5: no crates.io ring, no published semver diff; a repo needs two consumed commits
subclone live whole item spans from the room's node spans (nodes_in_file); span-less files counted files the consumed configuration did not compile
data_reach live (lane K, §10) sensitivity_of, ranking Room::edges(TOUCHES_TABLE) from the request's code (the direct touch: access, sites), one backward Room::dependents_by([fn], [CALLS]) per toucher inside the scope (the reachers), effective_visibility (public entry); the runtime-named sites counted and listed under unresolved P6; a lower bound by what §10 names as not covered
similar_code, semantic_clones not_wired scoring halves in mimir_clones.rs / mimir_analyze.rs — the embeddings lane (item embeddings from driver spans)

| data_reach | not_wired | sensitivity_of, ranking | — | P3 TOUCHES_TABLE has no producer | | similar_code, semantic_clones | live (lane J, §10) | analyze_duplicates / analyze_semantic_clones whole: the one predicate shares_one_chunk_vector, the trivial-family suppression, the precision filters | one vector per function node (vector::fnvec: node id → content hash → vector); signature from the PARAM nodes, callee set from CALLS, impl header from the block + IMPLEMENTS | no vector → vectors_missing / no_embedder (no count at all); N of M without one, or vectors of an older seq than the room → partial with the numbers; P1 (no return type in the signature); P6; the dependency ring's vectors are written but not compared |

The 6 mimir_calibrate tests deleted with the syn corpus are back (mimir_calibrate::tests::on_a_room_corpus): the corpus is lifted out of a code room (room_feed::corpus::from_room — item spans, parameter nodes, CALLS edges) on a generated tree; real_lake_calibration_corpus (ignored) runs the same oracle on a real lake. room_tests::real_lake_every_lens (ignored) asks every lens against a real lake.

6.5 Lane C — the other readers

surface kept half room feed missing
Knowledge.SymbolLookup / DefinedIn (+ MCP knowledge_* via knowledge_query_json, CLI knowledge query) wire types, KnowledgeView row model (edda leaf) name lookup → node rows with file:line R3, R1, R2, P1 (visibility)
Knowledge.Callers / Callees wire types callees reach(seed, [CALLS], Some(1)); callers dependents_by(seed, [CALLS]) one hop; site lines R3, R4, R2, R7 (count)
Knowledge.CallPath wire type (NameList) a CALLS path R6
knowledge feature-gates query — local nodes with a cfg R1 (cfg); P2 (active configuration only)
Graph-RAG knowledge context (knowledge::context::context_slices, takes the view as INPUT) the join, expand_call_radius, hydrate_source a bounded neighbourhood of the seeds (reach + dependents_by, N hops) with rows + spans R4, R1, R2
vector slices-index / slices-search index + search item spans R2
Introspect.Callers / Callees / PathBetween, Dwarf.Callers / Callees / CallPath (+ MCP callers_of / callees_of / path_between / dwarf_call*, CLI introspect …) wire types — ONE reader for both services source CALLS restricted to linked nodes: within = mask(provenances=[both]); INSTANCE_OF maps an instance to its generic. Evidence: inlined / indirect calls absent R3 (symbol → node), R6, R4, P4
Viz.Knowledge (🗺) draw_bubble_chart, KnowledgeSummary / RepoScanSummary / Bubble Room::stats() per repo (nodes by kind, edges by kind) + per-crate node / call / file counts + 30-day heat R8, P7
Viz.Architecture board (🏛) + Ops.RunArch + CLI / MCP arch * board renderer, wire type the module / crate quotient of CALLS ∪ USES_TYPE; history = Scope::repo(repo, Some(seq)) + Room::diff (replaces the historized architecture_wiring). Ops.RunArch has nothing left to generate (D4) R8
metro (🚇 tab + arch metro fuel, viz::metro_feed::build_metro_map, arch::metro) line / stop model, renderer entrypoint → reach(entry, [CALLS]) in hop layers; termini = tables (live, lane K §10: ArchPayload.access = each gRPC handler's NEAREST table along CALLS + TOUCHES_TABLE); lit overlay = surface_coverage R6, R1
viz callgraph lens (viz::callgraph) renderer, provenance colours (test_inject_graph_with_provenance shape) edges of a crate / repo mask, capped; provenance from NodeView R7
viz release tab deep edges (viz::release_depdag) DAG renderer, deep_state repo × repo quotient of CALLS ∪ USES_TYPE on Scope::current() R8
Viz.TestMatrix inventory, CLI test list the ok / fail / X join, results half (live) is_test nodes with crate / module / name / file:line live: is_heavy = the node's ignored (§9); a test consumed before mimir-schema 3 has none and is named as such
autonom completeness gate (autonom::gather_with_utfallsrum) — FAILS CLOSED allowlist, utfallsrum threshold, GateState, persist → surface_coverage surface = public local fns; covered = TESTS targets ∪ reach(tests, [CALLS]) P1, R1, R5 — stays closed until P1
release doctor semantic blast (release::doctor::run_with_warehouse_semantic_blast) — FAILS CLOSED link gate, audit record, fixers delta of the DIRTY tree vs the open set; then Room::dependents(changed) (the Jikes walk exists) on Scope::current() WIRED by lane E (§9): code_room::reader::pending_blast; still fails closed, typed (release::doctor::BlastRefused), when the blast cannot be derived
funnel decompose / funnel prompt grounding (funnel::decompose::decompose(task, retrievers, …)) decomposition, judge loop a Retriever over the room: name match → anchors with file R3, R2
Mimir.RepoOverview knowledge block (mimir::repo_overview) deps / dependents / build order Room::stats() for Scope::repo(repo, None) + offset sha / seq + evidence.missing none in the room (R13 / R14 only) — first lane C surface that can go live
Mimir SQL templates (MCP mimir_sql_template) — def-use = one-hop dependents_by over all kinds; change-radius = dependents; call-chain = path; table lineage = TOUCHES_TABLE (live, lane K §10) R4, R6
coderoom snapshot-diff / what-breaks (CLI) — Room::diff (exists) / diff + dependents P1 for "breaks"; ingest / compat = P5

6.6 Three lanes, disjoint files

Proposed first step, done by lane A ALONE before B and C branch — the seam commit. It adds no behaviour:

  1. root Cargo.toml: optional deps mimir-ingest, nornir-mimir-room behind code-room (§5 point 4); src/lib.rs: pub mod code_room;.
  2. src/code_room/mod.rs: the per-workspace handle (lake path, shared log handle, room(scope)), built in open_workspace_ctx, held as a new WorkspaceCtx field (WorkspaceCtx.mimir is already the dep graph — pick another name).
  3. Two dispatch points that today return exactly what the stubs return: mimir_lens::answer(lens, handle, params) -> Value (file owned by B) and code_room::reader::* (file owned by C). Every not_wired site in server_grpc.rs, mcp_stdio.rs and bin/nornir.rs is re-pointed at them ONCE.

After the seam, B and C edit only their own modules; the three shared files are touched again only by A.

lane A — server wiring lane B — lenses lane C — other readers
owns (new) src/code_room/{mod,consumer_loop,status}.rs src/mimir_lens/room_feed.rs (room → lens inputs, Evidence → feeds / evidence_note), body of mimir_lens::answer src/code_room/reader.rs
owns (existing) src/monitor.rs, src/registry.rs, src/warehouse/populate_completeness.rs, src/warehouse/workspace_events.rs, src/warehouse/clone_events.rs, root Cargo.toml, src/lib.rs, .woodpecker.yml, .nornir/running-as-a-server.md src/mimir_lens/*.rs, src/mimir_analyze.rs, src/mimir_calibrate.rs, src/mimir_clones.rs, src/mimir_subclone.rs src/knowledge/{query,context,mod}.rs, src/mimir.rs, src/autonom/mod.rs, src/release/{doctor,pipeline}.rs, src/funnel/decompose.rs, src/introspect/*, crates/nornir-introspect, src/arch/*, src/viz/{knowledge,arch_tab,metro_tab,metro_feed,callgraph,release_depdag,release_tab,remote}.rs, src/warehouse/sql.rs, src/vector/*
crates crates/mimir-ingest (I1–I6) crates/mimir-graph (R11) —
crates/mimir-room R13, R14, R15 R9, R10, R11, R12 R1–R8

Unavoidable overlaps and the split:

file A B C
src/server_grpc.rs (6 524 lines) WorkspaceCtx (136–175), WorkspacesSvc (4419–4942: register 4524, fetch 4777, repopulate 4910), OpsSvc::scip_ingest (5344), open_workspace_ctx (5379), fetch_served (5492), run_with_policy (5644–), tests mod (6434–) MimirSvc lens handlers only (2836–3090) IntrospectSvc (2321–2341), KnowledgeSvc (2346–2400), DwarfSvc (3428–3447), VizSvc::test_matrix 3927 / knowledge 3980 / architecture 4031, OpsSvc::run_arch 5330. (MimirSvc::repo_overview 2807 calls mimir::repo_overview — C edits src/mimir.rs, not the handler)
src/mcp_stdio.rs (5 580) workspace tools (workspace_register 1209 and the fetch / populate tools after it) mimir_* tools 1690–2226 knowledge_context 2379, mimir_sql_template 2630, callers_of / callees_of / path_between 3688–3731, arch_graph_for 4128, dwarf_* 4285–4328, knowledge_query_json 4722
src/bin/nornir.rs (16 763) run_workspace 8305–8550 run_mimir_analyze 9550–9600 run_funnel_prompt 4400, run_funnel_decompose 4774, run_map 5831, run_arch 6215, run_knowledge 6241, run_knowledge_context 6339, run_coderoom 9440, run_test_list 12640, run_introspect 15204, run_vector 15852
crates/mimir-room/src/query.rs, lib.rs, snapshot.rs handle model (R13–R15) — node attributes, spans, lookup, path, edges, quotient (R1–R8)

Each lane's hunks in the three big files sit in different impl blocks / functions, hundreds of lines apart, so they merge textually even without the seam; the seam makes them not overlap at all. The real contention is crates/mimir-room: B needs R1 / R2 / R4 / R5 / R8 that C owns. Order: A's seam + R13 first; then C lands R1–R8 as its first commits (API only, with tests) and B rebases on them; B's room work (R9–R12) is in files C does not open (ops.rs in mimir-graph, a new symbols.rs in mimir-room).

What can go live without any missing API, as the proof each lane starts with: A — consume one member + Repopulate report; B — anomaly (needs only P7's git-heat trigger); C — Mimir.RepoOverview knowledge block (Room::stats()).

7. Decisions needed

id decision suggestion
D1 lake location: own skade catalog under builds/mimir_v2/lake vs tables inside builds/catalog.redb own catalog
D2 first offset of a newly registered member: root commit (full history, hours for a big repo) vs tip tip (pass tip^ as initial_offset); backfill on request via Repopulate
D3 release doctor on a DIRTY tree: dry-run delta in ingest (I3) vs "the bump must be a commit on a branch, diff two seqs" dry-run delta — the gate runs before the commit exists
D4 Ops.ScipIngest and Ops.RunArch: delete the verbs (edda nornir-wire) vs re-purpose delete; they name deleted roads
D5 GraphBLAS: system library + package in images vs vendored static (§5) system library
D6 driver delivery to a server box: mimir-driver is a separate nightly artifact (two binaries + the pinned toolchain with rustc-dev) an ops-kit step; the server answers "driver not installed" by name until then (I5)
D7 publish = false on the mimir crates until the branch lands (§5 stage row) yes

8. Lane A — what landed (mimir-v2-wire-a, 2026-10-13)

step what where
seam code-room feature; CodeRoom on WorkspaceCtx; Reach, Refusal, State; the two dispatch points mimir_lens::answer(lens, reach, params) (B) and code_room::reader::{answer, knowledge_view, arch_graph} (C); every front door re-pointed once; guard tests/code_room_seam.rs src/code_room/{mod,reader}.rs, src/mimir_lens/mod.rs, the three shared files
R13 EventLog = a shareable handle: many readers (pin() = ONE committed state of the lake, never waits for the writer), one writer (LogWriter seat, held by the Consumer), ONE process (LakeHeld), and the lake's OWN runtime (an Iceberg table handle captures the runtime it was built on; the catalog caches handles) crates/mimir-ingest/src/log.rs
R14 Room::at_blocking / open_blocking, EventLog::block_on, CodeRoom::room(scope) crates/mimir-room/src/lib.rs, src/code_room/mod.rs
R15 ScopeError::{NotConsumed, PastOffset, NegativeSeq} → State::NotConsumed / InvalidArgument crates/mimir-room/src/scope.rs
I1 DumperPool<D> (a dumper per repo behind one consumer), impl Dumper for Box<D> crates/mimir-ingest/src/dump.rs
I2 Consumer::consume_run(repo, Run): budget, cancel between commits, on_begin / on_commit, replay_from crates/mimir-ingest/src/consumer.rs
I3 the dry-run delta (decision D3): DriverDumper::dump_tree(tree) (a FULL dump of every local unit of an uncommitted tree, a dumper of its own, the tree only read) + dry_delta(log, open, repo, dump) → DryDelta { nodes / edges / spans: added · changed · removed, failed_units } (Consumer::dry_delta, EventLog::dry_delta for a reader with no consumer); nornir CodeRoom::dry_run(driver, member, tree) / dry_delta_of. Nothing is written. The release doctor (src/release/doctor.rs, lane C) is NOT wired to it yet: reach.room.dry_run(…) → DryDelta::touched_nodes() → Room::dependents crates/mimir-ingest/src/{consumer,driver,log}.rs, src/code_room/consumer_loop.rs
I4 first offset = TIP (git::tip + offset_before); `--from <sha date> (git::resolve_from`); the first commit of a stream that does not start at the root is dumped even when docs-only
I5 DriverHost::{Installed, Wrapped} (the invocation seam), DriverSpec::probe → DriverUnavailable::*; nornir side code_room::driver::Settings → State::DriverNotAvailable crates/mimir-ingest/src/driver.rs, src/code_room/driver.rs
I6 status::drop_v1_tables (the nine syn-era tables, lens_*, the chain's ledgers) behind Repopulate{purge} src/code_room/status.rs
consumer CodeRoom::consume / status / behind / room_line; server spawn_consumer_loop, server_sweep, consume_served (the ONE door), Register / Fetch / Repopulate / Remove; CLI fat mode src/code_room/{consumer_loop,status}.rs, src/server_grpc.rs, src/bin/nornir.rs
P7 the feeds ride the offsets: git_heat, sbom, dep_graph, code_index, embeddings; ledger mimir_v2.consumer_feed_runs src/code_room/feeds.rs
D4 CLI arch generate, viz::remote::run_arch deleted; the two Ops trait methods answer the deleted-verb status until the proto drops the rpcs src/bin/nornir.rs, src/viz/remote.rs, src/server_grpc.rs
D5 code_room::preflight in release::pipeline::run_cargo_test; .woodpecker.yml src/code_room/preflight.rs
D7 publish = false on the five mimir crates that lacked it crates/*/Cargo.toml

Decisions as implemented: D1 the lake is <ws>/builds/mimir_v2/lake, its own catalog. D2 TIP, backfill by nornir workspace populate <ws> --from <sha|date> (inline; the wire has no field). D6 the driver is located by settings and probed; NORNIR_MIMIR_DRIVER_HOST is the wrapper line of the toolchain image. A lake of an OLDER mimir-schema that only lacks nullable columns is evolved in place on open (lane E, §9); any other difference is refused (LakeSchemaMismatch), and the message names the directory to remove.

Follow-ups outside this repo (edda)

where what
nornir-wire proto CLOSED by lane F (edda mimir-v2-consumers-2026-10-13): rpc RunArch / rpc ScipIngest + RunArchRequest / ScipIngestRequest are out of the proto, the two tombstone methods are out of server_grpc.rs (no caller in any sibling)
nornir-wire proto a from / member field on WorkspaceFetchRequest (or WorkspaceRepopulateRequest) so a backfill can be asked over the wire; a typed status RPC instead of strings in WorkspaceRepopulateReport
nornir-build-thing buildgate.rs:961–963 GraphBLAS preflight before cargo test --workspace --no-run --tests / cargo build --workspace --bins
nornir-testmatrix targetcheck.rs, testsurface.rs:748 the same preflight before cargo build --workspace --tests --keep-going / cargo test --workspace
nornir-release-core edition.rs:403 cargo doc --workspace --all-features switches on graphblas-sys/regenerate (bindgen, needs libclang-dev): take regenerate out of cargo features (land map §5.5)
toolchain image / cargo-test.Containerfile libgraphblas-dev; the driver pair + the pinned nightly with rustc-dev (D6)
runtime images / ops kits (gunnar and holger) libgraphblas7 + libgomp1 wherever nornir-server / dwarves-full ship
nornir-git gitio::fetch_repo CLOSED by lane F (same edda branch): (1) the re-fetch opens through open_for_ref_edits (the committer fallback); (2) a non-SSH re-fetch follows the remote's HEAD — branch, HEAD and working tree — like ssh_sync, so clone_or_fetch returns the new sha and a local-path / https member shows as behind. The consumer's own clone still does not go through it (mimir_ingest::git::sync_clone: there HEAD and the tree are the checkout step's)
nornir-warehouse-trait CLOSED by lane F: Warehouse::append_symbol_scan_async and Warehouse::load_latest_scip are gone from the trait (the Iceberg and remote impls with them)
nornir-release-core CLOSED by lane F: semantic_blast::compute no longer reads scip_occurrences / symbol_facts / call_edges; a bump with dependents fails with the typed CallGraphNotProduced. rank(view, …) is the kept half for a caller that holds a call graph; nornir's own doctor (lane E) builds its SemanticBlast from the room's dependents walk and calls neither
jera::jobs::kind kinds of its own for a consumed commit and a feed run (today: knowledge_member, and knowledge_scan / security_member / index_build / vector_embed)

Known limits

9. Lane E — closing items (mimir-v2-wire-e, 2026-10-13)

item what where
schema evolution a lake at an OLDER mimir-schema is evolved in place when it is opened: per table ONE metadata commit (an Iceberg transaction through skade's catalog: update_schema().add_column(optional) + the table properties mimir.schema = the version, mimir.added = the columns added in place). No data file is touched, no wipe. Evolved = the table only LACKS nullable payload columns outside the key (a bookkeeping table: any nullable column). Refused, typed (LakeSchemaMismatch { why: Unevolvable::… }): a column this build does not have, a retyped column, a missing key / non-nullable column, another sort order, a lake stamped NEWER than the build crates/mimir-ingest/src/{log,schema,delta}.rs
… the hash the payload hash covers a column added in place only where the row HAS a value (PayloadSpec::added, read from mimir.added): a row that is null there hashes as it did before the column existed, so the next consume writes an event only for a row the producer now says something about. A fresh lake hashes every column as before (no change for lakes that never evolved) delta::row_hashes
… the readers Iceberg scans at the SNAPSHOT's schema and an evolution takes no snapshot: until the first release after it, a scan is asked only for the columns the snapshot has and the others come back as nulls (scannable / null_filled); writes go in the table's column order (in_table_order: skade writes by position, added columns are last) log.rs
… the dumps the dumper's mirror is stamped log identity + schema (EventLog::dump_identity): the first consume after an evolution discards the kept dumps and dumps every unit again, once — so the new columns get their values at the member's next commit, for every crate, not only the ones the commit touched log.rs, consumer.rs
P1 attributes mimir-schema 3: code_nodes.ignored, code_nodes.doc_hidden (nullable booleans, appended); the driver reads them from rustc (AttributeKind::Ignore, tcx.is_doc_hidden); Viz.TestMatrix / Test.List is_heavy = ignored (a test with no value is counted and named, never false); undoc_api leaves out #[doc(hidden)] functions (own attribute or a hidden module above) crates/mimir-schema, crates/mimir-driver/src/walk.rs, src/code_room/reader/wired.rs, src/mimir_lens/room_feed/{graph,lenses}.rs
doctor blast (D3) release::doctor::run_with_warehouse_semantic_blast(repos, policy, reach, driver): per dirty repo code_room::reader::pending_blast — dry-run dump of the dirty tree → delta against the consumed state → the member's symbols the tree changes / removes (a param lifts to its fn, a field also to its struct; a version bump alone removes nothing: same path under the new version = same item) → the sites in OTHER repos that name them (one hop back over CALLS / USES_TYPE / IMPLEMENTS / FOR_TYPE / READS_FIELD) + the transitive Jikes count → SemanticBlast (source = "code_room"), the input of gate::semantic_link_gate. Fails closed, typed (BlastRefused): no room, lake held, the repo or a repo that depends on it not consumed, driver not available, the dirty tree does not compile. A dirty tree that changes no node = an empty blast. The one in-tree door: MCP release_doctor with semantic_blast: true src/code_room/reader/{blast.rs,../reader.rs}, src/release/doctor.rs, src/mcp_stdio.rs
heat Viz.Knowledge heat_30d = commits of the last 30 days on the crate's hottest file: each repo's newest git_heat_facts scan joined on the files the crate's items lie in. git_heat_facts is named missing only when no scan was read (whole table, or per repo) src/code_room/reader/wired.rs
target ids two targets of one package never share an id: a non-lib target's nodes start with <kind>=<crate_name>: (moniker::target_prefix: bin=lgz: , test=x: , bench=x: , example=x: ); which target a unit is comes from cargo metadata (the target whose root file is rustc's input), not from the crate name. Before: <crate_name>: for every non-lib target, and "lib-like" decided by name — a bin named like its lib had the lib's ids in its --test harness, and a test and a bench of one name shared all of theirs. The lib's ids are unchanged (rust-analyzer's, the byte-identity test) crates/mimir-schema/src/moniker.rs, crates/mimir-driver/src/{lib,walk}.rs, crates/mimir-binary/src/crates.rs
branch the branch a member follows is named — workspace descriptor, else the clone's own branch, else its remote's default (refs/remotes/origin/HEAD) — never guessed; with none, or a named branch the clone lacks (git::NoSuchBranch), the member's turn ends no_branch with what to set crates/mimir-ingest/src/git.rs, src/code_room/consumer_loop.rs

What an existing lake sees

Known limits (lane E)

10. Lane H — the wire door and the panes' readers (mimir-v2-wire-h, 2026-10-13)

A thin client (dwarves) had no way to ask the reader: only five Knowledge.* verbs and three Viz.* verbs were on the wire, typed or as JSON, and a refusal was a gRPC status (the state lost in a code). Lane H adds ONE door and the surfaces the dwarves panes draw.

item what where
wire door CodeRoom.Answer(surface, params_json) → JsonResponse (edda nornir-wire, branch mimir-v2-wire-h): any surface of reader::SURFACES by name. The body ALWAYS carries state and surface; a refusal is a body (refused: true, the state, its sentence in note), never a gRPC error; no served workspace selected answers no_room. Auth and transport failures stay gRPC errors reader::door / refusal_body, server_grpc.rs CodeRoomSvc
room identity every pane surface carries room: workspace, repo@seq with the commit, node count, edge counts by kind — the numbers a pane prints so they can be compared with the server reader/wired/panes.rs::identity
Viz.CrateGraph the quotient by crate: one row per name@version (local or not, nodes, symbols, fns, files), per pair depends_on / calls + call_sites / uses_type; per repo its crates and what they depend on; repo → repo. universe: true folds the ring panes::crate_graph
Viz.CallGraph the call-graph lens (call_graph) through the dispatch, with the room panes::call_graph_answer
Viz.FunctionTier per repo, per local crate: its fns / methods and the CALLS edges among them (indexes into the crate's fns, with sites) panes::function_tier
Knowledge.Symbols a capped listing of symbol rows (SymbolLookup is a search), with end_line, path, moniker panes::symbols
Knowledge.Source one item, named by moniker or name: its span, and the text of the span read from the blob at the consumer offset's commit in the consumer's own clone (<builds>/mimir_v2/clones/<member>). The caller names a node, never a file panes::source
Knowledge.TestReach every local fn / method with test_reachable (reach(tests, CALLS) ∪ TESTS targets), test_code, fan_in; every test with what it calls, names and reaches and the files those lie in panes::test_reach
Coderoom.SnapshotDiff also by repo alone (its last two consumer ordinals; with one consumed commit, the empty state before it — said in a note); by_crate (symbols / nodes added, removed, changed per name@version, crates in only one state); room_from / room_to wired::snapshot_diff, panes::diff_by_crate / last_two

Unchanged on purpose: Coderoom.Ingest / Compat / WhatBreaks (P5), table lineage (P3), the vector lenses, binary call edges (P4) — not_wired through the door as through every other front door.

10. Lane K — which code touches which table (mimir-v2-wire-k, 2026-10-13)

P3 closes: TOUCHES_TABLE has ONE producer, the rustc driver (crates/mimir-driver/src/walk/tables.rs — its header is the definition of "touching a table").

item what where
the anchor the storage engine's own typed API, crate iceberg: TableIdent::new / .name (a name), Catalog::load_table (name → table), Table::scan / reader_builder (READ), Transaction::new (WRITE), Catalog::create_table / drop_table / … (WRITE). No list of nornir, skade or edda fn names anywhere tables.rs engine_api, HANDLE_TYPES
derived, not listed every fn between a caller and the engine gets a summary: which parameter names a table it reads / writes, which table its return value stands for. A fn that only passes its parameter on is a conduit (no edge); the edge is written where the name stops being a parameter — a literal / const / static (const-evaluated, through re-exports, also a const list of names) → that table; a runtime value → the <unresolved> table node, counted tables.rs
across crates each compiled crate writes its summaries beside its sidecar (<key>.tables.json, keyed by DefPathHash, versioned TABLE_PASS); a call through a trait takes the union of the summaries of its impls (this crate's and its dependencies') mimir-driver lib.rs TableSidecar
the one annotation a trait whose impls are DOWNSTREAM of its callers has no body to derive from: #[cfg_attr(mimir, mimir::table(read | write | both, <param>))] on the method. cfg(mimir) and the tool exist under the driver only (the clippy / kani shape). Not needed anywhere in nornir tables.rs, main.rs
schema 4 additive: code_edges.access (nullable: read / write / both, set on TOUCHES_TABLE only), node kind table, the table id moniker::table(name) = rust-analyzer cargo @warehouse . \table=`:` — the NAME and nothing else, so two crates of two repos meet at one node. A schema-3 lake gains the column in place crates/mimir-schema
the room EdgeOut.access, Room::access_of (the union over the units that carry an edge), Room::edge_column_added_in_place (an older lake: no edge is not yet a fact) crates/mimir-room
data_reach per (table, fn that touches it): access, sites, the fns that reach it along CALLS inside the request's repos, public entry; per table: readers / writers / reach; unresolved: the runtime-named sites by fn and file:line src/mimir_lens/data_reach.rs, room_feed/lenses.rs
table lineage Mimir.SqlTemplate table-access-lineage {table[, repo]}: one row per site of the direct touchers + reached_from; a name no fn touches is invalid_argument and names the tables there are; <unresolved> lists the runtime-named sites src/code_room/reader/wired.rs
the board and the metro the board's Table nodes and reads / writes edges are the room's edges; ArchPayload.access = for every gRPC handler the table(s) nearest to it along CALLS + TOUCHES_TABLE (Room::min_hops_to + one bounded reach), which the 🚇 pane hands to metro_feed::build_metro_map. A handler impl is recognised under the trait's resolved name too (impl Bench for BenchSvc): before that no handler of a real room was a gRPC node wired.rs, src/viz/arch_tab.rs, src/arch/mod.rs
gone the closed map of accessor NAMES (accessor_table_access) that the v1 scan classified call sites with src/warehouse/access_scan.rs (the row shape stays)

Measured on real code (the nornir lib, default features)

The real driver (release build) over -p nornir --lib, tree = 46f10dd + this lane's work as it stood before the last lens / reader edits (the warehouse sources are those of the head):

Against the v1 scan (nornir main's access_scan.rs, unchanged, run over the tree of 46f10dd: 441 site rows, 22 tables by name + <dynamic>): v1 marks the CALLER of a named accessor, this marks the fn where the name meets the read / write, so the same fact sits one CALLS hop lower and the reachers hold v1's fn. v1 never saw 48 of the 67 tables (its closed list of accessor names did not have them, and it read string literals only — scan_preview(TABLE, …) was <dynamic>); it counted table_names() as an access (most of its 20 <dynamic> rows); about two thirds of its rows (290, counted by path and module name) are #[cfg(test)] modules, tests/ and examples/, which here belong to the harness / test / example units — not in this lib-only run. v1 is right where this is blind: its 5 release_events rows in the lib are calls into edda's release ledger (below).

Not covered (the readers say so; none of it is silently empty)

10. Lane I — the server runs all of it (mimir-v2-wire-i, 2026-10-13)

item what where
backfill workspace populate <ws> --from <sha|date> [--member m] writes ONE requested row per member into the lake (backfill_runs) and returns; the walk is a background job of the server, one commit per hold of the workspace's gate (WorkspaceGate::give_way before each turn, so a waiting question or sweep goes first). While it is open the member is Backfilling in every other run. A commit pushed meanwhile is the last commit of the same walk. After a restart the sweep reads the open rows and goes on after the offset (resume_backfills). The request rides Telemetry.SubmitJobs (a queued workspace_populate record whose detail is {"backfill":{"from","member"}}): the wire has no field for it src/code_room/backfill.rs, src/server_grpc.rs (run_backfill, request_backfill_served), crates/mimir-ingest/src/{log,schema}.rs
binary hook feed binary: after a commit's release the server looks in <builds>/mimir_v2/artifacts/<member>/<sha>/<profile>/<bin> (a cargo target dir of that commit, deps/ optional) and joins what is there against the lake's own source rows. It never builds. Nothing there = ledger status no_artifact with the directory it looked in; the feed is tried again when the directory appears. The binary lenses say no_artifact / "artifacts are of an EARLIER commit" instead of an empty answer src/code_room/feeds.rs, crates/mimir-ingest/src/artifact.rs, crates/mimir-binary/src/lib.rs (run_with)
std in the ring the first consume of a lake dumps the toolchain's std once (std@<toolchain> job) before the members. Knowledge.CallPath falls back to a room with the ring when the repo's own room has no path. mimir-schema 4: code_nodes.link_name (the symbol an extern item links to) src/code_room/consumer_loop.rs (ensure_std), src/code_room/reader/wired.rs, crates/mimir-schema, crates/mimir-driver/src/walk.rs
driver image containers/mimir-driver/Containerfile: debian:trixie by digest, the pinned nightly with rustc-dev + rust-src, gcc, git, pkg-config, the two driver binaries built in the image; it asserts its own contents. Run through NORNIR_MIMIR_DRIVER_HOST containers/mimir-driver/
embeddings a feed can be DEFERRED (Feed::prepare): the consume only reads the sources; the embed runs on the workspace's feeds lane with no gate and no consumer held src/code_room/feeds.rs, src/monitor.rs, src/server_grpc.rs
a failure outside the commit a dump that fails for a reason that is not in the commit's tree (a sibling clone missing, the toolchain, the disk) does not move the offset. After 3 failures of the same kind the member is blocked until it moves, repopulate / fetch --force, or a restart. A commit that is not ok says why on its line and in status crates/mimir-ingest/src/driver.rs (load_failure), src/code_room/consumer_loop.rs
siblings before any member is dumped every member's clone is fetched; while a member is dumped every sibling's clone is at the tip of its followed branch, not at its offset src/code_room/consumer_loop.rs (siblings_at_tip)
re-register Workspaces.Register on a served workspace keeps what the server knew of the members that stay, says what was added / removed, and clones an added member at once (outside the work gate). A fetch merges what it learned into the row as it is now (Registry::merge_member_states) — before, it wrote back the row it had read and undid a register that landed meanwhile src/registry.rs, src/monitor.rs, src/server_grpc.rs
a member and its published copy the driver decides "member" by the unit's manifest dir, not by name and version (VersionMap::is_member); in the lake the member keeps its plain owner id, the published copy is …#dep. This is what stopped edda (nornir-cli-outcome 0.1.4 is a member and also comes from the registry) crates/mimir-driver/src/{lib,walk,main}.rs, crates/mimir-ingest/src/driver.rs

Measured (one box, load 25–35 of 12 cores, release build of 82827fc)

Through the server door, workspace of one member (znippy-zoomies, 608 crates):

Known limits (lane I)

10. Lane J — the two vector lenses (mimir-v2-wire-j, 2026-10-13)

similar_code and semantic_clones answer from the code room again, on one vector per function. Design note: .nornir/vector.md § "One vector per function node".

item what where
chunk one chunk = the text of one function-like node: kind fn (rustc DefKind::Fn, not foreign) and method (DefKind::AssocFn: methods, associated fns, a trait's own methods), cut by the driver's span of that node at that commit, common indentation removed. Not chunks, counted: a function a macro wrote (EXPANDS on the item or on its impl / trait block — it is positioned at the invocation), a function in build-script output (absolute path, outside the checkout), a node without a span, ffi, closures. A text over 120 lines / 8 000 bytes is embedded in pieces and pooled into ONE vector src/vector/fnvec.rs, fnvec/room.rs
key node id (SCIP moniker) → content_hash (SHA-256 of the text) → vector. The vector is a row of embeddings; identical bodies share it fnvec.rs
link store embedding_node_events (repo · seq · sha · op · moniker · content_hash · chunker · ts), append-only, in the WAREHOUSE catalog beside embeddings; state at ordinal N = fold of seq ≤ N. embedding_node_runs = the ledger (one row per member run and per ring crate: model space, backend, counts, status, reason). embeddings_ring = the ring's vectors, the schema of embeddings, a table of its own (every reader of embeddings scans the model's rows) fnvec/links.rs, warehouse/iceberg_schema.rs, iceberg.rs
feed the embeddings feed runs two halves: FUNCTIONS (fnvec::room::embed_member: cut every function node at the consumed commit, hash, events only for the nodes whose text changed, embed only content without a vector; the model is loaded only when a text needs one), then the LINE WINDOWS as before (free-text search). FeedAt carries the event log; Sink.jobs the job ledger code_room/feeds.rs, consumer_loop.rs
ring not in a consume. When the ring's manifest snapshot moved, the feed — after its own run — starts ONE background thread with a vector_embed job row (target ring): unit crate@version, source = the crate's manifest directory from cargo metadata --offline in the consumer's clone, the whole job priced once by the embed budget, per-crate ledger rows, resumable. One embedding run at a time per process (fnvec::embed_turn: the feed for its run, the ring per crate). nornir workspace embed <ws> --ring runs it on request fnvec/ring.rs
lenses one SymbolVec per function node with a vector: signature from PARAM nodes (receiver bare, others in name order — no return type, P1), callee names from CALLS, impl Trait for T / impl T from the block + IMPLEMENTS; chunk_hash = the chunk INSTANCE repo ␟ file ␟ start-end, so shares_one_chunk_vector refuses two nodes cut from the same lines (one source compiled into two targets) and keeps a byte-identical copy elsewhere. Detection is workspace-wide, repo restricts reporting mimir_lens/room_feed/vectors.rs
states no_embedder · vectors_missing (new code_room::States: a refusal, no count in the answer) · partial with "vectors missing for N of M" / "vectors of seq a, room at seq b" · complete / empty_fact. Every computed answer carries vectors (functions, with a vector, distinct bodies, model, backend that embedded) and knn (device, kernel, comparisons) same
CLI nornir workspace embed <ws> [--ring] embeds the functions of every consumed member first; nornir mimir analyze duplicates [--repo --cosine --structural --same-crate --keep-trait-impls], and --calibration [--around X] [--files a.rs,b.rs]: the threshold instrument (cosine distribution, what each threshold keeps, the pairs around the bar) src/bin/nornir.rs, room_feed/vectors.rs

Measured (znippy-zoomies, dwarf, 2026-10-13)

Release build --no-default-features --features server,vector,embed-tract,mcp, scratch workspace, tract CPU, the box shared with three other lanes.

what number
consume @ 66a2891 (driver, 608 crates) 400 s, +3 242 285 rows; lake 916 MB
function nodes (fn + method, local, defined) 4 628: 3 546 chunks · 711 written by a macro · 371 in build-script output
embed (10 workers under a 4 GB cap) 3 377 distinct bodies, 3 473 pieces, 0.77 function/s, 4 364 s (72.7 min)
tables embeddings 16 MB · embedding_node_events 0.2 MB · embedding_node_runs 0.1 MB
similar_code, CLI door complete, 100 clusters / 219 pairs, 18 families withheld, 27 same-lines pairs refused; 1.0–2.3 s
semantic_clones, CLI door complete, 19 clusters / 74 pairs; 1.0–2.7 s
both, server door (MCP → gRPC Mimir.*) the same answers; 1.2–5.0 s
next commit (two function bodies changed) consume 58 s (58 crates re-dumped); function half: 2 vectors, 2 link events, 0.8 s
its ring (plan only) 143 464 function-like nodes, 90 185 with a chunk in 408 crates, every source directory found

Not measured there: the ring's price and its embed, the lenses through the server door after the second commit (through the CLI door they answered complete at seq 2 in 1.2–1.8 s), and the line-window half of the feed (never priced: stopped by hand after the function half). The server was stopped on the owner's order before those. With the default 10-minute embed budget the function half itself is refused on this CPU (priced at 30–100 min): the feed row is error and the lenses say vectors_missing with that reason.

The threshold (DupThresholds::cosine = 0.92, kept)

All pairs of the 3 546 vectors (6 285 258; mimir analyze duplicates --calibration): p50 0.204 · p90 0.381 · p99 0.561 · p99.9 0.750 · p99.99 0.931. At 0.92: 736 pairs, 211 of identical text; of the 525 others, 400 pass the structural gate and 97 are also in different crates (what the lens reports: 100 clusters with the identical ones).

Hand-checked against the source, cross-crate pairs that pass the structural gate:

cosine band pairs true copy near (same job, rewritten) not a duplicate
top 20 (0.9825 – 0.9998) 20 20 0 0
0.92 – 0.96 54 33 12 9 (5 of them len / is_empty: withheld as accessor families)
0.90 – 0.92 31 4 5 22 (14 are three-line test stubs)

0.92 is where the precision breaks, so it stays. What it does NOT find: real copies with systematic renames score far lower — ljar::main ≈ lzip::main (89 lines, 99 % the same text) 0.856, batch::fill_buffer (62 lines, 96 %) 0.813, batch::decompress_jars ≈ decompress_zips 0.866 — in a band (0.80 – 0.92) where 77 of the 91 pairs of ten lines or more share less than 85 % of their text. No cosine separates those; they are the sub-clone (text) lens's findings. Of the reference pair ljar.rs ≈ lzip.rs: decode_entry and physical_cores (identical) and local_data_offset (0.928) are reported, main is not.

Tests and red-proofs

cargo test --lib --no-default-features --features server,vector,viz (vector_tests, fnvec, room_feed, mimir_analyze, mimir_clones, mimir_calibrate, code_room, vector::, iceberg_schema, consumer_door_tests): 232 passed, 0 failed, 4 ignored. Guards no_second_parser 7/7, code_room_seam 5/5. cargo check green for server,vector,embed-tract,mcp, full-server, viz (no code-room).

rule broken tests that went red
embed every chunk every time (no dedupe, stored vectors ignored) every_function_node_is_linked_and_an_identical_body_is_embedded_once (10 vectors for 7 bodies), a_commit_embeds_only_the_functions_whose_text_changed, fnvec::the_same_body_nested_deeper_has_the_same_content_hash
shares_one_chunk_vector answers false similar_code_finds_the_copy_refuses_the_shared_lines_and_names_the_kernel (the shared lines become a cluster), semantic_clones_computes_over_the_same_vectors, the two predicate tests of mimir_analyze / mimir_clones
no vector at all is answered as a computed result without_vectors_the_lenses_say_vectors_missing_and_carry_no_headline, a_feed_run_that_failed_…, a_feed_that_could_not_embed_… (empty_gap with counts instead of the refusal)
default cosine 0.93 / 0.91 the_recorded_real_pairs_are_judged_as_a_person_judged_them (the copy at 0.928 lost / the non-duplicate at 0.916 reported)
the ring plan forgets what is done the_ring_is_embedded_once_per_crate_version_and_a_bump_embeds_only_the_new_one

NOT run: the default-feature cargo test --lib and cargo check (the box was needed elsewhere), and nothing on a GPU. --features vector-gpu did not compile on this tree (gpu.rs: wgpu 30 made get_mapped_range a Result); fixed in one line, compiled, its GPU tests skip on dwarf (no adapter).

What is not in this lane