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

nordisk/dwarves

.nornir/dwarves-lifecycle-and-ui.md

dwarves — fat node, thin UI, lifecycle, uninstall & deploy (operational reference)

⚠ AMENDED 2026-08-03 — the FIVE-ATOM LADDER, and MCP moved into the server

This document describes two binaries. The declaration now has five atoms, and .nornir/build-variants.toml is the authority, not this file:

atom role linkage bin
dwarves-server ⭐ server fat dwarves-server
dwarves client fat dwarves
dwarves-cli client thin dwarves-cli
dwarves-ui ⭐ client thin dwarves-ui
dwarves-solo ⭐ solo fat dwarves-solo

Two changes an operator will actually notice:

  • dwarves-mcp is gone as a binary. The MCP tools are dwarves::mcp in the library and are served by dwarves-server mcp on stdio. An MCP client config that said "command": "dwarves-mcp" becomes "command": "dwarves-server", "args": ["mcp"]. Nothing about the tools changed.
  • dwarves-solo is new — the UI with the forge compiled in. One binary, nothing to install, no DWARVES_SERVER to set. dwarves-ui stays thin: "dwarves UI skall ALDRIG default byggas med servern compiled in" is untouched, because solo is a different file, not a different mood.

dwarves-ui-fat and dwarves-default were also removed. The fat-read UI IS the solo shape; the default-features compile guard was never a shippable artifact and that surface is compiled by every plain cargo build -p dwarves / cargo test --workspace.

⚠ AMENDED 2026-08-02 — dwarves-server FOLDED INTO dwarves-server

Everything this document calls dwarves-full is now the dwarves-server bin. There is ONE server binary, ONE [[bin]], ONE variant and ONE systemd unit; the dwarves-full bin, its [[bin]] entry and its build variant are deleted. The occurrences below were renamed in place, because it is the same product under one name.

What the fold fixed: the repo held three disagreeing declarations of the same thing — .nornir/build-variants.toml called dwarves-server a thin role = "tool" that is "NOT the server", crates/dwarves/src/service.rs (the ONE ServiceSpec both bins installed) called it a Fat state-holding forge, and the live unit was named dwarves-server.service with ExecStart=/usr/local/bin/dwarves-full. Rickard settled it: dwarves-server IS the dwarves server — role=server, linkage=fat, state-holding, Skidbladnir compiled in so dwarves-server --install creates its own unit.

The fat/thin axis is now a FEATURE choice on one source, not a second file: --features embed-all,read-fat is THE server (:7878 + :7900, the declared atom); --features server is the lean forge daemon (:7900 only, reaches a remote nornir).

The file:line citations below are as-of 2026-07-30 and were already drifting; the dated SIZE/mtime measurements are kept as the history they are.

State of the forge's runtime shapes and service lifecycle as of 2026-07-30. Every claim below is cited to file:line + commit hash and was verified against the tree. Related design doc: service-lifecycle-L1 (the skidbladnir service seam); ui-embedding-and-standalone (the thin/fat gRPC split rationale).

1. Two binaries: dwarves-server (THE SERVER, fat) vs dwarves-ui (THIN)

dwarves-server — THE FAT NODE

ONE headless process serving nornir's full gRPC stack on :7878 (via nornir::server_grpc::run, the same entrypoint the standalone nornir-server uses) side-by-side with the forge's own dwarf.v1 on :7900, on one tokio runtime (crates/dwarves/Cargo.toml:35-54, crates/dwarves/src/bin/dwarves-server.rs).

As of commit 4b2e19a ("feat(dwarves): default features embed nornir + skade…", 2026-07-30 00:02) the crate default now includes embed-nornir + embed-skade, so a plain cargo build yields the full binary — no more --features embed-nornir trap:

dwarves-ui — THE THIN CLIENT (HOST-ONLY)

The [[bin]] dwarves-ui of the dwarves-gui crate (dwarves-gui/Cargo.toml:18-19; renamed from dwarves-gui for a uniform <repo>-ui launch name matching nornir-ui/korp-ui). egui/eframe front-end that knows exactly ONE server address — the dwarves forge (:7900) — and dials nothing else. It embeds NO nornir-server and NO skade, and depends on dwarves lean (default-features = false, Cargo.toml:345).

The suite-host-embedded-auth LAW. nornir is EMBEDDED in the dwarves server (the host); the UI defers to that host and reaches nornir/Mímir data THROUGH the host, never by dialing nornir itself. Concretely:

2. Lifecycle is Skidbladnir's, not dwarves'

dwarves declares a ServiceSpec and drives a skidbladnir::service::Lifecycle; it does NOT hand-roll unit files or shell systemctl (zero-shell law).

