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

nordisk/nornir

docs/v2/mimir/README.md

Mímir v2 — the Rust code graph

Mímir v2 is nornir's code graph. One producer (a rustc driver) writes Arrow IPC files per compiled crate. One consumer folds them into an Iceberg/Parquet lake. One in-memory room (GraphBLAS) answers questions through named reader surfaces and lenses. Every node id is a SCIP moniker string.

This document describes the code on main as it is today. File references are repo-relative paths in nornir unless another repo is named. A short list of planned items sits at the end, marked as planned.

Chapters:

file content
README.md (this file) flow, producer, ids, lake, room, readers, lenses, what is not there, the system boundary and the possible system calls
tables.md every table and every column

1. Glossary

word meaning
unit one crate compiled once by cargo (a lib, a bin, a test harness, a dependency)
dump the Arrow IPC files mimir-driver writes for one unit
moniker a node id: a SCIP symbol string, rust-analyzer cargo <package> <version> <descriptors>
member a repo the nornir workspace registry lists
consumer the nornir server loop that clones a member, checks out a commit, runs the dump and writes the lake
lake the skade warehouse (Iceberg tables, Parquet files) under <builds>/mimir_v2/lake
event table an append-only table of + / - rows; the graph at a commit is the fold of its events
open set the rows of a repo with a + and no later -
universe the dependency ring: dependency and toolchain crates, dumped once per crate@version and toolchain, kept apart from the members' rows
room mimir-room's in-memory Snapshot: node table, spans, one GraphBLAS matrix per edge kind
scope which repos at which sequence number, universe on or off
surface a named reader question (Viz.CrateGraph, Knowledge.Source, …) answered over gRPC CodeRoom.Answer
lens a named analysis over the room (generic_cost, zombie_features, change_risk, …)
skade our Iceberg/Parquet warehouse crate
gix the pure-Rust git library used to read file text at a commit

2. Flow

  repo clone at commit C
        │
        ▼
  cargo-mimir-dump ──► cargo check --workspace --all-targets
                       RUSTC_WRAPPER=mimir-driver  RUSTUP_TOOLCHAIN=nightly-2026-10-05
                       target dir: target/mimir     env: MIMIR_OUT MIMIR_REPO MIMIR_SHA …
        │
        │  per unit:  <crate>@<version>-<hash>.nodes.arrow
        │             <crate>@<version>-<hash>.edges.arrow
        │             <crate>@<version>-<hash>.spans.arrow
        │             <crate>@<version>-<hash>.symbols.arrow
        │             <crate>@<version>-<hash>.stats.json
        ▼
  ┌────────────────────────────────────────────┐   ┌──────────────────────────────────┐
  │ nornir code room (server)                  │   │ dwarves `cargo lens graph-package` │
  │ consumer: read_out_dir → delta vs open set │   │ reads *.edges.arrow, kind CALLS    │
  │ ONE skade atomic release per (repo, sha)   │   │ joins with cargo metadata + lock   │
  │ lake: <builds>/mimir_v2/lake (Parquet)     │   │ writes target/dwarves/             │
  │ mimir-room: fold → Snapshot (GraphBLAS)    │   │   graph-package.znippy             │
  │ reader::door surfaces + mimir_lens::answer │   │ (see dwarves docs/v2/README.md)    │
  │ gRPC CodeRoom.Answer :7878                 │   └──────────────────────────────────┘
  │   proxied by the dwarves forge on :7900    │
  └────────────────────────────────────────────┘
        │
        ▼
  dwarves-gui (Coderoom tab, graph source pane) → facett CoderoomPane

3. The producer

3.1 cargo-mimir-dump

crates/mimir-driver/src/bin/cargo-mimir-dump.rs. It is a cargo subcommand (cargo mimir-dump) that does two things:

  1. One cargo metadata, written to <out>/crate-versions.json (MIMIR_CRATE_VERSIONS).
  2. cargo check --workspace --all-targets (or cargo build with --build) with the driver as the rustc wrapper.
setting value where
toolchain nightly-2026-10-05 (RUSTUP_TOOLCHAIN) crates/mimir-driver/src/lib.rs MIMIR_NIGHTLY, crates/mimir-driver/rust-toolchain.toml
wrapper RUSTC_WRAPPER=mimir-driver (every crate, dependencies included); --workspace-wrapper uses RUSTC_WORKSPACE_WRAPPER instead (members only) cargo-mimir-dump.rs
target dir <workspace>/target/mimir by default (--target-dir); nightly fingerprints are kept apart from stable's cargo-mimir-dump.rs
output dir --out DIR, passed as MIMIR_OUT cargo-mimir-dump.rs
index dir MIMIR_INDEX_DIR = <target dir>/mimir-index cargo-mimir-dump.rs
repo name --repo NAME, passed as MIMIR_REPO (the directory name by default) cargo-mimir-dump.rs
commit git rev-parse HEAD, passed as MIMIR_SHA cargo-mimir-dump.rs
other flags --manifest-path, --workspace-only, --build, --std, --driver PATH, -- <cargo check args> cargo-mimir-dump.rs USAGE
exit code 0 ok; cargo's code when cargo check fails; 3 when cargo metadata fails cargo-mimir-dump.rs

The nornir consumer runs it with --keep-going, so every crate that compiles is dumped even when another does not (crates/mimir-ingest/src/driver.rs).

3.2 mimir-driver

crates/mimir-driver/src/main.rs. A clippy-shaped rustc driver: it drops cargo's rustc from argv, compiles in process with its linked nightly, and registers a rustc_driver::Callbacks whose after_analysis hook walks the TyCtxt and returns Compilation::Continue, so rustc still emits the metadata cargo expects. Proc macros, build scripts and --print calls are plain rustc. The walk is crates/mimir-driver/src/walk.rs (and walk/).

3.3 Helpers

binary role where
mimir-capture <stdout-file> -- <program> <args…> runs a tool, copies its stdout byte for byte to a file while streaming it; used on the container road, where the engine's log API frames stdout and would cut cargo metadata's one-line JSON crates/mimir-driver/src/bin/mimir-capture.rs
mimir-super <job.json> the super build: one cargo-mimir-dump per repo of a workspace, run together over one target dir with cargo's -Zfine-grain-locking and one shared jobserver; writes <out>/.super/{cargo.json,cargo.err,exit,ms} per repo crates/mimir-driver/src/bin/mimir-super.rs

3.4 The per-unit files

For every compiled unit the driver writes, under MIMIR_OUT:

<crate>@<version>-<hash>.nodes.arrow      table code_nodes
<crate>@<version>-<hash>.edges.arrow      table code_edges
<crate>@<version>-<hash>.spans.arrow      table code_spans
<crate>@<version>-<hash>.symbols.arrow    table code_symbols
<crate>@<version>-<hash>.stats.json       counts and timings

<hash> is the short hash of the rustc invocation. A toolchain crate (core, std, …) has a URL as its version; its files say @lang (walk.rs, the stem computation).

Format: Arrow IPC file format (arrow_ipc::writer::FileWriter), one RecordBatch per file, written to a temporary name and renamed (walk.rs write_ipc). Every batch carries two schema metadata keys: mimir.schema = 5 and mimir.table = the table name (crates/mimir-schema/src/lib.rs SCHEMA_VERSION, META_SCHEMA, META_TABLE).

The columns of the four tables, the node kinds and the edge kinds are in tables.md. In short:

table one row per key columns
code_nodes item (crate, mod, fn, struct, field, param, feature, …) id, kind, level, name, parent, repo, crate, version, file, start_line, end_line, cfg, origin, provenance, visibility and doc columns
code_edges semantic edge (from, to, kind) from, to, kind, count, provenance, negated/partial (GATED_BY), access (TOUCHES_TABLE)
code_spans position of a node or of one edge site target (node/edge), key, file, start_line, end_line
code_symbols linked symbol id, symbol, demangled, role, crate, version, binary, section, addr, size

