tests/viz_arch_metro.rs
//! 🚇 **Metro map** — inject-and-assert (LAW #1) + a matrix row (every-fix-earns-
//! a-test LAW) for the button → gRPC CALL PATH lens of the 🏛 Architecture tab.
//!
//! Proves, WITHOUT pixels, that:
//! 1. the `state_json["architecture"]["metro"]` slice carries the REAL structure
//! — selected button, every line's stations (fn + file:line + boundary kind),
//! the gRPC terminus, the emitter stations, and the `reached` flag;
//! 2. the `run_release_gate` line REACHES `Ops.RunRelease` with >=1 emitter
//! station and a UI head + gRPC terminus (the keystone the live drive mirrors);
//! 3. the metro slice is observable under `state_json["architecture"]["metro"]`,
//! and the RESTORED 🕸 Call Graph lens (separate from Metro) is a reachable
//! sub-view whose `state_json["callgraph"]` reports the function call graph;
//! 4. SENSITIVITY: with the call edges removed the line is an unreached STUB
//! (`reached:false`, 1 station) — exactly the blank state the matrix FAILS on.
//! This is the git-stash-equivalent proof baked into the matrix itself.
//!
//! Each real check also emits a `functional_status` row (under `--features
//! testmatrix`) so `nornir test` carries the metro surface forever.
//!
//! Run: `cargo test --features viz --test viz_arch_metro -- --nocapture`
#![cfg(feature = "viz")]
use nornir::arch::metro::button_registry;
use nornir::knowledge::symbols::{CallEdgeRow, SymbolRow};
use nornir::viz::UrdrThreadsApp;
use nornir::viz::arch_tab::{ArchPayload, ArchTabState};
const MEMBERS: [&str; 1] = ["nornir"];
fn sym(module: &str, name: &str, kind: &str, file: &str, line: u32) -> SymbolRow {
SymbolRow {
crate_name: "nornir".into(),
module_path: module.into(),
item_kind: kind.into(),
item_name: name.into(),
visibility: "pub".into(),
file: file.into(),
line,
doc_lines: 0,
signature: None,
}
}
fn call(caller: &str, callee: &str, file: &str, line: u32) -> CallEdgeRow {
CallEdgeRow {
crate_name: "nornir".into(),
caller_path: caller.into(),
callee_ident: callee.into(),
call_kind: "call".into(),
file: file.into(),
line,
}
}
/// A realistic `run_release_gate → Ops.RunRelease` slice: the `draw_release_op`
/// UI click-handler fires an emitter, then the RPC client fn, which reaches the
/// server's `OpsSvc::run_release` gRPC handler.
fn fixture_symbols() -> Vec<SymbolRow> {
vec![
sym(
"nornir::viz::release_tab::ReleaseTabState",
"draw_release_op",
"fn",
"src/viz/release_tab.rs",
207,
),
sym(
"nornir::viz::trace",
"emit_in",
"fn",
"src/viz/trace.rs",
90,
),
sym(
"nornir::viz::remote",
"run_release_rpc",
"fn",
"src/viz/remote.rs",
300,
),
sym(
"nornir_server",
"impl OpsSvcTrait for OpsSvc",
"impl",
"src/bin/nornir-server.rs",
3243,
),
sym(
"nornir_server::OpsSvc",
"run_release",
"fn",
"src/bin/nornir-server.rs",
3244,
),
]
}
fn fixture_calls() -> Vec<CallEdgeRow> {
vec![
call(
"nornir::viz::release_tab::ReleaseTabState::draw_release_op",
"emit_in",
"src/viz/release_tab.rs",
210,
),
call(
"nornir::viz::trace::emit_in",
"run_release_rpc",
"src/viz/trace.rs",
92,
),
call(
"nornir::viz::remote::run_release_rpc",
"OpsSvc::run_release",
"src/viz/remote.rs",
305,
),
]
}
fn app_with(symbols: Vec<SymbolRow>, calls: Vec<CallEdgeRow>) -> UrdrThreadsApp {
let pid = std::process::id();
let dir = std::env::temp_dir();
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe {
std::env::set_var(
"NORNIR_VIZ_TRACE",
dir.join(format!("nornir-metro-trace-{pid}.jsonl")),
)
};
let wh = dir.join(format!("nornir-metro-wh-{pid}"));
let _ = std::fs::create_dir_all(&wh);
let checkout = dir.join(format!("nornir-metro-co-{pid}"));
let _ = std::fs::create_dir_all(&checkout);
let mut app = UrdrThreadsApp::with_repos(
wh,
"nornir".to_string(),
checkout,
MEMBERS.iter().map(|s| s.to_string()).collect(),
);
app.inject_arch_scan_for_test(symbols, calls);
app.arch_select_metro_for_test("run_release_gate");
app
}
/// (0) ANTI-DRIFT: every `{id, rpc}` the registry claims is actually emitted by
/// the owning tab's `state_json` `ops.buttons` (nornir's `buttons`) literal, and
/// vice-versa. The metro map enumerates from the registry, so a tab that adds /
/// renames / re-rpc's a button without updating the registry is caught HERE.
#[test]
fn viz_button_registry_matches_tab_json() {
let app = app_with(fixture_symbols(), fixture_calls());
let state = app.state_json();
// (tab → the json path to its buttons array)
let buttons_of = |tab: &str| -> Vec<(String, String)> {
let arr = match tab {
// nornir's buttons live at the tab root; the others under `ops`.
"nornir" => &state["nornir"]["buttons"],
other => &state[other]["ops"]["buttons"],
};
arr.as_array()
.map(|a| {
a.iter()
.map(|b| {
(
b["id"].as_str().unwrap_or("").to_string(),
b["rpc"].as_str().unwrap_or("").to_string(),
)
})
.collect()
})
.unwrap_or_default()
};
let mut checked = 0;
for b in button_registry() {
let live = buttons_of(b.tab);
let hit = live.iter().any(|(id, rpc)| id == b.id && rpc == b.rpc);
nornir_testmatrix::functional_status(
"viz-arch-metro",
&format!("registry_button_{}_matches_tab_json", b.id),
hit,
&format!("tab={} id={} rpc={} live={live:?}", b.tab, b.id, b.rpc),
);
assert!(
hit,
"registry button {{tab={}, id={}, rpc={}}} not found in the live tab JSON {live:?}",
b.tab, b.id, b.rpc
);
checked += 1;
}
assert!(checked >= 5, "checked the known buttons");
}
/// (1)+(2) The metro slice reaches `Ops.RunRelease` with a real emitter stop,
/// a UI head and a gRPC terminus, all carrying file:line.
#[test]
fn metro_run_release_gate_reaches_grpc_with_emitter() {
let app = app_with(fixture_symbols(), fixture_calls());
let state = app.state_json();
let metro = &state["architecture"]["metro"];
// The selected button + a non-empty registry.
assert_eq!(metro["selected_button"], "run_release_gate");
assert!(
metro["registry"].as_array().unwrap().len() >= 5,
"registry enumerated: {metro}"
);
let sel = &metro["selected"];
assert_eq!(sel["rpc"], "Ops.RunRelease");
assert_eq!(sel["ui_handler"], "draw_release_op");
// It REACHES the gRPC handler — a blank/stub path is a FAIL.
let reached = sel["reached"].as_bool().unwrap();
let emitters = sel["emitter_stations"].as_u64().unwrap();
let stations = sel["stations"].as_array().unwrap();
nornir_testmatrix::functional_status(
"viz-arch-metro",
"run_release_gate_reaches_RunRelease",
reached && emitters >= 1 && stations.len() >= 3,
&format!(
"reached={reached} emitters={emitters} stations={}",
stations.len()
),
);
assert!(reached, "the metro line must reach the gRPC handler: {sel}");
assert_eq!(sel["grpc_terminus"], "run_release");
assert!(
emitters >= 1,
"at least one emitter station on the line: {sel}"
);
// Head = ui click-handler with file:line; terminus = gRPC handler with file:line.
let head = &stations[0];
assert_eq!(head["boundary"], "ui");
assert_eq!(head["fn"], "draw_release_op");
assert_eq!(head["file"], "src/viz/release_tab.rs");
assert_eq!(head["line"].as_u64(), Some(207));
let last = stations.last().unwrap();
assert_eq!(last["boundary"], "grpc");
assert_eq!(last["fn"], "run_release");
assert_eq!(last["file"], "src/bin/nornir-server.rs");
assert_eq!(last["line"].as_u64(), Some(3244));
// An emitter interchange exists along the line (state_json/emit*/trace).
let has_emitter = stations
.iter()
.any(|s| s["boundary"] == "emitter" && s["interchange"] == true);
assert!(has_emitter, "an emitter interchange station: {stations:?}");
}
/// (3) The 🚇 Metro slice lives under `state_json["architecture"]["metro"]`. The
/// Metro lens is the button→gRPC call-PATH map; it is observable here regardless
/// of which lens is active (LAW #6).
#[test]
fn architecture_metro_slice_carries_lines() {
let app = app_with(fixture_symbols(), fixture_calls());
let state = app.state_json();
let metro = &state["architecture"]["metro"];
assert!(
!metro.is_null(),
"the Architecture tab emits the metro slice"
);
assert!(
metro["line_count"].as_u64().unwrap() >= 5,
"lines present: {metro}"
);
assert!(
metro["reached_count"].as_u64().unwrap() >= 1,
"at least one line reaches gRPC: {metro}"
);
nornir_testmatrix::functional_status(
"viz-callgraph",
"architecture_metro_carries_lines",
metro["reached_count"].as_u64().unwrap() >= 1,
&format!(
"lines={} reached={}",
metro["line_count"], metro["reached_count"]
),
);
}
/// (3c) The 🕸 Call Graph lens (RESTORED, separate from Metro) is reachable as a
/// distinct Architecture sub-view, and its top-level `state_json["callgraph"]`
/// slice reports the function call-graph DATA (no longer the metro slice, and no
/// longer state-less). This is the regression-fix proof: switching to the lens is
/// observable AND the call graph emits its own readable data.
#[test]
fn callgraph_lens_is_a_distinct_reachable_subview() {
let mut app = app_with(fixture_symbols(), fixture_calls());
// The lens exists and is selectable (parity with a selector click).
assert!(
app.arch_set_sub_view_for_test("CallGraph"),
"the CallGraph lens is a real, switchable Architecture sub-view"
);
let state = app.state_json();
let arch = &state["architecture"];
let subs: Vec<&str> = arch["sub_views"]
.as_array()
.unwrap()
.iter()
.filter_map(|v| v.as_str())
.collect();
assert!(
subs.contains(&"CallGraph"),
"CallGraph enumerated as a lens: {subs:?}"
);
assert!(
subs.contains(&"Metro"),
"Metro is a SEPARATE lens: {subs:?}"
);
assert_eq!(
arch["sub_view"], "CallGraph",
"the lens switch is observable"
);
// The top-level `callgraph` slice is now the REAL function call graph (it
// carries the call-graph keys, NOT the metro `line_count`).
let cg = &state["callgraph"];
assert!(
!cg.is_null(),
"the call-graph lens emits its own state slice"
);
assert!(
cg.get("line_count").is_none(),
"callgraph is NOT the metro slice anymore: {cg}"
);
assert!(
cg["node_count"].is_u64(),
"the call graph reports a node_count: {cg}"
);
assert!(
cg["edge_count"].is_u64(),
"the call graph reports an edge_count: {cg}"
);
nornir_testmatrix::functional_status(
"viz-callgraph",
"callgraph_lens_reachable_and_reports_data",
subs.contains(&"CallGraph") && cg["node_count"].is_u64(),
&format!(
"sub_view={} node_count={}",
arch["sub_view"], cg["node_count"]
),
);
}
/// (4) SENSITIVITY — the matrix-baked git-stash proof: remove the call edges and
/// the SAME button's line becomes an unreached 1-station stub. The assert here
/// would FAIL if `build_metro_line` ever stubbed a path as "reached", so the row
/// genuinely flips on the bug.
#[test]
fn metro_stub_path_is_unreached_when_no_call_edges() {
let app = app_with(fixture_symbols(), Vec::new()); // no call edges
let state = app.state_json();
let sel = &state["architecture"]["metro"]["selected"];
let reached = sel["reached"].as_bool().unwrap();
let stations = sel["stations"].as_array().unwrap().len();
let emitters = sel["emitter_stations"].as_u64().unwrap();
nornir_testmatrix::functional_status(
"viz-arch-metro",
"stub_path_detected_as_unreached",
!reached && stations == 1 && emitters == 0,
&format!("reached={reached} stations={stations} emitters={emitters}"),
);
assert!(
!reached,
"no call edges → the line cannot reach the gRPC handler"
);
assert_eq!(stations, 1, "stub line is the head only");
assert_eq!(emitters, 0, "no emitter stops on a stub");
// The terminus is still KNOWN (the rpc maps to a handler symbol), proving the
// unreached state is the call-graph gap, not a missing handler.
assert_eq!(sel["grpc_terminus"], "run_release");
}
/// SCIP-PROVENANCE OVERLAY (LAW 1 inject-assert): fuel the metro with the
/// RESOLVED (SCIP) index tag — exactly what `query::load_preferred` returns when
/// the SCIP index answers — and assert the metro `state_json` surfaces the
/// per-line provenance so a reader SEES resolved-vs-syn-fallback (LAW 6):
/// * `fuel_provenance == "resolved/scip"` (the index that answered);
/// * the REACHED `run_release_gate` line is tagged `source == "resolved/scip"`;
/// * an UNREACHED registry line is tagged `source == "syn"` (the bin→lib gap
/// the syn scan misses — the honest fallback scip closes);
/// * the resolved-vs-syn line TALLY (the legend counts) reflects BOTH classes.
#[test]
fn metro_provenance_overlay_reports_resolved_and_syn_per_line() {
let mut app = app_with(fixture_symbols(), fixture_calls());
// Re-fuel the Architecture tab's metro with the RESOLVED/SCIP provenance tag
// (the same tag the load_preferred chokepoint returns under --features scip).
app.arch_inject_metro_fuel_for_test(fixture_symbols(), fixture_calls(), "resolved/scip");
app.arch_select_metro_for_test("run_release_gate");
let state = app.state_json();
let metro = &state["architecture"]["metro"];
// The INDEX that answered is surfaced (LAW 6) — not a mystery, not pixels.
assert_eq!(
metro["fuel_provenance"], "resolved/scip",
"the SCIP index is named: {metro}"
);
// The reached keystone line is RESOLVED.
let sel = &metro["selected"];
assert_eq!(
sel["reached"], true,
"the keystone line reaches its handler: {sel}"
);
assert_eq!(
sel["source"], "resolved/scip",
"a reached line is SCIP-resolved: {sel}"
);
// Across the registry there is BOTH a resolved AND a syn-fallback line (the
// unreached buttons have no resolving call edges → honest syn skeletons).
let lines = metro["lines"].as_array().unwrap();
let resolved = lines
.iter()
.filter(|l| l["source"] == "resolved/scip")
.count();
let syn = lines.iter().filter(|l| l["source"] == "syn").count();
assert!(resolved >= 1, "at least one resolved line: {lines:?}");
assert!(
syn >= 1,
"at least one syn-fallback line (an unresolved bin→lib gap): {lines:?}"
);
assert_eq!(
resolved + syn,
lines.len(),
"every line is classified resolved-or-syn"
);
// The legend's TALLY mirrors the per-line split (the counts the overlay paints).
assert_eq!(
metro["resolved_lines"].as_u64().unwrap() as usize,
resolved,
"resolved tally matches: {metro}"
);
assert_eq!(
metro["syn_lines"].as_u64().unwrap() as usize,
syn,
"syn tally matches: {metro}"
);
nornir_testmatrix::functional_status(
"viz-arch-metro",
"metro_provenance_overlay_resolved_and_syn",
metro["fuel_provenance"] == "resolved/scip" && resolved >= 1 && syn >= 1,
&format!(
"fuel={} resolved={resolved} syn={syn}",
metro["fuel_provenance"]
),
);
}
/// SENSITIVITY: with a SYN fuel tag (the default no-scip build), NO line is
/// reported resolved — the overlay honestly shows the whole map as syn-fallback,
/// never a fake resolved class. This is the inverse proof the per-line source
/// genuinely tracks the fuel, not a constant.
#[test]
fn metro_provenance_all_syn_when_fuel_is_syn() {
let mut app = app_with(fixture_symbols(), fixture_calls());
app.arch_inject_metro_fuel_for_test(fixture_symbols(), fixture_calls(), "syn");
app.arch_select_metro_for_test("run_release_gate");
let state = app.state_json();
let metro = &state["architecture"]["metro"];
assert_eq!(metro["fuel_provenance"], "syn", "syn fuel named: {metro}");
let lines = metro["lines"].as_array().unwrap();
let resolved = lines
.iter()
.filter(|l| l["source"] == "resolved/scip")
.count();
assert_eq!(
resolved, 0,
"syn fuel ⇒ NO resolved lines, even when reached: {lines:?}"
);
assert_eq!(metro["resolved_lines"].as_u64().unwrap(), 0);
// The reached keystone line is still reached — provenance is orthogonal to it.
assert_eq!(metro["selected"]["reached"], true);
assert_eq!(
metro["selected"]["source"], "syn",
"reached but syn-sourced fuel ⇒ syn class"
);
}
// ── THIN-MODE WAREHOUSE-BACKED METRO + the EMITTER (RAGNARÖK) ────────────────
//
// The fix under test: in thin/remote mode the metro has NO local checkout to
// scan, so the server ships the warehouse's `symbol_facts` + `call_edges` in the
// `Viz.Architecture` payload, and the thin pane builds REAL button→gRPC lines
// from them (not click-handler-head stubs). The EMITTER then publishes the
// populated-state as DATA (`metro_edges`/`metro_emitter_nodes`/`metro_stub_lines`)
// so the metro's correctness is asserted from the emit, never from pixels.
/// A populated `ArchPayload` exactly as the server's `Viz.Architecture` handler
/// builds it — the merged warehouse `symbol_facts` + `call_edges`.
fn warehouse_payload(symbols: Vec<SymbolRow>, calls: Vec<CallEdgeRow>) -> ArchPayload {
warehouse_payload_tagged(symbols, calls, "")
}
/// As [`warehouse_payload`], but with the server's `load_preferred` provenance
/// tag set ("resolved/scip" | "syn" | "" for an older server). The `Viz.Architecture`
/// handler now ships this so the thin metro styles resolved-vs-syn like the fat path.
fn warehouse_payload_tagged(
symbols: Vec<SymbolRow>,
calls: Vec<CallEdgeRow>,
metro_fuel_source: &str,
) -> ArchPayload {
ArchPayload {
graph: Default::default(),
members_with_data: vec!["nornir".into()],
errors: vec![],
coverage: Default::default(),
symbols,
calls,
metro_fuel_source: metro_fuel_source.into(),
access: vec![],
}
}
/// A thin (`remote`) Architecture tab — NO checkout, so the metro can ONLY draw
/// real lines if it reads the warehouse facts the payload carries.
fn thin_arch() -> ArchTabState {
ArchTabState::remote("http://127.0.0.1:1".into(), "tok".into(), "nordisk".into())
}
/// RAGNARÖK (populated): the thin metro, fuelled by warehouse `call_edges` +
/// `symbol_facts` (NOT a local scan — `repo_root` is empty), draws a REAL line
/// to `Ops.RunRelease`. The emitter aggregates prove it WITHOUT pixels:
/// `metro_edges > 0` AND `metro_stub_lines == 0` AND `metro_emitter_nodes > 0`.
/// Empty/stub here would be RED — it shall not pass.
#[test]
fn metro_thin_warehouse_backed_reaches_grpc_and_emits_populated() {
let mut arch = thin_arch();
// Inject the payload the RPC would return (warehouse-read facts). This drives
// the SAME seeding `ensure_loaded`'s remote branch runs.
arch.inject_remote_payload_for_test(warehouse_payload(fixture_symbols(), fixture_calls()));
arch.select_metro_button_for_test("run_release_gate");
arch.build_metro_for_test();
let metro = arch.metro_state_json();
// The thin metro is WAREHOUSE-fuelled, not a stub-degraded checkout.
assert_eq!(
metro["fuel_source"], "warehouse",
"thin metro reads the warehouse: {metro}"
);
// EMITTER aggregates — the verify-without-looking numbers.
let edges = metro["metro_edges"].as_u64().unwrap();
let nodes = metro["metro_nodes"].as_u64().unwrap();
let emitter_nodes = metro["metro_emitter_nodes"].as_u64().unwrap();
let stub_lines = metro["metro_stub_lines"].as_u64().unwrap();
let populated = edges > 0 && stub_lines == 0 && emitter_nodes > 0;
nornir_testmatrix::functional_status(
"viz-arch-metro",
"thin_warehouse_metro_populated_not_stub",
populated,
&format!(
"metro_edges={edges} metro_nodes={nodes} metro_emitter_nodes={emitter_nodes} metro_stub_lines={stub_lines}"
),
);
assert!(
edges > 0,
"warehouse-backed thin metro draws call-path EDGES: {metro}"
);
assert_eq!(
stub_lines, 0,
"NO line is a click-handler-head stub when populated: {metro}"
);
assert!(
emitter_nodes > 0,
"emitter stations are MARKED on the lines: {metro}"
);
assert!(
nodes > edges,
"nodes = stations > edges = rail segments: {metro}"
);
// The selected line REACHES the real gRPC handler — the call path is genuine.
let sel = &metro["selected"];
assert_eq!(
sel["reached"], true,
"the thin line reaches Ops.RunRelease: {sel}"
);
assert_eq!(sel["grpc_terminus"], "run_release");
assert_eq!(sel["rpc"], "Ops.RunRelease");
}
/// METRO FEED through the SERVER (PLAN §A, thin side): the `Viz.Architecture`
/// handler now routes member fuel through `query::load_preferred` and SHIPS its
/// provenance tag in the payload. The thin pane must ADOPT that tag, so a server
/// that answered over the RESOLVED (SCIP) index renders resolved rails remotely —
/// exactly as the fat path does — instead of blindly tagging "warehouse". The
/// inverse: an older server (no tag, "") still degrades honestly to "warehouse".
#[test]
fn thin_metro_adopts_server_resolved_provenance_tag() {
// Server resolved the call graph over the SCIP index → ships "resolved/scip".
let mut arch = thin_arch();
arch.inject_remote_payload_for_test(warehouse_payload_tagged(
fixture_symbols(),
fixture_calls(),
"resolved/scip",
));
arch.select_metro_button_for_test("run_release_gate");
arch.build_metro_for_test();
let metro = arch.metro_state_json();
// The thin metro now NAMES the resolved index (was always "warehouse" before).
assert_eq!(
metro["fuel_provenance"], "resolved/scip",
"thin metro adopts the server's resolved tag: {metro}"
);
// A reached line is styled SCIP-resolved, just like the fat path.
let resolved = metro["lines"]
.as_array()
.unwrap()
.iter()
.filter(|l| l["source"] == "resolved/scip")
.count();
assert!(
resolved >= 1,
"≥1 reached line is resolved/scip in thin mode: {metro}"
);
// The transport discriminator is still "warehouse" (read over Viz.Architecture);
// provenance ≠ transport.
assert_eq!(
metro["fuel_source"], "warehouse",
"transport is still warehouse-read: {metro}"
);
// An OLDER server ships no tag ("") → the thin metro degrades honestly to the
// "warehouse" provenance (never falsely claims resolved).
let mut old = thin_arch();
old.inject_remote_payload_for_test(warehouse_payload_tagged(
fixture_symbols(),
fixture_calls(),
"",
));
old.select_metro_button_for_test("run_release_gate");
old.build_metro_for_test();
let m2 = old.metro_state_json();
assert_eq!(
m2["fuel_provenance"], "warehouse",
"no server tag ⇒ honest warehouse: {m2}"
);
let resolved2 = m2["lines"]
.as_array()
.unwrap()
.iter()
.filter(|l| l["source"] == "resolved/scip")
.count();
assert_eq!(
resolved2, 0,
"untagged thin fuel claims NO resolved lines: {m2}"
);
}
/// RAGNARÖK (sensitivity): an EMPTY warehouse (the server shipped symbols but NO
/// `call_edges`) → every line is a head-only stub → the emit row is RED. This is
/// the inverse proof: the populated-state DATA flips on the empty input, so a
/// blank metro can never go green. (Populate-diagnostic: zero call_edges is the
/// "run a deep scan/populate" gap the user must close.)
#[test]
fn metro_thin_empty_warehouse_is_red_not_silently_green() {
let mut arch = thin_arch();
// Symbols present, but NO call edges — exactly the unpopulated-warehouse state.
arch.inject_remote_payload_for_test(warehouse_payload(fixture_symbols(), Vec::new()));
arch.select_metro_button_for_test("run_release_gate");
arch.build_metro_for_test();
let metro = arch.metro_state_json();
let edges = metro["metro_edges"].as_u64().unwrap();
let emitter_nodes = metro["metro_emitter_nodes"].as_u64().unwrap();
let stub_lines = metro["metro_stub_lines"].as_u64().unwrap();
let line_count = metro["line_count"].as_u64().unwrap();
// The emit verdict the populated test asserts TRUE must be FALSE here.
let populated = edges > 0 && stub_lines == 0 && emitter_nodes > 0;
nornir_testmatrix::functional_status(
"viz-arch-metro",
"thin_empty_warehouse_is_red",
!populated, // a RED metro is the CORRECT (passing) outcome for empty input
&format!(
"metro_edges={edges} metro_emitter_nodes={emitter_nodes} metro_stub_lines={stub_lines} lines={line_count}"
),
);
assert!(
!populated,
"empty warehouse MUST be a RED metro, never a silent green: {metro}"
);
assert_eq!(edges, 0, "no call edges → no rail segments drawn");
assert_eq!(emitter_nodes, 0, "no emitter stations on stub lines");
// The lines whose handler IS resolvable (Ops.RunRelease: run_release_gate +
// run_repo) degraded to head-only stubs with zero call edges — the RED signal.
assert!(
stub_lines >= 1,
"a resolvable line degraded to a stub: {metro}"
);
assert!(line_count >= 1, "the registry is enumerated: {metro}");
// The selected line still KNOWS its terminus (the rpc→handler map survives),
// proving the emptiness is the missing call_edges, not a broken registry.
assert_eq!(metro["selected"]["grpc_terminus"], "run_release");
assert_eq!(metro["selected"]["reached"], false);
}