Gunnar

nordisk/dwarves

.nornir/build-variants.toml

# dwarves — the suite build matrix, as data (nornir-build-thing).
#
# The dwarves member of the fleet build matrix
# (`workspace_nordisk/.nornir/build-matrix-2026-08-02.md`), loaded through
# `nornir_build_thing::BuildVariantSet` and gated by `validate()`.
#
# ── THE LADDER (2026-08-03): FIVE SHAPES, AND NOTHING ELSE ─────────────────────
#
# Rickard: *"ta bort allt inte stämmer med matrisen"*. The matrix is the declaration,
# and every member of the fleet takes the SAME five shapes:
#
#   ORIGINAL   dwarves-server ⭐  server   fat    dwarves-server
#   ORIGINAL   dwarves            client   fat    dwarves
#   ORIGINAL   dwarves-cli        client   thin   dwarves-cli
#   ORIGINAL   dwarves-ui ⭐      client   thin   dwarves-ui
#   ORIGINAL   dwarves-ui-gpu     client   thin   dwarves-ui-gpu
#   ORIGINAL   dwarves-solo ⭐    solo     fat    dwarves-solo
#
# SIX ROWS, FIVE SHAPES, and the difference is the point. `dwarves-ui-gpu` (2026-08-16)
# is not a sixth shape — it is `dwarves-ui` on the RENDERER axis, the same client with
# the instanced GPU cloud linked and installed. korp settled this with `korp-server-gpu`:
# a GPU/CPU pair is one shape declared twice, never a new rung on the ladder, and
# `the_gpu_and_cpu_ui_atoms_differ_only_in_the_renderer` is what keeps it that way.
#
# `<x>` is the FAT client — everything compiled in, works with no server running.
# `<x>-cli` is thin everywhere. `<x>-solo` is the UI with its server compiled in: one
# binary, nothing to install. (The matrix stars only the server; `check_defaults` demands
# exactly one normal case per ROLE group, so the client and solo groups name theirs too.)
#
# THREE ATOMS WERE REMOVED to get here, and none of them was a mistake when it was
# written — each was a real artifact that had grown its own row instead of taking one of
# the five shapes:
#
#   * `dwarves-mcp` — the Model-Context-Protocol server. **MCP is a capability of the
#     SERVICE, not a delivery beside it** (Rickard: *"mcp skall väl ingå inkompilerat i
#     server!"*), and `<x>-server` is precisely the atom that means "everything compiled
#     in". So `crates/dwarves/src/bin/dwarves-mcp.rs` became `dwarves::mcp` in the
#     library, `embed-all` gained `mcp`, and the tools are reached as
#     `dwarves-server mcp` (stdio). MEASURED before the move: `/usr/local/bin/dwarves-mcp`
#     (18,504,064 bytes, 2026-07-31) with TWO live sessions running on oden, configured in
#     `~/.claude.json` as `"command": "dwarves-mcp"`. That config becomes
#     `"command": "dwarves-server", "args": ["mcp"]`.
#
# ⚠ **`mixed` is NOT why any of them went.** `linkage = "mixed"` is a legitimate,
# documented value of `nornir_build_thing::plugin::Linkage` (`plugin.rs:312`, projected
# from `variant::ServiceShape::Mixed`) and it describes a real artifact: storage engine
# embedded, server reached over the wire. nornir measured exactly that on `nornir-ui` —
# 61,790 `datafusion` + 8,841 `iceberg` + 6,198 `parquet` symbols against ZERO
# `nornir::server` / `nornir_server` / `tonic::transport::server`. `build-matrix-2026-08-02.md`
# calls `mixed` "a fourth value nobody documented" in one paragraph and says the opposite
# in another; the code is the authority and the code defines it. The two atoms that
# carried it left because the matrix is five atoms and because MCP belongs in the server,
# not because the value is illegitimate.
#
#   * `dwarves-ui-fat` — the UI built with `dupes-fat` (the local Iceberg read the thin
#     gRPC seam provably cannot carry). It got its own `[[bin]]` only the night before
#     (de004d2) because it and `dwarves-ui` were writing ONE file. That fix was right; the
#     declaration is what changed. Its capability folded into `dwarves-solo`, which is the
#     fat UI — so `dupes-fat` still has a builder and does not rot.
#
#   * `dwarves-default` — a compile guard for the crate's DEFAULT feature surface.
#     MEASURED 2026-08-03 through the door (`cargo xtask build --suite ORIGINAL --list`),
#     its materialized command was exactly `cargo build -p dwarves --bin dwarves` — no
#     features, no profile, i.e. `target/debug/dwarves`. It shared bin `dwarves` with the
#     fat client and escaped `check_bins` only because the PROFILE differed, which is an
#     accident of the key, not a distinction between artifacts. It was also never a
#     shippable thing: its own comment called it "a COMPILE guard, not a shippable
#     artifact" while it carried `suites = ["ORIGINAL"]` — and carrying the suite tag is
#     what makes an atom a Build Thing. What it guarded is not lost: `cargo build -p
#     dwarves` and `cargo test --workspace` compile that exact default surface on every
#     run, which is the same argument `UNDECLARED_ON_PURPOSE` makes for `xtask` — a target
#     with no `required-features` is built by every ordinary invocation, a stronger
#     guarantee than a matrix cell.
#
# ── ONE BUILD DOOR ─────────────────────────────────────────────────────────────
#
#   cargo xtask build --suite ORIGINAL [--variant <v>] [--list]   # inside the repo
#   dwarves buildthing dwarves --suite ORIGINAL [--variant <v>]   # from the forge
#
# (`--atom` is the deprecated spelling of `--variant`; it still works.)
#
# Both resolve through the same `nornir_build_thing::suite_cli`, which reads THIS file.
# Neither door contributes a variant name, a feature string or a bin name.
#
# ── WHAT `embeds` MEANS HERE ───────────────────────────────────────────────────
#
# ONE BUILD ⇒ ONE VARIANT, and `embeds` names what the LINKER KEPT, not what cargo
# compiled. That rule is not decoration: on 2026-07-22 three variants declared
# `embeds = ["nornir","holger","skade"]` and a symbol count found the linker had kept
# ZERO bytes of nornir in two of them.
#
# `validate()` also refuses `embeds` on `kind = "exe"` ("kind 'exe' produces no composed
# service"), so an exe that genuinely links a sibling records the measurement in prose
# here rather than in a field that would claim more than it means.
#
# ── THE INHERITED-DEFAULT TRAP, AND WHY TWO ATOMS CARRY `no_default_features` ───
#
# dwarves' crate default is `["cli", "vault", "embed-nornir", "embed-skade"]`, and
# `BuildVariant::cargo_args()` emits `--no-default-features` ONLY when an atom asks. So an
# atom that omits the key compiles the nornir umbrella IN whatever its `embeds` says.
#
# This went false without anyone touching it: the default gained `embed-nornir` +
# `embed-skade` on 2026-07-30, and from that day the DECLARED "embeds nothing" atoms
# silently carried a whole nornir. MEASURED both ways on 2026-08-02 (oden), through the
# shared `LinkageAudit::of_families`:
#
#   | atom          | default ON                        | + no_default_features         |
#   |---------------|-----------------------------------|-------------------------------|
#   | `dwarves`     | 269,568,776 B / 2,468 `nornir::`  | 43,575,144 B / 0              |
#   | `dwarves-cli` | (same inherited shape)            | 17,471,680 B / 0 on every     |
#   |               |                                   | THIN_FORBIDDEN_SYMBOLS family |
#
# Dropping the default also drops `cli`, and both bins carry
# `required-features = ["cli"]` — so each atom must NAME its floor or cargo refuses to
# build the target at all. That is why the key cannot simply be pasted onto every atom.
#
# `crates/dwarves/tests/build_variants.rs` holds this file to all of the above, and runs
# the SHARED checks — `validate_all`, `check_defaults`, `check_bins` (artifact
# collisions), `check_bins_declared` and the reverse direction — with the bin roster
# WALKED from the manifests via `BuildVariantSet::cargo_bin_targets(repo_root)`, never a
# list written in the test.