MIR facts are not a separate file. They are extra rows at level part (mir_body per fn, mir_block per basic block) inside code_nodes and code_edges (walk.rs module doc).

Positions live apart from identity: an edge row has no line numbers; its sites are code_spans rows with target = "edge" and key = from ␞ to ␞ KIND (mimir_schema::edge_key, separator U+001E). Moving code changes spans, never an edge.

4. Ids: SCIP monikers, no SCIP tooling

Every node id is a SCIP symbol string in the shape rust-analyzer scip emits:

rust-analyzer cargo <package> <version> <descriptors>

The one formatter is crates/mimir-schema/src/moniker.rs. Nothing else assembles that string, so two producers that name the same definition write the same bytes.

descriptor suffix form used for
Namespace name/ modules, the crate root crate/
Type name# struct, enum, union, trait, type alias, variant
Term name. const, static, field
Method name(). fn, method
TypeParameter [name] generic params, impl self/trait slots
Parameter (name) fn params
Meta name: derive/attribute macros, non-lib target prefix ( bin=x: ), mono instances, cargo features ( feature=x: )
Macro name! bang macros

A non-lib target (bin, test, bench, example) gets one leading Meta descriptor naming the target, because rust-analyzer gives those the lib's ids. Impl scopes are rust-analyzer's: impl#[Self]new()., impl#[Dog][Greet]greet()..

What this is not: there is no index.scip, no scip-* indexer and no SCIP protobuf anywhere in the path. Only the id grammar is SCIP's. Readers that need the package of a callee parse it out of the moniker (the dwarves join does this in crates/dwarves/src/lens_graph.rs moniker_package).

5. Ingest into the lake

The nornir server's code room (src/code_room/) is the only writer of the lake.

5.1 The consumer loop

src/code_room/consumer_loop.rs, crates/mimir-ingest/src/consumer.rs. For a member:

  1. Its own clone under <builds>/mimir_v2/clones/<member> (git::sync_clone); the server's registry clone is not touched (src/code_room/mod.rs).
  2. Checkout of the next commit after the member's offset (checkout::checkout_changed).
  3. cargo-mimir-dump on that tree (section 3.1), on one of three roads:
road setting where
installed NORNIR_MIMIR_DRIVER = path of cargo-mimir-dump (its sibling mimir-driver is the wrapper); without it: <nornir home>/mimir-driver/, beside the binary, then PATH src/code_room/driver.rs
wrapped NORNIR_MIMIR_DRIVER_HOST = a command line every tool is started through (for example a podman run … <toolchain image>); NORNIR_MIMIR_TOOLCHAIN names the nightly when it is not the pinned one src/code_room/driver.rs
container NORNIR_MIMIR_CONTAINER_IMAGE (+ NORNIR_MIMIR_CONTAINER_DRIVER, a host dir with cargo-mimir-dump, mimir-driver, mimir-capture built for the image; NORNIR_MIMIR_CONTAINER_BINDS): every tool runs in that image through draupnir, under mimir-capture; tried first, the host road taken when it does not work src/code_room/container.rs, containers/mimir-driver/Containerfile
  1. read_out_dir reads the unit files back (crates/mimir-ingest/src/driver.rs).
  2. Delta against the open set: rows the new dump has and the open set lacks become + events, rows the open set has and the dump lacks become - events.
  3. One skade atomic release: the event tables, the consumer offset and the ledger rows (and the universe manifest when it moved) advance together or not at all (crates/mimir-ingest/src/log.rs commit, under one commit lock).

5.2 Where and how it is stored

item value where
warehouse skade, namespace mimir_v2 crates/mimir-ingest/src/schema.rs NAMESPACE
directory <builds>/mimir_v2/lake src/code_room/mod.rs
clones <builds>/mimir_v2/clones/<member> src/code_room/mod.rs
data files Parquet, zstd level 3, 256 k-row row groups log.rs write_props
catalog skade's (redb) skade
event prefix repo, seq, sha, op, krate, payload_hash, key_dup, rev ahead of the payload columns of section 3.4 schema.rs PREFIX
universe rows repo = "@universe", in their own universe_*_events tables once the lake is split schema.rs UNIVERSE_REPO, PROP_RING

