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

nordisk/nornir

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);
}