<!-- nornir:class: history -->
# build thing model — the plan (2026-08-26)
The dated plan for closing the gaps in [build-thing-model-design.md](build-thing-model-design.md). Waves, not a backlog: each wave is one or two lanes with a named acceptance, and a wave starts when the wave it builds on has pushed. Vocabulary: variant → features on the variant → settings / sub-settings → typ. Never "cell".
## Wave 0 — done today
| what | where | proof |
|---|---|---|
| the bijection laws A–E in the trait, every impl gated | edda `laws.rs` 0.1.9 (published), holger / gunnar (both mirrors) / dwarves / knut | `assert_bijective` green in each suite |
| the kernel donor edge, both doors | edda `InstallerBuild.kernel_donor` + `resolve_built_kernel`; dwarves CLI + ⚒ | live refusal: *"pin preset 'nuked' (d437b919…) ≠ choice (bd2d9dbc…)"*; with the veto: resolved |
| the kernel leaf absent until a kernel answer | facett `NavState.absent`, dwarves `leaf_absent` | `robot_the_kernel_leaf_does_not_exist_in_the_shipped_shell_until_declared` |
| the embed budget gate tells the truth | nornir `embed.rs` | draupnir committed 17.6 min vs 21.5 projected |
| the model written down | this directory | — |
## Wave 1 — running (started 2026-08-26 evening)
**Lane edda — the tree and the ABI** (gaps #1, #2, law C)
1. types first: one recursive node, kinds feature / setting / val, paths as ids, edges; derived from today's flat `OptionSpec`/`ParamSpec`/`kernel_donor` with no toml change; flat names accepted as unambiguous aliases. *Pushed early so the ⚒ lane can build on it.*
2. the trait answers: `options(path)`, `try_toggle(path, on)` (the kernel cascade is tunnr's answer), `actions(kind)` (the kind → verb table as data) — with defaults, over the wasm ABI, plus the one host import `fetch(coordinate) → bytes`; a test proves native and wasm32 give identical answers.
3. law C as **path ↔ flag**, law B as tree node ↔ declaration row.
Acceptance: 0.1.10 (not published), all existing gates green, holger green.
**Lane ⚒ — the pane reads the code** (gaps #9, #6, #7, honesty)
A. ⚒ loads and CALLS the build thing's wasm through the same loader as the CLI; a repo without a build-thing crate is refused by name (the toml is the named fallback, never a silent second path). Law B proven across the two doors.
B. the settings tree renderer — every node shows its path and the flag it equals; `kärna` is a node whose detail is the kconfig canvas; the kernel leaf retired as a concept; the four shapes (nothing / library / donor / own kernel) re-proven.
C. the honesty label on the canvas (edda's ~80 fragment symbols, prefix edges); verbs off the receipt's typ via `actions(kind)`.
D. `options` / `try_toggle` / `actions` over wasm wired; `fetch` fulfilled by the host, refused by name otherwise.
Acceptance: release binaries rebuilt into `~/.local/bin` (`dwarves-ui-gpu`, `dwarves-ui`); Rickard's four clicks.
**Lane korp — lawful** (gap #8, free-standing)
`korp-build-thing` crate in korp, both mirrors held by parity, `assert_bijective` in its suite; `korp-appliance-iso` still names no kernel edge (that is Rickard's product decision — the refusal says so).
## Wave 2 — after edda stage 2 has pushed
**Lane edges** (gap #3): the consumption edge generalised — any feature bound to `repo/variant : typ`, type-checked at declaration time; `IsoBuild` gets the same edge so `gunnar-appliance-iso` consumes `tunnr/tunnr-kernel`; korp's iso stays edgeless until told; the ONE reader stays one.
**Lane presets** (gap #5): `val` at a variant's root (`preset = airgap` = a bundle of choices); pin lookup for a preset's built kernel in the suite warehouse / ledger instead of `tunnr/.kernel-out`; picking a preset with no vetoes costs no build.
## Wave 3 — after wave 2
**Lane `--build`** (gap #4): `--build <path|coordinate>` builds the consumed build thing as a step with its own `--opt`, in ⚒ and on the prompt; the order is the edge DAG and **runa runs it** (the build-pipeline DAG already exists — see [build-pipeline-dag.md](build-pipeline-dag.md)); the step console shows the donor build honestly (minutes cold, ~145 s cached). The "never builds the donor's kernel" rule is retired the day this lands.
## Two rules over every wave (Rickard, 2026-08-26 evening)
- **Benches, not only tests.** Tree derivation, `resolve(path)` at Kconfig scale, the wasm ABI call latency vs native, `check_bijective` over a large tree; in ⚒ the graph frame time (gunnar scale, kconfig scale, 20k synthetic), maximise/restore, fold/unfold, wasm load+call cold/warm. Numbers in the reports; problems appended to the bench log per this box's protocol.
- **The CLI builds everything ⚒ builds.** `dwarves buildthing` goes through the same wasm-loaded build thing, the same tree flags (`flags_of` is the line the node menu shows), in the same container; proven by a robot that runs that line through the CLI door and compares receipts/decision fragments with BYGG (law B/C across doors).
- **Integration tests build through the CLI.** Each repo's `tests/*.rs` carries a matrix that walks every variant from `variants()`: the dry gate (`--list`) always, the real build in the toolchain container when the box enables it, asserting the receipt (typ, sha, fragment) and emitting one nornir-testmatrix row per variant — law E proven on real builds, and the 16 Absent gunnar/korp cells filled.
- **Commit, rebase, push often.** Every green sub-step, in the repo's voice; never more than ~30 minutes uncommitted. A dead session (token limit, API drop) loses only what was not pushed — three lanes proved it on 2026-08-26.
- **Containers build things — never the host's libs.** Every build ⚒ or the CLI starts (a build-thing crate to wasm32, a donor kernel, a variant) runs in the declared toolchain container (`nordisk-cargo-toolchain`, draupnir/skidbladnir). The `fetch(coordinate)` host import is fulfilled by a container by default; a host path is a named exception the refusal can spell, never a silent fallback.
## Rickard's decisions, still open
- #10 a kernel column in the five-column matrix (gunnar-kernel).
- korp's product pinned to a kernel (edge on `korp-appliance-iso`).
- real Kconfig from fetched source (a container round-trip per resolve) vs. edda's curated fragments — when the honesty label is allowed to come down.
## Landings
*(append-only: date · lane · SHA · what)*
- 2026-08-26 · gunnar 0.1.9 fold-in · `16fd859` · pin → 0.1.9, gate in the default suite, mirror carries donor/fixtures/options/toolchain/allow_unbuilt_bins.
- 2026-08-26 · model · `c3f5c9b` `3c1e36d` · the design and this plan.
- 2026-08-26 · korp lawful · `8a6f570` · korp-build-thing crate, both mirrors held by parity (0 differing fields), assert_bijective in the default suite, iso edgeless by named refusal.
- 2026-08-26 · nornir · `c5bd222` · mimir_lens::subclone rides the vector feature; nornir with default-features=false compiles again (korp's dev-dep).
- 2026-08-26 · edda wave 1 · `8d6110f` `dd1af62` `5c5b7cb` · the settings tree IS the model (rows ↔ tree identity, law A); options/try_toggle/actions on BuildThing and over the wasm ABI (ops 11/12/13), `nordisk::fetch` with the container contract (ContainerFetchRunner seam for dwarves); laws B/C/D over the tree, flat law checks deleted; 0.1.10 unpublished. Benches: 20k-node derive 10.8 ms, check_bijective 20k 374 ms, wasm options 389 µs vs native 158 µs, plugin load 1.3 s. Still reading flat rows: declaration-time spelling validation only (validate_options/specs/fixtures, apply_params_with).
- 2026-08-26 · korp lane 2 · `fc0fdf4` `7467b1d` `74e9d42` · gate on the tree laws (16 green), cdylib + export_build_thing + native==wasm identity (host-built module, named; container road refused: no [build.toolchain]), cli_matrix: 9/9 dry gates green with testmatrix rows, real tier refused by name. Coupling: korp's iso mirror names edda's uncommitted IsoBuild.kernel_donor/edges — wave 2 owns keeping those names.
- 2026-08-26 · holger lane 2 · edda `25d7e19` `9fa82f2`, holger `5671634` `d002b8c` `c188940` · mirror recopied (byte-equal, 13-day drift closed), drift guard walks the whole serialized set; holger-side gate `build-space/tests/edda_mirror_no_drift.rs` via path dev-dep; installer declares no features (env-runtime, not compile-time — evidence in the mirror test); "cell" 39→0 in holger toml; cli matrix 18/18 dry rows green over both deliveries; real tier refused by name (no [build.toolchain]).
- 2026-08-26 · gunnar lane 2 · `b6916b3` `1fd09c8` `03c1cdb` `497eea8` `f94caee` · tree laws over the installer tree (4 roots / 25 nodes / 50 flags, 0 problems) behind --features tree-laws until 0.1.10 publishes; cdylib + native==wasm identity (11 trees, 104 toggles); "cell" 321→4 in code (kept: the receipt wire key "cell", a receipt-version decision); cli_matrix 8/8 dry + REAL build of gunnar-cli in the toolchain container (5m23, receipt typ exe · sha c9a4fc6c…). Refusal: the image has no wasm32 target, so build-thing modules are host-built and say so.
- 2026-08-27 · wave 3, resurrected · edda `23676e4`, dwarves `fba8384` `fa12f88`, gunnar `6ca015f` · all four wave-3 lanes died ~00:15 on a FULL DISK (/ at 100 %, dead lanes' target dirs ~350 G; holger/knut/gunnar Cargo.lock truncated to 0 bytes — restored from HEAD). Their trees were committed the morning after, after `cargo check --all-targets` and the lanes' own gates. edda: `build_dag.rs` (the `--build` grammar, brace ≡ flat, `BuildDag::derive` donors first, cycles refused), `KernelResolution::NeedsBuild{step}` (the reader hands back the step instead of deciding), edges on container/kernel/vm rows, `BuildVariant.applied_preset`, `suite_cli` reads `builds` and refuses by name, `discover_validated` as the door's load path; 524 unit + all suites green. dwarves: `build_steps.rs` (resolve → plan → `run_build_chain` through runa, `ForgeBackend.with_steps`), a real edge accepted at `refuse_build_flags`, `--list --build` = dry pass; audit #4 #9 #11 #16 taken. ⚒: `BYGG <kant>` on edge nodes, the step console (five states), robots 7/7 — **but `DeclaredForge::build_edge` still returns `missing_runner()`**: the click plans and refuses, the runner exists one crate over and is not wired. Cross-lane bug found and fixed: the dry pass planned, then handed `builds` to edda's non-chaining `suite_cli::run`, which refused the same line. gunnar: `tests/iso_edge_linked.rs` RED on purpose (0.1.9 pin drops `IsoBuild.kernel_donor`). **Wave 3 is not done**: ⚒ not wired to the chain runner, no real chained build measured, audit #1/#3 (type check at load from every door) landed only as `discover_validated` — no door calls it yet, #17 open, 0.1.10 unpublished.
- 2026-08-27 · W6, the old reds · dwarves `fe2a3b0` `3d366b9` `6a427cc` · the three red integration targets in `cargo test -p dwarves`, each fixed or NAMED; default suite now **374 passed / 0 failed / 14 ignored** over 31 binaries, cargo exit 0. (1) `dark_lib_tests_guard`: the `galois-axis` arm recorded 9 lib unit tests and `feature_axis_source.rs` carries 12 — the three arrived with `bb961f1`/`06c41ce` and neither touched the record; bumped only after RUNNING the arm (12/12 green in it; whole arm 207 passed / 0 failed / 4 ignored). (2) `installer_required_param_door`: `compose_installer_row` is two functions, one per `appliance-iso` arm, and the 2026-08-26 "refuse a missing param before the 18 m 29 s build" fix landed in the COMPOSING half only — so the arm a default `cargo test` compiles answered *"you need --features appliance-iso"* to an operator who had also forgotten `admin_key`. Gate hoisted into `assemble_cell`'s Installer arm above the feature gate, beside the option gate that is there for the same stated reason; one spelling, both forges (default 2/2, `--features appliance-iso` 2/2, `installer_build_door` 11/11 unmoved). (3) `code_road` law-B leg `#[ignore]`d, NAMED: **`bisync` is 100 % yanked on crates.io** — all seven versions (`0.1.0 0.2.0 0.2.1 0.2.2 0.2.3 0.3.0 0.3.1`) — reached via `nornir-build-thing → nornir-git ^0.1.5 → gix 0.86.0 → gix-protocol 0.64.0 → bisync ^0.3.0`. Measured with a bare probe crate: 0.1.9 AND the freshly published 0.1.10 both fail identically, so publishing did not lift it. The escape is one crate upstream and is not dwarves': `gix-protocol 0.65.1` dropped `bisync`, reachable only through `gix 0.87.1`, so `nornir-git` must go `gix = "^0.86"` → `"^0.87"` and be republished. **Headline: from a cold registry cache no published `nornir-build-thing` back to 0.1.5 resolves today.**
- 2026-08-27 · W6, audit #20 (never run before) · dwarves · features test `--features kernel-build,holger` **COMPILES and is green: 376 passed / 0 failed / 14 ignored** over 31 binaries (+2 over default). `examples/bench_kernel_ledger` released and run, first numbers for the kernel ledger — bzImage 13 MiB, 10 000 index rows: `ledger_key_of` **12.18 µs** (merge + sha256) · record the build **18.07 ms** (3 files + 1 index row) · index load JSONL cold **5.76 ms** for 10 000 rows · lookup **COLD 14.17 ms** (materialise 13 MiB + pin parse + sha256 + fragment compare) · lookup **WARM 8.34 ms** (verify only) · local `.kernel-out`, no ledger, **8.30 ms**. Two readings: the ledger costs **0.04 ms (0.5 %) over the donor's own directory** when warm, so the ledger is not the expense — and the whole verification is dominated by the sha256 over the bzImage, not by the index (the HashMap lookup does not show among 10 000 rows). Against a kernel build at minutes cold / ~145 s cached, a hit trades **~145 s for ~14 ms**.
- 2026-08-27 · W4, ⚒ runs the chain · dwarves `d94d293` `0f8ea2b`, edda `04cdcd6` · `DeclaredForge::build_edge` returned `missing_runner()` on BOTH arms and named `BuildRun::spawn_chain`, a function that has never existed in any crate; the real runner had landed one crate over in `fba8384`. The click now walks the CLI door's own road, in `build_cells`' order and with nothing written twice: `refuse_build_flags` → `expand_opts_for` → `resolve_kernel_axis` → `plan_build_steps` → `describe_plan` → `run_build_chain_watched` on a worker thread. ⚒ is a second READER of that chain, never a second runner. New seam in `crates/dwarves`: `run_build_chain_watched(…, ChainWatch)` (the same registry, the same runa; `run_build_chain` is that call with `None`), `StepProgress::{Started,Settled}`, and `StepRegistry::settled` as the one place an outcome is printed, watched and filed. The console shows only what the runner said: the DECLARED image while a step runs, the pin's own path + fragment when nothing was built, the receipt's shas when it was, the step's own sentence when it went red — and, when the step's variant declares no toolchain, that the build takes the HOST's environment (blocker B1 on screen, not hidden). The forge answers the plan it STARTED, so a donor that is itself a consumer brings its donor along and the rows are the rows runa was given; each row carries the planner's own line verbatim. RED first: `robot_bygg_edge` failed on the consumer's coordinate, and its refusal changed from a missing runner's name to `resolve kernel donor checkout 'tunnr': no checkout found`.
- 2026-08-27 · W7, one door's judgement · dwarves `e725b7e` · the plan's premise was wrong in a way worth recording: `build_forge.rs:2036` `BuildVariantSet::discover` is inside `#[cfg(test)]`, and the shipped forge never had a second LOADER — ⚒ and the prompt have shared `build_thing::code::take` and the named `BuildRoad` toml fallback since gap #9. What was not shared was the JUDGE: `read_through_code` has refused an invalid set since `35729c2` (`dwarves-buildthing-4`) while the pane loaded the same set down the same road and asked nothing, so a declaration the prompt refuses rendered here as a matrix. `menu_for` now calls that door's own gate (`buildthing::refuse_unvalidated`, lifted out of `read_through_code`); the refusal is on the road line, in the note, and every build verb refuses by it. Proof: `robot_two_doors::the_chain_the_pane_plans_is_the_chain_the_prompt_plans` drives ⚒'s dry pass (`plan_edge`, the dry twin — its own method, because a parameter deciding whether minutes of kernel compute happen is one somebody passes wrong) on gunnar's REAL declaration and compares it with `plan_build_steps` + `describe_plan` called directly: same coordinates, same order (`tunnr/tunnr-kernel` → `gunnar/gunnar-installer`), every row carrying the prompt's line verbatim.
- 2026-08-27 · W4/W7, two drifts the comparison found · edda `04cdcd6`, dwarves `e725b7e` · (1) `kernel_nodes` renders `preset` AND `profile` as vals, ⚒ hands BYGG every flag the tree spells, and both readers below disagreed with the tree that drew them: `resolve_kernel_cli` judged `--opt kernel.profile=vanilla` as a symbol row (*"a symbol row takes on|off"*) and `kernel_decision` took the profile off the DECLARATION and ignored a chosen one. Two vals side by side, one of them a choice. Both now take it like the preset — refused by name when unknown or given twice — and the guard asserts the hardened and vanilla FRAGMENTS differ, since a profile that changes nothing in the fragment is a control that does nothing. This made `BYGG kärnan` unrunnable on gunnar's real declaration. (2) the ⚒ door collected its `--opt` line through a `BTreeSet`, which SORTED it; the kernel grammar applies the line in order, cascade and contract refusals included, so the prompt got the declaration's order and ⚒ an alphabetical one. Now first-appearance order with the consuming step's line leading, and the robot compares the two lines verbatim rather than as sets.
- 2026-08-27 · W4 benches (⚒, release, headless, oden 32 cores) · `dwarves.step_console_frame_50` **0.893 ms/frame** over a 50-step synthetic DAG (120 iters); `dwarves.hammer_graph_frame_gunnar` **0.672 ms/frame**, graph paint **41 µs**, 28 nodes, 120 iters, 1 `BYGG <kant>` verb on the menu. Both from `cargo run -p dwarves-gui --release --example step-console-bench`. No wave-3 ⚒ numbers existed before this line.
- 2026-08-27 · W4 binaries · `dwarves-ui` and `dwarves-ui-gpu` built at the declared variants' own features and profile (`vm-boot` / `gpu,vm-boot`, release) and installed into `~/.local/bin`; both stamp `dwarves v0.1.4 (5623aaf13c4d)` — this lane's pushed HEAD. NOT DONE: no mapped window under Xvfb :99 (the process starts and logs its host, `import -window root` comes back blank — no GL adapter there), so the on-screen evidence for this lane is the robots' rendered assertions (`label_visible` on the refusal line and the CLI line), not a screenshot of the installed binary.
- 2026-08-27 · wave 3 W1/W3 · dwarves `35729c2` `7881fd7`, edda `edd279d` `7030901` · the build door validates at load (`validate_all_in`, errcode dwarves-buildthing-4, red: `read_through_code` returned Ok with `valid: true` for an edge whose typ the donor contradicts); and `plan_build_steps` became a CONSUMER of `BuildDag::derive` — its own Kahn ordering is gone, so the donor's donor is planned, deepest first (red: the old planner against the fixed edda planned `["mellan/mellan-rootfs"]` alone). Found on the way: a `[[build.container]]`/`[[build.vm]]` consumption edge was INVISIBLE — both `edges_of` and `SettingsTree::of_set` let the required delegating `[[build.variant]]` row shadow the descriptor; the two dispatch orders are now guarded against drifting apart again.
- 2026-08-27 · wave 3 W2 · **THE CHAIN RAN.** `dwarves buildthing gunnar --variant gunnar-appliance-iso --build kernel --opt kernel.preset=nuked`: kernel step **COLD 64.6 s** (real container build, linux-6.12.104, digest 765513a223dbeeef, artifacts bzImage + kernel.config + kernel-pin.json) → **CACHED 0.1 s, "BY PIN — built nothing"**. The receipt's fragment `bd2d9dbc10f3d7b5…` is exactly what the dry pass predicted for the preset, so **law D is asserted on a real artifact rather than on a state**. ⚠ Two of this plan's own numbers were wrong in the direction that matters: cached is 0.1 s, not ~145 s, and cold is 64.6 s for this preset, not "minutes".
- 2026-08-27 · the containers rule made OPERABLE · edda `1ad1b77` `cbfbef6`, tunnr `8d17408` · Both toolchain images had a complete engine, a committed Containerfile and **no way for an operator to produce them**: `DraupnirCargoRunner::build_toolchain_image` had ZERO callers and `DraupnirKernelRunner` had no image-build method at all. So `[[build.kernel]]` fell through to bare `debian:trixie`, which apt-installs its compiler — and a real chain died there after 263.2 s on `Temporary failure resolving 'deb.debian.org'`. Both images now have a door and a live gate: `nordisk-cargo-toolchain` sha256:035814c4… (39.8 s), `nordisk-kernel-toolchain` sha256:f573ca0a… (61.8 s), the latter asserted to hold gcc/make/flex/bison/bc **with the network off**. ⚠ A digest pin is not satisfiable by REBUILDING — gunnar pins f29e517c… and this box builds 035814c4… — so `image_sha256` is a per-box fact until the image is pulled from a shared registry. **⚠ Corrected 2026-08-30 (lane AV): that is a property of an unpinned RECIPE, not a law of container images.** Edda's `kernel-toolchain.Containerfile` now pins its base by digest, installs from a Debian snapshot, pins every pip version, declares `SOURCE_DATE_EPOCH` and is handed the same instant by the builder — four mints into four fresh empty stores gave ONE image id `3f632569f198…` and ONE manifest digest `78eaa55a651d…`, and the bollard door agreed with the CLI door once the manifest FORMAT was pinned to OCI as well. So for that image a rebuild DOES satisfy the pin, and a registry is a way to skip 50 s and 1.2 GB rather than the only way to make a pin true. The claim still stands for `nordisk-cargo-toolchain`, which has had none of that work. Gate: edda `toolchain_images_live.rs::two_mints_of_the_kernel_toolchain_are_measured_against_each_other`.
- 2026-08-27 · `localhost/` CORRECTION · edda · podman 5.7.0 reads `localhost/` as a REGISTRY HOST: both `localhost/<name>:tag` and `localhost/<name>@sha256:…` fail with `pinging container registry localhost: dial tcp 127.0.0.1:443: connection refused`, through podman AND through draupnir. The BARE name works in both spellings. This corrects kernel.rs's own note, itself dated as a correction.
- 2026-08-27 · bisync · edda `c456c76`, `nornir-git 0.1.8` PUBLISHED · `bisync` is yanked on crates.io — all seven versions — and reached via nornir-build-thing → nornir-git → gix 0.86 → gix-protocol 0.64. So from a cold registry cache no published nornir-build-thing resolved, including 0.1.10. Fixed one crate upstream (`gix 0.87` → `gix-protocol 0.65.1`). Applied-output proof: a bare crate on `nornir-build-thing = "0.1.10"` now resolves nornir-git 0.1.8 + gix 0.87.1 and **zero bisync**. Still named: nornir-docgen, nornir-release-core, nornir-workspace and gunnar-pack pin gix 0.86, so bisync remains in THEIR published graphs.
- 2026-08-27 · `nornir-build-thing 0.1.10` PUBLISHED to crates.io on Rickard's order. gunnar's pin bumped, `iso_edge_linked` green BY THE PIN (red seen on 0.1.9 by downgrading the lock in place). W2's remaining open ends: the wasm code road is refused BY NAME because `nordisk-cargo-toolchain` carries no wasm32 std (fix named in the refusal: `rustup target add wasm32-unknown-unknown` + `targets = [...]` under `[build.toolchain]`), and the ISO step needs a forge built with `--features appliance-iso`.
- 2026-08-30 · lane AK · **WAVE 3 CLOSED, AND THE SILENCE UNDER IT.** The chain runs at DEPTH 2 on the real declarations (lane AF's toolchain surface): `step 1/2 kernel.toolchain → tunnr/nordisk-kernel-toolchain : container` → `step 2/2 kernel → tunnr/tunnr-kernel : kernel · after tunnr/nordisk-kernel-toolchain`, `chain · passed · 3/3 runa step(s)` — toolchain 0.3 s · kernel 78.5 s cold / 0.1 s BY PIN · installer 232.3 s. The planner needed no change (it has consumed `BuildDag::derive` since `7881fd7`) and the ⚒ pane needed none (it has walked `run_build_chain_watched` since `0f8ea2b`) — the ledger row saying otherwise was stale. **What the chain is FOR:** the same `slim` row is a different kernel down the two roads — standalone **0 requirer rows, no CONFIG_XFS_FS, 2 208 768 B**; through gunnar's edge **66 symbols required by 5 features, CONFIG_XFS_FS=y, 2 413 568 B**. So lane AH's B2 failure (`mount /dev/vda3 on /data as xfs: ENODEV`) was never a preset fact. **BOOT-PROVEN through the chain**, `gunnar-installtest installer`, B1 install + B2 serve + B3 power-cut reboot, ALL PASS: **slim** (bzImage 2 413 568 B, ISO 38 903 808 B) and **nuked** (2 376 704 B, 38 836 224 B) — the first `slim` appliance that has ever booted. **normal and full are REFUSED at build time through the closure** and that is the right answer: `# CONFIG_FS_POSIX_ACL is not set` and `# CONFIG_MULTIUSER is not set` crept back in, because both presets are defconfig-based and carry filesystems whose Kconfig selects POSIX ACL straight back, while gunnar's `data.fs = xfs` declares a ONE-USER /data. The canary now names the requirer, the feature and the reason for each. Matrix (#18): the chained nuked medium passes `qemu-local`, `qemu-local-bios`, `hetzner-cloud` and `upcloud` on top of the default shape; `gce-virtio` HANGS the door — its `boot_disk = VirtioScsi` against a recipe whose `target.buses` are ide-cdrom + virtio-blk, so the appliance refuses correctly on the UART (`hwdetect: no usable target disk — every fixed disk carries the installer medium`) and `gunnar-installtest installer` does not gate on it; `ovh-public-cloud` hangs with no serial captured, undiagnosed.
- 2026-08-27 · **WAVE 3'S PRODUCT GOAL, REACHED** · `chain · passed · 2/2 runa step(s)`, exit 0. ONE command — `dwarves buildthing gunnar --variant gunnar-appliance-iso --build kernel --opt kernel.preset=nuked` — builds the donor kernel with the customer's choice and then the ISO that boots it. Step 1 `tunnr/tunnr-kernel` BY PIN 0.0 s, fragment `bd2d9dbc10f3d7b5…` (the fragment the dry pass named for the preset). Step 2 `gunnar/gunnar-appliance-iso` BUILT in 12.5 s, consuming the PINNED kernel through the DECLARED edge (blake3 9820a8012442b96f). The artifact is real and asserted as an artifact: `.iso-out/gunnar-appliance-iso.iso`, 16 646 144 B, `ISO 9660 CD-ROM filesystem data 'GUNNAR_APPLIANCE' (bootable)`, sha256 e9c08210…, composed app 23 247 408 B → znippy 8 695 774 B (pin 6c31cf6057d79486), initramfs 10 075 631 B, ESP 16 598 528 B. Full COLD chain ≈ 77 s (kernel 64.6 s + iso 12.5 s).
Three things had to be closed on the way, each one a thing declared but not operable: the two toolchain images had no door, so the kernel fell through to a base image that apt-installs its compiler; `localhost/` in a declaration turns a local image into a registry pull; and the pinned path refused the kernel config sitting beside the bzImage because it derived a DISTRO name (`config-<version>`) while `[[build.kernel]]` writes `kernel.config` — one artifact, two names. The medium needs `KORP_ROOT_SIGNING_KEY` to be bootable-with-an-anchor; run with `=dev` here, which the composer names as proving the path and NOT an authenticity anchor.
Still open, both named by their own refusals: the wasm code road has no `wasm32` std in `nordisk-cargo-toolchain`, and `forge.rs` is behind `#[cfg(feature = "forge")]` so none of its 236 tests compile in a default `cargo test`.