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

nordisk/nornir

.nornir/metro-warehouse-read-and-emitter.md

🚇 Metro map — warehouse-backed read (thin mode) + the populated-state EMITTER

Design + guide for the fix that makes the 🏛 Architecture Metro map show the real button → gRPC call PATH in thin / remote mode, and emit its populated-or-empty state as DATA so it is verifiable WITHOUT a human looking at the GUI. (Authored 2026-06-22, branch agent/nornir-metro-emitter.)

The bug

src/viz/arch_tab.rs built the metro lines ONLY from a local checkout scan (ensure_symbols → crate::knowledge::scan_all(repo_root, repo)). In thin / remote mode repo_root is empty, so the scan produced no symbols / calls and every line degraded to a click-handler-head stub ("no local checkout — call edges weren't scanned"). That silent degrade is a BUG, not a fallback: the warehouse already holds call_edges + symbol_facts for the workspace (written by append_symbol_scan during republish), so thin mode should READ them, not give up.

The fix — read the warehouse over Viz.Architecture

The metro fuel now rides in the existing Viz.Architecture RPC payload.

Server (src/bin/nornir-server.rs, architecture handler)

After merging the board, the handler reads each member's latest knowledge snapshot via nornir::knowledge::query::load_latest(&wh, member) (the SAME read the knowledge MCP tools use) and merges every member's symbols + calls into the JSON payload:

{ "graph": …, "members_with_data": …, "errors": …, "coverage": …,
  "symbols": [SymbolRow…], "calls": [CallEdgeRow…] }

Payload (ArchPayload)

Gained symbols: Vec<SymbolRow> + calls: Vec<CallEdgeRow> (both #[serde(default)] for back-compat). SymbolRow / CallEdgeRow gained Deserialize so the thin client can decode them.

Client (arch_tab.rs)

fuel_source (warehouse thin / checkout fat) is on the metro state slice so a drive can prove thin mode is genuinely warehouse-backed.

The EMITTER — verify-without-looking (RAGNARÖK)

A metro draw is a pixel / render stage whose correctness can't be read from a return value (EMIT-DOCTRINE), so emit_metro_status() implants a functional_status row gated behind #[cfg(feature = "testmatrix")], plus an architecture.metro.status trace. It is called from ensure_metro (build time).

Aggregates (MetroAggregates, also folded into state_json["architecture"]["metro"]):

field meaning
metro_edges call-path rail segments drawn, summed over lines (0 ⇒ blank)
metro_nodes total stations (functions) drawn
metro_emitter_nodes stations MARKED emitters (emit*/trace/functional_status sites)
metro_stub_lines lines degraded to a head-only stub whose handler IS resolvable
reached_lines lines that reached their gRPC terminus

metro_stub_lines deliberately counts ONLY lines whose grpc_terminus resolved (the path COULD exist) yet didn't reach. A button whose handler symbol is simply absent from the current fuel set isn't a stub — it has no resolvable target. This makes metro_stub_lines == 0 the exact populated-state invariant: every line that could reach did.

Emit verdict: ok ⇔ metro_edges > 0 && metro_emitter_nodes > 0 && metro_stub_lines == 0.

The RAGNARÖK tests (tests/viz_arch_metro.rs)

Emitter-node marking — wired vs partial

Emitter nodes ARE marked: each station classified Boundary::Emitter by the shared coverage::classify is counted into metro_emitter_nodes and rendered as the "emitter" interchange (the ⟲ tag + point-colour station). Marking is therefore fully wired for any line that REACHES its gRPC terminus (the BFS path is walked and each station classified). A line that does NOT reach (an unresolved button or an unpopulated warehouse) has only its head station, so it contributes zero emitter nodes — correct by construction (there are no intermediate stations to mark). So: emitter marking is fully wired on reached lines; on unreached lines there is nothing to mark, which the emitter reports as metro_emitter_nodes contribution 0.

Live deploy note

The live server runs the INSTALLED binary; these changes need a human reinstall to go live. The tests run against a TEST warehouse (temp IcebergWarehouse), never the live server.