3. The uninstall fix — Skidbladnir 9456079 (NOT pushed)

Branch fix/uninstall-data-nuke-bus-optional in /home/rickard/git/Skidbladnir, head 9456079 ("idempotent / self-healing over HALF-REMOVED state…", 2026-07-30 00:23). The branch has no upstream — it is local-only, not pushed (predecessors 998f3ac scope auto-detect, ca131b9 refuse-on-mismatch). All in systemd::remove_systemd_scoped_with (Skidbladnir/src/systemd.rs:1767-1920):

4. Deploy / bootstrap on oden

The running server is a SYSTEM unit User=dwarves (+ Group=dwarves; asserted for system scope at service.rs:749/751, and User=root is negated at service.rs:754), warehouse at /home/dwarves/.dwarves/warehouse (resolver ladder $DWARVES_WAREHOUSE → $HOME/.dwarves/warehouse → system /home/dwarves/.dwarves/warehouse — crates/dwarves/src/store.rs:40-41), binary /usr/local/bin/dwarves-server.

5. (RESOLVED by the host-only change) add-workspace no longer dials nornir from the UI

The old bug: in dwarves-ui, Add workspace → Workspaces.Register failed with Unauthenticated: missing or invalid bearer token because the THIN client dialed nornir :7878 directly (token-gated) while carrying no bearer — a direct-nornir client, the very violation the host-only law forbids.

Now (LIVE via the proxy): the UI holds no nornir address and never dials :7878. The warehouse-write affordance (Add/Populate) — and the whole warehouse/Mímir read surface — is served THROUGH the host: the dwarves forge re-serves nornir.v1.Workspaces / nornir.v1.Warehouse / nornir.v1.Mimir on :7900 and forwards each call to its embedded nornir, stamping dwarves' OWN bearer on the hop (crate::warehouse_proxy, the SuiteHost authority — one token, no second ~/.nornir/token). So Add → Workspaces.Register now travels UI → dwarves host → embedded nornir, carrying the dwarves bearer the whole way; the Unauthenticated failure class is gone because there is no thin UI→nornir socket left, and the register is authenticated by the host, not an absent nornir token. Honesty flag: the host→embedded-nornir hop is a localhost gRPC forward (dial URL $NORNIR_SERVER_ADDR, default http://127.0.0.1:7878), not a literal in-process Rust call — a direct call would require dwarves to link nornir (a dependency cycle). A genuine SuiteHost reader trait injected through dwarves-server (where nornir IS linked) is the logged follow-up.

6. Robot-test coverage GAP

The robot-UI hard gate is real — the facett Severity a11y contract (facett/facett-core/src/a11y.rs:49-112; Severity::{Error,Warning,Info}). A low-cost continuous robot-UI test runner for oden was built today, but it lives in the korp repo, NOT dwarves: branch fix/w102-oden-robotui-runner, commit c4601bf ("tools(w102): low-cost continuous robot-UI test runner for oden", 2026-07-29 21:56, in /home/rickard/git/korp). W097/W102 are worklist ids tracked in workspace_nordisk/.nornir/ (not code constants).

The host-only law IS robot-gated (dwarves-gui/tests/robot_host_only.rs): a headless robot builds the SHIPPED shell given ONLY the dwarves host, with NORNIR_SERVER set to a poison :7878 value, and asserts over state_json that the full roster mounts, the Forge dials the dwarves host, and no :7878/nornir address appears anywhere in the shell — the client can never silently dial nornir. The Funnel is asserted embedded (up with nornir DOWN). The former gap — a live end-to-end "served-via-host warehouse read" — is now CLOSED: dwarves-gui/tests/robot_warehouse_proxy_live.rs stands up the REAL dwarves warehouse proxy in front of a mock nornir and drives the SHIPPED shell given ONLY that dwarves host, asserting the Warehouse navigator's world tier renders LIVE rows (synthetic_functions=false, real node/edge counts, real_source_desc names the dwarves host) with no :7878 anywhere. The proxy's own forwarding is proven at the gRPC level by crates/dwarves/tests/warehouse_proxy_live.rs (Warehouse.Scan + Workspaces.Register/List through the host return the upstream's live data; a dead upstream is honestly Unavailable).

6. The ⚒ Build-Thing + Mímir live-demo surface is robot-gated end to end

The panes that fail live (the ⚒ BUILD-THING roster, the 🔍 Lenses, the 🌐 Warehouse navigator) now have honest-state robot coverage on BOTH the display and the EFFECT axes — each with a RED-when-broken peer, so none can go green through an outage: