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

nordisk/gunnar

installtest/src/firmware.rs

//! **The firmware door**: the installer medium under the firmware the CUSTOMER's
//! machine has — SeaBIOS or UEFI — installed, booted, power-cut, booted again.
//!
//! ```text
//!   gunnar-installtest installer --target upcloud                 --iso PATH
//!   gunnar-installtest installer --target qemu-local-bios         --iso PATH
//!   gunnar-installtest installer --firmware seabios --serial none --iso PATH
//!   gunnar-installtest installer --firmware uefi                  --iso PATH
//! ```
//!
//! # `--target <preset>` (2026-08-29)
//!
//! The machine is RENDERED from edda's one roster
//! (`nornir_build_thing::target::presets::PRESETS`, 13 shapes) through
//! [`TargetPreset::qemu_shape`]: firmware, `-machine`, `-cpu`, the disk and NIC
//! device models, the NIC count, the COM-port map (no ttyS0 = `-serial none`
//! in that slot) and whether the medium rides `-cdrom`. A preset whose
//! [`TargetPreset::status`] is `needs-real-cloud` (ENA, gVNIC, Hyper-V, Xen,
//! bare metal) is REFUSED BY NAME with the preset's own reason and the
//! mimicable roster — never booted as a wrong default. `--firmware` and
//! `--serial` stay as explicit overrides ON TOP of the preset. When VGA is the
//! only console the door takes a QMP `screendump` of the guest as the proof
//! medium beside the port probe. Every leg that ran emits its
//! `appliance-boot/<preset>` row (`install_ok` / `serves` / `ready`) through
//! `isoproof::emit_boot_row`, the same writer `emit_kvm_row` is, so
//! `nornir_testmatrix::appliance::ApplianceBootGrid` folds it onto the roster
//! the code hands in. The proof names are the grid's own constants.
//!
//! # The four outcomes, and why a hang is not one of them (measured 2026-08-30)
//!
//! A leg ends `PASS`, `FAIL`, `REFUSED` or `TIMEOUT` ([`Verdict`]), and the
//! door's exit code says which ([`exit_code`]). It used to end `PASS` or `FAIL`,
//! and a hang was a `FAIL` you could only recognise by its 420 000 ms.
//!
//! Two presets HUNG this door, measured that day, 420.10 s and 420.29 s of
//! nothing each:
//!
//! * **`gce-virtio`** booted and the appliance refused CORRECTLY on the UART —
//!   `INSTALLER: FAILED hwdetect: no usable target disk` — because the preset's
//!   boot disk rides `virtio-scsi` and gunnar's recipe declares
//!   `buses = [ide-cdrom, virtio-blk]`. The door watched for one line, `Kernel
//!   panic`, so it sat out the whole budget and then blamed the CONSEQUENCES
//!   ("no protective MBR signature on the target") of an installer that had
//!   refused to touch the disk.
//! * **`ovh-public-cloud`** left **zero bytes** of serial: its roster row brings
//!   the medium as an imported qcow2, so no cdrom is attached; the firmware is
//!   SeaBIOS, which boots an MBR; and the medium's LBA 0 is all zeros. Nothing
//!   on that machine could hand over.
//!
//! Both are answered twice over, and neither by widening a match:
//!
//! 1. [`medium_cannot_install_here`] refuses both from the DECLARATIONS before
//!    a byte boots — 0.03 s, naming the bus or the attachment. It is asked of
//!    every preset and lets the four the deploy gate runs straight through.
//! 2. `korp_installer::serial::terminal_verdict` (inside `kvmproof::boot_hold`,
//!    so no door can forget it) ends a leg the instant the GUEST prints a
//!    terminal line — the installer's `FAILED`, either first stage's refusal, a
//!    panic — and the row quotes it. Proven live on the machine that hung:
//!    **420.10 s → 3.81 s**, `[REFUSED] B1 install 3753 ms  the installer ended
//!    the boot by name: INSTALLER: FAILED hwdetect: …`.
//!
//! And whatever else happens, a budget that runs out is `[TIMEOUT]` with its own
//! exit code ([`EXIT_TIMED_OUT`]), because nothing was measured and that is a
//! different fact from a measured failure.
//!
//! # Why this exists
//!
//! Every boot proof in this constellation ran under OVMF: `iso.rs` and `kvm.rs`
//! through tunnr's q35+OVMF launch, `installer_ssh.rs` through
//! `korp_installer::kvmproof`'s q35+OVMF doors. UpCloud boots **SeaBIOS only**,
//! attaches the ISO as an **IDE cdrom**, gives the guest **no UART** (VGA is the
//! only console) and **two NICs**. None of those four facts was declared
//! anywhere, and each one cost a blind lane against the real host (2026-08-28:
//! *"noll utdata, installeraren startar aldrig"*; 2026-08-29: the appliance went
//! dark after the kernel because `console=ttyS0` was `/dev/console` on a machine
//! with no ttyS0). The SeaBIOS boot path has existed in code since edda
//! `39cb702`; what did not exist was an automated proof of it. This is that
//! proof, and the hand-rolled `seabios-proof.sh` it replaces is the measurement
//! it reproduces.
//!
//! # The three legs
//!
//! * **B1 install** — the medium as IDE cdrom AND as a read-only virtio drive
//!   (the nuked kernel vetoes ATA; the PID-1 finds the volume by `CD001` on
//!   virtio), the blank target disk attached FIRST (`vda`), `-boot d`,
//!   `-no-reboot`. The installer's own reboot after `INSTALL-OK` exits QEMU.
//!   Gate: QEMU exited, no panic, and the DISK carries what an install lays —
//!   protective MBR + `korp/cmdline` on the ESP. With a UART, `INSTALL-OK` on
//!   serial too.
//! * **B2 serve** — the installed disk alone, no medium, two NICs, `:2222` and
//!   `:50051` forwarded. Gate: gunnar's own `SSH-2.0-gunnar` identification on
//!   the first and a TCP accept on the second. Then SIGKILL — a power cut.
//! * **B3 power-cut reboot** — B2 again, fresh ports. Same identity, serves
//!   again, from a disk that was never flushed.
//!
//! The declaration is the writer for the target disk size and its attachment
//! (`InstallerBuild::target_disk_bytes` / `kvm_disk_args`), the machine argv is
//! `kvmproof::Machine`'s — the SAME writer the UEFI installer proof uses — and
//! the port probe is this crate's own `ssh_banner`. Nothing here is a second
//! spelling of something another module already spells.

use std::io::Read as _;
use std::net::{SocketAddr, TcpStream};
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};

use korp_installer::isoproof::{self, ESP_CMDLINE_PATH, ESP_PARTITION, INSTALL_OK};
use korp_installer::kvmproof::{self, Firmware, Machine};
use nornir_build_thing::installer::InstallerBuild;
use nornir_build_thing::target::presets::{self, PresetStatus, TargetPreset};
use nornir_build_thing::target::Firmware as DeclaredFirmware;
use nornir_build_thing::variant::BuildVariantSet;
use nornir_testmatrix::appliance::APPLIANCE_BOOT_PROOFS;

/// gunnar's SSH listener inside the appliance.
const GUEST_SSH: u16 = 2222;
/// gunnar's control plane inside the appliance. A sealed medium declares
/// `--no-grpc` and this probe stays closed on every interface; an open one
/// binds the PRIVATE interface by name (`--grpc-bind eth1:50051`) and it must
/// then accept on the private NIC and REFUSE on the public one — see
/// [`Door::expect_grpc`] and `boot_installed`.
const GUEST_GRPC: u16 = 50051;
/// The cloud shape: a public and a utility interface (the historical door).
const NICS: usize = 2;
/// The slirp network the SECOND NIC gets. QEMU's default is `10.0.2.0/24` for
/// every `-netdev user`, so two NICs on the default network are two names for
/// one address — and Linux's weak host model then answers a packet arriving on
/// either from a socket bound to the other. On one network the public/private
/// asymmetry below passes whatever the listener did; on two it is real.
const PRIVATE_NET: &str = "10.0.3.0/24";
/// The virtio-blk device word — the one bus `InstallerBuild::kvm_disk_args`
/// (the declaration's writer for the target disk) spells. Any other bus is
/// rendered here as `-drive if=none` + the preset's device.
const VIRTIO_BLK: &str = "virtio-blk-pci";
/// What the medium's own SSH server says first.
const IDENT_PREFIX: &str = "SSH-2.0-gunnar";

/// Which firmware the door boots under. The word on the command line.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FirmwareChoice {
    Uefi,
    SeaBios,
}

impl FirmwareChoice {
    pub fn parse(s: &str) -> Result<Self, String> {
        match s.trim().to_ascii_lowercase().as_str() {
            "uefi" | "ovmf" => Ok(FirmwareChoice::Uefi),
            "seabios" | "bios" | "pc" => Ok(FirmwareChoice::SeaBios),
            other => Err(format!("unknown --firmware `{other}`: uefi | seabios")),
        }
    }

    pub fn label(self) -> &'static str {
        match self {
            FirmwareChoice::Uefi => "uefi",
            FirmwareChoice::SeaBios => "seabios",
        }
    }
}

/// **The machine the door boots**, resolved before anything runs: either the
/// historical flags (`--firmware` + `--serial`, 2 NICs, virtio everywhere,
/// medium as cdrom + virtio) or a roster preset rendered through
/// [`TargetPreset::qemu_shape`], with the flags as overrides on top.
#[derive(Debug, Clone)]
pub struct Shape {
    /// The `appliance-boot/<preset>` word when a preset was named; `None` =
    /// the flag door, which emits no roster row (it names no shape).
    pub preset: Option<&'static str>,
    pub firmware: FirmwareChoice,
    /// COM1..COM4 — `kvmproof::Machine::uarts`. A port may EXIST without any
    /// `console=` naming it (UpCloud's ttyS1/ttyS2): see `serial_console`.
    pub uarts: [bool; 4],
    /// A `console=` names a UART — the guest WRITES to one, so the door may
    /// gate on the serial log (`INSTALL-OK`). False = gate on applied output.
    pub serial_console: bool,
    pub cpu: Option<String>,
    /// `-machine` type when the preset's chipset is not the firmware's default.
    pub chipset: Option<String>,
    pub disk_device: &'static str,
    pub nic_device: &'static str,
    pub nics: usize,
    /// The medium rides `-cdrom` (IDE) beside its virtio face.
    pub medium_as_cdrom: bool,
    /// A VGA adapter is a declared console; with no UART it is THE console
    /// and the door screendumps it.
    pub vga: bool,
}

impl Shape {
    /// The pre-preset door, byte-for-byte: `--firmware` + `--serial`.
    pub fn flags(firmware: FirmwareChoice, serial: bool) -> Self {
        Shape {
            preset: None,
            firmware,
            uarts: if serial {
                [true, false, false, false]
            } else {
                [false; 4]
            },
            serial_console: serial,
            cpu: None,
            chipset: None,
            disk_device: VIRTIO_BLK,
            nic_device: "virtio-net-pci",
            nics: NICS,
            medium_as_cdrom: true,
            vga: false,
        }
    }

    /// A roster preset, rendered — refused by name when QEMU cannot mimic it.
    /// `firmware` / `serial` are the explicit overrides (`None` = the preset's).
    pub fn from_preset(
        name: &str,
        firmware: Option<FirmwareChoice>,
        serial: Option<bool>,
    ) -> Result<Self, String> {
        let mimicable = || {
            presets::PRESETS
                .iter()
                .filter(|p| p.status() != PresetStatus::NeedsRealCloud)
                .map(|p| p.name)
                .collect::<Vec<_>>()
                .join(" ")
        };
        let p: &'static TargetPreset = presets::preset(name).ok_or_else(|| {
            format!(
                "unknown --target `{name}`; the roster: {}",
                presets::names().join(" ")
            )
        })?;
        if p.status() == PresetStatus::NeedsRealCloud {
            return Err(format!(
                "--target {name} needs a real cloud ({}): {} — QEMU cannot mimic it; mimicable presets: {}",
                p.cloud,
                p.gap().unwrap_or("no QEMU model"),
                mimicable()
            ));
        }
        let q = p.qemu_shape().ok_or_else(|| {
            format!(
                "--target {name}: status {} but no QEMU shape renders",
                p.status().as_str()
            )
        })?;
        // `Both` = the medium carries both paths; the chipset picks the boot
        // firmware (q35 → OVMF, pc → SeaBIOS) unless `--firmware` says.
        let firmware = firmware.unwrap_or(match q.firmware {
            DeclaredFirmware::Uefi => FirmwareChoice::Uefi,
            DeclaredFirmware::SeaBios => FirmwareChoice::SeaBios,
            DeclaredFirmware::Both if q.machine == "q35" => FirmwareChoice::Uefi,
            DeclaredFirmware::Both => FirmwareChoice::SeaBios,
        });
        // `--serial uart` adds a ttyS0 AND a console on it (the kernel's
        // `console=ttyS0` from the medium's cmdline lands); `--serial none`
        // removes every port. Otherwise the preset's map and its `console=`.
        let (uarts, serial_console) = match serial {
            None => (q.uarts, q.serial),
            Some(true) => {
                let mut u = q.uarts;
                u[0] = true;
                (u, true)
            }
            Some(false) => ([false; 4], false),
        };
        let default_machine = match firmware {
            FirmwareChoice::Uefi => "q35",
            FirmwareChoice::SeaBios => "pc",
        };
        Ok(Shape {
            preset: Some(p.name),
            firmware,
            uarts,
            serial_console,
            cpu: q.cpu.map(str::to_string),
            chipset: (q.machine != default_machine).then(|| q.machine.to_string()),
            disk_device: q.disk_device,
            nic_device: q.nic_device,
            nics: usize::from(q.nics),
            medium_as_cdrom: q.medium_as_cdrom,
            vga: q.vga,
        })
    }

    pub fn has_uart(&self) -> bool {
        self.uarts.iter().any(|p| *p)
    }

    /// VGA is the only console: the screendump is the proof medium.
    pub fn vga_only(&self) -> bool {
        self.vga && !self.serial_console
    }

    /// The one-line description on every report.
    pub fn label(&self) -> String {
        let uarts: Vec<String> = self
            .uarts
            .iter()
            .enumerate()
            .filter(|(_, p)| **p)
            .map(|(i, _)| format!("ttyS{i}"))
            .collect();
        format!(
            "{}firmware={} machine={} cpu={} disk={} nic={}×{} uarts=[{}]{} medium={}",
            self.preset
                .map(|p| format!("target={p} · "))
                .unwrap_or_default(),
            self.firmware.label(),
            self.chipset.as_deref().unwrap_or(match self.firmware {
                FirmwareChoice::Uefi => "q35",
                FirmwareChoice::SeaBios => "pc",
            }),
            self.cpu.as_deref().unwrap_or("qemu-default"),
            self.disk_device,
            self.nic_device,
            self.nics,
            if uarts.is_empty() {
                "none".to_string()
            } else {
                uarts.join(",")
            },
            if self.vga_only() {
                " console=vga(screendump)"
            } else if self.serial_console {
                " console=serial"
            } else {
                " console=none"
            },
            if self.medium_as_cdrom {
                "cdrom+virtio"
            } else {
                "virtio"
            },
        )
    }

    /// The target disk's attachment: the declaration's writer for virtio-blk,
    /// else `-drive if=none` + the preset's device model (+ the cell's GPU).
    fn disk_args(&self, cell: &InstallerBuild, disk: &str) -> Vec<String> {
        if self.disk_device == VIRTIO_BLK {
            return cell.kvm_disk_args(disk);
        }
        let fmt = if disk.ends_with(".qcow2") {
            "qcow2"
        } else {
            "raw"
        };
        let mut a = vec![
            "-drive".to_string(),
            format!("file={disk},if=none,id=target,format={fmt}"),
        ];
        match self.disk_device {
            "virtio-scsi-pci" => a.extend(
                [
                    "-device",
                    "virtio-scsi-pci,id=scsi0",
                    "-device",
                    "scsi-hd,drive=target,bus=scsi0.0",
                ]
                .map(String::from),
            ),
            "nvme" => a.extend(["-device", "nvme,drive=target,serial=korp0"].map(String::from)),
            other => a.extend(["-device".to_string(), format!("{other},drive=target")]),
        }
        a.extend(cell.kvm_gpu_args());
        a
    }
}

