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

nordisk/dwarves

.nornir/dwarves-guide.md

⚒ dwarves — how-to guide

📍 THIS DOC LIVES IN dwarves (moved from nornir/.nornir/, 2026-07-22, move=delete). The code it documents is dwarves' own (crates/dwarves/src/…), so the doc follows the code. nornir keeps only the read-only viz surface that renders this engine's output. Source links below are dwarves-repo-relative.

Class: guide · how to use dwarves. What it is: dwarves-design.md. Zero-knowledge: dwarves-for-idiots.md. The forge details: brokkr-guide.md.

dwarves = the brokkr pipeline forge + the sindri scheduler, shipped as a standalone tool AND a library nornir embeds.

Declare a pipeline

A repo declares its CI/build pipeline as data in brokkr.toml (schema: brokkr-guide.md):

[pipeline]
name = "holger-ci"

[[pipeline.trigger]]          # fire when the poll sweep sees HEAD move
kind = "poll"
events = ["push"]

[[pipeline.trigger]]          # …and nightly
kind = "cron"
cron = "0 3 * * *"

[[pipeline.stage]]
name = "build"
[[pipeline.stage.step]]
name = "compile"
run = { variant = "server" }  # real cargo build (zero shell) under --real

[[pipeline.stage]]
name = "test"
[[pipeline.stage.step]]
name = "unit"
needs = ["compile"]
run = { container = { name = "rust:1.85", cmd = ["cargo", "test"] } }

The standalone dwarves tool

cargo build -p dwarves --bin dwarves          # build the tool
dwarves                                     # bare: startklar banner + readiness + menu
dwarves doctor                              # one-shot readiness (store reachable? exit!=0 if not)
dwarves --help                              # the verbs

dwarves validate                            # validate ./brokkr.toml
dwarves plan --matrix                       # topo-ordered step DAG + matrix cells
dwarves run                                 # run through the demo runner, historize
dwarves run --real                          # `variant` steps build via cargo (zero shell)
dwarves run --matrix                        # fan over every matrix cell
dwarves trigger push --branch main          # which triggers an event would fire
dwarves history --project holger            # historized runs (JSONL under ./.nornir)
dwarves badge holger-ci                     # a status SVG badge for the latest run
dwarves agent ls                            # the build-agent registry
dwarves tune probe <row>                    # Heimdall tuner: measure a row's seccomp profiles
dwarves tune approve <row>                  # record the review (approved.toml)
dwarves tune check <row> [--json]           # drift + review gate (exit 1 on findings)

dwarves serve — the scheduler daemon

serve runs the sindri loop: cron → poll-enqueue → drain → dispatch.

# One deterministic tick (demos + CI), injecting a HEAD-move for project "holger":
dwarves serve --once --project holger --poll holger
#   sindri tick: 0 cron, 1 enqueued, 1 dispatched (1 green), 0 blocked
#     ✓ holger-ci passed — 2/2 steps

# The real daemon: loop every 30s (cron + drain each tick):
dwarves serve --interval 30

Add --json to any verb for machine-readable output.

dwarves tune — the Heimdall tuner verbs

dwarves tune probe|gen|check|approve <artifact> [--workload <driver>], where <artifact> is a row or <repo>:<row>. dwarves tune --help lists the verbs and --json gives the document the UI reads. The README section "Dwarves tune" has the whole workflow.

Callable from nornir

nornir depends on dwarves by path and delegates: the nornir brokkr … verbs run through dwarves's re-exported host wiring (nornir::brokkr::{RealRunner, DemoRunner, run_pipeline_job, store} are now dwarves::…). Nothing about the nornir brokkr surface changed.

nornir brokkr validate
nornir brokkr run --real
nornir brokkr history

Embedded in the server (the running server IS a CI server)

nornir-server's poll sweep drives sindri automatically: when a monitored member that ships a poll-triggered brokkr.toml gets a HEAD-move, the server enqueues, drains and runs its pipeline (sindri_sweep), historizing to the served warehouse and tracking each run as a CI_BUILD job. Watch the server log for nornir-dwarves: <ws> sindri tick: ….

Call it as a library

use dwarves::{Sindri, RegisteredPipeline, MonitorReport, DemoRunner};

