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

nordisk/Skidbladnir

.nornir/orchestrate-design.md

orchestrate — design

Class: design · Function axis: f8 (AIRGAP8: ordered bring-up + telemetry). States only the shape we ship.

Dependency-ordered bring-up

orchestrate::start_waves(products) -> Vec<Vec<String>> (orchestrate.rs:42) Kahn-topo-sorts the products into waves: wave i holds every product whose depends_on are all satisfied by earlier waves. Each wave is sorted for determinism, and products within a wave can start concurrently (the caller uses std::thread::scope — no rayon).

The node is product::Product (product.rs:235) — name, depends_on, user, ports — the dep-graph view a ComponentSpec yields via as_product(). Deps that point outside the product set (e.g. network.target) are ignored (orchestrate.rs:52). A cycle is detected after the Kahn drain (orchestrate.rs:87) and returned as an error naming the stuck products.

config::LakeSpec::start_waves() (lib.rs:137) is the whole-lake entry point — it maps each ComponentSpec to a Product and calls start_waves.

Telemetry — airgap_events

Every pipeline boundary emits one row. events::AirgapEvent (events.rs:49) — run_id, seq, ts_micros, product, op, phase, status, detail, depends_on. Vocab: op (events.rs:24) = pack | fetch | unfold | install | start | matrix_probe; phase (events.rs:35) = start | end | skip; status (events.rs:40) = ok | fail | running.

events::EventSink { append(&event) } (events.rs:71) is the seam; JsonlSink (events.rs:76) is the default — an append-only JSONL under the warehouse root (under_warehouse, events.rs:83), never /tmp. EventEmitter (events.rs:116) mints the run_id + a monotonic seq and emits best-effort (a sink error logs to stderr, never aborts the bring-up, events.rs:132). read_jsonl (events.rs:161) reads rows back sorted by seq.

Introspection — functional_status surface emits (nornir's coverage gate)

Separate from the airgap_events DAG, each lifecycle surface self-reports an ok/RED verdict into nornir's constellation-wide introspection matrix via the functional_status shim (lib.rs:44, → nornir-testmatrix, reused — no twin). The emit is implanted in the runtime path itself (via the surface_status! macro, lib.rs), so the matrix sees the surface's actual return/state, not a test's re-derivation. Compiled out entirely unless --features testmatrix is on (the lean core stays dep-free, zero format! cost). Wired surfaces + components:

surface component check fires
pack skidbladnir/pack pack_collect_staging_tree pack::pack (pack.rs) — RED if the staging tree can't be walked
fetch skidbladnir/fetch verify_on_receipt fetch::verify_on_receipt (fetch.rs) — RED on a corrupt/unauthentic bundle
unfold skidbladnir/unfold extract_all archive::TarZstd::extract_all (archive.rs) — RED on a bad/missing bundle
install skidbladnir/install install_product install::install_product (install.rs) — RED if any step throws
start skidbladnir/start start_waves_ordered orchestrate::start_waves (orchestrate.rs) — RED on a dependency cycle
pki skidbladnir/pki issue_leaf pki::MachineCa::issue_leaf (pki.rs) — RED if rcgen can't sign the leaf
systemd skidbladnir/systemd install_unit systemd::install_systemd_scoped (systemd.rs) — RED when a secret-carrying spec has no private env-file

drain_functional_rows (lib.rs, testmatrix-only) reads the process-global buffer back — the seam the red-when-broken test uses. The matrix harness collects the same rows a cargo test --features testmatrix subprocess wrote (nornir #32), exactly like the sibling crates (ordning-core, korp-collectors).

Contract (inject-assert tests)

topo_waves_from_fixture_dep_graph (orchestrate.rs:118), teardown_is_reverse_of_start (orchestrate.rs:155), cycle_is_rejected (orchestrate.rs:172), external_deps_are_ignored (orchestrate.rs:147), emits_install_dag_to_warehouse_jsonl_and_reads_back (events.rs:184).