impl Shape {
    /// The proof script's machine: 2 GiB, 2 vCPUs by default — gunnar serving
    /// with a git store wants more than the 512 MiB install-only doors give. The
    /// COM map, `-cpu` and `-machine` are the shape's (a preset's, or the flags').
    ///
    /// **Sizing overrides** (appliance-rig, 2026-08-31): the real
    /// git.example.test appliance is 4 vCPU / 16 GiB, and a proof that a number
    /// "runs on appliance hardware" must boot on THAT shape, not the 2/2048
    /// install default. `GUNNAR_INSTALLTEST_SMP` and `GUNNAR_INSTALLTEST_MEM_MB`
    /// drive the vCPU count and RAM without moving the default — every earlier
    /// proof keeps 2/2048, and the rig sets `SMP=4 MEM_MB=16384`. A value that
    /// does not parse is ignored (the default stands), never a silent 0.
    pub fn machine(&self) -> Result<Machine, String> {
        let firmware = match self.firmware {
            FirmwareChoice::Uefi => {
                let (code, vars) = kvmproof::locate_ovmf()
                    .ok_or_else(|| "no OVMF CODE+VARS pair (install ovmf/edk2)".to_string())?;
                Firmware::Uefi {
                    code,
                    vars_template: vars,
                }
            }
            FirmwareChoice::SeaBios => Firmware::SeaBios,
        };
        let env_u32 = |name: &str, default: u32| -> u32 {
            std::env::var(name)
                .ok()
                .and_then(|v| v.trim().parse::<u32>().ok())
                .filter(|&n| n > 0)
                .unwrap_or(default)
        };
        Ok(Machine {
            firmware,
            uarts: self.uarts,
            mem_mb: env_u32("GUNNAR_INSTALLTEST_MEM_MB", 2048),
            smp: env_u32("GUNNAR_INSTALLTEST_SMP", 2),
            cpu: self.cpu.clone(),
            chipset: self.chipset.clone(),
        })
    }
}

/// One run of the door.
pub struct Door {
    pub iso: PathBuf,
    pub shape: Shape,
    /// Scratch on DISK — a sparse 64 GiB target has no business in `/tmp`.
    pub work: PathBuf,
    /// Whether `:50051` must accept. `true` for a medium whose declared
    /// `korp.args=` binds the control plane off the loopback, else `false`.
    pub expect_grpc: bool,
    /// **`--grpc-only`**: `Some(role)` proves B2/B3 by `Bootstrap.Status` on the
    /// private interface — a build stamp and this declared role — instead of
    /// the SSH banner, for a medium that serves no git (`--role twin`). A banner
    /// on `:2222` then FAILS the leg. `None` = the historical banner door.
    pub grpc_only: Option<String>,
    /// How the medium's virtio face is attached — see [`Twin`]. Until edda's
    /// twin rule the installer ranked a target-sized twin above the blank disk
    /// (a coin toss the enumeration order decided) and a larger one ALWAYS,
    /// and wiped it (2026-08-29).
    pub twin: Twin,
    /// **Extra fixed disks beside the target** (`--disks 75,75,75,75`, GiB),
    /// attached after it as `vdb, vdc, …` in BOTH the install and the boot
    /// legs — the multi-disk /data rule measured at the door: the
    /// smallest disk boots, the largest equal group becomes ONE striped xfs,
    /// an odd disk is left untouched. Empty = the single-disk door as before.
    pub disks: Vec<u64>,
    /// **The appliance-rig hook** (`--drive-cmd PATH`, 2026-08-31): a host
    /// program run ONCE in B2 the instant the guest's ports answer — invoked
    /// as `PATH <ssh_port> <grpc_private_port> <grpc_public_port>` with the
    /// forwarded ports as argv. A non-zero exit FAILS B2 (its stderr is the
    /// detail); a zero exit's stdout goes in the leg detail. This is how the
    /// full sitetest / bench / torture battery is driven against a live,
    /// appliance-shaped guest without hand-rolling a second qemu: the
    /// door owns the KVM/OVMF/disks/boot-proof, the script owns the battery.
    /// `None` = the historical B2 (probe the ports, power-cut, done).
    pub drive_cmd: Option<PathBuf>,
}

/// How the medium's virtio face is attached in B1.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Twin {
    /// The ISO file, read-only, after the target — the historical shape.
    Iso,
    /// A writable sparse image seeded with the ISO bytes and padded to the
    /// target's size, attached BEFORE the target — UpCloud's shape: the ISO
    /// imported into a 10 G storage beside a 10 G target. Same class, same
    /// capacity: class→capacity ranking is decided by enumeration order.
    Target,
    /// As `Target`, padded to the target's size + 1 GiB: capacity ranking
    /// picks it EVERY time — the deterministic negative control for the rule.
    Larger,
}

impl Twin {
    pub fn parse(s: &str) -> Result<Self, String> {
        match s {
            "iso" => Ok(Twin::Iso),
            "target" => Ok(Twin::Target),
            "larger" => Ok(Twin::Larger),
            other => Err(format!("unknown --twin `{other}`: iso | target | larger")),
        }
    }
}

/// **How a leg ended.** Four outcomes, never three: the row and the exit code
/// both have to tell a hang from a failure from a pass.
///
/// The third and fourth were split apart on 2026-08-30, having been one
/// word. `--target gce-virtio` and `--target ovh-public-cloud` each cost
/// **420 s** and then printed `[FAIL] … QEMU never exited … no protective MBR …`
/// — four sentences about consequences, none of them the one the appliance
/// itself had said 400 seconds earlier, and nothing anywhere saying the budget
/// had simply run out. A gate that hangs burns the whole budget, blames the
/// wrong thing, and to anything holding only an exit code it is a pass.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
    /// Every gate this leg has formed.
    Pass,
    /// A gate did not form, for a reason the DOOR computed (no MBR on the
    /// target, nothing answering on `:2222`, the twin wiped).
    Failed,
    /// **The GUEST ended it, by name** — `korp_installer::serial::terminal_verdict`
    /// found a line the appliance, a first stage or the kernel printed and
    /// stopped on. Its words are in the detail, verbatim.
    Refused,
    /// **The budget ran out** with no verdict at all. Never a failure OF the
    /// appliance and never, ever a pass: nothing was measured.
    TimedOut,
}

impl Verdict {
    /// The word in the row. `<ops>/deploy.sh`'s gate greps for these.
    pub fn label(self) -> &'static str {
        match self {
            Verdict::Pass => "PASS",
            Verdict::Failed => "FAIL",
            Verdict::Refused => "REFUSED",
            Verdict::TimedOut => "TIMEOUT",
        }
    }
}

/// One leg's verdict, with its measurement.
#[derive(Debug, Clone)]
pub struct Leg {
    pub name: &'static str,
    pub verdict: Verdict,
    pub ms: u128,
    pub detail: String,
}

impl Leg {
    pub fn pass(&self) -> bool {
        self.verdict == Verdict::Pass
    }

    /// Downgrade a leg a later check found wanting. It only ever moves AWAY
    /// from `Pass`: a `TimedOut` leg that also has no MBR on its target is
    /// still a leg that timed out, and letting a second check overwrite the
    /// first would put the hang back where it cannot be seen.
    pub fn demote(&mut self, to: Verdict) {
        if self.verdict == Verdict::Pass {
            self.verdict = to;
        }
    }

    fn line(&self) -> String {
        format!(
            "  [{}] {:<18} {:>8} ms  {}",
            self.verdict.label(),
            self.name,
            self.ms,
            self.detail
        )
    }
}

/// The whole ladder. Stops at the first failed leg — B2 on a disk B1 did not
/// write would measure nothing — and hands back every leg that ran.
/// **The red switch for the console gate.** Set it and a machine the
/// declarations already refuse is booted anyway, so the appliance's own refusal
/// can be watched ending the leg. A gate nobody can see fire is not a gate
/// — and after [`medium_cannot_install_here`] landed, `gce-virtio`
/// stops being the live demonstration it was.
pub const BOOT_ANYWAY: &str = "GUNNAR_INSTALLTEST_BOOT_ANYWAY";

/// **Can this medium install on this machine at all?** Asked of the two
/// DECLARATIONS and of the medium's own first sector, before a byte boots.
/// `Some(why)` names every reason, and the door refuses instead of hanging.
///
/// Both reasons were measured on 2026-08-30, each costing **420 s**
/// of nothing per run:
///
/// * **the bus.** `--target gce-virtio` boots its disk on `virtio-scsi`;
///   gunnar's recipe declares `buses = [ide-cdrom, virtio-blk]`, so the
///   appliance kernel carries no SCSI and the target disk is not there. The
///   appliance said exactly that on the UART — `FAILED hwdetect: no usable
///   target disk — every fixed disk carries the installer medium` — and it was
///   RIGHT: a machine that declares a disk on a bus its own medium cannot
///   drive is not a door, it is a contradiction, and it can be read off the
///   two declarations without booting anything.
/// * **the attachment.** `--target ovh-public-cloud` brings its medium as an
///   imported qcow2, so the door attaches no cdrom; the firmware is SeaBIOS,
///   which boots an MBR; and the medium is a pure El Torito ISO whose LBA 0 is
///   **all zeros** (measured: `[510..512] = 00 00`, not `55 AA`). Nothing on
///   that machine is bootable, which is why the run left **zero bytes** of
///   serial. `gce-virtio` shares the no-ISO-attach property but boots anyway:
///   OVMF reads El Torito off any block device. The firmware is the difference,
///   so the check names it rather than refusing every no-cdrom preset.
///
/// Neither refusal is a widened match that makes something pass. Both say the
/// same thing the roster already says — this platform has no installer path,
/// or has one the medium cannot reach — in seconds instead of minutes.
fn medium_cannot_install_here(iso: &Path, shape: &Shape, cell: &InstallerBuild) -> Option<String> {
    let name = shape.preset?;
    let p = presets::preset(name)?;
    let mut why: Vec<String> = Vec::new();

    // 1. the bus the machine's boot disk rides, against the buses the MEDIUM
    //    was built to drive.
    if let Some(bus) = p.boot_disk.target_bus() {
        if !cell.target.has_bus(bus) {
            why.push(format!(
                "THE BUS: `{name}` boots its disk on `{}`, and this medium's recipe declares \
                 `[build.installer.target] buses = [{}]` — so its kernel carries no driver for \
                 that bus and the target disk is not there to install on. The appliance refuses \
                 this correctly on its own console (`FAILED hwdetect: no usable target disk`), \
                 420 s later; the two declarations say it now",
                bus.as_str(),
                cell.target
                    .buses
                    .iter()
                    .map(|b| b.as_str())
                    .collect::<Vec<_>>()
                    .join(", "),
            ));
        }
    }

    // 2. whether the medium can be REACHED on this machine at all.
    if !shape.medium_as_cdrom && shape.firmware == FirmwareChoice::SeaBios {
        let lba0 = kvmproof::read_at(iso, 510, 2);
        let bootable_mbr = lba0.len() == 2 && (lba0[0], lba0[1]) == (0x55, 0xAA);
        if !bootable_mbr {
            why.push(format!(
                "THE ATTACHMENT: `{name}` takes {}, so the door attaches no cdrom — and this \
                 medium is a pure El Torito ISO ({} at LBA 0[510..512], not 55 AA), which SeaBIOS \
                 cannot boot off a hard-disk device. Nothing here hands over: the run leaves ZERO \
                 bytes of serial and burns its whole budget. This preset has no INSTALLER path; a \
                 KVM proof of it could only ever prove the INSTALLED disk boots on its device set",
                p.medium.describe(),
                lba0.iter()
                    .map(|b| format!("{b:02x}"))
                    .collect::<Vec<_>>()
                    .join(" "),
            ));
        }
    }

    if why.is_empty() {
        return None;
    }
    Some(format!(
        "--target {name} cannot be installed by this medium, and it is the DECLARATIONS that say so, \
         not a boot:\n  · {}\nSet {BOOT_ANYWAY}=1 to boot it anyway and watch the guest refuse.",
        why.join("\n  · ")
    ))
}

pub fn run(door: &Door) -> Result<Vec<Leg>, String> {
    // ── preflight, by name ──────────────────────────────────────────────────
    if !door.iso.is_file() {
        return Err(format!("no medium at {}", door.iso.display()));
    }
    let qemu = kvmproof::which("qemu-system-x86_64")
        .ok_or_else(|| "no qemu-system-x86_64 on PATH".to_string())?;
    // `accel=kvm:tcg` falls back to TCG silently, and every number below would
    // then be a statement about an emulator. Refuse, like the kvm arm does.
    if !Path::new("/dev/kvm").exists() {
        return Err("this host has no /dev/kvm; a TCG boot would time every leg wrong".into());
    }
    let shape = &door.shape;
    let machine = shape.machine()?;

    // ── the declaration is the writer for the target disk ───────────────────
    let cell = declared_installer(&door.iso)?;

    // ── the medium against the machine, BEFORE anything boots ───────────────
    if let Some(why) = medium_cannot_install_here(&door.iso, shape, &cell) {
        if std::env::var_os(BOOT_ANYWAY).is_some() {
            eprintln!("installer door · {BOOT_ANYWAY} is set — booting a machine the declarations already refuse:\n{why}");
        } else {
            return Err(why);
        }
    }

    let _ = std::fs::remove_dir_all(&door.work);
    std::fs::create_dir_all(&door.work).map_err(|e| format!("{}: {e}", door.work.display()))?;
    let disk = door.work.join("target.img");
    kvmproof::create_blank_disk(&disk, cell.target_disk_bytes());
    let extras = create_extra_disks(&door.work, &door.disks);

    eprintln!(
        "installer door · {} · {} GiB target{} · medium {}",
        shape.label(),
        cell.target_disk_gib,
        describe_extras(&door.disks),
        door.iso.display()
    );

    let mut legs = Vec::new();

    // ── B1 install ──────────────────────────────────────────────────────────
    let (mut b1, run) = install_leg(
        &qemu,
        &machine,
        shape,
        &cell,
        &door.iso,
        &disk,
        &extras,
        &door.work.join("b1-install"),
        door.twin,
        "B1 install",
    );
    match data_set_verdict(&disk, cell.target_disk_bytes(), &extras) {
        Ok(d) => b1.detail.push_str(&format!("; {d}")),
        Err(e) => {
            b1.demote(Verdict::Failed);
            b1.detail.push_str(&format!("; DATA SET: {e}"));
        }
    }
    eprintln!("{}", b1.line());
    emit_row(shape, 0, &b1);
    legs.push(b1);
    if !legs[0].pass() {
        eprintln!("── B1 serial tail ──\n{}", tail(&run.serial, 30));
        return Ok(legs);
    }

    // ── B2 serve, B3 power-cut reboot ───────────────────────────────────────
    for (i, name) in [(2usize, "B2 serve"), (3, "B3 power-cut reboot")] {
        let (boot_disk, others) = boot_disk_of(&disk, cell.target_disk_bytes(), &extras);
        let boot_arg = boot_disk.display().to_string();
        // The rig hook runs in B2 only, and only when `--drive-cmd` was given;
        // B3 always probes-then-power-cuts so the power-cut leg keeps its exact
        // historical meaning (a fresh boot from a disk that was never flushed).
        let grpc_public = isoproof::free_port();
        let mut drive = drive_closure(&door.drive_cmd, grpc_public, i == 2);
        let leg = boot_installed(
            &qemu,
            &machine,
            shape,
            &cell,
            &boot_arg,
            &others,
            &door.work.join(format!("b{i}-boot")),
            name,
            door.expect_grpc,
            door.grpc_only.as_deref(),
            drive
                .as_mut()
                .map(|d| d as &mut dyn FnMut(u16, u16) -> Result<String, String>),
        );
        eprintln!("{}", leg.line());
        emit_row(shape, i - 1, &leg);
        let pass = leg.pass();
        legs.push(leg);
        if !pass {
            break;
        }
    }
    Ok(legs)
}

/// The B2 rig hook as a `boot_installed` drive closure: run `cmd` once, with
/// the two forwarded ports (ssh, grpc-private) plus a third the door forwards
/// on the PUBLIC NIC, as argv. `None` when there is no `--drive-cmd` or this is
/// not the B2 leg — the historical probe-only path.
fn drive_closure(
    cmd: &Option<PathBuf>,
    _grpc_public: u16,
    is_b2: bool,
) -> Option<impl FnMut(u16, u16) -> Result<String, String>> {
    let cmd = cmd.clone().filter(|_| is_b2)?;
    Some(
        move |ssh_port: u16, grpc_port: u16| -> Result<String, String> {
            eprintln!(
                "  rig: driving {} against ssh :{ssh_port} grpc :{grpc_port} …",
                cmd.display()
            );
            let out = std::process::Command::new(&cmd)
                .arg(ssh_port.to_string())
                .arg(grpc_port.to_string())
                .output()
                .map_err(|e| format!("{}: {e}", cmd.display()))?;
            // The battery's own stdout/stderr is the record — stream it up so a
            // failing run is legible in the door's log, not just a red leg.
            let so = String::from_utf8_lossy(&out.stdout);
            let se = String::from_utf8_lossy(&out.stderr);
            if !so.trim().is_empty() {
                eprintln!("── rig stdout ──\n{}", so.trim_end());
            }
            if !se.trim().is_empty() {
                eprintln!("── rig stderr ──\n{}", se.trim_end());
            }
            if out.status.success() {
                Ok(format!(
                    "rig {} PASSED ({} B stdout)",
                    cmd.display(),
                    out.stdout.len()
                ))
            } else {
                Err(format!(
                    "rig {} exited {}: {}",
                    cmd.display(),
                    out.status,
                    se.trim()
                ))
            }
        },
    )
}

/// **One run of the installer medium onto `disk`** — B1's body, so the
/// upgrade door (`run_upgrade`) runs it twice on the SAME image with two
/// media. `twin` attaches the medium's virtio face per [`Twin`]; the leg
/// gates on `install_verdict` (UART marker where there is one, the disk
/// always) and on the twin's PVD surviving.
#[allow(clippy::too_many_arguments)]
fn install_leg(
    qemu: &Path,
    machine: &Machine,
    shape: &Shape,
    cell: &InstallerBuild,
    iso: &Path,
    disk: &Path,
    extras: &[(PathBuf, u64)],
    workdir: &Path,
    twin: Twin,
    name: &'static str,
) -> (Leg, kvmproof::BootRun) {
    let serial = shape.serial_console;
    let disk_arg = disk.display().to_string();
    // `--twin target`: the medium's virtio face is a writable image of the
    // TARGET's size, seeded with the ISO bytes, attached FIRST — so it is `vda`,
    // same class and capacity as the blank target `vdb`. Class→capacity ranking
    // alone is a coin toss the enumeration order decides in the twin's favour;
    // the installer must refuse it by its bytes and pick `vdb`.
    let twin_bytes = match twin {
        Twin::Iso => None,
        Twin::Target => Some(cell.target_disk_bytes()),
        Twin::Larger => Some(cell.target_disk_bytes() + (1 << 30)),
    };
    let twin_img = twin_bytes.map(|bytes| {
        let twin = workdir.parent().unwrap_or(workdir).join("twin.img");
        std::fs::copy(iso, &twin).expect("seed the twin with the ISO bytes");
        std::fs::OpenOptions::new()
            .write(true)
            .open(&twin)
            .and_then(|f| f.set_len(bytes))
            .expect("pad the twin");
        twin
    });
    let mut args = Vec::new();
    if let Some(twin) = &twin_img {
        args.push("-drive".into());
        args.push(format!("file={},if=virtio,format=raw", twin.display()));
    }
    // **On a machine that takes NO cdrom, the MEDIUM IS THE BOOT DISK.** That is
    // not a convenience, it is what the platform does: OVH/Scaleway import a
    // glance image, GCE a `disk.raw`, AWS a VM Import — you CREATE the instance
    // FROM the image and attach the install target as a second volume. Rendering
    // it the other way round is a machine no customer has.
    //
    // MEASURED 2026-08-30 on the ovh-public-cloud shape with a medium
    // that DOES carry a hybrid boot sector: with the blank target first, SeaBIOS
    // said `Booting from Hard Disk... Boot failed: not a bootable disk` and then
    // `No bootable device` — it tries the FIRST disk and does not walk on to the
    // second. Same medium, same argv, medium first: `korp-boot-bios: legacy BIOS
    // stage`, the kernel off 512-byte LBAs, `INSTALLER: vda: carries the
    // installer medium … excluded`, `INSTALLER: INSTALL-OK`.
    //
    // With `--twin` the medium's face is already the first drive (that is what a
    // twin IS), so this only moves the plain case.
    let medium_first = twin_img.is_none() && !shape.medium_as_cdrom;
    if medium_first {
        isoproof::attach_iso_as_virtio(&mut args, iso);
    }
    args.extend(shape.disk_args(cell, &disk_arg));
    attach_extra_disks(&mut args, extras);
    if twin_img.is_none() && !medium_first {
        isoproof::attach_iso_as_virtio(&mut args, iso);
    }
    if shape.medium_as_cdrom {
        isoproof::attach_cdrom(&mut args, iso);
    }
    isoproof::attach_slirp_nics_model(&mut args, &[], shape.nics, shape.nic_device);
    let qmp = shape.vga_only().then(|| Qmp::attach(&mut args, workdir));
    let mut polls = 0u32;
    let run = kvmproof::boot_hold(
        qemu,
        machine,
        &args,
        workdir,
        Duration::from_secs(420),
        // End early only on a panic; otherwise the installer's own reboot
        // (turned into an exit by `-no-reboot`) ends the leg, so the FAT on P3
        // is whatever the guest flushed before IT chose to reboot, not whatever
        // QEMU had at the microsecond a marker landed. On a VGA-only shape the
        // screen is dumped at 1 s and then every 5 s so the last frame before
        // the reboot is kept — the installer's own console, which no UART
        // carries.
        |_serial| {
            polls += 1;
            if let Some(q) = &qmp {
                if polls == 4 || polls % 20 == 0 {
                    let _ = q.screendump();
                }
            }
            // No gate of our own: `boot_hold` ends the run on the guest's OWN
            // terminal line (a panic among them), so this closure only has to
            // keep the screen dumps coming.
            false
        },
    );
    let (boot_disk, _) = boot_disk_of(disk, cell.target_disk_bytes(), extras);
    let mut leg = install_verdict(&run, &boot_disk, serial);
    leg.name = name;
    if let Some(q) = &qmp {
        leg.detail.push_str(&format!("; {}", q.describe()));
    }
    if let Some(twin) = &twin_img {
        // The twin's PVD (`CD001` at sector 16) must survive B1 untouched: a
        // wiped twin is THE bug, whatever the target says.
        let magic = kvmproof::read_at(twin, 16 * 2048 + 1, 5);
        if magic == b"CD001" {
            leg.detail
                .push_str("; twin vda intact (CD001 at sector 16), target vdb installed");
        } else {
            leg.demote(Verdict::Failed);
            leg.detail
                .push_str("; the medium's TWIN was WIPED (no CD001 at sector 16 on vda)");
        }
    }
    (leg, run)
}

/// The ESP record korp-installer writes (`korp/install`): `mode=upgrade` +
/// the kept `korp-data` span, or `mode=fresh`.
const ESP_INSTALL_RECORD: &str = "korp/install";
/// The `korp-data` partition's slot in the GPT (p3).
const DATA_PARTITION: usize = 2;

/// blake3 over the whole `korp-data` span of `disk` — the measurement that
/// says "kept byte for byte". Sparse holes read fast; a 48 GiB P3 costs
/// seconds, not minutes.
fn hash_data_partition(
    disk: &Path,
    extras: &[(PathBuf, u64)],
) -> Result<(String, u64, u64), String> {
    // A multi-disk box keeps /data on the data disks (`korp-data-<i>`, one
    // partition each): the hash is over every member's partition, in order,
    // and "moved" means any member's span moved. A single-disk box: p3.
    let members: Vec<&(PathBuf, u64)> = extras
        .iter()
        .filter(|(p, _)| gpt_entry_name(p, 0).starts_with("korp-data-"))
        .collect();
    if !members.is_empty() {
        let mut joined = String::new();
        let (mut first0, mut total) = (0u64, 0u64);
        for (i, (p, _)) in members.iter().enumerate() {
            let (first, last) = kvmproof::gpt_entry(p, 0);
            let len = (last - first + 1) * 512;
            let mut f = std::fs::File::open(p).map_err(|e| format!("{}: {e}", p.display()))?;
            let part = korp_installer::selftest::hash_installed_region(&mut f, first * 512, len)
                .map_err(|e| format!("hash {}: {e:#}", p.display()))?;
            joined.push_str(&part.to_hex());
            joined.push_str(&format!("@{first}+"));
            if i == 0 {
                first0 = first;
            }
            total += len;
        }
        return Ok((joined, first0, total));
    }
    let (first, last) = kvmproof::gpt_entry(disk, DATA_PARTITION);
    if last < first || first == 0 {
        return Err(format!(
            "no korp-data entry (p{}) on {}",
            DATA_PARTITION + 1,
            disk.display()
        ));
    }
    let mut f = std::fs::File::open(disk).map_err(|e| format!("{}: {e}", disk.display()))?;
    let len = (last - first + 1) * 512;
    let hash = korp_installer::selftest::hash_installed_region(&mut f, first * 512, len)
        .map_err(|e| format!("hash p{}: {e:#}", DATA_PARTITION + 1))?;
    Ok((hash.to_hex().to_string(), first, len))
}

/// The extra disks of a door: `data<i>.img`, sparse, beside the target.
fn create_extra_disks(work: &Path, gib: &[u64]) -> Vec<(PathBuf, u64)> {
    gib.iter()
        .enumerate()
        .map(|(i, g)| {
            let p = work.join(format!("data{i}.img"));
            let bytes = g << 30;
            kvmproof::create_blank_disk(&p, bytes);
            (p, bytes)
        })
        .collect()
}

fn describe_extras(gib: &[u64]) -> String {
    if gib.is_empty() {
        String::new()
    } else {
        format!(
            " + {} extra disk(s) [{}] GiB",
            gib.len(),
            gib.iter()
                .map(|g| g.to_string())
                .collect::<Vec<_>>()
                .join(", ")
        )
    }
}

/// `-drive file=…,if=virtio,format=raw` per extra disk, after the target:
/// `vdb, vdc, …` — the same face on the install and the boot legs.
fn attach_extra_disks(args: &mut Vec<String>, extras: &[(PathBuf, u64)]) {
    for (p, _) in extras {
        args.push("-drive".into());
        args.push(format!("file={},if=virtio,format=raw", p.display()));
    }
}

/// The UTF-16LE name of GPT entry `index` on `disk` (empty when the slot is unused).
fn gpt_entry_name(disk: &Path, index: usize) -> String {
    let e = kvmproof::read_at(disk, 2 * 512 + index as u64 * 128, 128);
    if e[..16].iter().all(|&b| b == 0) {
        return String::new();
    }
    let units: Vec<u16> = e[56..128]
        .chunks(2)
        .map(|c| u16::from_le_bytes([c[0], c[1]]))
        .take_while(|&u| u != 0)
        .collect();
    String::from_utf16_lossy(&units)
}

const MD_SB_MAGIC: u32 = 0xa92b_4efc;

/// **The multi-disk rule, measured on the images after B1.** Expected from
/// the sizes alone (the installer's own rule, restated here so a drift shows
/// as red): the smallest disk (ties → the first attached) carries the ESP;
/// with no other disk it also carries `korp-data` (p3); otherwise the
/// largest equal-size group (1 %) of the rest carries one `korp-data-<i>`
/// each — an md superblock at sector 8 naming the group's size when ≥ 2,
/// the xfs straight on it when 1 — and the boot disk has NO p3; every other
/// disk is untouched (its first sector still zero). Returns the band's fact.
/// **The disk the installer made bootable** — the smallest one, ties by
/// attach order (the installer's own rule, `korp-installer::dataset`). B1's
/// disk gate and B2/B3's boot device follow THIS disk, never `target.img` by
/// habit: measured 2026-08-29 (`--disks 75,75,75,40`), the installer laid the
/// ESP on vde (40 GiB) and the door read vda for the MBR and called it red.
fn boot_disk_of(
    target: &Path,
    target_bytes: u64,
    extras: &[(PathBuf, u64)],
) -> (PathBuf, Vec<(PathBuf, u64)>) {
    let mut all: Vec<(PathBuf, u64)> = vec![(target.to_path_buf(), target_bytes)];
    all.extend(extras.iter().cloned());
    let boot = all
        .iter()
        .enumerate()
        .min_by_key(|(i, d)| (d.1, *i))
        .map(|(i, _)| i)
        .unwrap();
    let boot_path = all[boot].0.clone();
    let others: Vec<(PathBuf, u64)> = all
        .into_iter()
        .enumerate()
        .filter(|(i, _)| *i != boot)
        .map(|(_, d)| d)
        .collect();
    (boot_path, others)
}

fn data_set_verdict(
    target: &Path,
    target_bytes: u64,
    extras: &[(PathBuf, u64)],
) -> Result<String, String> {
    let mut all: Vec<(PathBuf, u64, String)> =
        vec![(target.to_path_buf(), target_bytes, "vda".into())];
    for (i, (p, b)) in extras.iter().enumerate() {
        all.push((p.clone(), *b, format!("vd{}", (b'b' + i as u8) as char)));
    }
    let boot = all
        .iter()
        .enumerate()
        .min_by_key(|(i, d)| (d.1, *i))
        .map(|(i, _)| i)
        .unwrap();
    let rest: Vec<usize> = (0..all.len()).filter(|&i| i != boot).collect();
    let equal = |a: u64, b: u64| {
        let (lo, hi) = if a < b { (a, b) } else { (b, a) };
        (hi - lo) * 100 <= hi
    };
    let mut data: Vec<usize> = Vec::new();
    for &pivot in &rest {
        let g: Vec<usize> = rest
            .iter()
            .copied()
            .filter(|&i| equal(all[i].1, all[pivot].1))
            .collect();
        if g.len() > data.len()
            || (g.len() == data.len() && !data.is_empty() && all[pivot].1 > all[data[0]].1)
        {
            data = g;
        }
    }
    let gib = |b: u64| format!("{}GiB", b >> 30);
    let mut why = Vec::new();
    let (bp, bb, bn) = &all[boot];
    if gpt_entry_name(bp, 0) != "EFI System" {
        why.push(format!(
            "{bn} ({}) should be the boot disk (smallest) but carries no ESP",
            gib(*bb)
        ));
    }
    let p3 = gpt_entry_name(bp, DATA_PARTITION);
    if data.is_empty() && p3 != "korp-data" {
        why.push(format!(
            "single disk: {bn} should carry korp-data (p3), has {p3:?}"
        ));
    }
    if !data.is_empty() && !p3.is_empty() {
        why.push(format!(
            "{bn} carries a p3 ({p3}) although /data is on the data disks"
        ));
    }
    for (role, &i) in data.iter().enumerate() {
        let (p, _, n) = &all[i];
        let name = gpt_entry_name(p, 0);
        if name != format!("korp-data-{role}") {
            why.push(format!("{n}: expected korp-data-{role}, found {name:?}"));
            continue;
        }
        let (first, _) = kvmproof::gpt_entry(p, 0);
        if data.len() >= 2 {
            let sb = kvmproof::read_at(p, first * 512 + 4096, 256);
            let magic = u32::from_le_bytes(sb[0..4].try_into().unwrap());
            let raid_disks = u32::from_le_bytes(sb[0x5C..0x60].try_into().unwrap());
            let dev = u32::from_le_bytes(sb[0xA0..0xA4].try_into().unwrap());
            if magic != MD_SB_MAGIC || raid_disks != data.len() as u32 || dev != role as u32 {
                why.push(format!("{n}: md superblock magic {magic:#x} raid_disks {raid_disks} dev {dev} — expected the {}-member set, role {role}", data.len()));
            }
        } else if kvmproof::read_at(p, first * 512, 4) != b"XFSB" {
            why.push(format!(
                "{n}: a lone data disk should carry xfs straight on korp-data-0"
            ));
        }
    }
    let mut odd = Vec::new();
    for &i in &rest {
        if !data.contains(&i) {
            let (p, b, n) = &all[i];
            if kvmproof::read_at(p, 0, 512).iter().any(|&x| x != 0) {
                why.push(format!(
                    "{n} ({}) is unequal and should be untouched, but its first sector was written",
                    gib(*b)
                ));
            } else {
                odd.push(format!("{n} {} untouched", gib(*b)));
            }
        }
    }
    if !why.is_empty() {
        return Err(why.join("; "));
    }
    let data_fact = if data.is_empty() {
        format!("data on {bn} p3")
    } else {
        format!(
            "data {}×{} {} xfs [{}]",
            data.len(),
            gib(all[data[0]].1),
            if data.len() >= 2 { "raid0" } else { "plain" },
            data.iter()
                .map(|&i| all[i].2.clone())
                .collect::<Vec<_>>()
                .join(", ")
        )
    };
    Ok(format!(
        "boot {bn} {} · {data_fact}{}",
        gib(*bb),
        if odd.is_empty() {
            String::new()
        } else {
            format!(" · {}", odd.join(", "))
        }
    ))
}

/// Read `korp/install` off the installed ESP.
fn esp_install_record(disk: &Path) -> Option<String> {
    let (first, last) = kvmproof::gpt_entry(disk, ESP_PARTITION);
    kvmproof::read_fat_file(disk, first, last, ESP_INSTALL_RECORD)
        .map(|b| String::from_utf8_lossy(&b).trim().to_string())
}

/// **The upgrade door** — the data-survives rule (edda c09dd54, decided
/// 2026-08-29: data must survive an upgrade) measured end to end:
///
/// * **U1 install (old)** — `old_iso` onto a blank target, exactly B1.
/// * **U2 serve + bootstrap + push** — the installed disk alone; when the ports
///   answer the door bootstraps the admin key over gRPC (`gunnar-cli control
///   bootstrap`), pushes a repository over ssh (create-on-push, a nonce in its
///   README) and reads `control status` = closed. Then a power cut.
/// * **U3 reinstall (new)** — `door.iso` onto the SAME image, no blank. Gates:
///   B1's own verdict, the ESP record says `mode=upgrade`, and blake3 over the
///   whole `korp-data` span is IDENTICAL to the hash taken after U2 (the UART,
///   where there is one, must say `KEPT`).
/// * **U4 serve after upgrade** — `control status` must say closed (never
///   OPEN: a reinstall that re-opened bootstrap is the defect), and the
///   repository pushed in U2 must clone with its nonce intact.
///
/// `cli` is the `gunnar-cli` that drives the guest from the host.
pub fn run_upgrade(door: &Door, old_iso: &Path, cli: &Path) -> Result<Vec<Leg>, String> {
    if !door.iso.is_file() {
        return Err(format!("no medium at {}", door.iso.display()));
    }
    if !old_iso.is_file() {
        return Err(format!("no OLD medium at {}", old_iso.display()));
    }
    if !cli.is_file() {
        return Err(format!(
            "no gunnar-cli at {} (build it: cargo build --release -p gunnar-cli)",
            cli.display()
        ));
    }
    let qemu = kvmproof::which("qemu-system-x86_64")
        .ok_or_else(|| "no qemu-system-x86_64 on PATH".to_string())?;
    if !Path::new("/dev/kvm").exists() {
        return Err("no /dev/kvm".into());
    }
    let shape = &door.shape;
    let machine = shape.machine()?;
    let cell = declared_installer(&door.iso)?;
    let _ = std::fs::remove_dir_all(&door.work);
    std::fs::create_dir_all(&door.work).map_err(|e| format!("{}: {e}", door.work.display()))?;
    let disk = door.work.join("target.img");
    kvmproof::create_blank_disk(&disk, cell.target_disk_bytes());
    let disk_arg = disk.display().to_string();
    let extras = create_extra_disks(&door.work, &door.disks);
    eprintln!(
        "upgrade door · {} · {} GiB target{} · old medium {} · new medium {}",
        shape.label(),
        cell.target_disk_gib,
        describe_extras(&door.disks),
        old_iso.display(),
        door.iso.display()
    );
    let mut legs = Vec::new();

    // ── U1 ──
    let (mut u1, run) = install_leg(
        &qemu,
        &machine,
        shape,
        &cell,
        old_iso,
        &disk,
        &extras,
        &door.work.join("u1-install-old"),
        door.twin,
        "U1 install (old)",
    );
    match esp_install_record(&disk) {
        Some(r) if r.starts_with("mode=fresh") => {
            u1.detail.push_str("; ESP korp/install: mode=fresh")
        }
        Some(r) => u1.detail.push_str(&format!(
            "; ESP korp/install: {}",
            r.lines().next().unwrap_or_default()
        )),
        None => u1
            .detail
            .push_str("; (old medium wrote no korp/install record — pre-rule installer)"),
    }
    eprintln!("{}", u1.line());
    legs.push(u1);
    if !legs[0].pass() {
        eprintln!("── U1 serial tail ──\n{}", tail(&run.serial, 30));
        return Ok(legs);
    }

    // ── U2: serve, bootstrap, push, status ──
    // Absolute: ssh runs from inside the push tree, and a relative `--work`
    // would name a key that is not there (measured: `Identity file … not
    // accessible`).
    let keys = std::path::absolute(door.work.join("keys")).map_err(|e| e.to_string())?;
    std::fs::create_dir_all(&keys).map_err(|e| e.to_string())?;
    let key = keys.join("admin");
    crate::keygen(&key)?;
    let ssh = format!(
        "ssh -i {} -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR",
        key.display()
    );
    let nonce = format!(
        "data-survives {} {}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_millis())
            .unwrap_or(0)
    );
    let principal = "upg";
    let repo = "survives";
    let mut drive_u2 = |ssh_port: u16, grpc_port: u16| -> Result<String, String> {
        let at = format!("127.0.0.1:{grpc_port}");
        let status = cli_out(cli, &["control", "status", "--at", &at])?;
        if !status.contains("bootstrap      OPEN") {
            return Err(format!("before bootstrap, status must say OPEN:\n{status}"));
        }
        let key_s = key.display().to_string();
        cli_out(
            cli,
            &["control", "bootstrap", principal, "-i", &key_s, "--at", &at],
        )?;
        let status = cli_out(cli, &["control", "status", "--at", &at])?;
        if !status.contains("bootstrap      closed") {
            return Err(format!(
                "after bootstrap, status must say closed:\n{status}"
            ));
        }
        let tree = door.work.join("u2-push");
        let _ = std::fs::remove_dir_all(&tree);
        std::fs::create_dir_all(&tree).map_err(|e| e.to_string())?;
        // sha256: what a create-on-push repository is (server default; post-install.sh pushes the same way).
        crate::git(
            &tree,
            &ssh,
            &["init", "-q", "--object-format=sha256", "-b", "main"],
        )?;
        std::fs::write(tree.join("README"), format!("{nonce}\n")).map_err(|e| e.to_string())?;
        crate::git(&tree, &ssh, &["add", "README"])?;
        crate::git(
            &tree,
            &ssh,
            &[
                "-c",
                "user.name=installtest",
                "-c",
                "user.email=installtest@example.invalid",
                "commit",
                "-qm",
                "data survives",
            ],
        )?;
        let url = format!("ssh://{principal}@127.0.0.1:{ssh_port}/{principal}/{repo}");
        crate::git(&tree, &ssh, &["push", "-q", &url, "main"])?;
        Ok(format!(
            "bootstrapped `{principal}` (status closed), pushed {principal}/{repo} with nonce"
        ))
    };
    let u2 = boot_installed(
        &qemu,
        &machine,
        shape,
        &cell,
        &disk_arg,
        &extras,
        &door.work.join("u2-serve"),
        "U2 bootstrap+push",
        door.expect_grpc,
        None,
        Some(&mut drive_u2),
    );
    eprintln!("{}", u2.line());
    let pass = u2.pass();
    legs.push(u2);
    if !pass {
        return Ok(legs);
    }
    let (p3_before, p3_first, p3_len) = hash_data_partition(&disk, &extras)?;

    // ── U3: the NEW medium on the SAME disk ──
    let (mut u3, run) = install_leg(
        &qemu,
        &machine,
        shape,
        &cell,
        &door.iso,
        &disk,
        &extras,
        &door.work.join("u3-reinstall-new"),
        door.twin,
        "U3 reinstall (new)",
    );
    let mut why = Vec::new();
    match esp_install_record(&disk) {
        Some(r) if r.starts_with("mode=upgrade") => {}
        Some(r) => why.push(format!(
            "ESP korp/install says `{}`, not mode=upgrade",
            r.lines().next().unwrap_or_default()
        )),
        None => why.push("no korp/install record on the ESP after the reinstall".into()),
    }
    if shape.serial_console
        && !run
            .serial
            .contains("KEPT; GPT untouched; ESP + rootfs rewritten (mode upgrade")
    {
        why.push("the UART never said `/data … KEPT … (mode upgrade)`".into());
    }
    let (p3_after, first_after, len_after) = hash_data_partition(&disk, &extras)?;
    if (first_after, len_after) != (p3_first, p3_len) {
        why.push(format!(
            "korp-data moved: LBA {p3_first}+{p3_len} B → LBA {first_after}+{len_after} B"
        ));
    }
    if p3_after != p3_before {
        why.push(format!(
            "korp-data CHANGED: blake3 {}… → {}…",
            &p3_before[..12],
            &p3_after[..12]
        ));
    }
    if !why.is_empty() {
        u3.demote(Verdict::Failed);
        u3.detail = format!("{}; {}", why.join("; "), u3.detail);
    } else {
        u3.detail.push_str(&format!(
            "; ESP korp/install: mode=upgrade; korp-data p{} ({} GiB at LBA {}) blake3 {}… IDENTICAL before/after",
            DATA_PARTITION + 1,
            p3_len >> 30,
            p3_first,
            &p3_after[..12]
        ));
    }
    eprintln!("{}", u3.line());
    let pass = u3.pass();
    legs.push(u3);
    if !pass {
        eprintln!("── U3 serial tail ──\n{}", tail(&run.serial, 40));
        return Ok(legs);
    }

    // ── U4: serve after the upgrade — closed, and the repo is there ──
    let mut drive_u4 = |ssh_port: u16, grpc_port: u16| -> Result<String, String> {
        let at = format!("127.0.0.1:{grpc_port}");
        let status = cli_out(cli, &["control", "status", "--at", &at])?;
        if status.contains("bootstrap      OPEN") {
            return Err(format!(
                "bootstrap is OPEN after the upgrade — /data did not survive:\n{status}"
            ));
        }
        if !status.contains("bootstrap      closed") {
            return Err(format!("status after the upgrade:\n{status}"));
        }
        let tree = door.work.join("u4-clone");
        let _ = std::fs::remove_dir_all(&tree);
        std::fs::create_dir_all(&tree).map_err(|e| e.to_string())?;
        let url = format!("ssh://{principal}@127.0.0.1:{ssh_port}/{principal}/{repo}");
        crate::git(&tree, &ssh, &["clone", "-q", &url, "c"])?;
        let readme = std::fs::read_to_string(tree.join("c").join("README"))
            .map_err(|e| format!("README after clone: {e}"))?;
        if readme.trim() != nonce {
            return Err(format!(
                "the cloned README is not the pushed one: {readme:?} vs {nonce:?}"
            ));
        }
        Ok(format!("status closed (enrolled `{principal}` from U2); {principal}/{repo} cloned, nonce intact"))
    };
    let u4 = boot_installed(
        &qemu,
        &machine,
        shape,
        &cell,
        &disk_arg,
        &extras,
        &door.work.join("u4-serve"),
        "U4 serve after upgrade",
        door.expect_grpc,
        None,
        Some(&mut drive_u4),
    );
    eprintln!("{}", u4.line());
    legs.push(u4);
    Ok(legs)
}

/// Run `gunnar-cli` on the host and hand back stdout; a non-zero exit is an
/// error carrying stderr.
fn cli_out(cli: &Path, args: &[&str]) -> Result<String, String> {
    let out = std::process::Command::new(cli)
        .args(args)
        .output()
        .map_err(|e| format!("{} {}: {e}", cli.display(), args.join(" ")))?;
    if !out.status.success() {
        return Err(format!(
            "gunnar-cli {} failed ({}): {} {}",
            args.join(" "),
            out.status,
            String::from_utf8_lossy(&out.stdout).trim(),
            String::from_utf8_lossy(&out.stderr).trim()
        ));
    }
    Ok(String::from_utf8_lossy(&out.stdout).into_owned())
}

/// The `appliance-boot/<preset>` row for one leg — only when a preset was
/// named (the flag door names no shape). Proof names are the grid's own.
fn emit_row(shape: &Shape, leg: usize, l: &Leg) {
    if let Some(preset) = shape.preset {
        let detail = format!(
            "{} ms; {}",
            l.ms,
            l.detail.lines().next().unwrap_or_default()
        );
        isoproof::emit_boot_row(preset, APPLIANCE_BOOT_PROOFS[leg], l.pass(), &detail);
    }
}

// ── QMP screendump: the proof medium when VGA is the only console ───────────

/// A QMP control socket on the guest, for `screendump`. Pure std: a unix
/// stream, the capabilities handshake, one command. Nothing here asserts —
/// the dump is EVIDENCE beside the port probe, not the gate.
struct Qmp {
    sock: PathBuf,
    ppm: PathBuf,
}