let pipeline = dwarves::discover_pipeline(repo_root)?.expect("a brokkr.toml");
let mut sindri = Sindri::new(vec![RegisteredPipeline::new("holger", pipeline)]);
let report = sindri.tick(
    chrono::Utc::now(),
    Some(&MonitorReport::changed(["holger"])),
    &DemoRunner,
    None,                     // Option<&nornir_jobs::JobSink>
    warehouse,
);
assert_eq!(report.dispatched.len(), 1);

Watch the UI move

The Brokkr tab drives a pipeline run and lights the DAG stations green:

# Headless robot proof (stations transition dark → green):
cargo test --features viz --test viz_brokkr_robot -- --nocapture

# Watch it live on the desktop:
DISPLAY=:0 cargo run --features viz --bin nornir -- viz      # open the ⚒ Brokkr tab

Launch the desktop client (dwarves-ui) — HOST-ONLY

dwarves-ui is a THIN client that knows exactly ONE server address: the dwarves forge. nornir is embedded in the dwarves server (the host); the UI defers to that host and never dials nornir itself — the nornir :7878 port is for EXTERNAL nornir consumers only.

# The ONE launch command — DWARVES_SERVER is the sole knob (no NORNIR_SERVER):
DWARVES_SERVER=http://oden:7900 dwarves-ui

Do NOT set NORNIR_SERVER for the UI — the client ignores it. Warehouse/Mímir panes are served THROUGH the host: the dwarves forge re-serves the nornir.v1 READ surface (Warehouse.{Tables,Scan} · Workspaces.* · the Mimir.* lens folds) on its OWN :7900 socket and forwards each call to its embedded nornir (crate::warehouse_proxy). So the Warehouse navigator · Data view · Dupes · Coverage · 🔍 Lenses panes render LIVE rows through the host with no :7878 anywhere. A standalone (non-fat) server has no embedded nornir, so a proxied read returns an honest Unavailable — never a silent empty, never a direct :7878 socket. (Proven end-to-end by robot_warehouse_proxy_live.rs / robot_lenses_live.rs and their dead-upstream twin robot_warehouse_dead_upstream.rs.)

The ⚒ Build-Thing tab — select & build anything, and watch korp from Windows

The ⚒ Build Thing tab reads every side-by-side checkout's .nornir/build-variants.toml (under $DWARVES_CONSTELLATION, else the parent of the repo root) through build-thing's own model and shows an HONEST roster of build cells + launch cells — never a blank/false-green pane; an absent constellation reads as a named "NO CHECKOUTS" state.

The words this tab uses. Build Thing is the SYSTEM — the declarations plus the gate — and never a name for one artifact. A product is what you click first (gunnar · holger · korp). A suite is a tag (ORIGINAL · MVP) that filters which atoms are real deliverables. An atom is what you click second: ONE declared buildable. Its kind is its shape — exe · service · container · iso · vm · installer. A param is a declared input (--param hub_key=…) and renders as a field in the pane — a kind = "text" param landing as a staged_file (a hub.conf) renders as a MULTI-LINE editor, and a param whose cell declares a fixture ([build.installer.fixtures] hub_conf = "wg-hub") gets an "använd fixtur (wg-hub)" checkbox: ticked, the pane supplies the CLI's own @fixture word and the forge MINTS the fixture at build time (a userspace WireGuard hub, no root, no /etc/wireguard); the result head then prints param hub_conf ← fixture wg-hub (pub=… endpoint=… key=…) beside the receipt. The pane knows no fixture by name — it renders whatever label the recipe declares, and nothing where it declares none. [[build.variant]] stays the TOML section name for the exe/service kinds — it declares one kind of atom, not the concept, which is why the UI says atom and the declaration says variant.

# Point the roster at the paved constellation and open the tab:
DWARVES_CONSTELLATION=$HOME/git DWARVES_SERVER=http://oden:7900 dwarves-ui

Test it

cargo test -p dwarves                                   # the lib (25 tests incl. sindri + cache)
cargo run  -p dwarves --bin dwarves -- validate           # the standalone tool
cargo test --features viz --test viz_brokkr_robot     # the moving robot scenario
cargo build                                           # the whole workspace