[build]

# ── THE DELIVERY (utgåva) AXIS ─────────────────────────────────────────────────
# dwarves ships ONE delivery, named ORIGINAL, and **no other suite name exists in this
# repo**. Every variant below carries `suites = ["ORIGINAL"]`, which on a single-delivery
# repo looks redundant and is not: carrying the suite tag is WHAT MAKES AN ATOM A BUILD
# THING, and it is what makes `cargo xtask build --suite ORIGINAL` the same command in
# korp, dwarves, nornir and modgunn as in holger — which alone ships two deliveries
# (MVP + ORIGINAL) and is the reason the axis exists at all.
#
# Read ORIGINAL as "the full delivery", never as "the legacy one".
base_suite = "ORIGINAL"

# ── THE BINS NO ATOM BUILDS, each with its reason ─────────────────────────────────
# ⚒ Build's gate (`buildgate::tally_bins`) counts every `[[bin]]` cargo can see in this
# tree against what the atoms produced. MEASURED 2026-10-16 (build-thing sweep, loki):
# six of seven built, `xtask` neither built nor excused — the delivery was RED on the
# gate after a whole build. `xtask/tests/build_gate.rs` asks the same question now.
[build.allow_unbuilt_bins]
xtask = "the `cargo xtask` door (the dwarves-gui live web demo build, docs) — repository tooling, never shipped."

# The suite dependency graph as an embed-allowlist: member -> siblings strictly
# DOWNSTREAM of it (the only siblings a fat variant of that member may compile IN).
# dwarves sits at the bottom of the graph, so it may embed nornir + holger + skade;
# nornir must NOT embed dwarves (that would invert the graph).
[build.downstream]
dwarves = ["nornir", "holger", "skade"]
nornir  = ["holger", "skade"]
holger  = ["skade"]