impl Qmp {
    fn attach(args: &mut Vec<String>, workdir: &Path) -> Self {
        std::fs::create_dir_all(workdir).expect("create the boot workdir");
        let sock = workdir.join("qmp.sock");
        let _ = std::fs::remove_file(&sock);
        args.push("-qmp".into());
        args.push(format!("unix:{},server,nowait", sock.display()));
        Qmp {
            sock,
            ppm: workdir.join("vga.ppm"),
        }
    }

    /// One `screendump` to `vga.ppm` (overwritten). `Err` when the socket is
    /// not up yet or QEMU refused — the caller just tries again later.
    fn screendump(&self) -> Result<(), String> {
        use std::io::{BufRead, BufReader, Write};
        let mut s =
            std::os::unix::net::UnixStream::connect(&self.sock).map_err(|e| e.to_string())?;
        s.set_read_timeout(Some(Duration::from_secs(5)))
            .map_err(|e| e.to_string())?;
        let mut r = BufReader::new(s.try_clone().map_err(|e| e.to_string())?);
        let mut line = String::new();
        r.read_line(&mut line).map_err(|e| e.to_string())?; // the greeting
        s.write_all(b"{\"execute\":\"qmp_capabilities\"}\n")
            .map_err(|e| e.to_string())?;
        Self::reply(&mut r)?;
        let cmd = format!(
            "{{\"execute\":\"screendump\",\"arguments\":{{\"filename\":\"{}\"}}}}\n",
            self.ppm.display()
        );
        s.write_all(cmd.as_bytes()).map_err(|e| e.to_string())?;
        Self::reply(&mut r)
    }

    /// Read lines until a `return` or an `error` (events in between are skipped).
    fn reply(r: &mut impl std::io::BufRead) -> Result<(), String> {
        loop {
            let mut line = String::new();
            if r.read_line(&mut line).map_err(|e| e.to_string())? == 0 {
                return Err("QMP closed".into());
            }
            if line.contains("\"return\"") {
                return Ok(());
            }
            if line.contains("\"error\"") {
                return Err(line.trim().to_string());
            }
        }
    }

    /// What the last dump shows: its size and whether anything was drawn.
    /// A PPM is `P6\n<w> <h>\n255\n` + RGB bytes; "blank" = every pixel black.
    fn describe(&self) -> String {
        let Ok(bytes) = std::fs::read(&self.ppm) else {
            return "VGA screendump: none taken".into();
        };
        let mut fields = 0;
        let mut at = 0;
        while fields < 4 && at < bytes.len() {
            while at < bytes.len() && !bytes[at].is_ascii_whitespace() {
                at += 1;
            }
            fields += 1;
            at += 1;
        }
        let lit = bytes
            .get(at..)
            .map(|px| px.iter().filter(|b| **b > 0x20).count())
            .unwrap_or(0);
        format!(
            "VGA screendump {} ({} B, {} lit bytes{})",
            self.ppm.display(),
            bytes.len(),
            lit,
            if lit == 0 { " — BLANK" } else { "" }
        )
    }
}

/// **What the medium says about itself** — read off the ISO, not off its name.
///
/// Two facts, both already on every composed medium and neither of them a new
/// thing to write:
///
///  * the **ISO-9660 volume identifier** in the primary volume descriptor
///    (`GUNNAR_INSTALL` for an installer, `GUNNAR_APPLIANCE` for a live
///    appliance) — one 2 KiB read at LBA 16;
///  * the **`korp/cmdline` on the ESP**, the exact kernel command line this
///    medium boots, which is what tells one installer row from another:
///    `korp.args=serve,--no-grpc,…` on the sealed medium against
///    `korp.args=serve,--grpc-bind,eth1:50051` on the open one.
///
/// `None` when the path is not a readable ISO-9660 image at all. A partial
/// answer is normal and useful: an image whose ESP cannot be read still names
/// its volume, and the caller narrows on what it has.
struct MediumIdentity {
    volume_id: String,
    cmdline: Option<String>,
}

/// The ESP image the EFI half of the medium boots from, by the name the ISO
/// writer gives it (`nornir_build_thing::image_build`: `w.set_efi_boot(
/// "efiboot.img")`). Matched case-insensitively and without the ISO-9660
/// `;1` version suffix, because that is what the directory record holds.
pub(crate) const ESP_IMAGE_NAME: &str = "efiboot.img";

/// One 2 048-byte ISO block.
pub(crate) const ISO_BLOCK: u64 = 2048;

pub(crate) fn read_range(iso: &Path, offset: u64, len: usize) -> Option<Vec<u8>> {
    use std::io::{Read, Seek, SeekFrom};
    let mut f = std::fs::File::open(iso).ok()?;
    f.seek(SeekFrom::Start(offset)).ok()?;
    let mut buf = vec![0u8; len];
    f.read_exact(&mut buf).ok()?;
    Some(buf)
}

/// `(extent_lba, data_len)` of a file in the ISO's ROOT directory, by name.
///
/// One level deep on purpose: the ESP image is a root-level file on every
/// medium this door drives, and a recursive walk would be code with no caller.
pub(crate) fn iso_root_file(iso: &Path, want: &str) -> Option<(u64, u64)> {
    let pvd = read_range(iso, 16 * ISO_BLOCK, ISO_BLOCK as usize)?;
    if pvd.first() != Some(&1) || &pvd[1..6] != b"CD001" {
        return None;
    }
    // The root directory record is embedded in the PVD at byte 156.
    let root_lba = u32::from_le_bytes(pvd[158..162].try_into().ok()?) as u64;
    let root_len = u32::from_le_bytes(pvd[166..170].try_into().ok()?) as usize;
    let dir = read_range(iso, root_lba * ISO_BLOCK, root_len)?;
    let mut at = 0usize;
    while at < dir.len() {
        let rec_len = dir[at] as usize;
        if rec_len == 0 {
            // Padding to the end of this block; records never straddle one.
            at = (at / ISO_BLOCK as usize + 1) * ISO_BLOCK as usize;
            continue;
        }
        let rec = dir.get(at..at + rec_len)?;
        let name_len = *rec.get(32)? as usize;
        let name = rec.get(33..33 + name_len)?;
        // `.` and `..` are the one-byte names 0x00 and 0x01.
        let name = String::from_utf8_lossy(name);
        let name = name.split(';').next().unwrap_or_default();
        if name.eq_ignore_ascii_case(want) {
            let lba = u32::from_le_bytes(rec.get(2..6)?.try_into().ok()?) as u64;
            let len = u32::from_le_bytes(rec.get(10..14)?.try_into().ok()?) as u64;
            return Some((lba, len));
        }
        at += rec_len;
    }
    None
}

fn medium_identity(iso: &Path) -> Option<MediumIdentity> {
    let pvd = read_range(iso, 16 * ISO_BLOCK, ISO_BLOCK as usize)?;
    if pvd.first() != Some(&1) || &pvd[1..6] != b"CD001" {
        return None;
    }
    let volume_id = String::from_utf8_lossy(&pvd[40..72]).trim().to_string();
    // The ESP, and the command line inside it. Both halves are allowed to
    // fail: a medium that names its volume and hides its ESP is still a
    // medium that named its volume.
    let cmdline = iso_root_file(iso, ESP_IMAGE_NAME)
        .and_then(|(lba, len)| read_range(iso, lba * ISO_BLOCK, usize::try_from(len).ok()?))
        .and_then(|esp| kvmproof::read_esp_image_file(&esp, ESP_CMDLINE_PATH))
        .map(|b| String::from_utf8_lossy(&b).trim().to_string());
    Some(MediumIdentity { volume_id, cmdline })
}

/// Does this declared cell's command line **fit** the one the medium boots?
///
/// Every token the cell declares must be a token of the medium's cmdline. Not
/// equality: the composer adds tokens of its own (`initrd=`,
/// `korp.installer_root=`, the payload's `korp.znippy=`), and a cell that
/// declares a subset of what was composed is still the cell it was composed
/// from. What it does discriminate is exactly what differs between two rows —
/// `korp.args=serve,--no-grpc,…` is not a token of a medium that boots
/// `korp.args=serve,--grpc-bind,eth1:50051`.
fn cell_fits_cmdline(cell: &InstallerBuild, cmdline: &str) -> bool {
    let on_medium: Vec<&str> = cmdline.split_whitespace().collect();
    cell.effective_boot_params()
        .iter()
        .all(|p| on_medium.iter().any(|t| *t == p))
}

/// The `[[build.installer]]` this medium was composed from — **by the medium,
/// then by the file stem**.
///
/// # Why not the stem alone
///
/// It used to be the stem alone (`.iso-out/<name>.iso`), and a renamed file
/// was therefore a refused run: *"no `[[build.installer]]` named X"*. That has
/// cost two lanes a run, and it is the wrong question to ask — a copy of a
/// medium under another name is the same medium, and the identity is **on** it:
/// the ISO-9660 volume id and the `korp/cmdline` on its ESP
/// ([`medium_identity`]).
///
/// The order is: narrow the declared rows to the ones whose `volume_id` the
/// medium carries, narrow again to the ones whose declared boot parameters the
/// medium's own command line contains, and take a **unique** survivor. Anything
/// less than unique falls back to the stem with a named warning, because a
/// guess between two rows is worse than the old refusal — and a stem that
/// matches is still the composer's own convention and is right almost always.
///
/// Refused by name when neither answers, and the refusal says **both** what
/// the medium said and which cells are declared.
fn declared_installer(iso: &Path) -> Result<InstallerBuild, String> {
    let root = crate::workspace_root();
    let set = BuildVariantSet::discover(&root)
        .map_err(|e| format!("reading gunnar's declaration under {}: {e}", root.display()))?
        .ok_or_else(|| format!("gunnar declares no build space under {}", root.display()))?;
    let stem = iso
        .file_stem()
        .map(|s| s.to_string_lossy().into_owned())
        .unwrap_or_default();

    // ── (1) the medium ──────────────────────────────────────────────────────
    let identity = medium_identity(iso);
    let mut medium_said = "the medium could not be read as an ISO-9660 image".to_string();
    if let Some(id) = &identity {
        let by_volume: Vec<&InstallerBuild> = set
            .installers
            .iter()
            .filter(|i| i.volume_id == id.volume_id)
            .collect();
        let narrowed: Vec<&InstallerBuild> = match &id.cmdline {
            Some(c) => by_volume
                .iter()
                .copied()
                .filter(|i| cell_fits_cmdline(i, c))
                .collect(),
            None => by_volume.clone(),
        };
        medium_said = format!(
            "the medium says volume_id={:?}{}, which fits {}",
            id.volume_id,
            match &id.cmdline {
                Some(_) => " and carries a korp/cmdline",
                None => " and no readable korp/cmdline",
            },
            if narrowed.is_empty() {
                "no declared installer".to_string()
            } else {
                narrowed
                    .iter()
                    .map(|i| i.name.as_str())
                    .collect::<Vec<_>>()
                    .join(", ")
            }
        );
        if let [only] = narrowed.as_slice() {
            if only.name != stem {
                eprintln!(
                    "  note: the medium {} is named `{stem}` but IS `{}` — resolved off the \
                     volume id and its korp/cmdline, not off the filename.",
                    iso.display(),
                    only.name
                );
            }
            return Ok((*only).clone());
        }
        eprintln!(
            "  warning: {medium_said}; falling back to the file stem `{stem}`. A medium that \
             cannot name exactly one declared installer is resolved by its filename, which is \
             the composer's convention and not a fact about the bytes."
        );
    } else if iso.exists() {
        eprintln!(
            "  warning: {} is not a readable ISO-9660 image, so its `[[build.installer]]` is \
             resolved by the file stem `{stem}` instead of by the medium.",
            iso.display()
        );
    }

    // ── (2) the stem ────────────────────────────────────────────────────────
    set.installers
        .iter()
        .find(|i| i.name == stem)
        .cloned()
        .ok_or_else(|| {
            format!(
                "no `[[build.installer]]` named `{stem}` (the medium's file stem), and {medium_said} \
                 — declared: {}. Either name the ISO after the cell it was composed from, or \
                 compose it from a cell whose volume_id and boot parameters this medium carries.",
                set.installers.iter().map(|i| i.name.as_str()).collect::<Vec<_>>().join(", ")
            )
        })
}

/// B1's gate, read off the DISK (and the UART when there is one).
fn install_verdict(run: &kvmproof::BootRun, disk: &Path, serial: bool) -> Leg {
    // The guest's own terminal line is the WHOLE answer and it arrives first:
    // every gate below is downstream of a boot that stopped. Report it verbatim
    // and stop — a row that instead says "no protective MBR signature" about an
    // installer that refused to touch the disk blames the consequence.
    if let Some(t) = &run.terminal {
        return Leg {
            name: "B1 install",
            verdict: Verdict::Refused,
            ms: run.elapsed.as_millis(),
            detail: t.say(),
        };
    }
    if run.timed_out {
        return Leg {
            name: "B1 install",
            verdict: Verdict::TimedOut,
            ms: run.elapsed.as_millis(),
            detail: format!(
                "NO VERDICT: the budget ran out with the guest still running and nothing terminal on \
                 the console ({} B of serial). Nothing was measured — this is not a failed install, \
                 it is an unmeasured one.{}",
                run.serial.len(),
                if run.serial.is_empty() {
                    " ZERO serial bytes: the medium never handed over — firmware, not appliance."
                } else {
                    ""
                }
            ),
        };
    }
    let mut why = Vec::new();
    match run.exited {
        Some(_) => {}
        None => {
            why.push("QEMU never exited: the installer did not reboot within the budget".into())
        }
    }
    if serial && !run.serial.contains(INSTALL_OK) {
        why.push(format!("no `{INSTALL_OK}` on the UART"));
    }
    let mbr = kvmproof::read_at(disk, 510, 2);
    if mbr.len() != 2 || (mbr[0], mbr[1]) != (0x55, 0xAA) {
        why.push("no protective MBR signature on the target".into());
    }
    let cmdline = {
        let (first, last) = kvmproof::gpt_entry(disk, ESP_PARTITION);
        kvmproof::read_fat_file(disk, first, last, ESP_CMDLINE_PATH)
            .map(|b| String::from_utf8_lossy(&b).trim().to_string())
    };
    match &cmdline {
        Some(c) if c.contains("korp.entry=") => {}
        Some(c) => why.push(format!("the installed cmdline names no product: {c}")),
        None => why.push(format!("no {ESP_CMDLINE_PATH} on the installed ESP")),
    }
    let detail = if why.is_empty() {
        format!(
            "{}; installed cmdline: {}",
            if serial {
                "INSTALL-OK on the UART, then the installer rebooted"
            } else {
                "no UART — gated on the disk: MBR + korp/cmdline"
            },
            cmdline.unwrap_or_default()
        )
    } else {
        why.join("; ")
    };
    Leg {
        name: "B1 install",
        verdict: if why.is_empty() {
            Verdict::Pass
        } else {
            Verdict::Failed
        },
        ms: run.elapsed.as_millis(),
        detail,
    }
}

