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

nordisk/holger

.nornir/stinky/11-dwarves-on-oden.md

11 — dwarves on oden: the door the other lanes can ASK

Date 2026-09-20 · Box oden (32 cores, 499 GB RAM) · Branch stinky/dwarves

The owner: "få gärna liv i dwarves … med nordisk_workspace och mcp-verktyg — den analyserar allt du behöver", then, the same day: "om jag bad dig få igång dwarves på loki — så stäng av den. Istället: refresha (bygg om, cleana allt) dwarves på oden."

The reason it was worth the hour: on this one day several lanes each re-derived the constellation by hand — which repo declares which variant, what a row resolves to, which repos still have no code recipe — by opening .nornir/build-variants.toml files one at a time. dwarves buildthing inventory has answered all three in one second since August. The capability was there; the door an agent takes was not. It is now.

Everything below is labelled MEASURED (I ran it) or READ (I read it).


1. WHAT RUNS ON ODEN NOW

thing as bra detail
dwarves-server.service done systemd SYSTEM unit, User=dwarves, ExecStart=/home/dwarves/.dwarves/software/current/dwarves-server/dwarves-server, DWARVES_SERVER_ADDR=0.0.0.0:7900, warehouse /home/dwarves/.dwarves/warehouse
version new was v0.1.7 (4c748370f1fd), generation g4c74837, installed 2026-09-10 · now v0.1.7 (0088060f3640), generation g0088060, 2026-09-20
/usr/local/bin/dwarves new was v0.1.7 (ca28e986c8ba) (ten days stale) · now v0.1.7 (0088060f3640), sha256 6d096650b45d…
/usr/local/bin/dwarves-server new v0.1.7 (0088060f3640), sha256 ee612b2fbe95… — installed so any user can run dwarves-server mcp; the service copy under /home/dwarves is not world-readable
embedded nornir done 127.0.0.1:7878, same process

MEASURED, in order:

cargo clean                      # 85 409 files, 71.4 GiB reclaimed — the "cleana allt"
cargo build --release -p dwarves --bin dwarves-server --no-default-features \
  --features embed-all,read-fat,nornir/embed-ort,build-image,appliance-iso,container-oci
                                 # 4m23s, exit 0
cargo build --release -p dwarves --bin dwarves --no-default-features \
  --features cli,cli-install,vault,release,read-fat,rehearse,build-image,appliance-iso,container-oci
                                 # 44s, exit 0
sudo dwarves install --prefix /usr/local/bin
sudo dwarves install --from …/dwarves-server --as dwarves-server --prefix /usr/local/bin
sudo env CARGO_TARGET_DIR=… CARGO_HOME=… HOME=… dwarves-server --refresh --system

--refresh is dwarves' own rollback-safe door: it builds the declared dwarves-server variant from HEAD (never a feature string typed at the prompt), packs it, lands it as a new generation, flips current, restarts and health-checks. MEASURED after: current -> g0088060 is still a symlink, still dwarves:dwarves, service active, dwarves doctor --server http://127.0.0.1:7900 → dwarves-server ok · v0.1.7 (0088060f3640) · 0 pipeline(s) · 7 run(s).

rehearse IS compiled in (MEASURED): dwarves workspace-release <workspace_nordisk> --rehearse started the Gate-B /sparring rehearsal instead of answering Gate B … is COMPILED OUT of this build. The eight generations under software/ are the rollback set; nothing was deleted.


2. THE MCP DOOR — how another agent asks

MCP is a capability of the SERVICE, served on stdio by dwarves-server mcp. There is no MCP port: the agent spawns the process and speaks JSON-RPC to it.

From an agent working on oden — /home/rickard/git/.mcp.json (written 2026-09-20):

{ "mcpServers": { "dwarves": {
    "command": "/usr/local/bin/dwarves-server",
    "args": ["mcp"],
    "env": { "DWARVES_SERVER": "http://127.0.0.1:7900" } } } }

From an agent on another box (t14s, loki) — same tools, over ssh:

{ "mcpServers": { "dwarves": {
    "command": "ssh",
    "args": ["-o","BatchMode=yes","oden","/usr/local/bin/dwarves-server","mcp"] } } }