The tables (details in tables.md):

stream tables
source (driver dumps) code_node_events, code_edge_events, code_span_events
universe ring universe_node_events, universe_edge_events, universe_span_events, universe_manifest
binary (mimir-binary) code_symbol_events, bin_node_events, bin_edge_events, bin_ingest_runs, bin_offsets
bookkeeping consumer_offsets, ingest_runs, consumer_feed_runs, backfill_runs

Provenance (source_only / binary_only / both) is derived at read time by joining the source and binary streams on the moniker; it is not stored as a fact of the room.

6. mimir-room: the in-memory snapshot

crates/mimir-room/src/lib.rs, snapshot.rs, scope.rs.

A Scope is a map repo → seq plus universe: bool. Folding the log at a scope gives an immutable Snapshot:

part content where
Monikers every node id, sorted; the position is the node's dense ordinal snapshot.rs
NodeTable interned strings and every payload column of code_nodes by name snapshot.rs
spans and sites node spans and edge sites (file, lines) of the scope's repos snapshot.rs, attrs.rs
Matrices one boolean CSR GrB_Matrix per EdgeKind; unions over several kinds and transposes built on first use and kept snapshot.rs
symbols the linked symbols (size, section, role, artifact) snapshot.rs
provenance derived per node (mimir_schema::derive_provenance) lib.rs

Room = Arc<Snapshot>. A built snapshot is cached per process, keyed by the log identity (the event tables' UUIDs) and the resolved scope; a new commit to any event table moves the log's stamp and drops that log's entries.

The graph engine is crates/mimir-graph: a small safe layer over SuiteSparse:GraphBLAS through crates/graphblas-sys. It imports boolean adjacency from CSR or from Arrow (from_ord, to_ord) batches and provides the semiring operations the lenses use: reachability forward and reverse with many seeds, fan-in/fan-out, fewest hops (MIN_PLUS), worst value on a path (MAX_SECOND), scope masks, A·Aᵀ similarity, connected components, diff. Every handle is RAII (GrB_free on drop); the library is initialised once per process.

The typed query API (no query language):

question call
what is this node Room::node, attr, display_path, span, sites
which node is a::b::c Room::lookup
node sets Room::mask, mask_of, Mask::union
walks Room::reach, dependents, dependents_by, hops_from, min_hops_to, path
weights Room::bottleneck, worst_on_path
edges out, collapsed Room::edges, Room::quotient (by crate, module, repo, file)
binary Room::symbols, symbols_of, instances_of
identity Room::duplicate_keys, Room::diff

Every answer carries Evidence: which tables fed the room, which edge kinds the query walked, and what is missing. With the universe off, walks stop at the reference stubs of dependency and std items and the evidence says so (query.rs). A SYSCALLS walk says MISSING until the universe is folded in: the boundary producer writes its edges into the universe (section 10).

7. Readers: surfaces, the code room door, and the panes

src/code_room/reader/wired.rs dispatches a surface name to a function over a Reach (the room plus the workspace it was read from). The surfaces on main:

Knowledge.SymbolLookup  Knowledge.DefinedIn   Knowledge.Callers    Knowledge.Callees
Knowledge.CallPath      Knowledge.FeatureGates Knowledge.Context   Knowledge.Map
Knowledge.Symbols       Knowledge.Source      Knowledge.TestReach
Viz.Knowledge  Viz.Architecture  Viz.CrateGraph  Viz.CallGraph  Viz.FunctionTier
Viz.CodeMap    Viz.Binary        Viz.TestMatrix.inventory
Test.List      Test.Affected     Arch.History    Mimir.SqlTemplate   Mimir.SystemBoundary

Two of them are what the code room panes draw:

surface answer where
Viz.CrateGraph the room collapsed by crate: one node per name@version, one join per pair with its DEPENDS_ON, CALLS and USES_TYPE counts (room.quotient(GroupBy::Crate, …)) src/code_room/reader/wired/panes.rs crate_graph
Knowledge.Source { moniker } where the item is (repo, file, span) and the text of the span, read with gix from the consumer's clone at the commit the room was consumed at (scope().offset_shas[repo]), so line numbers match the spans panes.rs source, blob_at

Wire: gRPC CodeRoom.Answer on the nornir server (loopback :7878 by default, --addr / NORNIR_SERVER_ADDR; src/server_grpc.rs). The dwarves forge proxies the same service on its own port (:7900, dwarves crates/dwarves/src/warehouse_proxy.rs CodeRoomProxy), so one URL gives a client the forge and the room.

Who asks: dwarves-gui's Coderoom tab (dwarves-gui/src/coderoom.rs) mounts facett's shared CoderoomPane (facett-coderoom-view) behind a CoderoomDataSeam that asks Viz.CrateGraph and Coderoom.SnapshotDiff; the graph source pane (dwarves-gui/src/graph_source.rs) asks Knowledge.Source for the node a person clicked. Neither reads a table or a file; the seam is dwarves-gui/src/room.rs (ForgeRoom, CodeRoom.Answer through the forge).

8. Lenses

src/mimir_lens/mod.rs answer(lens, reach, params) -> Value is the one entry. The names it accepts (LENSES):

generic_cost  zombie_features  change_risk  similar_code  semantic_clones  weak_tests
bench_gaps  villa  churn  anomaly  world_impact  binary_drift  data_reach  trust
name_collision  symbol_identity  reachable_vuln  undoc_api  system_boundary
exact_clones  near_clones  structural_clones  role_neighbours  same_intent
semantic_twins  reimplements_shared

An unknown name is refused with the known list. Each lens module sits beside mod.rs (bloat.rs answers generic_cost, zombie.rs answers zombie_features, risk.rs answers change_risk, …). The dwarves CLI's short names map onto them in dwarves crates/dwarves/src/room_read.rs: bloat → Mimir.GenericCost, zombie → Mimir.ZombieFeatures, risk → Mimir.ChangeRisk.

A lens answer carries the same Evidence as a room query; a lens whose room feed is not wired for a repo says not_wired instead of returning zeros.

9. What is NOT there

item state where
symbol_facts, call_edges, feature_gate_facts, scip_occurrences, test_inventory, warehouse_access_edges, architecture_wiring, knowledge_scans, semantic_clusters, lens_runs, feed_runs v1 tables with no writer; listed so no reader is written for them again src/code_room/status.rs V1_TABLES
universal SCIP import (index.scip from any scip-* indexer) and the syn tarball scan removed with v2; only the moniker grammar stayed crates/mimir-schema/src/moniker.rs
Coderoom.Compat, Coderoom.WhatBreaks answered not_wired: the world ring (crates.io versions of other crates) has no producer dwarves-gui/src/coderoom.rs module doc
a git remote URL per repo in the lake not stored; the registry row has it src/registry.rs
file text in the lake not stored; read on demand with gix (section 7) panes.rs

10. The system boundary and the possible system calls

The kernel tooling as a whole (Build Thing, Heimdall tooling, Mímir, and the files between them) is described in heimdall docs/v2/kernel-tooling.md.

10.1 The boundary in the universe

crates/mimir-ingest/src/boundary/ writes the system boundary into the dependency universe, once per toolchain and target, after a consume (src/code_room/consumer_loop.rs ensure_boundary → boundary::ensure):

owner rows where
@syscall@<arch>.r1:table one syscall node per call of the architecture's kernel table (ty = its class, doc = number and entry point) plus <indirect> boundary/mod.rs table_rows
@syscall@<owner>.<key>.r1:map per dependency owner: SYSCALLS edges from its ffi nodes (through the glibc / musl map), from fns that hand the raw door a number, and from the raw door to <indirect>; key is the libc on x86_64 and <arch>-<libc> on every other architecture boundary/mod.rs map_edges, map_owner, Target::map_key
@kernel@<release>+<hash>:btf the kernel's types and functions from BTF; CALLS from each syscall node to its entry function boundary/mod.rs kernel_rows, boundary/btf.rs

The syscall tables:

arch source file
x86_64 linux-6.12.104 arch/x86/entry/syscalls/syscall_64.tbl (common + 64), numbers past it from the UAPI <asm/unistd_64.h> of linux-libc-dev 7.0.0-38 boundary/x86_64.rs (generated by boundary/gen/gen_x86_64.py)
aarch64 linux-6.12.104 scripts/syscall.tbl with the ABIs arm64 builds (common, 64, renameat, rlimit, memfd_secret), numbers past it from the UAPI <asm-generic/unistd.h> of linux-libc-dev 7.0.0-38 boundary/aarch64.rs (generated by boundary/gen/gen_aarch64.py)

The libc map names x86_64's calls. aarch64 has no legacy calls (open, stat, fork, pipe …): boundary::calls_on(arch, name) turns each one into the call(s) the C library makes there instead (boundary::GENERIC_STAND_INS: open → openat, rename → renameat, renameat2, …), so no mapped call is dropped on aarch64. A legacy call with no stand-in (iopl, modify_ldt, the x86 thread-area calls) has no edge.

10.2 The possible system calls (the max)

crates/mimir-room/src/possible.rs, Room::possible_syscalls(entries, opts), and the CLI verb:

mimir-room <lake> possible [ENTRY…] [--bin NAME] [--target TRIPLE] [--binary NAME] [--out FILE] [--runtime off] [--repo NAME]…

ENTRY is a moniker or a readable path of a fn; --bin NAME takes the main of that bin target (moniker ending bin=NAME:main(). , - read as _: the crate name). The room is opened with the universe folded in. The verb refuses to write a file when an entry is unknown or the room holds no syscall node of the target's architecture.

With --bin the program is a linked executable (PossibleOpts::runtime, off with --runtime off): std's rt::lang_start and rt::lang_start_internal are entries too when the room holds them, and the calls the C runtime's start-up makes before main are possible, with the path <C runtime start-up, before main> (mimir_ingest::boundary::libc_map::startup: musl on x86_64 arch_prctl, set_tid_address; glibc adds set_robust_list and, uncertain, rseq, prlimit64, brk, mmap, mprotect, readlinkat, getrandom). A source graph never sees that code.

The walk is breadth-first over CALLS and SYSCALLS from the entries; a syscall node is an end (the walk does not go on into kernel functions). It runs twice: first over resolved edges only (what it reaches is direct), then from everything reached, widened at every unresolved site:

site how the driver writes it how the walk widens it
a trait method with no resolved impl (dyn Trait, a generic bound) CALLS to the trait's own method (walk.rs resolve keeps the trait method when the args are generic or dyn) every method of that name in every impl that IMPLEMENTS the trait, in the whole room
a call through a value: a closure, a Box<dyn Fn…>, a generic F: Fn… CALLS to the Fn / FnMut / FnOnce method rustc records for the overloaded call (resolved to the impl when the type is concrete) the impls of that Fn* trait, and every fn a static or const initializer names (once)
a call through a fn pointer CALLS to core::ops::function::Fn::call (a fn pointer implements Fn) as above
a fn named as a value (x.map(a::tiny)) CALLS from the body that names it none needed: the edge is there
our own extern "C" item (no SYSCALLS edge: the producer maps dependencies only) an ffi node the libc map, calls_on for the architecture
the raw door (libc::syscall, a rustix raw stub) SYSCALLS to <indirect> none possible: listed, the number is chosen at run time

Closure bodies are part of the fn that defines them (walk.rs BodyVisitor), so their calls are reached with that fn.

The output is the Graph JSON that dwarves-tune-graph's read_graph reads, with extra fields its reader ignores:

field type content
format string mimir.possible-syscalls/1
binary, arch string the program's name, the architecture
modules array of {name, path, reached_functions} per crate (name@version) the fns reached; path = the repo or @universe
entries array of string the entry points (readable paths)
syscalls map name → {nr, direct, path, site, sites, uncertain} path = the shortest chain entry → … → the fn that makes the call (a direct maker first); site = that fn with its crate; sites = how many reached fns make it; uncertain = every maker's hop is UNCERTAIN
unnamed array of number empty (a source graph names every call)
unresolved_sites array of {module, function, offset, direct, callee, reason, targets} every widened site and every raw door reached; offset is 0; targets = how many targets the site was widened to
missing_libs array empty (the room holds every crate)
unresolved_imports array of string reached C functions the libc map has no answer for (they may make any call)
dlopen bool a reached C function is dlopen / dlmopen
indirect_sites number widened sites (dispatch + value calls)
reached_functions number fns, methods and ffi items reached

The evidence (stderr of the verb) says MISSING when the universe is off, when no syscall table exists for the architecture, and when a reached C function is unmapped.

The binary walk and the source-graph stubs inside dwarves-tune-graph (walk.rs, srcgraph/) stay the owner's and are not used here.

10.3 Dependency sources from Holger

The walk covers third-party crates because the room holds them: the driver compiles and dumps every crate a program links (RUSTC_WRAPPER, §3.1). Their exact sources come from a registry. With MIMIR_CRATE_MIRROR set to a cargo sparse index that mirrors crates.io (Holger's pull-through sparse+<holger>/production/crates-io/, or the dev pair's sparse+http://127.0.0.1:18444/cache/index/), they come from that mirror and never from the network (crates/mimir-ingest/src/crate_mirror.rs):

setting effect
MIMIR_CRATE_MIRROR=sparse+… DriverDumper::with_env_crate_mirror writes <target dir>/mimir-cargo-home/config.toml ([registries.holger], [source.crates-io] replace-with = "holger"); every cargo the dumper starts (clean, unit graph, the dump, the super build's entries) gets CARGO_HOME there
MIMIR_CRATE_MIRROR_TOKEN_ENV=<VAR> the read token is taken from <VAR> and handed to cargo as CARGO_REGISTRIES_HOLGER_TOKEN (credential-provider = "cargo:token"); it never lands in a file or an argv
MIMIR_CRATE_MIRROR_CAINFO=<file> [http] cainfo for a mirror behind a private CA

Before the dump, cargo fetch --locked gets every Cargo.lock entry from the mirror, so a crate the mirror cannot serve fails there, by name. Holger is read through the registry door it already serves (the sparse index and the download). Cargo checks each .crate against the lockfile's checksum. A repo's own .cargo/config.toml (a vendored source) still wins. The code room's dumpers (src/code_room/consumer_loop.rs) and the consume example take the mirror from the environment.

Proof (2026-11-04, Holger's scan worker as in §10.2, Holger's dev pair with the rust-remote download fix): 758 registry crates came from Holger's /cache and none from crates.io; the max is 118 calls (77 direct), the scan probe's 23 and the job probe's 30 measured calls are all inside it, 0 gaps; 25 of the 118 are made in a registry crate Holger served (libc 0.2.191, rustix: io_uring_setup, landlock_create_ruleset, epoll_*, futex, mmap, …).

11. Consumers outside nornir

consumer reads where
dwarves cargo lens graph-package the driver's *.edges.arrow (kind CALLS) of a dump it runs or is given; never the lake dwarves crates/dwarves/src/lens_graph.rs
dwarves-gui CodeRoom.Answer and the Mimir.* lens services through the forge dwarves-gui/src/room.rs, mimir_lens.rs
facett plain node/edge rows handed to CoderoomPane, Graph3D, WarehouseDeck; no Mímir reader of its own facett facett-coderoom-view/src/lib.rs

Next (planned, not built)