/// **Is a gRPC server BOUND behind this forwarded port?**
///
/// ★ A bare `TcpStream::connect` cannot answer this and never could. QEMU's
/// `hostfwd` is a real listening socket ON THE HOST: `connect()` to it
/// succeeds whatever is or is not bound inside the guest, and the failure only
/// shows up afterwards as an immediate EOF. So `:50051 accepts` was true for
/// every medium this door has ever driven — a green that could not go red.
/// MEASURED 2026-08-31: with the appliance's listener provably on
/// `10.0.3.15:50051` (its own log line), the forward on the OTHER NIC still
/// accepted.
///
/// The applied output instead: an h2 server sends its SETTINGS frame as the
/// server connection preface, unprompted, the moment the connection is up
/// (RFC 9113 §3.4). Measured against the live appliance, that is 27 bytes
/// arriving with nothing sent first. A forward to nothing yields EOF.
///
/// Bytes read = a server is bound. Zero bytes = there is not one.
fn grpc_speaks(port: u16) -> bool {
    let addr: SocketAddr = ([127, 0, 0, 1], port).into();
    let Ok(mut sock) = TcpStream::connect_timeout(&addr, Duration::from_millis(500)) else {
        return false;
    };
    if sock
        .set_read_timeout(Some(Duration::from_millis(1200)))
        .is_err()
    {
        return false;
    }
    let mut buf = [0u8; 64];
    matches!(sock.read(&mut buf), Ok(n) if n > 0)
}

/// **How many interfaces this medium brings up**, delegated to
/// [`gunnar_recipe::declared_interface_count`] — the one reader of
/// `korp.env.GUNNAR_NET`, which is the declaration that owns the fact.
///
/// The rig carried its own copy of this parse until 2026-09-06. Its own doc
/// had already argued that a second place for the number would drift; a second
/// place for the PARSE is the same defect one level down, and the gate in
/// `gunnar-recipe` that refuses a door on an unconfigured interface must agree
/// with the rig that boots one, exactly.
///
/// **Not** the option's `bakes.when_on` (which is the value that actually
/// reaches the medium): this reads the CELL as declared, which is what a door
/// is handed. They are held equal by `recipe.rs`, which spells both from
/// `CLOUD_GUNNAR_NET`.
fn declared_interface_count(cell: &InstallerBuild) -> usize {
    usize::from(gunnar_recipe::declared_interface_count(&cell.boot_params))
}

/// B2/B3: the installed disk alone, probed over the forwarded ports, then a
/// power cut (the door's kill IS the cut — no ACPI, no flush).
fn boot_installed(
    qemu: &Path,
    machine: &Machine,
    shape: &Shape,
    cell: &InstallerBuild,
    disk_arg: &str,
    extras: &[(PathBuf, u64)],
    workdir: &Path,
    name: &'static str,
    expect_grpc: bool,
    // `--grpc-only`: the role the medium declares. The leg is then proved by
    // `Bootstrap.Status` on the private interface (build + role), not a banner.
    grpc_only: Option<&str>,
    // The upgrade door's hands on the guest: run ONCE when the ports answer,
    // its `Err` fails the leg, its `Ok` text goes in the detail.
    mut drive: Option<&mut dyn FnMut(u16, u16) -> Result<String, String>>,
) -> Leg {
    let ssh_port = isoproof::free_port();
    // TWO probes of :50051, one per interface. The public one is the whole
    // point: `gunnar control repo list --at <public>:50051` answered an
    // uncredentialled caller from the desktop on 2026-08-31, and the only way a door
    // can say that is closed is to ask on the public side and be REFUSED.
    let grpc_public_port = isoproof::free_port();
    let grpc_private_port = isoproof::free_port();
    let mut args = shape.disk_args(cell, disk_arg);
    attach_extra_disks(&mut args, extras);
    assert!(
        !args.iter().any(|a| a.ends_with(".iso") || a == "-cdrom"),
        "the boot leg must carry NO medium: {args:?}"
    );
    // **The machine must be at least what the MEDIUM configures.** A preset's
    // `nics` is what a cloud hands out; the medium's `korp.env.GUNNAR_NET`
    // names the interfaces it brings up, and a medium that configures two and
    // binds its control plane on the second cannot serve on a machine with
    // one. Proving it on such a machine is not a test of the product — it goes
    // red for a reason that has nothing to do with the appliance (MEASURED
    // 2026-08-31 on a one-NIC qemu-local door: `no interface named "eth1"`,
    // then the init panic).
    let declared_nics = declared_interface_count(cell);
    let nics = shape.nics.max(declared_nics);
    if nics > shape.nics {
        eprintln!(
            "  machine: {nics} NIC(s) — the preset offers {}, and the medium's GUNNAR_NET names \
             {declared_nics}",
            shape.nics
        );
    }
    let specs: Vec<isoproof::SlirpNic> = (0..nics)
        .map(|n| isoproof::SlirpNic {
            // n0 keeps QEMU's default network so every existing forward and
            // every existing expectation is byte-identical; each later NIC gets
            // its own /24 so an address is one interface's and not both's.
            net: (n > 0).then(|| format!("10.0.{}.0/24", n + 2)),
            hostfwds: match n {
                0 => vec![(ssh_port, GUEST_SSH), (grpc_public_port, GUEST_GRPC)],
                1 => vec![(grpc_private_port, GUEST_GRPC)],
                _ => Vec::new(),
            },
        })
        .collect();
    debug_assert_eq!(specs[0].net, None, "n0 must stay on QEMU's default network");
    isoproof::attach_slirp_nic_specs(&mut args, &specs, shape.nic_device);
    // With one NIC there is no private side to ask, so the private probe is the
    // public one and the asymmetry below is not asserted.
    let has_private_nic = nics > 1;
    let qmp = shape.vga_only().then(|| Qmp::attach(&mut args, workdir));

    let mut banner: Option<String> = None;
    let mut grpc_open = false;
    let mut grpc_public_open = false;
    // `--grpc-only`: the verdict on the first `Bootstrap.Status` answer.
    let mut control: Option<Result<String, String>> = None;
    let mut ready_ms: Option<u128> = None;
    let mut driven: Option<Result<String, String>> = None;
    let mut polls = 0u32;
    let started = Instant::now();
    let run = kvmproof::boot_hold(
        qemu,
        machine,
        &args,
        workdir,
        Duration::from_secs(180),
        |_serial| {
            polls += 1;
            // Every 5 s on a VGA-only shape: a boot that never answers leaves its
            // LAST frame behind — the dark-boot evidence a UART-less cloud's VNC
            // is the only other way to see.
            if let Some(q) = &qmp {
                if polls == 4 || polls % 20 == 0 {
                    let _ = q.screendump();
                }
            }
            if banner.is_none() {
                let addr: SocketAddr = ([127, 0, 0, 1], ssh_port).into();
                if let Ok(b) = crate::ssh_banner(&addr) {
                    if b.starts_with("SSH-") {
                        banner = Some(b);
                    }
                }
            }
            let formed = if let Some(want) = grpc_only {
                // No banner to wait for: a twin binds its forge listener to
                // loopback. The control plane names who answers (src/control.rs).
                if control.is_none() {
                    let private: SocketAddr = ([127, 0, 0, 1], grpc_private_port).into();
                    if let Ok(a) =
                        crate::control::bootstrap_status(private, Duration::from_millis(1500))
                    {
                        grpc_open = true;
                        control = Some(crate::control::grpc_only_verdict(&a, want));
                    }
                }
                if control.is_some() && !grpc_public_open {
                    grpc_public_open = grpc_speaks(grpc_public_port);
                }
                control.is_some()
            } else {
                if banner.is_some() && !grpc_open {
                    grpc_open = grpc_speaks(grpc_private_port);
                }
                // The PUBLIC side, asked every poll and never gated on the private one:
                // "closed" must be an answer this loop can form on its own, or a public
                // listener that came up first would be recorded as absent.
                if banner.is_some() && !grpc_public_open {
                    grpc_public_open = grpc_speaks(grpc_public_port);
                }
                banner.is_some() && (grpc_open || !expect_grpc)
            };
            if formed && ready_ms.is_none() {
                ready_ms = Some(started.elapsed().as_millis());
                // The console as a human on the cloud's VNC would see it, at the
                // moment the ports answered.
                if let Some(q) = &qmp {
                    let _ = q.screendump();
                }
                if let Some(d) = drive.as_mut() {
                    driven = Some(d(ssh_port, grpc_private_port));
                }
            }
            formed
        },
    );

    // The guest refused or died by name, and it said why — quote it, and do not
    // bury it under "nothing answered on :2222", which is merely what follows.
    if let Some(t) = &run.terminal {
        return Leg {
            name,
            verdict: Verdict::Refused,
            ms: ready_ms.unwrap_or(run.elapsed.as_millis()),
            detail: format!(
                "{}{}\n── serial tail ──\n{}",
                t.say(),
                qmp.as_ref()
                    .map(|q| format!("; {}", q.describe()))
                    .unwrap_or_default(),
                tail(&run.serial, 20)
            ),
        };
    }
    if run.timed_out && banner.is_none() && control.is_none() {
        return Leg {
            name,
            verdict: Verdict::TimedOut,
            ms: run.elapsed.as_millis(),
            detail: format!(
                "NO VERDICT: the budget ran out — nothing ever answered on :{GUEST_SSH} and the \
                 console printed nothing terminal ({} B of serial){}\n── serial tail ──\n{}",
                run.serial.len(),
                qmp.as_ref()
                    .map(|q| format!("; {}", q.describe()))
                    .unwrap_or_default(),
                tail(&run.serial, 20)
            ),
        };
    }
    let mut why = Vec::new();
    if let Some(want) = grpc_only {
        match &control {
            Some(Ok(_)) => {}
            Some(Err(e)) => why.push(e.clone()),
            None => why.push(format!(
                ":{GUEST_GRPC} never answered Bootstrap.Status on the PRIVATE interface within the budget"
            )),
        }
        if let Some(b) = &banner {
            why.push(format!(
                "a --grpc-only medium (role {want}) serves git on :{GUEST_SSH}: {b:?} — a twin binds \
                 its forge listener to loopback"
            ));
        }
    } else {
        match &banner {
            Some(b) if b.starts_with(IDENT_PREFIX) => {}
            Some(b) => why.push(format!(
                "the listener on :{GUEST_SSH} is not gunnar's: {b:?}"
            )),
            None => why.push(format!(
                "nothing answered on :{GUEST_SSH} within the budget"
            )),
        }
    }
    if grpc_only.is_none() && expect_grpc && !grpc_open {
        why.push(format!(
            ":{GUEST_GRPC} never accepted on the PRIVATE interface — the medium declares \
             `--grpc-bind eth1:50051` and either eth1 never took a lease or the listener \
             refused to bind (the serial says which)"
        ));
    }
    // **THE ASYMMETRY.** A control plane on the interface that faces the
    // internet is the defect this door exists to catch, and the reason is
    // `Bootstrap`: it is the ONE service mounted outside the auth gate (a gated
    // `Bootstrap` on a server with an empty key table could be reached by
    // nobody), so on a keyless medium `Bootstrap.Enroll` is open to whoever
    // reaches the port first, and that caller becomes gunnar-admin. A wildcard
    // bind passes every other assertion in this function — it accepts on the
    // private side too — so this is the only line that can tell `eth1:50051`
    // from `0.0.0.0:50051`.
    //
    // ⚠ This comment used to justify itself with "on git.example.test, `repo
    // list`, `user list` and `repo create` all answered a caller from the desktop with
    // NO credential (2026-08-31)". That is FALSE and the correction is recorded
    // in `gunnar-build-thing/src/recipe.rs::OPEN_KORP_ARGS`: `gunnar control`
    // signs with `~/.ssh/id_ed25519` when `-i` is absent, and the desktop's key is
    // enrolled on that box as gunnar-admin. Re-measured with a raw gRPC request
    // carrying no metadata, every gated service answered `grpc-status: 16`.
    // The leg below is still right; it was resting on the wrong fact.
    if has_private_nic && grpc_public_open {
        why.push(format!(
            ":{GUEST_GRPC} ACCEPTED on the PUBLIC interface. `Bootstrap` is mounted outside \
             the auth gate by design, so on a medium that declares no `--admin-key` the \
             enrolment window is open to anyone who can route to the box, and the first \
             caller to take it becomes gunnar-admin. Bind the private interface by name \
             (`--grpc-bind eth1:50051`), never a wildcard"
        ));
    }
    if run.exited.is_some() && banner.is_none() && control.is_none() {
        why.push("QEMU exited on its own before anything served".into());
    }
    let driven_note = match &driven {
        Some(Ok(text)) => format!("; {text}"),
        Some(Err(e)) => {
            why.push(format!("driving the guest failed: {e}"));
            String::new()
        }
        None => String::new(),
    };
    let detail = if why.is_empty() {
        format!(
            "{} on :{GUEST_SSH}{}{}{driven_note}; then SIGKILL (power cut)",
            match &control {
                Some(Ok(c)) => format!("{c} on the PRIVATE interface, no git"),
                _ => banner.clone().unwrap_or_default(),
            },
            if grpc_open {
                format!(
                    ", :{GUEST_GRPC} accepts on the PRIVATE interface and is {} on the PUBLIC one",
                    if grpc_public_open { "OPEN" } else { "closed" }
                )
            } else if has_private_nic {
                format!(
                    ", :{GUEST_GRPC} closed on both interfaces (public probe: {})",
                    if grpc_public_open { "OPEN" } else { "closed" }
                )
            } else {
                String::new()
            },
            qmp.as_ref()
                .map(|q| format!("; {}", q.describe()))
                .unwrap_or_default()
        )
    } else {
        format!(
            "{}{}\n── serial tail ──\n{}",
            why.join("; "),
            qmp.as_ref()
                .map(|q| format!("; {}", q.describe()))
                .unwrap_or_default(),
            tail(&run.serial, 20)
        )
    };
    Leg {
        name,
        verdict: if why.is_empty() {
            Verdict::Pass
        } else {
            Verdict::Failed
        },
        ms: ready_ms.unwrap_or(run.elapsed.as_millis()),
        detail,
    }
}

/// The role the medium's `korp.args=` declares (`primary` when none) — what
/// `--grpc-only` holds the booted box's `Bootstrap.Status` to.
pub fn declared_role(iso: &Path) -> Result<String, String> {
    declared_installer(iso).map(|c| crate::control::declared_role(&c.effective_boot_params()))
}

/// Whether the declared `korp.args=` binds the control plane off the loopback.
/// **Does this medium's declaration bring a control plane up off the loopback?**
///
/// It matched `--grpc-bind,0.0.0.0` and nothing else until 2026-08-31, which
/// was true of every medium that had one, because a wildcard was the only
/// spelling `--grpc-bind` accepted for "reachable from another host". It is no
/// longer: the keyless installer names the PRIVATE interface
/// (`--grpc-bind,eth1:50051`), and a probe expectation still keyed on the old
/// literal would read the fixed medium as having no control plane at all — the
/// door would then pass without ever asking whether :50051 answers.
///
/// So: any `--grpc-bind` value whose host is neither a loopback literal nor
/// absent. The same question `gunnar_build_thing::reachability::
/// binds_control_plane_offbox` answers about the same argv; this crate is
/// detached and cannot depend on that one, and the alternative — a literal
/// that goes stale silently — is what this comment is about.
pub fn declared_grpc_open(iso: &Path) -> bool {
    declared_installer(iso)
        .map(|c| {
            c.effective_boot_params()
                .iter()
                .filter_map(|p| p.strip_prefix("korp.args="))
                .flat_map(|tail| tail.split(','))
                .scan(false, |after_flag, tok| {
                    let take = *after_flag;
                    *after_flag = tok == "--grpc-bind";
                    Some(if take {
                        Some(tok.to_string())
                    } else {
                        tok.strip_prefix("--grpc-bind=").map(str::to_owned)
                    })
                })
                .flatten()
                .any(|bind| {
                    let host = bind.rsplit_once(':').map(|(h, _)| h).unwrap_or(&bind);
                    let host = host.trim_start_matches('[').trim_end_matches(']');
                    !(host.is_empty()
                        || host.eq_ignore_ascii_case("localhost")
                        || host == "::1"
                        || host.starts_with("127."))
                })
        })
        .unwrap_or(false)
}

