<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

<!-- nornir:class: readme -->
<!--
  GOLD README PATTERN. This is the *source* — the repo-root README.md is GENERATED
  from `.nornir/README.md` by `nornir docs render` and carries a
  "⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file" banner.
  Edit here, never the root copy.
-->

![dwarves](assets/dwarves.webp)

# Dwarves

CI/CD software forge inspired by the dwarven craftsmanship that created Odin's spear Gungnir and the gods' treasures, as recorded in the Icelandic Prose Edda (c. 1220 CE).

It binds tightly to **[Nornir](https://git.gunnar.rs/nordisk/nornir)** — handling code's time-space dimensional warehouse — and to **[Holger](https://git.gunnar.rs/nordisk/holger)**, the artifact registry guarding your artifacts at rest.

## Measured — the forge's planner hot paths

<!--
  `benches`, not `mashup`: dwarves benches no RIVAL system, so there is nothing for
  mashup's N-way head-to-head to compare against — and mashup infers its "system"
  token from the leading `_`-delimited segment of a result name, which folds
  `dwarves.incremental_cold` and `dwarves.incremental_warm` into ONE row and silently
  drops the warm (cache-hit) arm. `benches winner=row` keeps every arm as its own row.
  Numbers come from `nornir bench run dwarves` (bench/examples/nornir-bench.rs).
-->
<!-- nornir:gen:start:benches winner=row -->
**v0.1.4 · oden · 32 cores · 2026-07-22**

| workload | build_ms | builds_per_sec | cache_hits_per_sec | elapsed_ms | ids_per_sec | iters | replan_ms | run_ms | steps | steps_per_sec | telem_cores_busy_avg | telem_cores_busy_max | telem_cpu_pct_avg | telem_cpu_pct_max | telem_elapsed_ms | telem_mem_pct_max | telem_mem_peak_mb | telem_n_cores |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dwarves.dag_build | 0.12 | 8546 | | 2340 | | 20000 | | | 150 | | 2 | 2 | 6.63 | 7.04 | 2342 | 5.44 | 16.98 | 32 |
| dwarves.incremental_cold | | | | **78** | | 200 | | 0.39 | 150 | 386271 | 2 | 2 | 6.25 | 6.25 | **79.07** | 5.44 | 17.55 | 32 |
| dwarves.incremental_warm | | | 520476 | 144 | | 500 | 0.29 | | 150 | | 2 | 2 | 6.44 | 6.44 | 145.91 | 5.45 | 17.55 | 32 |
| dwarves.step_input_id | | | | 361 | 1.38M | 500000 | | | | | 2 | 2 | 7.03 | 7.03 | 361.39 | 5.46 | 17.55 | 32 |
<!-- nornir:gen:end:benches -->

---

## Dwarves release — usage

**Dwarves release** is a multi-language, industrialised implementation of cunning, the cargo/crate spec, and academia — built to solve the Codeberg/Nordisk release: **154 crates across 17 repos**, cross-repo cycles and all.

```sh
dwarves workspace-release .             # DEFAULT dry-run — PLAN ONLY: plan the constellation, no build, no branch
dwarves workspace-release . --verify    # + the pre-publish cargo-test suite
dwarves workspace-release . --rehearse  # + Gate-B: rehearse the cascade into a throwaway local registry
dwarves workspace-release . --execute   # for real — verify + rehearse, then crates.io (only after both gates pass)
```

The run is **tiered** and **cheap-first, build-last**: the default dry-run **plans only** (deps-first order, ship-versions, the static Gate-A precheck, advisory graph analysis) and STOPS — no build, no `cargo test`, no branch — in **milliseconds**. `--verify` adds the cargo-test suite; `--rehearse` adds the Gate-B `/sparring` rehearsal; `--execute` implies **both** (a real publish always verifies + rehearses), then uploads. Nothing reaches crates.io until **two gates go green** — *Gate A* (static) and *Gate B* (a rehearsal into a throwaway local registry). A build never runs on an already-failed run, and a `--verify`/`--rehearse` dry-run builds into the **same target `--execute` reuses**, warming its cache.

In the Dwarves UI, the picture below shows a dry-run error — caught by the static Gate A in **milliseconds**, before any build. With `--rehearse`/`--execute`, Dwarves also **builds** the whole cascade into a **staged Holger** (a local repo):

<p align="center"><img src="assets/codevault-red.webp" alt="dwarves codevault — dry-run error: holger-server-lib red, missing the online feature" width="760"></p>

Fix the culprit and run again — green means the cascade publishes **deps-first**:

<p align="center"><img src="assets/codevault-green.webp" alt="dwarves codevault — both gates green, publishing deps-first" width="760"></p>

### Several repositories in one run, and the live dry-run

`dwarves release` takes several repository paths and runs them in the order given.
`--only` spans all of them. A name that no path publishes, or that two paths publish,
is refused. `monetize-impl` is never published: a run that names it, or finds it, is
refused before anything is packaged.

```sh
# from the estate's parent directory: read-only, uploads nothing
dwarves release tunnr edda monetize Skidbladnir --only korp-installer,appliance-grow --live-dry-run
# the same line with --execute publishes (byte-identical crates are skipped, a 429 waits and retries)
```

`--live-dry-run` asks crates.io what is already published, runs the size gate, and
runs `cargo publish --dry-run` for each crate that would go up, then runs Gate B (the
loopback `/sparring` rehearsal) exactly as `--execute` would.

### The pre-flight size gate

`dwarves release <repo>` weighs every crate it is about to upload **before** it uploads
anything, because **crates.io refuses an oversized upload with a bare HTTP 503** that
names neither a size nor a reason — cargo prints `failed to get a 200 OK response, got
503` and nothing else.

```sh
dwarves release .                       # dry-run: prints the registry's limit and where the number came from
dwarves release . --check-size          # + packages every crate NOW and weighs the .crate
dwarves release . --execute             # the size pass runs automatically, before any upload
dwarves release . --max-upload-mib 20   # a granted limit increase (or another registry's limit)
```

An over-limit crate **refuses the whole cascade** — exactly like the same-version drift
halt — naming the crate, its compressed size, the limit *and its source*, and the
**biggest directories and files in the `.crate`**, so the `exclude` to write is on
screen instead of behind a local `cargo package`. The two honest remedies are printed
with it: ship less, or get the limit raised and pass `--max-upload-mib`.

The limit is never a magic number. crates.io's **10 MiB** is a documented constant (no
registry endpoint publishes it, and a staff-granted per-crate increase is invisible to
the API); it is overridden by `--max-upload-mib`, `DWARVES_MAX_UPLOAD_BYTES_<ALIAS>` or
`DWARVES_MAX_UPLOAD_BYTES`, and for a non-crates.io `--registry` with no override the
limit is honestly **unknown** and refuses nothing. If a 503 still arrives mid-cascade,
it is translated: over the limit ⇒ "this 503 IS the size refusal", within it ⇒ "reads
as transient — retrying", never measured ⇒ it says so.

📖 **[Release pseudocode (Rust)](dry-run.md)** — the dry-run step by step, the two gates, and the formal model, with diagrams.

📐 **[Release invariants](release-invariants.md)** — what a release *guarantees*, independent of how it is sequenced: the cross-repo dependency model, the invariants upheld today, the ones measured as absent, and why a gate can state the right condition and still be green by construction.

*The release process is **AI-free** — 100% dwarven-forged: deterministic Rust, no model in the loop.*

## Dwarves tune — the Heimdall tuner

`dwarves tune` measures a Build Thing row's programs into seccomp profiles, has a
person review them, and gates on drift. `<artifact>` is a row (`rusternetes-node`, in
the checkout you stand in) or `<repo>:<row>` (`tunnr:rusternetes-node`, a checkout
beside it or under `--checkouts`).

```sh
dwarves tune probe   rusternetes-node                    # probe + gen: profiles beside the row
dwarves tune probe   rusternetes-node --workload smoke   # + the runs of the [[driver]] "smoke"
dwarves tune gen     rusternetes-node                    # regenerate from the probe evidence (no probe)
dwarves tune approve rusternetes-node --note "reviewed"  # record the review in approved.toml
dwarves tune check   rusternetes-node                    # fail on a new call or an unapproved profile
dwarves tune check   tunnr:rusternetes-node --json       # the same, as the stable JSON document
dwarves tune show    rusternetes-node                    # the review: calls, near-misses, approval, kernel join
```

The row lives in `.nornir/profiles/<row>/measure.toml` (its programs, runs, optional
`[[driver]]` workload drivers, and the `heimdall` tool pinned by version and sha256).
`approve` writes `approved.toml` beside it: per program and libc, the sha256 of each
reviewed `.json` / `.oci.json`, the reviewer's name from `git config user.name` (never an
e-mail address), the date and the note. After that, `check` fails on any profile that
changed since its approval: `not approved: <program> <file> changed since <date>`.

A workload driver without `command` adds corpus runs, named `<driver>/<run>`. A driver
with `command` drives a served app (the served-app runner comes from lane HEIM-APP; until
it lands, such a driver is refused by name). `gen` and `check` reuse the drivers the last
probe used.

Every verb prints text, or one JSON document with `--json` (schema `dwarves.tune/1`:
`ok`, `tool`, `programs`, `checked`, `drift`, `out_of_sync`, `vm_later`, `approval`,
`review`, `problems`, `next`, `text`; a refusal is `{ok: false, error}`). The exit code is 0 when
ok, 1 on findings and 2 on a refusal. A missing tool, an unbuilt binary or an unknown row
is one line that names the fix.

### Review in the UI

The Build Thing UI (dwarves-gui) has a **Syscall profiles** tab in the Build area. Pick a
`<repo>:<row>` (every row under `$DWARVES_CHECKOUTS`, else `$DWARVES_CONSTELLATION`, else
beside the checkout the UI runs in) and it shows what `dwarves tune show` answers:

- per program and libc, every call of the profile with its action; a call seen only on
  error paths is marked **near-miss** (with the test), a bare allow is marked **BARE**;
- what the probe cannot cover off a VM (the programs and paths that wait for the boot);
- each reviewed file's approval: approved by whom and when, changed since a date, or
  never approved;
- the drift the last check found, by call and test;
- the kernel options each call needs and the drops a measured call vetoes, when the row
  has a saved join: `kernel needs <row> --json > .nornir/profiles/<row>/kernel-needs.json`.

The **Check**, **Probe** and **Approve** buttons (Approve takes a note; the reviewer is
`git config user.name`) run the same verbs through `dwarves::tune`, each as a queued jera
job (`dwarves::tune::jobs`, kind `dwarves_tune`). The pane is redrawn from the verb's own
JSON document; it reads no profile itself.

The licence boundary: Heimdall is AGPL-3.0-only and dwarves is MIT OR Apache-2.0.
`dwarves tune` calls edda's `nornir_build_thing::measure`, which runs the `heimdall`
binary as a separate process and reads its JSON. No heimdall crate is linked.

---

Every crate in the constellation is held to **[Brokkr's Software Criteria](brokkr-software-criteria.md)** —
baremetal, minimal-dependency, zero-copy / multi-core / SIMD.