⚠ The lenses resolve the constellation from the process's cwd (NORDISK_ROOT, else the side-by-side repo dir). Start the server with cwd /home/rickard/git on oden, or the roster comes back empty.

13 tools, MEASURED from a real tools/list:

tool what it answers
buildthing_inventory every declared build thing in the constellation + the ROAD (kod/toml)
buildthing_search term → which repo declares a variant matching it
buildthing_info one product's full ladder: variant → features → settings → typ
buildthing_resolve (product, suite, variant) → the exact cargo line
release_plan · release_publishable · release_drift · release_gates · release_verification the release lenses (offline + live crates.io), publish nothing
release_publish the one ACT; dry-run unless the server carries DWARVES_MCP_ALLOW_PUBLISH=1 and confirm = PUBLISH-TO-CRATES-IO, and Gate B still runs
funnel_submit_idea · funnel_decompose · autonom_kick the funnel/intake legs

Worked example — "what does mvp-server resolve to?"

tools/call buildthing_resolve {"product":"holger","suite":"MVP","variant":"mvp-server"} MEASURED on oden:

buildthing holger: /home/rickard/git/holger — resolved by child of cwd
delivery 'MVP' — 1 variant(s):
  mvp-server               server    service  holger-mvp
      cargo build -p holger --bin holger-mvp \
        --features mvp-server,appliance,auth-passkey --no-default-features --profile release

That is the whole point of a describe verb: not "an atom called mvp-server exists" but what will actually be compiled.

Worked example — the roster

tools/call buildthing_inventory {} MEASURED on oden:

SUMMA  ok  19 repo · 140 varianter · kod 3 · toml 16 · drift 0 · vägrad 0

kod = the repo's Rust <repo>-build-thing recipe was read (the source of truth); toml = only the emitted .nornir/build-variants.toml was readable and the code road was refused, always by name. drift 0 = where both roads were readable they agreed.


3. WHAT IT CANNOT ANSWER — read this before trusting it

  1. It is not a deployment oracle. It reads declarations and manifests. What is actually installed, what is running, which generation a box carries, whether the twin is up — none of that. Ask the box.
  2. The roster is box-dependent, and the number moved. Same commit, same day: t14s answered 21 repo · 142 varianter · kod 5, oden 19 repo · 140 · kod 3. Nothing drifted — a kod road needs the repo's build-thing crate to compile to wasm32 inside the pinned toolchain image, and the image set differs per box. Treat kod/toml as a fact about this box today, not about the repo.
  3. dwarves deps <dir> counts worktrees as repos. MEASURED on t14s: 85 repo(s) and dozens of "produced by multiple repos — ambiguous, excluded" lines, because dwarves-laneB, gunnar-wt-open, holger-wt-arch … sit beside their parents. buildthing inventory dedups; deps does not. Do not quote a cross-repo dependency answer from a box full of worktrees. NOT on MCP anyway.
  4. Not on MCP at all: deps, dupes, coverage, mimir, docs, pipelines, history. Those are CLI verbs, and the last three read nornir's warehouse.
  5. buildthing_info <product> refuses without --suite/suite when the product declares more than one delivery. holger declares ORIGINAL and MVP, so {"product":"holger"} alone answers "this repo ships 2 deliveries — say which one". That is correct behaviour, not a failure.
  6. buildthing_inventory is slow (tens of seconds — it evaluates code recipes, some in a container). Fine from a real MCP client; a probe that closes stdin early loses the answer to timed out draining in-flight responses.
  7. It does not build in anger from the lens. Every buildthing tool here is read-only by construction. A real build is dwarves buildthing <product> --variant <v>, in a container or a KVM, never on the host.

4. THE CONSTELLATION ON ODEN — fresh, stale, unreachable

workspace_nordisk/nornir-workspace.toml [repos] lists 25 repos (READ — the brief said 23). MEASURED on oden after the sweep:

