<!-- nornir:class: design -->
# build thing — `target` facts (firmware · consoles · buses · nic) — design
> **Class:** `design` · **Function axis:** `f0` the `target` setting group · `f1` where it is declared · `f2` who consumes it and the typed consequence per typ · `f3` the proof door
> **Strict:** states only what the `target` group IS. Refines [build-thing-code-is-source-design.md](build-thing-code-is-source-design.md) (typed consequence, f2) and [build-thing-model-design.md](build-thing-model-design.md) (settings tree, f1).
> Written 2026-08-29 after the UpCloud lanes: three platform facts were declared NOWHERE and each cost a blind lane against a SeaBIOS-only, UART-less, IDE-cdrom host.
An appliance medium is built FOR a machine shape, and that shape is a **setting group on the installer variant**, not folklore in a shell script. The facts are: which **firmware** starts the medium, which **consoles** exist, which **buses** carry the medium and the target disk, and which **nic** model the guest sees. Every one is a typed input to a build, and every one has a typed consequence.
## The group (f0) — `target.*` on `[[build.installer]]`
| path | values | default | why the default |
|---|---|---|---|
| `target.firmware` | `uefi` · `seabios` · `both` | `both` | today's medium boots both (edda 39cb702); nothing built today changes |
| `target.consoles` | list of `serial` · `vga` (order = `console=` order, LAST is `/dev/console`) | `[vga, serial]` | today's cmdline `console=tty0 console=ttyS0,115200` |
| `target.buses` | set of `ide-cdrom` · `virtio-blk` · `virtio-scsi` | `{ide-cdrom, virtio-blk}` | UpCloud attaches the ISO as IDE cdrom; the nuked kernel vetoes ATA, so the medium must ALSO ride virtio-blk |
| `target.nic` | `virtio` · `e1000` | `virtio` | the only NIC an appliance initramfs can drive today (`VIRTIO_NET=y`) |
## Where it is declared (f1)
**One place: edda `nornir-build-thing`, `installer.rs` → `InstallerBuild.target: Target`** (`target.rs`). `#[serde(default)]` and skipped when default, so a published 0.1.x reader loads an old recipe unchanged and an emitted toml only carries the row when something was chosen. Each repo's `impl BuildThing` sets it in Rust; the toml row `[build.installer.target] firmware = "both"` is the EMITTED description, never read back as authority. gunnar's mirror crate pins the published 0.1.10 and cannot spell the field until the pin advances — the same gap `options`/`fixtures` already live in — so until then gunnar's committed toml carries the row explicitly and `recipe.rs` names the gap.
## Who consumes it, and the typed consequence (f2)
| consumer | toggle | consequence (typed) |
|---|---|---|
| **installer recipe** (edda `korp-installer::isocompose`, the ONE composer) | `firmware=both` | El-Torito: EFI section (efiboot.img) **and** the real BIOS default entry (`korp-boot-bios` stage, patch table → `vmlinuz` + `bios/initrd` by LBA). Target disk gets MBR code + `korpbios.bin` on its ESP |
| | `firmware=uefi` | EFI section only; the BIOS default entry is the **refusal stub** (`BIOSREF.BIN`: *"This medium boots UEFI only …"*) — SeaBIOS prints one sentence and halts instead of running FAT bytes. No BIOS stage staged, no MBR code laid |
| | `firmware=seabios` | BIOS stage laid; the ESP is still composed (the target disk's `korpbios.bin` lives there) but the medium's EFI El-Torito section is dropped — `IsoFormat::Bios` |
| | `consoles` | the `console=` tokens on the installer AND installed cmdline, in declared order; a list without `serial` puts `tty0` last (= `/dev/console`), without `vga` puts `ttyS0` alone. Empty list is refused by name (a boot nobody can see) |
| | `buses`, `nic` | the KVM install proof's attachment argv (`kvm_disk_args` + medium attachment): `ide-cdrom` → `-cdrom`; `virtio-blk` → the medium ALSO as a read-only virtio drive, target disk FIRST (vda); `nic` → the `-device` model |
| **tunnr kernel recipe** (Kconfig closure, when tunnr gets its `impl`) | `consoles ∋ serial` | `SERIAL_8250 SERIAL_8250_CONSOLE` in; absent → out |
| | `consoles ∋ vga` | `VT VT_CONSOLE VGA_CONSOLE PRINTK` in (VT selects INPUT); absent → out. `kernel::PID1_BOOT_CONTRACT` still refuses a `.config` with NEITHER console |
| | `buses` | `ide-cdrom` → `ATA ATA_PIIX BLK_DEV_SR ISO9660_FS`; `virtio-blk` → `VIRTIO_BLK VIRTIO_PCI VIRTIO_PCI_LEGACY`; `virtio-scsi` → `SCSI_VIRTIO SCSI BLK_DEV_SD` |
| | `nic` | `virtio` → `VIRTIO_NET`; `e1000` → `E1000` |
| **installtest** (gunnar, the proof door) | `--firmware uefi\|seabios` | the qemu MACHINE: `q35` + OVMF pflash pair vs `pc` (SeaBIOS, no pflash). `--serial none` = no UART at all (UpCloud). Attachments follow `buses`: target virtio disk first, ISO as IDE cdrom AND virtio raw, `nic` count 2 (UpCloud: public + utility) |
The consequence is measured, not asserted: firmware → which El-Torito entries the writer laid (`IsoLayout.describe()`); consoles → the cmdline read back off the installed ESP (`korp/cmdline`); kernel → the bzImage blake3 delta per group.
## The proof door (f3) — `gunnar-installtest installer --firmware seabios [--serial none] --iso PATH`
B1 install (`-boot d`, `-no-reboot` ⇒ qemu exits at the installer's reboot; gate = `INSTALL-OK` on serial OR, with `--serial none`, the protective MBR + `korp/cmdline` read back off the target) → B2 boot the installed disk alone, probe `SSH-2.0-gunnar` on hostfwd :2222 and a TCP accept on :50051 → B3 SIGKILL (power cut), boot again, same probe. Timings per leg are the arm's stages. The boot doors are `korp_installer::kvmproof` (`Machine` = firmware + serial + memory → argv), the SAME writer the UEFI installer proof uses — one harness, two firmware shapes. The hand-rolled `seabios-proof.sh` this replaced is the measurement this door reproduces.
## What is left, named
- gunnar's recipe mirror spells `target` only after the `nornir-build-thing` pin passes 0.1.10 (publish is Rickard's order).
- `korp-installer::install` laying MBR code + `korpbios.bin` on the target disk is unconditional today; `target.firmware` reaches PID 1 as `korp.target.firmware=` and the laying follows it — a `korp-installer` change, held back while another lane owns `install.rs`.
- tunnr's `impl BuildThing` does not exist yet (the tillskott); the Kconfig rows above are the contract it implements.