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

nordisk/dwarves

.nornir/brokkr-guide.md

⚒ brokkr — the CI/build-server — 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 · audience: a techie writing a brokkr.toml or driving the pipeline from the CLI / viz. The what is in brokkr-design.md; the zero-knowledge walkthrough is brokkr-for-idiots.md.

f0 — the whole workflow

You declare a pipeline as data in brokkr.toml, then drive it locally:

  1. Write brokkr.toml — triggers, an optional matrix, stages of DAG-ordered steps.
  2. Validate — nornir brokkr validate catches structural breakage before any run.
  3. Plan — nornir brokkr plan [--matrix] prints the topo order (and matrix cells).
  4. Run — nornir brokkr run executes it (demo runner), --real builds variant steps via cargo.
  5. Read back — nornir brokkr history, nornir brokkr badge <pipeline>, or the 🧵 Brokkr viz tab.
  6. Trigger check — nornir brokkr trigger <event> [--branch B] shows what an event would fire.

Every leaf reads the local brokkr.toml (discovered in the cwd, or --path) — no server round-trip. Each run is a tracked CI_BUILD job, visible in nornir jobs --kind ci_build.

f1 — a brokkr.toml you can paste

[pipeline]
name = "nornir-ci"

[[pipeline.trigger]]
kind = "webhook"
events = ["push", "pull_request"]
branch = "main"

[[pipeline.trigger]]
kind = "cron"
cron = "0 3 * * *"

[pipeline.matrix]
rust = ["1.85", "stable"]
target = ["x86_64"]

[[pipeline.stage]]
name = "build"
[[pipeline.stage.step]]
name = "compile"
run = { variant = "server" }
artifacts = ["target/release/nornir"]

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

[[pipeline.stage]]
name = "deploy"
[[pipeline.stage.step]]
name = "ship"
needs = ["unit", "lint"]
run = { vm = "debian" }
when = { branch = "main" }

[pipeline.notify]
on = ["failure", "fixed"]
via = ["slack"]

Rules the validator enforces (descriptor.rs:77):

f2 — validate, plan, run

# Validate the discovered ./brokkr.toml (or --path):
nornir brokkr validate
#  ✓ pipeline 'nornir-ci' is valid — 3 stage(s), 4 step(s)

# Plan the topo-ordered step DAG; add --matrix to expand the cells:
nornir brokkr plan --matrix
#  plan 'nornir-ci' (4 steps, topo order):
#    [build] compile (variant)
#    [test]  unit (container) needs ["compile"]
#    [test]  lint (container) needs ["compile"]
#    [deploy] ship (vm) needs ["unit", "lint"]
#  matrix: 2 cell(s)

# Run through the deterministic demo runner (no toolchain, greens every step):
nornir brokkr run
#  run 'nornir-ci' passed — 4/4 steps × 2 matrix cells (12 ms)
#    passed compile — would build variant 'server'
#    ...

# Run for real: `variant` steps actually build via cargo (build-thing, zero shell);
# container/vm steps fall back to the demo runner.
nornir brokkr run --real

# Run just one stage's steps:
nornir brokkr run --stage test

The matrix fans out: with rust=[1.85,stable] the run executes once per cell, each its own CI_BUILD job and its own history row, and the CLI reports the aggregate verdict across every cell (brokkr_run fan-out loop, nornir.rs:13005). A red step poisons its downstream dependents (they show skipped), and a run that ran-but-went-red exits non-zero with the detail preserved.

Add --json (global, so both brokkr --json run and brokkr run --json parse) to get the machine CommandOutcome.

f3 — trigger, history, badge, agents

# Would a push on main fire this pipeline? (Canonical firing logic — brokkr::trigger.)
nornir brokkr trigger push --branch main
#  event 'push' on main FIRES pipeline 'nornir-ci' (1 trigger(s) match)
nornir brokkr trigger push --branch feature/x
#  event 'push' on feature/x does not fire pipeline 'nornir-ci'

# List historized runs (green/red, trigger, passed/total, duration):
nornir brokkr history
nornir brokkr history --project nornir --json

# Emit a shields-style status SVG for a pipeline's latest run (embeddable, no fetch):
nornir brokkr badge nornir-ci > badge.svg      # green "passing" / red "failing" / grey "unknown"

# The build-agent registry (capability tags / busy-idle):
nornir brokkr agent ls
nornir brokkr agent register linux-1 --tag cargo --tag container

Non-firing is a legitimate answer (ok=true, real data), not an error. Empty history is a legitimate cold-start (reported with a count).

f5 — the Brokkr viz tab

Open nornir viz and pick the ⚒ Brokkr tab. A sub-view selector switches the five facets:

Everything on the tab is drivable headless: state_json carries one slice per facet, and tests/viz_brokkr_robot.rs clicks Run and asserts the run goes green across all five.

Where the same data shows up