# ── ⭐ THE DWARVES SERVER (`dwarves-server`) — the service, everything compiled in ─
#
# ONE headless, STATE-HOLDING process, two ports: nornir `:7878`
# (`nornir::server_grpc::run_with_host`, the SAME entrypoint the standalone
# `nornir-server` bin calls) + the forge `dwarf.v1` `:7900` (`dwarves::server::serve`),
# on one tokio runtime, through the shared `dwarves::server::serve_all`. `grpc-web` (via
# `embed-all`) makes `:7900` browser-reachable on the same socket. Skidbladnir is compiled
# IN, so `dwarves-server --install` renders and installs its OWN systemd unit from
# `dwarves::service`'s single `ServiceSpec`.
#
# **It also serves MCP** (`dwarves-server mcp`, stdio) since 2026-08-03 — see the ladder
# note at the top. `embed-all` carries `mcp`, so the capability arrives with the fat node
# rather than in a binary beside it.
#
# THE ONE VARIANT WITH A NON-EMPTY `embeds`, and the ONE builder of `embed-nornir`.
#
# ⚠ THIS BLOCK WAS A LIE UNTIL 2026-08-02. It said `role = "tool"`, `linkage = "thin"`,
# `embeds = []`, under a comment arguing "this variant is called `dwarves-server` but it is
# NOT the dwarves server". The measurement it rested on (0 `nornir::` symbols) was REAL —
# and irrelevant, because it was taken on a build that never enabled `embed-nornir`.
# Rickard settled it the other way: dwarves-server IS the server, `dwarves-full` folded in,
# and the feature set moved with the capability.
#
# `bin = "dwarves-server"` carries `required-features = ["server"]`, which means
# `cargo build --all-targets` SILENTLY SKIPS it. Build it through the door, or by name:
#   cargo build -p dwarves --bin dwarves-server --features embed-all,read-fat --profile release
#
# ⚠ THAT BY-NAME COMMAND SHIPPED A GPU-LESS SERVER UNTIL 2026-08-04. `nornir/embed-ort`
# lived ONLY in this atom's `features` below, so the escape hatch above — and the
# identical one in `crates/dwarves/Cargo.toml`'s header — silently built a binary with
# no ort compiled in. MEASURED: the live oden server was 285,600,160 bytes with ZERO
# `OrtGetApiBase`/`ORT_DYLIB_PATH` strings, embedding at 3–4 chunk/s beside an idle
# RTX 4090 that `nornir vector doctor` proved usable BY THE SERVICE USER (768 dims,
# 81 ms). The GPU now rides in `embed-nornir` itself, so no command line can drop it;
# `nornir/embed-ort` stays named below because this table declares what SHIPS, and a
# declaration that agrees with construction is the point, not duplication of it.
#
# `reaches = []` — and that is the POINT of a fat node, not an omission. `embed-all` pulls
# `warehouse`, so the artifact does link `nornir-warehouse-embed`'s thin `Telemetry`
# client; in this shape that client dials the nornir this SAME process is serving on
# `:7878`. Nothing external has to be UP, which is what a deployer reads `reaches` for.
#
# holger and skade are listed in `embeds` because they arrive THROUGH nornir (nornir
# embeds both): `embeds` names what is in the ARTIFACT, not what this crate's Cargo.toml
# wrote.
#
# ── MEASURED 2026-08-02 on oden, on the artifact of exactly this atom ──────────
#   cargo build -p dwarves --bin dwarves-server \
#     --features embed-all,read-fat,nornir/embed-ort --profile release
#   -> 297,862,000 bytes
#
# Counted through the SHARED audit — `nornir_build_thing::LinkageAudit::of_families` ->
# `count_families` -> `is_compiler_destructor_glue` — NOT a hand-rolled grep. Glue is
# reclassified, never counted, never discarded. Cross-checked against
# `nm -C --defined-only` (617,635 symbols total); the non-glue column reproduces exactly.
#
#   | family                     | non-glue (LINKAGE) |    raw | glue  |
#   |----------------------------|--------------------|--------|-------|
#   | nornir::                   |              5,864 |  7,450 | 1,586 |
#   | holger::                   |                  5 |      5 |     0 |
#   | holger_server_lib::        |              1,036 |  1,617 |   581 |
#   | skade::                    |                458 |    698 |   240 |
#   | skidbladnir::              |                287 |    366 |    79 |
#   | nornir_warehouse_embed::   |                722 |  1,216 |   494 |
#   | nornir_funnel_embed::      |                  0 |      0 |     0 |
#   | iceberg                    |              7,005 |      — |     — |
#   | parquet                    |              4,771 |      — |     — |
#   | dwarves::server            |                156 |      — |     — |
#   | tonic::transport::server   |                531 |      — |     — |
#
# Verdict `EmbeddedAndEmbeds`. No family is glue-only, so the glue rule changed no verdict
# here — it is applied because the rule is shared, not because this artifact needed it.
# `skidbladnir::` 287 is the measurement that says `--install` is real code in this
# artifact and not a declaration. ⚠ That table predates the `mcp` fold (2026-08-03), which
# adds rmcp/schemars/autonom to this artifact; the families above are unchanged by it but
# the byte count is STALE and is not re-stated as current.
[[build.variant]]
name = "dwarves-server"
suites = ["ORIGINAL"]
member = "dwarves"
package = "dwarves"
bin = "dwarves-server"
kind = "service"
role = "server"
default = true
linkage = "fat"
embeds = ["nornir", "holger", "skade"]
reaches = []
# `read-fat` grew `docs-fat` (2026-08-18): the doc-book + mashup verbs' EMBEDDED data
# leg (`dwarves docs render|check|book` reading `bench_runs` off the local Iceberg
# tree). No new weight here — this atom already embeds nornir via `embed-all`; the
# bundle is exactly how the verb ships ALIVE on the server instead of dead.
# `appliance-iso` + `build-image` ADDED 2026-08-25, and it is a CORRECTION of a shipped
# absence, not a new capability — the same class as korp-ui's missing `map3d-gpu`.
# ⚒ Build Thing's ▶ Build button spawns `dwarves::build_thing::BuildRun` INSIDE THIS
# BINARY, so a `kind = "installer"` / `kind = "iso"` cell is composed by the SERVER, not
# by whatever CLI the operator typed. Without these two features that composer is not
# linked, and the cell can only ever refuse. MEASURED on oden 2026-08-25 against gunnar:
# `gunnar-installer` came back `could not start: … needs this forge built with --features
# appliance-iso`, while the pane's own headline said `chained variant 'gunnar-server'
# failed to build` — and `gunnar-server` builds green on its own in 2m03s. The pane
# offered a button its worker could not honour, then named the wrong cell as the cause.
# The fat `dwarves` CLI has carried both features since 2026-08-14 (see the atom below);
# the server was simply never given them, so every installer/ISO cell in the UI was dead
# by declaration.
# `container-oci` is what makes the containerised cargo build REACHABLE. Without it
# `RealRunner::build_one` takes the refusal branch for every variant that declares a
# `[build.toolchain]` — measured 2026-08-26: gunnar declares one (image pinned by digest
# `f29e517c…`, five siblings), and the forge answered the refusal for all of it.
# MEASURED before adding: `draupnir` and `bollard` are ALREADY in this variant's
# dependency graph, so the feature turns a path on and adds no weight. It is the door,
# not the engine.
features = [
    "embed-all",
    "read-fat",
    "nornir/embed-ort",
    "build-image",
    "appliance-iso",
    "container-oci",
]
profile = "release"

# ── THE FAT CLIENT (`dwarves`) — self-contained by READ, not by embedding ──────
# The bare `<x>` is the FAT client: all functionality compiled in, works with no server
# running. `release,read-fat` gives the release/deps ACT surfaces plus the three
# warehouse-READ legs (dupes · coverage · mimir) opening a local Iceberg tree in-process
# through arrow/parquet. Shut every server in the constellation down and this still
# answers — which is what "self-contained" means here, and why the linkage is `fat`.
#
# `no_default_features = true` — see the inherited-default trap at the top. `embeds = []`
# and `reaches = []` are TRUE of the 43,575,144-byte artifact: 0 `nornir::`,
# 0 `holger_server_lib::`, 0 `nornir_warehouse_embed::`, 0 `dwarves::server`,
# 0 `tonic::transport::server`.
#
# `iceberg` 497 + `parquet` 1,511 ARE present and are the point: `read-fat` is HOW this is
# self-contained. `skade::` 130 is also present, transitively through the
# `nornir-warehouse-trait` edda leaf that `release` pulls — type surface (`SkadeError`,
# `ObjectStoreConfig`, `erased_serde` vtables), no instantiated catalog.
#
# ⚠ STALE AS OF 2026-08-18 (`docs-fat` joined `read-fat`): `docs-fat` needs the concrete
# `IcebergWarehouse`, so `read-fat` now implies `embed-nornir` — the next build of THIS
# atom will link nornir (and, through `embed-nornir`, the tonic server stack), so the
# measured zeros above and `embeds = []` no longer describe what this feature set
# produces. Decide at the next measured build: either accept the fat client embedding
# nornir (re-measure + set `embeds = ["nornir"]`), or move `docs-fat` out of `read-fat`
# onto the server atom's own feature list and let this client take the thin+jsonl docs
# legs only. Until then this declaration records the LAST measured artifact, not the next.
#
# NOT the client default: the UI is what a human means by "the client"; this CLI is the
# SCRIPTED case, reached deliberately. (holger settles it the same way.) It is a one-shot
# CLI, so it does not hold the warehouse open the way a long-running UI would — which is
# why the never-embed rule lands on the UI and not here.
[[build.variant]]
name = "dwarves"
suites = ["ORIGINAL"]
member = "dwarves"
package = "dwarves"
bin = "dwarves"
kind = "exe"
role = "client"
linkage = "fat"
embeds = []
reaches = []
#
# ⚠ `rehearse` ADDED 2026-08-04, and it is not an optional extra — it is Gate B.
# Without it the served-holger `/sparring` publish rehearsal is COMPILED OUT, and this is
# the binary that runs `dwarves workspace-release --execute`. MEASURED: installed
# `/usr/local/bin/dwarves 0.1.4` was built from this line without it, so every release it
# drove printed `Gate B … SKIPPED — not built with --features rehearse` and then uploaded
# to crates.io anyway. The note was accurate and stopped nobody; nine crates shipped that
# day, seven of them carrying a dependency requirement that did not contain the API they
# call. A release tool whose rehearsal is a feature flag someone can forget is a hollow
# green before an IRREVERSIBLE operation.
#
# `release_doctor::gate_b` now REFUSES `--execute` when it is absent rather than noting
# it, so a build from the old line fails closed instead of publishing — but the
# declaration is the real fix, and this is the declaration.
# ⚠ `build-image` + `appliance-iso` ADDED 2026-08-14, and they are the difference
# between a fat client that can build the fleet's variants and one that can only LIST them.
#
# MEASURED: the declared forge could not build a `kind = "container"` or `kind = "iso"`
# atom of ANY member. `dwarves buildthing gunnar --suite ORIGINAL --list` prints seven
# atoms and the last two say "not a cargo build — built by the container/iso builder";
# without these two features that builder is not compiled in, so the rows are declared
# and unreachable. gunnar's own xtask has NO `build` verb, so this binary is the ONLY
# door to them — which made the 30 MB gunnar appliance ISO a thing you could only
# produce by hand-rolling a cargo line, i.e. exactly the session folklore the epic's
# LAW 2 forbids ("any manual step must be ADDED TO the Build Thing declaration").
# workspace_nordisk's descriptor has carried it as a Rickard-hands item since 08-12.
#
# `build-image` was free the whole time: it is already in the crate's `default` feature
# list (Cargo.toml:122), and this atom only missed it because `no_default_features =
# true` never re-listed it. That is an oversight, not a decision.
#
# `appliance-iso` was a REAL cost until today and is not one now. It pulls
# `korp-installer`, whose payload is a `.znippy`, which pulled `openzl-sys-rs` 0.2.0 —
# whose `build.rs` shelled out to `curl` and `cmake`, and whose cmake run demanded
# `x86_64-linux-musl-g++`, a package that DOES NOT EXIST (Debian's `musl-tools` ships
# `musl-gcc` and `musl-ldd` only). Hence the crate comment "a forge that never masters
# appliance media should not have to have a C toolchain" (crates/dwarves/Cargo.toml:531).
# znippy bumped to `openzl-sys-rs` 0.3 today (znippy 76f9a88): C sources vendored,
# built through `cc`, zero `Command::new`. PROVEN on dwarf with sabotage stubs for
# cmake/curl/tar/git/wget ahead of the real binaries on PATH — `cargo build/test -p
# znippy-common --features openzl --offline` passed 108 tests including the OpenZL
# roundtrip, and not one stub was ever invoked. What remains is `cc`, which is not an
# argument. See znippy e32f5f4.
#
# So this stays ONE atom in ORIGINAL rather than a second suite: a second variant
# emitting bin `dwarves` would collide in `check_bins`, which keys on
# `(suite, target, profile, package, bin)` — a bin name is a FILE PATH — and there is
# no second product here anyway. `<x>` is the FAT client; "all functionality compiled
# in" has to include the builders it is the only door to.
features = [
    "cli",
    # `cli-install` — ADDED 2026-09-20. It is DEFAULT-ON in Cargo.toml for a stated
    # reason ("a door behind an off-by-default flag is the same non-door"), and this row
    # carries `no_default_features = true`, so the declared artifact was the one build of
    # dwarves that could not install itself. MEASURED on oden the same day: the freshly
    # built declared `dwarves` answered `error: unrecognized subcommand 'install'`, while
    # `/usr/local/bin/dwarves` was ten days stale at `ca28e986` — exactly the drift the
    # verb exists to end, reproduced by the declaration that ships it.
    "cli-install",
    "vault",
    "release",
    "read-fat",
    "rehearse",
    "build-image",
    "appliance-iso",
]
no_default_features = true
profile = "release"