pub fn render(door: &Door, legs: &[Leg]) -> String {
    let mut s = format!(
        "\n══ gunnar installer door · {} ══\n  medium: {}{}\n",
        door.shape.label(),
        door.iso.display(),
        match door.twin {
            Twin::Iso => "",
            Twin::Target => " (virtio twin padded to the target's size, attached first)",
            Twin::Larger => " (virtio twin padded to the target's size + 1 GiB, attached first)",
        }
    );
    for l in legs {
        s.push_str(&l.line());
        s.push('\n');
    }
    // The ladder stops at the first failed leg, so "every leg that ran passed" is
    // "the ladder completed" — 3 legs on the plain door, 4 on the upgrade door.
    let all = !legs.is_empty() && legs.iter().all(|l| l.pass());
    let timed_out = legs.iter().any(|l| l.verdict == Verdict::TimedOut);
    let refused = legs.iter().any(|l| l.verdict == Verdict::Refused);
    s.push_str(match (all, timed_out, refused) {
        (true, _, _) => "  ALL PASS\n",
        // Named first and separately: a timeout measured NOTHING, and a report
        // that folds it into "not proven" invites the reader to treat the two
        // the same. They are not the same. One is evidence; the other is its
        // absence.
        (_, true, _) => "  NOT PROVEN — TIMED OUT (no verdict formed; nothing was measured)\n",
        (_, _, true) => {
            "  NOT PROVEN — REFUSED BY THE GUEST (its own words are in the row above)\n"
        }
        _ => "  NOT PROVEN\n",
    });
    s
}

/// **The door's own exit code**, so a caller holding nothing else can still
/// tell the three apart: `0` proven, [`EXIT_NOT_PROVEN`] a real failure or a
/// refusal, [`EXIT_TIMED_OUT`] a budget that ran out.
pub fn exit_code(legs: &[Leg], want: usize) -> u8 {
    if legs.iter().any(|l| l.verdict == Verdict::TimedOut) {
        EXIT_TIMED_OUT
    } else if legs.len() == want && legs.iter().all(|l| l.pass()) {
        0
    } else {
        EXIT_NOT_PROVEN
    }
}

/// A leg failed or the guest refused — something was measured and it was bad.
pub const EXIT_NOT_PROVEN: u8 = 1;
/// **A leg timed out** — nothing was measured. Distinct from
/// [`EXIT_NOT_PROVEN`] on purpose: `2` is the shell's usage-error convention
/// and `124` is `timeout(1)`'s own, so neither could be told from this door's.
pub const EXIT_TIMED_OUT: u8 = 3;

