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

nordisk/nornir

.nornir/supporting-subsystems-design.md

Supporting subsystems (this run) — design

Class: design · Function axis: four independent subsystems that landed alongside the ⚒ brokkr and 🎬 saga headliners, grouped because each supports making the whole system observable, tracked, and gated green: A the funnel project-flow simulator · B the job-ledger's new op-family coverage · C the robot-UI test matrix (every surface a scenario) · D the warehouse / release / SCIP read + gate surfaces. Strict: this file states only what these are. How-to: supporting-subsystems-guide.md; zero-knowledge: supporting-subsystems-for-idiots.md. The headliner still here: saga-design.md. (The brokkr forge design MOVED to dwarves/.nornir/brokkr-design.md on 2026-07-22 — move=delete: brokkr's code is dwarves::brokkr, so its docs live with it. nornir keeps only the read-only viz Brokkr tab.)


A — funnel: the project-flow time-stepper (simulate_flow)

funnel-core::simulate_flow (topo.rs:147) time-steps the whole project DAG to completion: repeatedly take topo_ready (topo.rs:40) and advance every ready node to Done, recording each stage transition. This is distinct from the LLM task-decomposition decompose::simulate: it advances nodes through statuses over time, which topo_ready alone never does. A DAG whose nodes all start Pending reaches all-Done in N waves, where N is the longest dependency chain.

Viz surface. The 🗂 Funnel tab's ▶ Simulate flow button (src/viz/funnel_tab.rs) calls run_simulate_flow (funnel_tab.rs:751): it replays the funnel from disk, time-steps every plan's DAG to completion, and stores the FlowSim at state_json["funnel"]["sim"] (funnel_tab.rs:110). Local-mode only (funnel_tab.rs:687). A simulate_flow_for_test seam drives it headless; tests/robot_funnel_journey.rs exercises intake + demo + simulate-flow as one journey.


B — job-ledger coverage: every long op is a tracked job

The jobs ledger (see jobs-design.md) gained op-family kind constants so this run's new long operations self-register as tracked jobs. Each is a one-line pub const in crates/nornir-jobs/src/jobs.rs's kind module — a new op needs no schema change (the kind is a free-form string):

kind const the op it wraps
saga_capture jobs.rs:169 the viz ⏺Record/⏹Stop drain → saga_events (one job per Stop)
monitor_fetch jobs.rs:172 the nornir monitor poll loop's per-tick workspace fetch sweep
ci_build jobs.rs:177 a brokkr pipeline run (the parent; a start_child per step)
security_scan jobs.rs:111 the local (fat) SBOM/security scan — tracked at nornir.rs:6094
docs_render/_export/_book/_book_svg jobs.rs:91–94 the docs render/export/book op family

The lifecycle invariants they inherit are the ledger's, not each op's: a JobHandle writes a running row on start, a terminal done/failed on finish/fail, and the Drop guard (jobs.rs:482) records failed ("dropped without finish/fail") so a panic or early ? never leaves a zombie running row. The saga ⏹ Stop path (test_tab.rs:931) is the canonical example: it opens a SAGA_CAPTURE job, and any early return before finish Drop-fails it.


C — the robot-UI test matrix: every surface is a scenario

The headless robot harness (nornir-robotui + RobotSession) drives the real egui viz through AccessKit and asserts on state_json. This run's charter: every viz surface has a scenario, so a silently-broken pane is a RED matrix row.

The +7 surfaced tabs. TAB_PANE_BLOCK (uiplane_plan.rs:49) is the plane→state_json-block resolver the uiplane walk iterates. Seven tabs were reachable but surfaceless — their panes emitted a top-level state_json block that no surface declaration pointed at, so the walk skipped them (silent holes): Metro, Helix, Bloodstream, WarehouseDeck, Build, Claude, Models (uiplane_plan.rs:73–79). Declaring each surface makes tests/viz_uiplane_walk.rs run a real row for it.

The multi-tab robot journeys (added this run):

test what it drives
tests/robot_full_nav.rs clicks through ~25 tab headers, asserts each renders
tests/robot_funnel_journey.rs funnel intake + demo + ▶ Simulate flow as one journey
tests/robot_misc_tabs.rs the miscellaneous / uncovered tabs
tests/robot_ops_journeys.rs arch / ops operator journeys
tests/robot_arch_metro.rs the Architecture / Metro surface
tests/viz_brokkr_robot.rs the ⚒ Brokkr tab — click Run, assert green across all five facets

The pattern is uniform: build a Harness over the app, RobotSession::run_scenario (or click_label + assert state_json), and every surface's block is a matrix row that is GREEN when it renders and RED when it doesn't — the same inject-assert LAW the saga replay uses.


D — warehouse / release / SCIP: read surfaces + gates

Three write-only or half-wired tables/graphs got their reader + CLI + gate completed so the data is usable, not just recorded.

D1 — warehouse readers for write-only tables.

D2 — release gate: lower-is-better + ELF cross-check.

D3 — SCIP resolved find-usages. nornir scip (ScipOp, nornir.rs:1429; feature scip) ingests a rust-analyzer scip index into scip_occurrences (SHA-keyed to HEAD). This run added the resolved find-usages query surface: nornir scip query <repo> usages <symbol> (run_scip, nornir.rs:5000) does IDE-grade resolved find-usages over the latest persisted SCIP map (defs and the resolved-vs-syn delta are the sibling kinds). The knowledge graph full-wiring prefers the RESOLVED SCIP map when present (nornir.rs:4888), falling back to the syn map otherwise. tests/cli_scip_find_usages.rs covers the resolved query; tests/ra_ingest_coverage_wiring.rs is the coverage-wiring tripwire.

Feature flags

flag subsystem effect
viz A (funnel tab), C (robot harness) compiles the ▶ Simulate flow button + the uiplane surfaces + the robot tests
robot C the nornir-robotui headless driver
scip D3 nornir scip ingest + query (SCIP scip_occurrences)
ra-ingest D3 in-process rust-analyzer (--in-process), no subprocess
net-scan B (security_scan) lets the SBOM scan clone missing path-dep siblings; drop it for an airgapped scan

Subsystems A, B, D1, D2 are always present in the nornir binary — no gate.