# ── THE THIN CLIENT (`dwarves-cli`) — no siblings in; nornir reached over gRPC ──
# `<x>-cli` is thin EVERYWHERE in the fleet, matching holger's `cli`. The lean forge CLI
# links the thin gRPC clients (`funnel-action` pulls `nornir-funnel-embed` +
# `nornir-warehouse-embed`) and reaches a remote nornir.
#
# `reaches = ["nornir"]`, not the old `["nornir","holger","skade"]`: `funnel-action` links
# exactly two nornir clients and NO holger or skade client, so the other two entries named
# a wire that does not exist in this build. A `reaches` that lists a sibling the binary
# cannot call is the same failure as an `embeds` that lists one the linker dropped — a
# deployer reads this line to know what must be UP.
#
# ⚠ RENAMED 2026-08-02: `dwarves-thin` → `dwarves-cli`, bin `dwarves` → `dwarves-cli`. It
# and the fat `dwarves` above BOTH emitted bin `dwarves` at `--profile release`: one file
# written twice, last build wins, nothing on disk saying which shape you are holding.
# `nornir-build-thing`'s `no_sibling_repo_declares_two_variants_onto_one_artifact` was RED
# on exactly this pair; the fix is holger's — distinct `[[bin]]` names off one `main.rs`.
#
# ── MEASURED 2026-08-02, oden ─────────────────────────────────────────────────
#   cargo build -p dwarves --bin dwarves-cli --no-default-features \
#     --features cli,vault,funnel-action --profile release   -> 17,471,680 bytes
#
# ZERO on every family in `nornir_build_thing::linkage::THIN_FORBIDDEN_SYMBOLS`
# (`nornir_server`, `nornir::server`, `dwarves::server`, `tonic::transport::server`,
# `iceberg`, `parquet`, `datafusion`), and 0 `nornir::`, 0 `skade::`,
# 0 `holger_server_lib::`. Shared `LinkageAudit` verdict: `ThinAndClean`.
#
# The one thing it DOES link is `nornir_warehouse_embed::` (110 non-glue symbols) — the
# thin gRPC read client, which is exactly what `reaches = ["nornir"]` means and exactly
# what `THIN_FORBIDDEN_SYMBOLS` deliberately does NOT name (a thin client legitimately
# links thin clients; a guard that failed that would fail every client there is).
[[build.variant]]
name = "dwarves-cli"
suites = ["ORIGINAL"]
member = "dwarves"
package = "dwarves"
bin = "dwarves-cli"
kind = "exe"
role = "client"
linkage = "thin"
embeds = []
reaches = ["nornir"]
features = ["cli", "vault", "funnel-action"]
no_default_features = true
profile = "release"

