Self-emitters — nornir wiring its OWN functional_status matrix (design + guide)
nornir consumes the
nornir-testmatrixfunctional_statusemitter across its own enumerated functional surface (viz tabs, server RPCs, warehouse paths, CLI verbs, arch/funnel engines). The emitter feeds one functional row per real check into thenornir testmatrix. Built 2026-06-16 (branchfeat/self-emitters). Sibling of aut5-surface-coverage-gate.md; plan: nordisk_plan.md.
1. EMIT-DOCTRINE (the law)
DEFAULT → assert on the RETURN VALUE / object state (the returned result + state()/state_json).
FALLBACK → ONLY when the result can't be observed from outside (a render / side-effect): implant a
functional_status(...) INSIDE the function under test, then read the row back in the test.
RELEASE → every emit AND its detail-building is gated `#[cfg(feature = "testmatrix")]`, so a plain
build strips it to nothing. No emit exists in release.
2. The bridge
src/selftest.rs— the single internal call site.selftest::emit(component, check, ok, detail)forwards tonornir_testmatrix::functional_status; a no-op without the feature.assert_emit!(exported at crate root) is the doctrine's preferred shape: itassert!s on the real value AND records the verdict as a matrix row.- Root
Cargo.tomlfeaturetestmatrix = ["nornir-testmatrix/testmatrix"]— the passthrough that lights the emitter and every gated call site at once.
3. What is wired
-
return-value / object-state (default doctrine) — asserted on the real returned value / the fat-app
state_json()over a KNOWN seeded warehouse, thenassert_emit!'d:- viz object-state: nornir_tab, timetravel/timeline, depgraph + depgraph_layout, funnel_tab, live, release_tab, knowledge, warehouse_tab, mcp_tab, search, gates, bench, test_tab/Matrix, leaderboard, security_tab, preflight, facett_ui, action_log, sentinel-ban, palette switch, control-channel (VizCommand) —
tests/self_emitters_viz.rs. - core return-value: Mímir deps/dependents/affected/build-order/dep-path, dep_graph SVG, knowledge call-path, arch generate→SVG, funnel idea→plan→ready DAG, warehouse append→read roundtrip + scan, bench submit→history, the testmatrix functional drain (ok=false → red row) —
tests/self_emitters_core.rs.
- viz object-state: nornir_tab, timetravel/timeline, depgraph + depgraph_layout, funnel_tab, live, release_tab, knowledge, warehouse_tab, mcp_tab, search, gates, bench, test_tab/Matrix, leaderboard, security_tab, preflight, facett_ui, action_log, sentinel-ban, palette switch, control-channel (VizCommand) —
-
emit-fallback (implanted inside the side-effect, read back) — the render/PNG paths:
viz/graph.rs::draw_dep_graph→dep_graph_rendered_non_blank(painted node/edge count).viz/callgraph.rs::draw→callgraph_rendered_non_blank.viz/funnel3d.rs::ui→funnel3d_rendered_non_blank.viz/warehouse_deck_tab.rs::draw→deck_built_lit(ok ⇔ the composed deck built ≥1 pane with no error; a warehouse-locked build-error or an empty deck is RED — the last unmarked pane in the §B named viz set). Driven headlessly by injecting a one-paneWarehouseDeck, running one bareegui::Contextframe, and draining the row back (deck_draw_emits_green_when_built_red_when_empty).viz/app.rs::handle_screenshot_image→screenshot_command_pending_then_png_written(PNG bytes written to disk). Driven headlessly via#[cfg(feature = "testmatrix")]test hooks (test_feed_screenshot_frame,callgraph::test_inject_graph,app::test_render_callgraph_and_funnel3d_nonblank) and the rows read back from the sink.
4. Run it
cargo build --workspace --features testmatrix # green, emits stripped in any non-test build
cargo test --features "viz,server,testmatrix" \
--test self_emitters_core --test self_emitters_viz -- --test-threads=1
5. Per-crate emit roster (the internal-crate allow-list)
Each crates/nornir-* leaf carries its OWN selftest::emit bridge + a #[cfg(all(test, feature = "testmatrix"))] wiring test that drives its real surfaces and drains the rows back. The roster (12 internal crates):
| crate | emits functional_status |
how |
nornir-testmatrix |
— (IS the emitter) | defines functional_status / the matrix |
nornir-selftest |
✅ | nornir's own emit + assert_emit! bridge |
nornir-git |
✅ | src/selftest.rs — real git ops |
nornir-guard |
✅ | src/selftest.rs |
nornir-jobs |
✅ | src/selftest.rs — job lifecycle |
nornir-bench |
✅ | src/selftest.rs |
nornir-cli-outcome |
✅ | src/selftest.rs |
nornir-introspect |
✅ | src/selftest.rs |
nornir-coverage |
✅ | runner emits |
nornir-saga |
✅ | src/record.rs |
nornir-build-thing |
✅ | src/selftest.rs — variant validate / cargo argv / surface projection (wired task #8) |
nornir-robotui |
EXEMPT | test-harness lib: emitting TestResultRows IS its product surface (a bridge would be a self-referential no-op) |
The nornir-robotui EXEMPT row is the allow-list entry: it is excused because it emits matrix rows by construction, not through a consumer bridge (see the crate's module docs). Every other internal crate MUST carry a bridge.
functional_status appends one JSONL row per check to $NORNIR_TESTMATRIX_OUT (default target/nornir-functional.json), which nornir test's Unit aspect reads back into the matrix (aspect = functional). Without --features testmatrix both test files compile to 0 tests and every emit is a no-op.