fn tail(text: &str, n: usize) -> String {
    let lines: Vec<&str> = text.lines().collect();
    lines[lines.len().saturating_sub(n)..].join("\n")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_firmware_word_is_read_and_refused_by_name() {
        assert_eq!(
            FirmwareChoice::parse("seabios").unwrap(),
            FirmwareChoice::SeaBios
        );
        assert_eq!(FirmwareChoice::parse("UEFI").unwrap(), FirmwareChoice::Uefi);
        let err = FirmwareChoice::parse("coreboot").unwrap_err();
        assert!(err.contains("coreboot") && err.contains("seabios"), "{err}");
    }

    /// The roster renders: UpCloud is SeaBIOS + pc + EPYC-Rome, nothing at
    /// 0x3f8 (ttyS1/ttyS2 exist), 2 NICs, VGA the only console, medium as
    /// cdrom; qemu-local-bios has a UART; the flag door is unchanged.
    #[test]
    fn the_preset_renders_the_machine_and_the_flags_override_it() {
        let up = Shape::from_preset("upcloud", None, None).unwrap();
        assert_eq!(up.preset, Some("upcloud"));
        assert_eq!(up.firmware, FirmwareChoice::SeaBios);
        assert_eq!(up.uarts, [false, true, true, false]);
        assert_eq!(up.cpu.as_deref(), Some("EPYC-Rome"));
        assert_eq!(up.chipset, None, "pc IS SeaBIOS's default");
        assert_eq!(
            (up.disk_device, up.nic_device, up.nics),
            (VIRTIO_BLK, "virtio-net-pci", 2)
        );
        assert!(up.medium_as_cdrom && up.vga_only(), "{up:?}");
        let label = up.label();
        assert!(
            label.contains("target=upcloud")
                && label.contains("ttyS1,ttyS2")
                && label.contains("screendump"),
            "{label}"
        );

        let bios = Shape::from_preset("qemu-local-bios", None, None).unwrap();
        assert!(bios.has_uart() && bios.serial_console && !bios.vga_only());
        let local = Shape::from_preset("qemu-local", None, None).unwrap();
        assert_eq!(
            local.firmware,
            FirmwareChoice::Uefi,
            "Both on q35 boots OVMF"
        );
        let gce = Shape::from_preset("gce-virtio", None, None).unwrap();
        assert_eq!(
            gce.chipset.as_deref(),
            Some("pc"),
            "pc under UEFI is the override case"
        );

        // Overrides on top of the preset.
        let forced = Shape::from_preset("upcloud", Some(FirmwareChoice::Uefi), Some(true)).unwrap();
        assert_eq!(forced.firmware, FirmwareChoice::Uefi);
        assert_eq!(forced.uarts, [true, true, true, false]);
        assert!(forced.serial_console && !forced.vga_only());
        assert_eq!(
            Shape::from_preset("qemu-local-bios", None, Some(false))
                .unwrap()
                .uarts,
            [false; 4]
        );

        // The flag door is byte-identical to before.
        let f = Shape::flags(FirmwareChoice::SeaBios, false);
        assert_eq!(
            (f.preset, f.uarts, f.nics, f.cpu, f.serial_console),
            (None, [false; 4], NICS, None, false)
        );
    }

    /// **The medium says how many NICs it needs, and the door listens.**
    ///
    /// `GUNNAR_NET=eth0:dhcp,eth1:dhcp` plus `--grpc-bind eth1:50051` is not
    /// satisfiable on a one-NIC machine, and the `qemu-local` preset offers
    /// one. Read it wrong and the door boots a guest with no eth1, `gunnar
    /// serve` refuses by name, and the row blames the appliance for the
    /// harness (MEASURED under KVM 2026-08-31, before this existed).
    #[test]
    fn the_interface_count_comes_from_the_mediums_own_gunnar_net() {
        let cell = |net: &str| InstallerBuild {
            boot_params: vec![
                "console=tty0".to_string(),
                format!("korp.env.GUNNAR_NET={net}"),
                "korp.entry=gunnar".to_string(),
            ],
            ..InstallerBuild::default()
        };
        assert_eq!(declared_interface_count(&cell("eth0:dhcp,eth1:dhcp")), 2);
        assert_eq!(declared_interface_count(&cell("eth0:dhcp")), 1);
        assert_eq!(
            declared_interface_count(&cell("eth0:10.0.2.15/24:10.0.2.2,eth1:dhcp")),
            2,
            "a static public address with a DHCP private one is still two interfaces"
        );
        assert_eq!(
            declared_interface_count(&cell("eth0:dhcp,eth1:dhcp,eth2:dhcp")),
            3
        );
        // A medium that names no network still gets a machine with a NIC —
        // never zero, which would be a guest that cannot be reached at all.
        assert_eq!(
            declared_interface_count(&InstallerBuild::default()),
            1,
            "an absent GUNNAR_NET reads as one interface, never none"
        );
    }

    /// The REAL declaration, by name: `gunnar-installer-open` is the medium
    /// git.example.test is installed from, and it must ask for two.
    #[test]
    fn the_shipped_keyless_installer_asks_for_two_interfaces() {
        let root = crate::workspace_root();
        let Ok(Some(set)) = BuildVariantSet::discover(&root) else {
            eprintln!("no build space at {}; nothing to read", root.display());
            return;
        };
        let Some(cell) = set
            .installers
            .iter()
            .find(|i| i.name == "gunnar-installer-open")
            .cloned()
        else {
            panic!("gunnar declares a `[[build.installer]]` named gunnar-installer-open");
        };
        assert_eq!(
            declared_interface_count(&cell),
            2,
            "the open installer binds --grpc-bind eth1:50051; a one-NIC machine cannot serve it. \
             boot_params: {:?}",
            cell.boot_params
        );
    }

    /// needs-real-cloud is refused BY NAME with the preset's reason; unknown
    /// names get the roster.
    #[test]
    fn a_real_cloud_preset_is_refused_by_name() {
        for name in ["aws-nitro", "azure-gen2", "scaleway-elastic-metal"] {
            let err = Shape::from_preset(name, None, None).unwrap_err();
            assert!(
                err.contains(name) && err.contains("needs a real cloud") && err.contains("upcloud"),
                "{err}"
            );
        }
        let err = Shape::from_preset("digitalocean", None, None).unwrap_err();
        assert!(
            err.contains("digitalocean") && err.contains("qemu-local-bios"),
            "{err}"
        );
        // Every mimicable preset renders.
        for p in presets::PRESETS
            .iter()
            .filter(|p| p.status() != PresetStatus::NeedsRealCloud)
        {
            Shape::from_preset(p.name, None, None).unwrap_or_else(|e| panic!("{}: {e}", p.name));
        }
    }

    /// The scsi/nvme buses render outside the declaration's virtio-blk writer.
    #[test]
    fn a_non_virtio_blk_bus_renders_its_own_device() {
        let cell = declared_installer(Path::new("/x/gunnar-installer-open.iso")).unwrap();
        let mut gce = Shape::from_preset("gce-virtio", None, None).unwrap();
        assert_eq!(gce.disk_device, "virtio-scsi-pci");
        let a = gce.disk_args(&cell, "/t/target.img");
        assert!(
            a.iter().any(|x| x == "scsi-hd,drive=target,bus=scsi0.0"),
            "{a:?}"
        );
        gce.disk_device = "nvme";
        assert!(gce
            .disk_args(&cell, "/t/target.img")
            .iter()
            .any(|x| x.starts_with("nvme,drive=target")));
        let up = Shape::from_preset("upcloud", None, None).unwrap();
        assert_eq!(
            up.disk_args(&cell, "/t/target.img"),
            cell.kvm_disk_args("/t/target.img")
        );
    }

    /// A door on the gunnar checkout resolves its cell from the ISO's stem when
    /// there is no medium to read, and says which installers exist when the
    /// stem names none.
    #[test]
    fn the_medium_names_its_cell_or_is_refused_with_the_roster() {
        let open = declared_installer(Path::new("/x/gunnar-installer-open.iso")).unwrap();
        assert_eq!(open.name, "gunnar-installer-open");
        assert!(open.target_disk_bytes() >= 1 << 30);
        assert!(declared_grpc_open(Path::new(
            "/x/gunnar-installer-open.iso"
        )));
        assert!(
            !declared_grpc_open(Path::new("/x/gunnar-installer.iso")),
            "the sealed medium is --no-grpc"
        );
        let err = declared_installer(Path::new("/x/nothing-declares-this.iso")).unwrap_err();
        assert!(
            err.contains("gunnar-installer-open") && err.contains("nothing-declares-this"),
            "{err}"
        );
        // …and the refusal now says what the MEDIUM said too, not only what is
        // declared, so a run that failed here can tell "wrong name" from
        // "wrong bytes" without opening the ISO by hand.
        assert!(
            err.contains("could not be read as an ISO-9660 image"),
            "{err}"
        );
    }

    /// ★ **The command line discriminates the two installer rows.**
    ///
    /// This is the fact the whole medium-first lookup rests on: the sealed and
    /// the open installer carry the SAME `volume_id` (`GUNNAR_INSTALL`), so the
    /// volume alone can never tell them apart — `korp.args=` can, and does.
    ///
    /// The cmdline below is the real one, read out of
    /// `.iso-out/gunnar-installer-open.iso`'s ESP with `mcopy` on 2026-08-30.
    #[test]
    fn the_command_line_on_the_esp_tells_the_sealed_cell_from_the_open_one() {
        let root = crate::workspace_root();
        let set = BuildVariantSet::discover(&root).unwrap().unwrap();
        let open_medium = "console=tty0 console=ttyS0,115200 panic=10 \
             korp.env.GUNNAR_DATA_ROOT=/data korp.env.GUNNAR_APPLIANCE=1 \
             korp.args=serve,--grpc-bind,utility:50051 korp.entry=gunnar \
             korp.znippy=/app.znippy korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp korp.data.fs=xfs \
             korp.data.io=uring korp.appliance=lean korp.require_signed=0 \
             initrd=\\initrd korp.installer_root=/run/installer \
             KORP_INSTALLER_ROOT=/run/installer";
        let fits: Vec<&str> = set
            .installers
            .iter()
            .filter(|i| i.volume_id == "GUNNAR_INSTALL" && cell_fits_cmdline(i, open_medium))
            .map(|i| i.name.as_str())
            .collect();
        assert_eq!(
            fits,
            ["gunnar-installer-open"],
            "exactly one declared installer boots this command line; the volume id alone \
             fits both, which is why the cmdline is read at all"
        );
        // The tokens the composer added and the cell never declared (initrd=,
        // korp.znippy=, korp.installer_root=) do not disqualify it: the rule is
        // "every declared token is on the medium", not equality.
        let open = set
            .installers
            .iter()
            .find(|i| i.name == "gunnar-installer-open")
            .unwrap();
        assert!(open
            .effective_boot_params()
            .iter()
            .all(|p| open_medium.split_whitespace().any(|t| t == p)));
        assert!(!open_medium.contains("--no-grpc"));
    }

    /// ★ **A renamed medium still resolves — off its bytes.**
    ///
    /// The regression this closes: `gunnar-installtest` looked its cell up by
    /// the ISO's file STEM, so `cp gunnar-installer-open.iso run-7.iso` refused
    /// the run with *"no `[[build.installer]]` named run-7"*. It cost two lanes
    /// a run. A copy of a medium under another name is the same medium.
    ///
    /// Driven against a REAL composed ISO, hard-linked (not copied — the medium
    /// is 38 MB and `/home` is tight) under a stem that names no declared cell.
    /// Skipped, loudly, when no medium has been composed on this box: this
    /// asserts about bytes and there is nothing honest to assert without them.
    #[test]
    fn a_renamed_medium_resolves_off_its_volume_and_its_cmdline() {
        let composed = crate::workspace_root().join(".iso-out/gunnar-installer-open.iso");
        if !composed.exists() {
            eprintln!(
                "SKIP: no {} on this box — compose one to drive this",
                composed.display()
            );
            return;
        }
        // The identity is on the medium…
        let id = medium_identity(&composed).expect("a composed ISO names its volume");
        assert_eq!(id.volume_id, "GUNNAR_INSTALL");
        let cmdline = id.cmdline.expect("korp/cmdline on the ESP");
        assert!(cmdline.contains("korp.args="), "{cmdline}");

        // …and it survives a rename that the old stem lookup refused outright.
        let dir = std::env::temp_dir().join(format!("laneAH-rename-{}", std::process::id()));
        let _ = std::fs::remove_dir_all(&dir);
        std::fs::create_dir_all(&dir).unwrap();
        let renamed = dir.join("nothing-declares-this-stem.iso");
        std::fs::hard_link(&composed, &renamed)
            .or_else(|_| std::fs::copy(&composed, &renamed).map(|_| ()))
            .expect("stage the renamed medium");
        let cell = declared_installer(&renamed).expect("the medium names its own cell");
        assert_eq!(cell.name, "gunnar-installer-open");
        assert!(
            declared_grpc_open(&renamed),
            "and the door reads its control plane off it"
        );
        let _ = std::fs::remove_dir_all(&dir);
    }

    // ── the doors that hang (2026-08-30) ────────────────────────────────────

    /// A medium file at a stem `declared_installer` resolves, with LBA 0 laid
    /// as asked: `None` = a pure El Torito ISO (all zeros, what the composer
    /// actually writes), `Some((0x55, 0xAA))` = a hybrid one SeaBIOS could boot.
    fn medium_file(tag: &str, mbr: Option<(u8, u8)>) -> std::path::PathBuf {
        let dir = std::env::temp_dir().join(format!("aq-medium-{tag}-{}", std::process::id()));
        let _ = std::fs::remove_dir_all(&dir);
        std::fs::create_dir_all(&dir).unwrap();
        let p = dir.join("gunnar-installer-open.iso");
        let mut bytes = vec![0u8; 4096];
        if let Some((a, b)) = mbr {
            bytes[510] = a;
            bytes[511] = b;
        }
        std::fs::write(&p, bytes).unwrap();
        p
    }

    /// **gce-virtio, in seconds instead of 420 of them.** MEASURED before this
    /// check existed: the door booted, the appliance printed `FAILED hwdetect:
    /// no usable target disk` at once and the door still sat for 420.10 s. The
    /// contradiction is between two DECLARATIONS and needs no boot at all.
    #[test]
    fn a_machine_whose_disk_bus_the_medium_cannot_drive_is_refused_by_name() {
        let iso = medium_file("bus", None);
        let cell = declared_installer(&iso).unwrap();
        // The premise, stated: this is the medium gunnar actually ships.
        assert_eq!(
            cell.target
                .buses
                .iter()
                .map(|b| b.as_str())
                .collect::<Vec<_>>(),
            ["ide-cdrom", "virtio-blk"],
            "gunnar's recipe stopped declaring the buses this test is about"
        );
        let shape = Shape::from_preset("gce-virtio", None, None).unwrap();
        let why = medium_cannot_install_here(&iso, &shape, &cell).expect("refused, not booted");
        assert!(why.contains("THE BUS"), "{why}");
        assert!(
            why.contains("virtio-scsi"),
            "names the bus the machine wants: {why}"
        );
        assert!(
            why.contains("ide-cdrom, virtio-blk"),
            "names the buses the medium has: {why}"
        );
        assert!(
            why.contains(BOOT_ANYWAY),
            "says how to see the guest refuse: {why}"
        );
    }

    /// **ovh-public-cloud: zero serial bytes, and why.** SeaBIOS boots an MBR;
    /// this preset attaches no cdrom; the medium has no MBR. Measured: LBA 0
    /// `[510..512] = 00 00` on the real 38 680 576 B ISO, and a 420.29 s run
    /// with a `serial.log` of **0 bytes**.
    #[test]
    fn a_seabios_machine_that_attaches_no_cdrom_is_refused_by_name() {
        let iso = medium_file("attach", None);
        let cell = declared_installer(&iso).unwrap();
        let shape = Shape::from_preset("ovh-public-cloud", None, None).unwrap();
        assert!(
            !shape.medium_as_cdrom,
            "the preset's own roster row brings no cdrom"
        );
        assert_eq!(shape.firmware, FirmwareChoice::SeaBios);
        let why = medium_cannot_install_here(&iso, &shape, &cell).expect("refused, not booted");
        assert!(why.contains("THE ATTACHMENT"), "{why}");
        assert!(
            why.contains("00 00"),
            "quotes the bytes it read, not a flag: {why}"
        );
        assert!(
            why.contains("qcow2"),
            "names how this platform DOES take a medium: {why}"
        );
        // It reads the medium, not the preset: the same machine with a hybrid
        // ISO has a boot path and is not refused for the attachment.
        let hybrid = medium_file("hybrid", Some((0x55, 0xAA)));
        let cell2 = declared_installer(&hybrid).unwrap();
        assert_eq!(
            medium_cannot_install_here(&hybrid, &shape, &cell2),
            None,
            "an MBR at LBA 0 is a boot path SeaBIOS can take"
        );
        // And it is the FIRMWARE that decides: gce-virtio brings no cdrom
        // either, and OVMF reads El Torito off any block device — so it is
        // refused for the BUS and never for the attachment.
        let gce = Shape::from_preset("gce-virtio", None, None).unwrap();
        let gce_why = medium_cannot_install_here(&iso, &gce, &cell).unwrap();
        assert!(
            !gce_why.contains("THE ATTACHMENT"),
            "UEFI boots this medium: {gce_why}"
        );
    }

    /// The negative control that keeps the two refusals from being a way to
    /// make anything go quiet: every preset the deploy pipeline gates on must
    /// still boot.
    #[test]
    fn the_presets_the_deploy_gate_runs_are_not_refused() {
        let iso = medium_file("gate", None);
        let cell = declared_installer(&iso).unwrap();
        for name in ["qemu-local", "qemu-local-bios", "upcloud", "hetzner-cloud"] {
            let shape = Shape::from_preset(name, None, None).unwrap();
            assert_eq!(
                medium_cannot_install_here(&iso, &shape, &cell),
                None,
                "{name} must still run"
            );
        }
        // The flag door names no preset and is never refused by this check.
        let flags = Shape::flags(FirmwareChoice::Uefi, true);
        assert_eq!(medium_cannot_install_here(&iso, &flags, &cell), None);
    }

    /// **The two media gunnar ships answer two different machines** — the
    /// declaration side of the 2026-08-30 measurement, asserted from the recipe and
    /// not from a boot, so it is red the moment either row loses its reason.
    ///
    /// The RED it replaces was measured on the shared medium: `--target
    /// gce-virtio` **420.10 s → refused, THE BUS**, and `--target
    /// ovh-public-cloud` **420.29 s → refused, THE ATTACHMENT**. Both refusals
    /// are still exactly right about THAT medium — the appliance's kernel has
    /// no SCSI and never should — so neither was widened. What changed is that
    /// gunnar now declares a medium each machine can take:
    ///
    /// * every medium is `medium_boot = "hybrid"`, so it carries a `55 AA` boot
    ///   sector and SeaBIOS can start it off an imported disk image;
    /// * `gunnar-installer-gce` declares `buses ∋ virtio-scsi` and a donor whose
    ///   preset does not veto `CONFIG_SCSI`.
    ///
    /// Both media were then booted: ovh-public-cloud 3/3 legs on the shared one
    /// (7 s), gce-virtio 3/3 on the scsi one (15 s).
    #[test]
    fn the_two_media_gunnar_ships_answer_two_different_machines() {
        use nornir_build_thing::installer::MediumBoot;
        use nornir_build_thing::target::{Bus, Firmware};
        let root = crate::workspace_root();
        let set = BuildVariantSet::discover(&root).unwrap().unwrap();
        let by = |n: &str| {
            set.installers
                .iter()
                .find(|i| i.name == n)
                .unwrap_or_else(|| panic!("{n}"))
        };

        // 1. EVERY medium is bootable off a disk it was imported as. A medium
        //    that quietly went back to `el-torito` would be the 0-byte-serial
        //    run again.
        //
        //    Asserted as the RULE and not as a constant. This said
        //    `== MediumBoot::Hybrid` until 2026-09-05, which was the same claim
        //    only while `hybrid` was the ONLY way to be disk-bootable. It is
        //    not any more: edda grew `MediumBoot::Gpt` (a GPT ESP reachable in
        //    the same reserved system area, no boot code at LBA 0) for exactly
        //    the machine `hybrid` cannot serve. Naming one spelling made the
        //    guard fire on `gunnar-installer-azure` — a medium that IS disk-
        //    bootable, by the other word.
        //
        //    The two words are not interchangeable and the firmware decides
        //    which is legal, so that is what is checked:
        //
        //    * `hybrid` puts real boot code at LBA 0 and edda's composer
        //      REFUSES it unless `target.firmware` offers a legacy stage — "a
        //      `55 AA` that chainloads nothing is worse than none, because
        //      firmware stops looking once it finds one";
        //    * `gpt` is the UEFI-only answer (Azure Gen2: no legacy devices at
        //      all, so there is no firmware to run a boot sector).
        //
        //    Checking the pair catches both halves: a `gpt` medium on a machine
        //    that needs a BIOS sector, and a `hybrid` one on a machine with no
        //    BIOS to read it — the second of which edda would refuse at compose
        //    time, i.e. after the bytes were written.
        for i in &set.installers {
            // The EFFECTIVE firmware, not the raw field: a row declared by
            // `preset` (azure-gen2, aws-nitro) carries the field at its default
            // `both`, and the machine it names is what the composer consumes.
            let uefi_only = i.target.effective().firmware == Firmware::Uefi;
            assert_ne!(
                i.medium_boot,
                MediumBoot::ElTorito,
                "'{}' must stay bootable off a disk it was imported as — `el-torito` alone \
                 parks on a zeroed LBA 0 (measured: 420 s of no serial at all)",
                i.name
            );
            if uefi_only {
                assert_eq!(
                    i.medium_boot,
                    MediumBoot::Gpt,
                    "'{}' declares firmware = uefi, which has nothing that reads a `55 AA` \
                     boot sector; `gpt` is the word for that machine and edda's composer \
                     refuses `hybrid` there by name",
                    i.name
                );
            } else {
                assert_eq!(
                    i.medium_boot,
                    MediumBoot::Hybrid,
                    "'{}' can be started by a legacy BIOS, so its medium carries the boot \
                     sector that starts it",
                    i.name
                );
            }
        }

        // 2. The appliance media are still the default machine — no SCSI, no
        //    bytes for a bus their customers do not have.
        let open = by("gunnar-installer-open");
        assert!(open.target.is_default());
        assert!(!open.target.buses.contains(&Bus::VirtioScsi));

        // 3. The GCE medium is the one that answers a virtio-scsi disk, and it
        //    takes a DIFFERENT donor — the shared one's `nuked` preset vetoes
        //    CONFIG_SCSI by name, so widening the shared row would not build.
        let gce = by("gunnar-installer-gce");
        assert!(gce.target.buses.contains(&Bus::VirtioScsi));
        assert_ne!(
            gce.kernel_donor, open.kernel_donor,
            "a different bus is a different kernel"
        );
        assert_ne!(
            gce.volume_id, open.volume_id,
            "a medium must be able to say which cell it is"
        );

        // 4. And the preflight agrees, both ways round, on a medium with a boot
        //    sector: the shared row is still refused for THE BUS on gce-virtio
        //    (which is the truth about its kernel), the GCE row is not, and
        //    neither is refused for THE ATTACHMENT any more.
        let iso = medium_file("as-hybrid", Some((0x55, 0xAA)));
        let gce_shape = Shape::from_preset("gce-virtio", None, None).unwrap();
        let ovh_shape = Shape::from_preset("ovh-public-cloud", None, None).unwrap();
        let why = medium_cannot_install_here(&iso, &gce_shape, open)
            .expect("the shared medium still cannot");
        assert!(
            why.contains("THE BUS") && !why.contains("THE ATTACHMENT"),
            "{why}"
        );
        assert_eq!(
            medium_cannot_install_here(&iso, &gce_shape, gce),
            None,
            "the scsi medium can"
        );
        assert_eq!(
            medium_cannot_install_here(&iso, &ovh_shape, open),
            None,
            "a boot sector is a boot path"
        );
        assert_eq!(medium_cannot_install_here(&iso, &ovh_shape, gce), None);
    }

    /// **A timeout is not a failure and above all not a pass** — in the row and
    /// in the exit code. Before this, `[FAIL]` after 420 s of nothing was the
    /// only thing either of them said.
    #[test]
    fn the_four_outcomes_are_told_apart_in_the_row_and_in_the_exit_code() {
        let leg = |v: Verdict| Leg {
            name: "B1 install",
            verdict: v,
            ms: 1,
            detail: String::new(),
        };
        assert!(leg(Verdict::Pass).line().contains("[PASS]"));
        assert!(leg(Verdict::Failed).line().contains("[FAIL]"));
        assert!(leg(Verdict::Refused).line().contains("[REFUSED]"));
        assert!(leg(Verdict::TimedOut).line().contains("[TIMEOUT]"));

        assert_eq!(
            exit_code(
                &[leg(Verdict::Pass), leg(Verdict::Pass), leg(Verdict::Pass)],
                3
            ),
            0
        );
        assert_eq!(exit_code(&[leg(Verdict::Failed)], 3), EXIT_NOT_PROVEN);
        assert_eq!(exit_code(&[leg(Verdict::Refused)], 3), EXIT_NOT_PROVEN);
        assert_eq!(exit_code(&[leg(Verdict::TimedOut)], 3), EXIT_TIMED_OUT);
        // A ladder that stopped early is not a pass even if every leg that ran
        // passed — the historical rule, kept.
        assert_eq!(exit_code(&[leg(Verdict::Pass)], 3), EXIT_NOT_PROVEN);
        // Every non-zero code reaches a shell as red, and none of them collides
        // with `timeout(1)`'s 124 or the usage-error 2.
        assert!(EXIT_NOT_PROVEN != 0 && EXIT_TIMED_OUT != 0);
        assert!(EXIT_TIMED_OUT != 2 && EXIT_TIMED_OUT != 124 && EXIT_TIMED_OUT != EXIT_NOT_PROVEN);
    }

    /// A later check may downgrade a leg but must never launder one: a leg that
    /// timed out and also has no MBR on its target still TIMED OUT.
    #[test]
    fn a_second_check_cannot_hide_a_timeout_behind_a_failure() {
        let mut l = Leg {
            name: "B1 install",
            verdict: Verdict::TimedOut,
            ms: 1,
            detail: String::new(),
        };
        l.demote(Verdict::Failed);
        assert_eq!(l.verdict, Verdict::TimedOut);
        let mut ok = Leg {
            name: "B1 install",
            verdict: Verdict::Pass,
            ms: 1,
            detail: String::new(),
        };
        ok.demote(Verdict::Failed);
        assert_eq!(ok.verdict, Verdict::Failed);
    }

    /// The summary line says WHICH kind of "not proven" happened — a report
    /// that folded a hang into the same three words as a measured failure is
    /// what let two doors hang unnoticed for a day.
    #[test]
    fn the_summary_names_a_timeout_apart_from_a_failure() {
        let door = Door {
            iso: PathBuf::from("/x/gunnar-installer-open.iso"),
            shape: Shape::flags(FirmwareChoice::Uefi, true),
            work: PathBuf::from("/x/work"),
            expect_grpc: false,
            grpc_only: None,
            twin: Twin::Iso,
            disks: Vec::new(),
            // `None` = the historical B2 this test was written against
            // (probe the ports, power-cut, done). Added 2026-09-05: the
            // `--drive-cmd` field landed without this initializer, so the
            // whole `--lib` test target stopped COMPILING — every unit test
            // in this crate, not only this one, has been unrunnable since,
            // which is the quietest possible way for a guard to stop being
            // able to say red.
            drive_cmd: None,
        };
        let leg = |v: Verdict| Leg {
            name: "B1 install",
            verdict: v,
            ms: 1,
            detail: String::new(),
        };
        assert!(render(&door, &[leg(Verdict::Pass)]).contains("ALL PASS"));
        assert!(render(&door, &[leg(Verdict::TimedOut)]).contains("TIMED OUT"));
        assert!(render(&door, &[leg(Verdict::Refused)]).contains("REFUSED BY THE GUEST"));
        let plain = render(&door, &[leg(Verdict::Failed)]);
        assert!(
            plain.contains("NOT PROVEN") && !plain.contains("TIMED OUT"),
            "{plain}"
        );
    }
}