# ── ⭐ THE UI (`dwarves-ui`) — THIN, and that is a RULE ─────────────────────────
#
#   "dwarves UI skall ALDRIG default byggas med servern compiled in. dwarves UI
#    skall alltid default vara thin och nyttja en dwarves server."
#
# This refines the fat-is-default doctrine rather than contradicting it. Fat is the
# default for the thing that OWNS STATE — the server. A **UI is a client**, and a client
# that embeds its own server shows its own in-process world instead of the world the real
# server sees, with nothing on screen to say which.
#
# It is also a correctness rule, not only a UX one: ONE process owns a warehouse at a
# time. A UI that opens the warehouse in-process while `dwarves-server` holds it is a
# SECOND WRITER against a lock that assumes one.
#
# So: `linkage = "thin"`, `embeds = []`, and it REACHES the forge (`dwarves`, `:7900`);
# the warehouse/Mímir data is served THROUGH that host by its embedded nornir, which is
# why `nornir` is in `reaches` as a requirement and not as a socket the UI opens itself.
# `default = true` — this is the normal client, and the zero-config
# `cargo build -p dwarves-gui` must produce it. `--server` stays the runtime selector;
# the build never decides for you.
#
# The fat UI now has a NAME of its own and it is `dwarves-solo` below — which is the
# honest place for it, because a UI that embeds a server IS the solo shape.
[[build.variant]]
name = "dwarves-ui"
suites = ["ORIGINAL"]
member = "dwarves"
package = "dwarves-gui"
bin = "dwarves-ui"
kind = "exe"
role = "client"
linkage = "thin"
embeds = []
reaches = ["dwarves", "nornir"]
default = true
# `vm-boot` — the ⚒ pane's ▶ Build & Boot, wired to a REAL boot through tunnr's
# `boot_test` under QEMU/OVMF/KVM, with the guest's serial AND its QMP `screendump`
# framebuffer streamed into the pane. Declared 2026-08-16 because WITHOUT it the button
# is not a button: `boot_run.rs:1890`'s feature-off `TunnrBootLauncher` returns the coded
# refusal `dwarves-launch-4` ("needs the `vm-boot` feature") for every variant, so clicking
# gunnar-installer in a stock UI could never install anything. The refusal is honest —
# it never fakes a boot — but an operator cannot act on a capability nobody compiled.
#
# It sits on the CLIENT atoms and not on `dwarves-server` on purpose: the boot happens in
# the UI's own process (`BuildRun::spawn_recording`), so the machine that must own QEMU,
# /dev/kvm and OVMF is the one showing the pane.
features = ["vm-boot"]
profile = "release"