state repos
present + level with origin 21 of 25 — nornir · modgunn · funnel-core · holger · facett · edda · skade · znippy · znippy-zoomies · knut · korp · korp-collectors · ordning · Skidbladnir · tunnr · dwarves · workspace_nordisk · holger-ui · monetize-impl · private-gunnar-ops · draupnir
cloned by me today holger-ui (was MISSING; git@github.com:vetra-automation/holger-ui.git, now at fb293f0)
pulled forward Skidbladnir 3 behind → fdbfc77; private-gunnar-ops 31 behind → 8924e55
on a lane branch, not main draupnir on wip/oden-2026-09-12 (1 ahead / 5 behind origin/main); monetize-impl on lane/oden-webhook-secrets-2026-09-12 — dwarves resolves against whatever is checked out, so these answer for the LANE, not for main
unreachable gunnar, gunnar-ui, monetize, private-holger-ops — all four have ssh://git@git.gunnar.rs:2222/… as origin. The forge is the appliance and a new gunnar estate is being stood up, so these were deliberately not touched. Their working copies are from 2026-09-19 and cannot be verified. private-holger-ops is MISSING on oden entirely.

Auth is NOT the problem anywhere else: oden authenticates to codeberg (mikronova, key odin) and to github (Ignalina).

⚠ A trap found on loki, worth knowing wherever it repeats: ~/git/funnel-core there is not a clone at all — it is an unpacked crates.io .crate (it carries .cargo_vcs_info.json pinning 8d0ed523, Cargo.toml.orig, and 2006 mtimes). git fetch cannot tell you it is stale, because there is no git. oden's funnel-core IS a real clone (d55cde4).

The pinned toolchain image

MEASURED: oden did not carry nordisk-cargo-toolchain@sha256:3d5115ba…, so every code road that needs it was refused with 403 denied: requested access to the resource is denied and the inventory fell back to the emitted toml. Moved it t14s → oden with podman push dir: + rsync + podman pull dir: (never podman save|load, which silently re-digests and breaks every pin), tagged localhost/nordisk-cargo-toolchain:live-uefi. kod went 2 → 3 and holger's roster now comes off holger's own Rust recipe.

The rest are honest, named refusals, and two classes are DECLARATION bugs, not box problems:


5. WHAT I FIXED IN DWARVES (the standing rule is FIX DWARVES)

fix repo · commit why
The build-thing lenses on MCP — buildthing_inventory · _search · _info · _resolve dwarves 419de78 The CLI has answered "which repo declares which variant" since August; the MCP surface carried only funnel + release tools, so the door an AGENT takes could not ask. All four enter the SAME crate::buildthing functions the CLI enters — no second resolver, no second renderer.
suite_cli's --list rendering becomes VALUES (list_lines + list_resolved) edda 58c8a84 run's --list leg printed the variant rows with println!. MEASURED: over a stdio MCP session the tool returned its one header line and wrote the other three into the middle of the JSON-RPC stream. One renderer, two sinks; the CLI output is byte-identical. 682 lib tests green.
buildthing_resolve goes through the non-printing door dwarves f7a17aa Follow-up to the above; the lens is now read-only by construction — that path has no build leg at all.
The fat dwarves client gets its own install door back (cli-install on the declared row) dwarves 0088060 cli-install is default-ON in Cargo.toml with the reason written next to it — "a door behind an off-by-default flag is the same non-door" — but the dwarves variant sets no_default_features = true and never named it. MEASURED on oden: the freshly built declared dwarves answered error: unrecognized subcommand 'install' while /usr/local/bin/dwarves sat ten days stale, which is the exact drift the verb was added to end.

What I would fix next, and did not


6. WHAT WAS SHUT DOWN ON LOKI

Nothing was ever serving there — the build had not finished and no dwarves-server was ever started. Removed: the in-flight cargo build, the tmux session dwarves (the sessions arch, bench, estate, gunnarbench, holger, pullsweep belong to other work and were left alone), the _wt/stinky-dwarves worktree and its branch, /tmp/build-dwarves.sh, /tmp/dwarves-build.log, and ~/git/dwarves/target (cargo clean: 40 744 files, 31.3 GiB reclaimed). loki's dwarves and edda clones were left level with origin.