# ── ⭐ dwarves-ui-gpu — the CPU half's twin on the renderer axis ────────────────
#
# NEW 2026-08-16, after korp settled the shape with `korp-server-gpu`. Same client, same
# `src/main.rs`, one difference: `features = ["gpu"]` links the instanced cloud renderer
# and `main` installs it into the live `RenderState`, so the Warehouse Navigator's
# UNIVERSE mode paints its node cloud on the adapter instead of the CPU dot painter.
#
# NOT `default` — `dwarves-ui` keeps the client star, because the GPU lane is a hardware
# claim and a default must hold on a box that has no adapter. A host without a
# `RenderState` gets a loud line and the CPU painter, never a silent downgrade.
#
# `linkage = "thin"` and `reaches` are IDENTICAL to the atom above on purpose: the
# renderer is a paint lane, not a dependency direction. If these two ever differ in
# anything but `features`, `the_gpu_and_cpu_ui_atoms_differ_only_in_the_renderer` goes red.
[[build.variant]]
name = "dwarves-ui-gpu"
suites = ["ORIGINAL"]
member = "dwarves"
package = "dwarves-gui"
bin = "dwarves-ui-gpu"
kind = "exe"
role = "client"
linkage = "thin"
embeds = []
reaches = ["dwarves", "nornir"]
# `vm-boot` is on BOTH ui atoms, never only this one: the pair's whole claim is that it
# differs in the RENDERER and nothing else, and a boot capability is not a renderer.
# Putting it here alone would make `dwarves-ui-gpu` quietly mean "GPU *and* can boot",
# which is the overclaim `the_gpu_and_cpu_ui_atoms_differ_only_in_the_renderer` exists to
# catch — and it would go red, correctly.
features = ["gpu", "vm-boot"]
profile = "release"

# ── ⭐ SOLO (`dwarves-solo`) — the UI WITH ITS SERVER COMPILED IN ───────────────
#
# NEW 2026-08-03. The fifth shape, and the one dwarves never had: *"the UI with its server
# compiled in. One binary, nothing to install."* You start it and it puts the UI up with
# the forge running INSIDE itself — no port to configure, no second process to keep alive,
# nothing to connect to.
#
# `role = "solo"` is its own role in `nornir_build_thing::plugin::Role` and not a flavour
# of the other two, for a reason that matters to whoever consumes this menu: a `client`'s
# defining question is *which server am I pointed at*, and solo has no answer because
# there is no remote; a `server` is headless and gets deployed under a service account
# with no display, and this one opens a window. Both mistakes are silent.
#
# **REUSE, not a fork of the UI.** `dwarves-solo` is a distinct `[[bin]]` off the SAME
# `dwarves-gui/src/main.rs` as `dwarves-ui` — the shape `check_bins` exists to enforce
# (two shapes of one source must not emit one file name). Under `--features solo`, `main`
# calls `dwarves::server::serve_all` — the EXACT function `dwarves-server`'s `main` calls.
# That function was moved out of `src/bin/dwarves-server.rs` into the library on
# 2026-08-03 for precisely this: a `fn` inside a `[[bin]]` is unreachable from another
# crate, so the only alternative was a second bring-up, and this repo already knows what
# two bring-ups of one server look like (`dwarves-full` vs `dwarves-server`).
#
# `features = ["solo"]` on package `dwarves-gui` expands to `dwarves/embed-all` +
# `dwarves/read-fat` + this crate's `dupes-fat` — so the server half is the SAME
# capability as the `dwarves-server` atom, not a reduced one, and the UI half gets the
# local warehouse read that `dwarves-ui-fat` used to carry.
#
# `linkage = "fat"` is REQUIRED of this role, not chosen: a solo build that reaches a
# sibling over the wire is not solo, it is a mixed client. `reaches = []` says so.
# `embeds = []` is a SCHEMA limit and not a claim — `validate()` refuses `embeds` on
# `kind = "exe"` — so what this artifact contains is written here in prose: the nornir
# umbrella, holger's server lib and skade, the same set as the server atom above.
#
# `default = true` because it is the only member of the `solo` role and `check_defaults`
# demands exactly one normal case per group. It does NOT make solo the normal dwarves —
# `dwarves-ui` keeps the client star, and the never-embed rule above is untouched: you
# cannot get a fat UI when you asked for `dwarves-ui`, because they are different files.
#
# ⚠ NOT YET MEASURED. Unlike every other block in this file there is no symbol table here,
# because the atom is one day old and the count has not been taken. Writing one before
# taking it is the exact defect this file exists to catch. What IS proven: the atom
# resolves and builds through the declared door. The artifact's family counts are OPEN.
[[build.variant]]
name = "dwarves-solo"
suites = ["ORIGINAL"]
member = "dwarves"
package = "dwarves-gui"
bin = "dwarves-solo"
kind = "exe"
role = "solo"
default = true
linkage = "fat"
embeds = []
reaches = []
features = ["solo"]
profile = "release"

# ── MEASURED 2026-08-03, oden — all five atoms built through the door ──────────
#
#   CARGO_NET_OFFLINE=true cargo xtask build --suite ORIGINAL
#   -> "built dwarves-server, dwarves, dwarves-cli, dwarves-ui, dwarves-solo"
#
# Five distinct files, five distinct md5s. Symbol families counted with
# `nm -C --defined-only` (a raw substring count, NOT the shared `LinkageAudit`, so glue
# is INCLUDED — these numbers are upper bounds and are not comparable to the non-glue
# tables above):
#
#   | bin            |       bytes | datafusion | iceberg | parquet | nornir::server | tonic::transport::server | dwarves::server | rmcp:: |
#   |----------------|-------------|-----------:|--------:|--------:|---------------:|-------------------------:|----------------:|-------:|
#   | dwarves-cli    |  17,471,720 |          0 |       0 |       0 |              0 |                        0 |               0 |      0 |
#   | dwarves        |  43,575,224 |          0 |     559 |   1,749 |              0 |                        0 |               0 |      0 |
#   | dwarves-ui     |  82,480,192 |          0 |    17 † |       0 |              0 |                        0 |               0 |      0 |
#   | dwarves-server | 303,676,632 |     61,708 |  10,068 |   6,320 |          2,082 |                      824 |             237 |  2,477 |
#   | dwarves-solo   | 350,992,248 |     61,733 |  10,180 |   6,457 |          2,139 |                      800 |             240 |      7 |
#
# † **The 17 are a FALSE POSITIVE and are worth the footnote.** Every one is
# `facett_icebergview::IcebergView` — a facett UI component whose NAME contains
# "iceberg", not the `iceberg` crate. A substring family grep cannot tell a crate from a
# crate that is named after it, which is the same class of error as the destructor-glue
# trap the shared `LinkageAudit` exists for. `dwarves-ui` links ZERO storage engine and
# ZERO server: `linkage = "thin"` is TRUE of the artifact, not just of the intent.
#
# Reading the rows:
#   * `dwarves-cli` is clean on every family — `thin` is true.
#   * `dwarves` carries iceberg + parquet and NO server and NO nornir — `fat by read`.
#   * `dwarves-solo` carries the storage engine AND `nornir::server` 2,139 +
#     `tonic::transport::server` 800 + `dwarves::server` 240. **The server is really in
#     the solo binary.** Confirmed at RUNTIME too: one PID bound BOTH `:17878` (nornir)
#     and `:17900` (forge) under an alternate-port probe, and the UI then dialled
#     `http://127.0.0.1:17900` — its own forge, not the compiled default.
#   * `rmcp::` 2,477 in `dwarves-server` vs 7 in `dwarves-solo`: both compile `mcp` (it
#     is in `embed-all`), and the linker keeps the tool router only where something calls
#     it — the `mcp` verb, which only `dwarves-server` has. The solo binary pays 7 glue
#     symbols for a capability it does not expose, which is the honest cost of putting
#     `mcp` in `embed-all` rather than in one atom's feature list.
#
# The MCP move is proven by APPLIED OUTPUT, not by a symbol count: a real stdio
# `initialize` + `tools/list` handshake against `dwarves-server mcp` returned rmcp 1.8.0
# and all NINE tools — `funnel_submit_idea`, `funnel_decompose`, `autonom_kick`,
# `release_plan`, `release_publishable`, `release_drift`, `release_gates`,
# `release_verification`, `release_publish` — the same surface `dwarves-mcp` served.

# ── THE TOOLCHAIN (2026-09-26): dwarves builds in the pinned container, never on the host ──
#
# Until today dwarves declared no `[build.toolchain]`, so `dwarves buildthing dwarves` and
# ⚒ BYGG took the host-cargo branch (build_thing.rs, "THE CONTAINERS RULE"): the host's
# rustc, linker and registry decided the artifact. MEASURED on loki the same morning: the
# declared `dwarves` row built on the host in 19m28s, the build the law forbids.
#
# The pin is the constellation's: the same nordisk-cargo-toolchain manifest digest gunnar,
# edda, Skidbladnir, draupnir, znippy and znippy-zoomies declare. `targets` is the host triple
# alone, because no dwarves row names a `target`.
#
# `siblings` is every repo a dwarves path dependency walks into, read off `cargo metadata`
# (packages with no registry source) on loki: draupnir, edda, facett, funnel-core, modgunn,
# nornir, skade, Skidbladnir, tunnr, znippy, znippy-zoomies. Also holger (holger-server-lib,
# which metadata missed and the escape check named) and gunnar, for the optional
# `gunnar-client` (crates/dwarves/Cargo.toml:884). And openzl-sys-rs (2026-10-14): znippy-common
# path-depends on `../../openzl-sys-rs`, so the container without it could not resolve
# the workspace (gunnar's cli_matrix tests were red on it). Each name is one more tree whose
# contents end up in the artifact, which is why it is declared rather than discovered. A
# path dep escaping into an unlisted sibling is refused BY NAME before the container starts.
#
# `cpus = 4` is the build-job budget runs on shared boxes are held to (CARGO_BUILD_JOBS=4);
# 16 GiB and two hours because the fat client is the heaviest cargo row in the constellation.
[build.toolchain]
image = "nordisk-cargo-toolchain"
image_sha256 = "86d575e874677da3eb9aff3ba6e38ca600781ee9b7948a9df43ef89a6213c762"
rust_version = "1.97.1"
targets = ["x86_64-unknown-linux-gnu"]
timeout_secs = 7200
mem_limit_mb = 16384
cpus = 4
siblings = [
    "draupnir",
    "edda",
    "facett",
    "funnel-core",
    "gunnar",
    "holger",
    "modgunn",
    "nornir",
    "openzl-sys-rs",
    "skade",
    "Skidbladnir",
    "tunnr",
    "znippy",
    "znippy-zoomies",
]