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

nordisk/Skidbladnir

src/socket.rs

//! **Build Thing SOCKETS** — the bulb→socket deployment/topology foundation
//! (feature `sockets`). Authoritative design:
//! `workspace_nordisk/.nornir/socket-deployment-design.md`; role note:
//! `.nornir/socket-deployment.md`.
//!
//! A Build Thing is a **bulb** (an output artifact of a [`BuildThingType`]); every type
//! has a matching **socket** — the fixture graph it plugs into to light up and be
//! observed. The dwarves ⚒ pane reads the bulb's type and fires the matching socket
//! **automatically** (no per-cell config). Sockets carry a [`Mode`] (`Test` / `Prod`),
//! so this is a deployment system, not merely a testbed.
//!
//! **Skidbladnir owns the sockets** — this module is their home, whole:
//!
//! * the [`BuildThingType`] taxonomy and [`Mode`];
//! * the **type→socket registry** — [`socket_for`] hands the pane the [`Socket`] to fire
//!   for a bulb type, and [`SocketPlan::for_type`] is the fixture composition behind it
//!   (the exact table from the design);
//! * the **fixture registry** — [`fixture_for_kind`], one concrete [`Fixture`] per
//!   [`FixtureKind`], which is what keeps a socket's PLAN and its lit DEPLOYMENT in
//!   lockstep;
//! * the four seam traits — [`Socket`], [`Fixture`], [`NetworkBackend`], [`Probe`];
//! * the **deployment lifecycle** — [`Socket::light`] → [`SocketDeployment`] →
//!   [`confirm`](SocketDeployment::confirm) → [`teardown`](SocketDeployment::teardown);
//! * the [`WgKeyPanel`] — the WireGuard keys-panel data model the `Mode::Test`
//!   `tunnr-appliance` socket surfaces in the UI (own pubkey, peer list, add-a-peer with
//!   real base64/32-byte validation, handshake state).
//!
//! # Two honesty rules this module is built around
//!
//! **1. Nothing is green until it is observed.** A fixture never lights `Serving`; it
//! lights `Ready` and is promoted only by [`SocketDeployment::confirm`] reading real
//! evidence off a booted node's console. A fixture nothing provisions reports
//! [`FixtureHealth::NotImplemented`] with the reason, and drags
//! [`all_ok`](SocketDeployment::all_ok) to `false` — the pane shows the gap rather than a
//! dead button.
//!
//! **2. `Mode::Prod` never serves a test fixture.** [`FixtureRuntime::is_test_grade`]
//! marks the fakes (today: QEMU user-mode/slirp, whose built-in DHCP + DNS are a test
//! fixture, not a network). A `Prod` socket turns any such fixture into
//! [`FixtureHealth::Down`] with the reason — at light-up AND on the evidence path, so
//! even a genuine slirp lease cannot make a Prod fixture green. This is fail-closed by
//! default: [`NetworkBackend::is_test_grade`] returns `true` unless a backend explicitly
//! claims production fitness.

use std::collections::BTreeSet;

/// The **mode** a socket lights in. The SAME socket, two postures — which is why this is
/// a deployment/topology system, not just a testbed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
    /// Permissive + inspectable: open test net, the WireGuard keys panel, a pre-filled
    /// NoCloud seed, one-click teardown.
    Test,
    /// Hardened: customer-owned keys, closed net, real secrets.
    Prod,
}

impl Mode {
    pub fn is_test(self) -> bool {
        matches!(self, Mode::Test)
    }
    pub fn as_str(self) -> &'static str {
        match self {
            Mode::Test => "test",
            Mode::Prod => "prod",
        }
    }
}

/// The **type of a bulb** (a Build Thing's output artifact type) — the key the pane uses
/// to fire the matching socket automatically.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuildThingType {
    /// A plain executable — a process host + pty → a console.
    Exe,
    /// A live terminal program.
    Console,
    /// A KVM/VM appliance boot (QEMU+OVMF) — graphics/serial console. *(exists today)*
    Kvm,
    /// A bootable installer/live ISO (QEMU+OVMF + blank disk). *(exists today)*
    BootableIso,
    /// A tunnr OS-less network appliance — needs a test net + DHCP + DNS + a WireGuard
    /// peer, and (in `Test`) a WireGuard keys panel.
    TunnrAppliance,
    /// An OCI container — a `ContainerBackend` (youki/microVM/podman) → logs/console.
    Container,
    /// A Kubernetes workload — a control-plane + workers + CNI.
    K8sWorkload,
}

impl BuildThingType {
    /// Parse the `build-variants.toml` / launch-cell `kind` string into a bulb type.
    /// `None` for an unknown kind (the caller decides the default socket).
    pub fn from_kind(kind: &str) -> Option<BuildThingType> {
        Some(match kind {
            "exe" => BuildThingType::Exe,
            "console" => BuildThingType::Console,
            "vm" | "kvm" => BuildThingType::Kvm,
            "iso" | "bootable-iso" | "installer" => BuildThingType::BootableIso,
            "tunnr-appliance" | "appliance" => BuildThingType::TunnrAppliance,
            "container" => BuildThingType::Container,
            "k8s-workload" | "k8s" => BuildThingType::K8sWorkload,
            _ => return None,
        })
    }

    pub fn as_str(self) -> &'static str {
        match self {
            BuildThingType::Exe => "exe",
            BuildThingType::Console => "console",
            BuildThingType::Kvm => "kvm",
            BuildThingType::BootableIso => "bootable-iso",
            BuildThingType::TunnrAppliance => "tunnr-appliance",
            BuildThingType::Container => "container",
            BuildThingType::K8sWorkload => "k8s-workload",
        }
    }
}

/// One infrastructure dependency inside a socket — the fixture catalog from the design.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FixtureKind {
    /// A process host (runs an `exe` bulb) + pty.
    ProcessHost,
    /// A pty/tty for a live terminal.
    Pty,
    /// The QEMU/OVMF/KVM boot of the bulb itself (the node under test).
    KvmBoot,
    /// The test network the appliance's fixtures share.
    TestNet,
    /// A DHCP server fixture (a tiny tunnr payload).
    Dhcp,
    /// A DNS resolver fixture (a tiny tunnr payload).
    Dns,
    /// A NAT/gateway fixture.
    Nat,
    /// A WireGuard peer fixture (the `wg-appliance` tunnr payload).
    WireGuardPeer,
    /// A NoCloud/metadata seed fixture.
    NoCloudSeed,
    /// The container runtime (`ContainerBackend`) a `container` bulb runs in.
    ContainerRuntime,
    /// A MinIO/S3 object store (feeds FAT sidecar images offline).
    MinioS3,
    /// An OCI registry fixture.
    OciRegistry,
    /// A Kubernetes control-plane node.
    K8sControlPlane,
    /// A Kubernetes worker node.
    K8sWorker,
    /// A Kubernetes CNI fixture.
    Cni,
}

impl FixtureKind {
    pub fn as_str(self) -> &'static str {
        match self {
            FixtureKind::ProcessHost => "process-host",
            FixtureKind::Pty => "pty",
            FixtureKind::KvmBoot => "kvm-boot",
            FixtureKind::TestNet => "test-net",
            FixtureKind::Dhcp => "dhcp",
            FixtureKind::Dns => "dns",
            FixtureKind::Nat => "nat",
            FixtureKind::WireGuardPeer => "wireguard-peer",
            FixtureKind::NoCloudSeed => "nocloud-seed",
            FixtureKind::ContainerRuntime => "container-runtime",
            FixtureKind::MinioS3 => "minio-s3",
            FixtureKind::OciRegistry => "oci-registry",
            FixtureKind::K8sControlPlane => "k8s-control-plane",
            FixtureKind::K8sWorker => "k8s-worker",
            FixtureKind::Cni => "cni",
        }
    }
}

/// The **composed fixture graph** a bulb type lights in a mode — the registry's output
/// (the PLAN the pane renders + a future `Socket::light` provisions). Pure data, so the
/// auto-fire table is deterministic + testable without booting anything.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SocketPlan {
    /// The bulb type this plan lights.
    pub bulb: BuildThingType,
    /// The mode (`Test` opens the net + shows the keys panel).
    pub mode: Mode,
    /// The socket's stable name (for the pane header / logs).
    pub socket_name: &'static str,
    /// The fixtures to provision, in dependency order (the bulb's own `KvmBoot`/host last
    /// so its net + services are up first).
    pub fixtures: Vec<FixtureKind>,
    /// How many node consoles the pane renders (a grid, one per node).
    pub consoles: usize,
    /// Whether the pane shows the WireGuard keys panel (Test-mode tunnr-appliance).
    pub shows_wg_panel: bool,
}

impl SocketPlan {
    /// **The type→socket registry.** The auto-fired fixture composition per bulb type —
    /// the exact table from the design. Adding a socket = extend this match (+ impl the
    /// [`Socket`] trait); zero pane changes (same pattern as `ContainerBackend`).
    pub fn for_type(bulb: BuildThingType, mode: Mode) -> SocketPlan {
        use BuildThingType::*;
        use FixtureKind::*;
        let (socket_name, fixtures, consoles): (&'static str, Vec<FixtureKind>, usize) = match bulb
        {
            Exe => ("exe-socket", vec![ProcessHost, Pty], 1),
            Console => ("console-socket", vec![Pty], 1),
            Kvm => ("kvm-socket", vec![KvmBoot], 1),
            BootableIso => ("iso-socket", vec![KvmBoot], 1),
            Container => ("container-socket", vec![ContainerRuntime], 1),
            TunnrAppliance => (
                "tunnr-test-socket",
                // TestNet + the net services first, then the appliance boot itself.
                vec![TestNet, Dhcp, Dns, WireGuardPeer, NoCloudSeed, KvmBoot],
                1,
            ),
            K8sWorkload => (
                "k8s-socket",
                vec![
                    TestNet,
                    K8sControlPlane,
                    K8sWorker,
                    K8sWorker,
                    Cni,
                    OciRegistry,
                ],
                4, // control-plane + 2 workers + the workload node
            ),
        };
        let shows_wg_panel = bulb == TunnrAppliance && mode.is_test();
        SocketPlan {
            bulb,
            mode,
            socket_name,
            fixtures,
            consoles,
            shows_wg_panel,
        }
    }

    /// Whether this plan provisions any WireGuard fixture (→ a keys panel is meaningful).
    pub fn has_wireguard(&self) -> bool {
        self.fixtures.contains(&FixtureKind::WireGuardPeer)
    }

    /// The **test network** this socket runs its fixtures on, if it composes a
    /// [`TestNet`] (e.g. the tunnr-appliance socket). `None` for a bare single-boot
    /// socket (kvm/iso) that needs no shared net. Mode-aware: `Test` opens the net +
    /// hands out DHCP; `Prod` closes it. This is the seam the pane renders the topology
    /// + the WG keys panel over.
    pub fn net(&self) -> Option<NetSpec> {
        self.fixtures
            .contains(&FixtureKind::TestNet)
            .then(|| TestNet::default().spec(self.mode))
    }

    /// The socket's **topology** — one [`FixturePlan`] per composed fixture, with the
    /// address each net-service fixture serves on this socket's [`net`](Self::net). The
    /// pane renders this as the node/fixture graph. Pure descriptor (what WOULD be
    /// provisioned) — live provisioning is a later increment.
    pub fn topology(&self) -> Vec<FixturePlan> {
        let net = self.net();
        self.fixtures
            .iter()
            .map(|&kind| FixturePlan::for_kind(kind, net.as_ref()))
            .collect()
    }
}

/// A single fixture's **plan** inside a socket's topology — its kind, a human detail, and
/// the address it serves on the socket's net (for a net service), or `None` (a boot/host
/// fixture serves no address).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FixturePlan {
    /// Which fixture.
    pub kind: FixtureKind,
    /// A human one-liner for the topology view.
    pub detail: String,
    /// The `addr[:port]` this fixture serves on the net, if any.
    pub endpoint: Option<String>,
}

impl FixturePlan {
    /// Describe `kind` on `net` (the socket's shared net, if it has one). Net-service
    /// fixtures (dhcp/dns/wireguard/nat) get their endpoint on the gateway; boot/host
    /// fixtures serve no address.
    pub fn for_kind(kind: FixtureKind, net: Option<&NetSpec>) -> FixturePlan {
        let gw = net.map(|n| n.gateway.as_str());
        let (detail, endpoint) = match kind {
            FixtureKind::Dhcp => (
                net.and_then(|n| n.dhcp_range.as_ref())
                    .map(|(a, b)| format!("DHCP pool {a}–{b}"))
                    .unwrap_or_else(|| "DHCP (disabled — static/seeded)".to_string()),
                gw.map(|g| format!("{g}:67")),
            ),
            FixtureKind::Dns => ("DNS resolver".to_string(), gw.map(|g| format!("{g}:53"))),
            FixtureKind::Nat => ("NAT/gateway".to_string(), gw.map(|g| g.to_string())),
            FixtureKind::WireGuardPeer => (
                "WireGuard peer".to_string(),
                gw.map(|g| format!("{g}:51820")),
            ),
            FixtureKind::NoCloudSeed => ("NoCloud metadata seed".to_string(), None),
            FixtureKind::TestNet => (
                net.map(|n| format!("test net {} (open={})", n.subnet_cidr, n.open))
                    .unwrap_or_else(|| "test net".to_string()),
                None,
            ),
            FixtureKind::KvmBoot => ("the bulb's QEMU/OVMF boot".to_string(), None),
            FixtureKind::ProcessHost => ("process host".to_string(), None),
            FixtureKind::Pty => ("pty/tty".to_string(), None),
            FixtureKind::ContainerRuntime => ("container runtime".to_string(), None),
            FixtureKind::MinioS3 => (
                "MinIO/S3 object store".to_string(),
                gw.map(|g| format!("{g}:9000")),
            ),
            FixtureKind::OciRegistry => {
                ("OCI registry".to_string(), gw.map(|g| format!("{g}:5000")))
            }
            FixtureKind::K8sControlPlane => (
                "k8s control-plane".to_string(),
                gw.map(|g| format!("{g}:6443")),
            ),
            FixtureKind::K8sWorker => ("k8s worker".to_string(), None),
            FixtureKind::Cni => ("k8s CNI".to_string(), None),
        };
        FixturePlan {
            kind,
            detail,
            endpoint,
        }
    }
}

/// A **network specification** the fixtures inside a socket share — the test/deploy net.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NetSpec {
    /// The subnet CIDR, e.g. `10.9.0.0/24`.
    pub subnet_cidr: String,
    /// The gateway address on the subnet.
    pub gateway: String,
    /// The DHCP pool `(first, last)` handed out in `Test` mode; `None` when DHCP is off
    /// (a hardened `Prod` net uses static/seeded addressing only).
    pub dhcp_range: Option<(String, String)>,
    /// The DNS resolver addresses.
    pub dns: Vec<String>,
    /// Whether the net is OPEN (no NAT/firewall lockdown — ping/curl straight through the
    /// tunnel). `Test` = open + inspectable; `Prod` = closed.
    pub open: bool,
}

impl NetSpec {
    /// The QEMU **user-mode (slirp)** `-netdev` VALUE that REALIZES this net — the honest
    /// runtime for the DHCP + DNS fixtures. slirp has a **built-in DHCP server** (leases
    /// from `dhcpstart`) and a **built-in DNS forwarder** (`dns=<gateway>`), so on the
    /// TestNet these two fixtures need no separate server process: they ARE slirp. An OPEN
    /// (`Test`) net is `restrict=off` (ping/curl straight through); a hardened net is
    /// `restrict=on`. `dhcpstart`/`dns` are emitted only when the spec provides them, so a
    /// hardened `Prod` net (no pool) yields a static/seeded-only netdev — the same subnet,
    /// no DHCP. The consumer prepends `-netdev ` and pairs it with a `-device virtio-net`.
    pub fn qemu_user_netdev(&self, id: &str) -> String {
        let restrict = if self.open { "off" } else { "on" };
        let mut s = format!(
            "user,id={id},net={},host={},restrict={restrict}",
            self.subnet_cidr, self.gateway
        );
        if let Some((first, _last)) = &self.dhcp_range {
            s.push_str(&format!(",dhcpstart={first}"));
        }
        if let Some(dns) = self.dns.first() {
            s.push_str(&format!(",dns={dns}"));
        }
        s
    }

    /// Read what `console` proves about the **DHCP** fixture on this net.
    ///
    /// Failure first, then a **bound** line carrying an address **inside this net's pool**.
    /// Both halves matter: `udhcpc: broadcasting select for 10.9.0.157, server 10.9.0.1` is
    /// a real captured line that carries a real pool address and is only a DHCPREQUEST —
    /// requiring a bound marker rejects it, and requiring the pool rejects a static
    /// fallback address that happens to sit on the subnet.
    pub fn dhcp_evidence(&self, console: &str) -> Evidence {
        if let Some(line) = first_line_matching(console, DHCP_FAILURE_MARKERS) {
            return Evidence::Failed(line);
        }
        if self.leased_address(console).is_some() {
            return Evidence::Served;
        }
        // Say WHY it is unsettled, distinguishing "it tried and we cannot tell" from
        // "nothing about DHCP happened at all".
        match first_line_matching(console, DHCP_ATTEMPT_MARKERS) {
            Some(line) => Evidence::Unknown(format!(
                "a DHCP attempt but no bound lease in {}: {line}",
                self.pool_description()
            )),
            None => Evidence::Unknown("no DHCP activity on the console".to_string()),
        }
    }

    /// A human description of the address pool (or its absence).
    fn pool_description(&self) -> String {
        match &self.dhcp_range {
            Some((a, b)) => format!("the pool {a}–{b}"),
            None => "the subnet (no pool configured)".to_string(),
        }
    }

    /// Whether `addr` falls inside this net's DHCP pool and is not the gateway. With no
    /// pool configured, nothing can be *leased from the pool*, so this is always `false`.
    pub fn in_dhcp_pool(&self, addr: std::net::Ipv4Addr) -> bool {
        let Some((first, last)) = &self.dhcp_range else {
            return false;
        };
        let (Ok(lo), Ok(hi)) = (
            first.parse::<std::net::Ipv4Addr>(),
            last.parse::<std::net::Ipv4Addr>(),
        ) else {
            return false;
        };
        let (a, lo, hi) = (u32::from(addr), u32::from(lo), u32::from(hi));
        let gw = self
            .gateway
            .parse::<std::net::Ipv4Addr>()
            .ok()
            .map(u32::from);
        a >= lo && a <= hi && Some(a) != gw
    }

    /// The address a client is shown to have **bound** from this net's pool, if any: an
    /// address inside the pool appearing on a line that also carries a bound marker
    /// (`lease of …` / `bound to …` / `DHCPACK`). Attempt lines carry addresses too, so the
    /// marker is what separates a lease from a request.
    pub fn leased_address(&self, console: &str) -> Option<std::net::Ipv4Addr> {
        console.lines().find_map(|line| {
            let low = line.to_ascii_lowercase();
            if !DHCP_BOUND_MARKERS.iter().any(|m| low.contains(m)) {
                return None;
            }
            addresses_in(line)
                .into_iter()
                .find(|a| self.in_dhcp_pool(*a))
        })
    }

    /// Whether `console` shows a client that leased an address in this net's DHCP pool —
    /// the runtime proof the DHCP fixture actually SERVED.
    ///
    /// This now means exactly what it always said: a **bound** lease of an address **in the
    /// pool**. It used to mean "any address on the subnet prefix that is not the gateway",
    /// which let both an attempt line and a static fallback address read as a lease.
    pub fn saw_dhcp_lease(&self, console: &str) -> bool {
        self.leased_address(console).is_some()
    }
}

/// What a booted node's console proves about one fixture. The point of the type is that
/// **`Unknown` is not `Served`** — a fixture nobody saw work is not working, it is just
/// unobserved, and it stays `Ready` rather than going green.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Evidence {
    /// The console shows the fixture actually served (a bound lease in the pool, a
    /// COMPLETED handshake, a resolution).
    Served,
    /// The console shows the fixture **failed**, carrying the line that says so.
    Failed(String),
    /// The console does not settle it, and says **why** it does not — "only a handshake
    /// attempt was seen", "no bound-lease line", "the DHCP lease it would inherit failed".
    /// Never defaults either way.
    Unknown(String),
}

impl Evidence {
    /// The human reason this is `Failed`/`Unknown`.
    pub fn why(&self) -> Option<&str> {
        match self {
            Evidence::Failed(w) | Evidence::Unknown(w) => Some(w),
            Evidence::Served => None,
        }
    }
    /// Stable label for a report.
    pub fn as_str(&self) -> &'static str {
        match self {
            Evidence::Served => "served",
            Evidence::Failed(_) => "failed",
            Evidence::Unknown(_) => "unknown",
        }
    }
}

/// Every dotted-quad IPv4 address appearing in `line`. Splitting on anything that is not a
/// digit or a dot means punctuation (`10.9.0.157,`) does not defeat the parse, and
/// `Ipv4Addr`'s strict four-octet `FromStr` means a version (`1.37.0`) or a lease time
/// (`3600`) is not mistaken for an address.
fn addresses_in(line: &str) -> Vec<std::net::Ipv4Addr> {
    line.split(|c: char| !(c.is_ascii_digit() || c == '.'))
        .filter_map(|t| t.parse::<std::net::Ipv4Addr>().ok())
        .collect()
}

/// The first line of `console` containing any of `markers` (case-insensitive), trimmed.
fn first_line_matching(console: &str, markers: &[&str]) -> Option<String> {
    console
        .lines()
        .find(|l| {
            let low = l.to_ascii_lowercase();
            markers.iter().any(|m| low.contains(m))
        })
        .map(|l| l.trim().to_string())
}

// ── The evidence vocabularies ────────────────────────────────────────────────────────
//
// These are transcribed from the REAL tools, not from memory of the happy path:
//
// * **busybox udhcpc 1.37.0** — `strings /usr/bin/busybox` on this host, giving the exact
//   format strings the client can print, plus live captures against `dnsmasq` in an
//   unprivileged network namespace (see the `transcripts` test module).
// * **boringtun 0.7.1** — `~/.cargo/registry/.../boringtun-0.7.1/src/noise/{timers,errors}.rs`
//   (the `tracing::` messages and `WireGuardError`).
// * **ISC dhclient** and **wg(8)** — their documented output.
//
// The rule that governs all of them: **a success marker must never be able to appear inside
// a failure or attempt line.** Three real cases where the naive check got that wrong:
//   `udhcpc: broadcasting select for 10.9.0.157, server 10.9.0.1`  (an attempt, carries a
//        pool address, read as a lease)
//   `Sending handshake_initiation`                                 (an attempt, read as a
//        completed handshake)
//   `HANDSHAKE(REKEY_TIMEOUT)`                                     (boringtun's retry WARN,
//        read as a completed handshake)
// `no_success_marker_can_hide_inside_a_failure_or_attempt_marker` enforces it structurally.

/// Lines that mean the DHCP client lost, released or was refused an address.
/// busybox: `no lease, failing` · `no lease, forking to background` ·
/// `received DHCP NAK (%u)` · `lease lost, entering init state` · `entering released state`
/// · `broadcasting decline` (the client itself rejected the offer, e.g. an ARP conflict).
/// ISC dhclient: `No DHCPOFFERS received.` · `Unable to obtain a lease on first try.`
const DHCP_FAILURE_MARKERS: &[&str] = &[
    "no lease",
    "dhcp nak",
    "dhcpnak",
    "lease lost",
    "leasefail",
    "entering released state",
    "broadcasting decline",
    "no dhcpoffers",
    "unable to obtain a lease",
    "dhcp timed out",
    "dhcp timeout",
    "dhcp failed",
    "lease failed",
];

/// Lines that mean an address was actually **BOUND**. busybox prints
/// `lease of %s obtained from %s, lease time %u`; ISC dhclient prints
/// `bound to 10.9.0.157 -- renewal in 1800 seconds.` and `DHCPACK from 10.9.0.1`.
/// A bound marker is required *in addition to* an in-pool address, because the attempt
/// lines (`broadcasting select for %s, server %s`, `sending renew to server %s`) carry
/// addresses too and prove nothing.
const DHCP_BOUND_MARKERS: &[&str] = &["lease of", "bound to", "dhcpack"];

/// Lines that are DHCP **attempts** — real activity, zero proof. Used only to explain why
/// the evidence is `Unknown`, never to promote anything.
/// busybox: `broadcasting discover` · `broadcasting select for %s, server %s` ·
/// `broadcasting renew` · `sending renew to server %s`. ISC: `DHCPDISCOVER on eth0`.
const DHCP_ATTEMPT_MARKERS: &[&str] = &[
    "broadcasting discover",
    "broadcasting select",
    "broadcasting renew",
    "sending renew",
    "dhcpdiscover",
    "dhcprequest",
];

/// Lines that mean the resolver did not resolve.
const DNS_FAILURE_MARKERS: &[&str] = &[
    "temporary failure in name resolution",
    "servfail",
    "could not resolve",
    "name resolution failed",
    "dns timed out",
    "dns timeout",
    "no nameserver",
];

/// Lines that prove the resolver DID resolve.
const DNS_SUCCESS_MARKERS: &[&str] = &["dns query ok", "resolved ", "nameserver responded"];

/// Lines that mean the WireGuard tunnel did **not** come up, or has died.
/// boringtun 0.7.1 `timers.rs`: `HANDSHAKE(REKEY_TIMEOUT)` (the initiation is being
/// retried — note it contains the word "handshake"), `SESSION_EXPIRED(REJECT_AFTER_TIME)`,
/// `CONNECTION_EXPIRED(REJECT_AFTER_TIME * 3)`. boringtun `errors.rs` `WireGuardError`:
/// `NoCurrentSession`, `WrongKey`, `InvalidMac`, `InvalidAeadTag`, `ConnectionExpired`.
/// Kernel wireguard / wg-quick: `Handshake did not complete after 5 seconds, retrying`.
const WG_FAILURE_MARKERS: &[&str] = &[
    "handshake did not complete",
    "handshake(rekey_timeout)",
    "session_expired",
    "connection_expired",
    "connectionexpired",
    "no current session",
    "nocurrentsession",
    "handshake failed",
    "handshake timed out",
    "handshake timeout",
    "no handshake",
    "wrong key",
    "wrongkey",
    "invalid mac",
    "invalidmac",
    "invalid aead tag",
    "invalidaeadtag",
    "wireguard: failed",
    "wg: failed",
    "tunnel down",
];

/// Lines that prove a handshake **COMPLETED** — the only thing that makes the peer serving.
/// boringtun logs `Received handshake_response` on the initiator once the session is up,
/// and `Sending handshake_response` on the responder once it has accepted a valid
/// initiation. `wg show` prints `latest handshake:` only when one actually exists.
const WG_COMPLETION_MARKERS: &[&str] = &[
    "received handshake_response",
    "sending handshake_response",
    "handshake completed",
    "latest handshake:",
];

/// Lines that are WireGuard **attempts** — real activity, zero proof. boringtun's
/// `Sending handshake_initiation` / `Received handshake_initiation` are the whole reason
/// the old bare `contains("handshake")` check was wrong: an initiation that is never
/// answered is a tunnel that never came up.
const WG_ATTEMPT_MARKERS: &[&str] = &[
    "sending handshake_initiation",
    "received handshake_initiation",
    "handshake initiation",
];

/// Read what `console` proves about the **DNS** fixture. `slirp_co_served` says the
/// resolver IS the same slirp stack the DHCP fixture rides, in which case a DHCP lease is
/// genuine evidence for it too; a DNS fixture running as its own payload must show its own.
pub fn dns_evidence(console: &str, slirp_co_served: bool, dhcp: &Evidence) -> Evidence {
    if let Some(line) = first_line_matching(console, DNS_FAILURE_MARKERS) {
        return Evidence::Failed(line);
    }
    if first_line_matching(console, DNS_SUCCESS_MARKERS).is_some() {
        return Evidence::Served;
    }
    // ── The inherited-evidence rule, directional ────────────────────────────────────
    // The DNS fixture may lean on the DHCP fixture's evidence ONLY when it is literally
    // the same slirp stack, and then it may never be MORE confident than what it inherits.
    // Note the asymmetry: a DHCP lease proves the stack is answering, but a DHCP *failure*
    // does not prove the resolver is broken — it makes the resolver UNKNOWN, not failed.
    // Inheriting a failure as a failure would blame DNS for DHCP's problem.
    if !slirp_co_served {
        return Evidence::Unknown(
            "this resolver is its own payload, so it needs its own evidence — it cannot \
             inherit the DHCP fixture's"
                .to_string(),
        );
    }
    match dhcp {
        Evidence::Served => Evidence::Served,
        Evidence::Failed(w) => Evidence::Unknown(format!(
            "the slirp DHCP lease it shares a stack with failed, which leaves the resolver \
             unproven rather than proven broken: {w}"
        )),
        Evidence::Unknown(w) => Evidence::Unknown(format!(
            "nothing resolved, and the shared slirp stack is unproven: {w}"
        )),
    }
}

/// Read what `console` proves about the **WireGuard peer** fixture.
///
/// Failure first, then a **COMPLETED** handshake — never a mere attempt. boringtun's
/// `Sending handshake_initiation` and its `HANDSHAKE(REKEY_TIMEOUT)` retry warning both
/// contain the word "handshake"; an initiation nobody answered is a tunnel that never
/// came up.
pub fn wg_evidence(console: &str) -> Evidence {
    if let Some(line) = first_line_matching(console, WG_FAILURE_MARKERS) {
        return Evidence::Failed(line);
    }
    if first_line_matching(console, WG_COMPLETION_MARKERS).is_some() {
        return Evidence::Served;
    }
    match first_line_matching(console, WG_ATTEMPT_MARKERS) {
        Some(line) => Evidence::Unknown(format!(
            "a handshake was attempted but never observed to complete: {line}"
        )),
        None => Evidence::Unknown("no WireGuard activity on the console".to_string()),
    }
}

/// A **network backend** — how a socket's shared net is realized. The registry pattern:
/// add a backend = impl this trait ([`slirp`/user-net](TestNet) today; qemu-socket-mesh /
/// bridge-tap / router-VM are future impls, per the design).
pub trait NetworkBackend {
    /// The backend's stable name.
    fn name(&self) -> &str;
    /// The [`NetSpec`] this backend provides in `mode`.
    fn spec(&self, mode: Mode) -> NetSpec;
    /// Whether this backend is **only** fit to test against (see
    /// [`FixtureRuntime::is_test_grade`]). A [`Mode::Prod`] socket refuses to serve the
    /// fixtures a test-grade backend realizes, rather than labelling them production.
    /// Default `true` — a new backend must *claim* production-fitness explicitly.
    fn is_test_grade(&self) -> bool {
        true
    }
}

/// The **test network** backend — a user-mode/slirp-style net for the KVM socket. `Test`
/// mode: OPEN + a DHCP pool + a resolver, so the appliance + its fixtures reach each other
/// and the operator pings straight through. `Prod`: the SAME subnet, CLOSED + no DHCP
/// (static/seeded addressing, hardened) — which is why a socket is a deployment system,
/// not just a testbed.
#[derive(Debug, Clone)]
pub struct TestNet {
    /// Subnet CIDR (default `10.9.0.0/24`).
    pub subnet_cidr: String,
    /// Gateway (default `10.9.0.1`).
    pub gateway: String,
}

impl Default for TestNet {
    fn default() -> Self {
        TestNet {
            subnet_cidr: "10.9.0.0/24".to_string(),
            gateway: "10.9.0.1".to_string(),
        }
    }
}

impl NetworkBackend for TestNet {
    fn name(&self) -> &str {
        "test-net"
    }
    fn spec(&self, mode: Mode) -> NetSpec {
        NetSpec {
            subnet_cidr: self.subnet_cidr.clone(),
            gateway: self.gateway.clone(),
            // Test hands out DHCP .100–.200; Prod is static/seeded only.
            dhcp_range: mode
                .is_test()
                .then(|| ("10.9.0.100".to_string(), "10.9.0.200".to_string())),
            dns: vec![self.gateway.clone()],
            open: mode.is_test(),
        }
    }
}

/// The marker every refusal/gap reason carries so the pane can render the remedy
/// separately, and so a test can assert no reason is a dead end.
pub const REMEDY_MARKER: &str = "TO FIX:";

/// Why a `Mode::Prod` socket refuses `kind`, **and what would have to change for it to be
/// production-grade**. A refusal that only says "test-grade" leaves an operator stuck; every
/// one of these names the concrete next step, so the pane renders an instruction rather than
/// a wall.
pub fn prod_refusal_reason(kind: FixtureKind, runtime: &FixtureRuntime) -> String {
    let remedy = match kind {
        // Everything slirp serves has the same single fix: a real network backend.
        FixtureKind::TestNet | FixtureKind::Dns | FixtureKind::Dhcp | FixtureKind::Nat => {
            "implement `NetworkBackend` with `is_test_grade() -> false` — a bridge-tap, \
             router-VM or qemu-socket-mesh backend per the design — and compose it in \
             place of `TestNet`. Nothing else in this layer needs to change."
        }
        FixtureKind::NoCloudSeed => {
            "give `NoCloudSeedFixture` a real credential source; Skidbladnir already owns \
             `pki` and `openbao`, so wire one of those in rather than seeding an \
             auth-less image."
        }
        _ => {
            "replace this fixture's runtime with one that is not test-grade, or mark it \
             `NotApplicable` in Prod if it should serve nothing there."
        }
    };
    format!(
        "refused in Mode::Prod: {} would be served by the test-grade runtime '{}', and a \
         Prod socket does not serve test fixtures. {REMEDY_MARKER} {remedy}",
        kind.as_str(),
        runtime.as_str()
    )
}

/// A **socket** — the fixture graph for a bulb type, and its whole lifecycle. The design's
/// seam, complete: which bulb fits ([`accepts`](Socket::accepts)), what it composes
/// ([`plan`](Socket::plan) / [`fixtures`](Socket::fixtures)), lighting it
/// ([`light`](Socket::light)), the consoles the pane grids ([`consoles`](Socket::consoles)),
/// what asserts against it ([`probes`](Socket::probes)), and tearing it down
/// ([`SocketDeployment::teardown`]).
///
/// Only `accepts` + `plan` are required: [`fixtures`](Socket::fixtures) maps the plan
/// through the [`fixture_for_kind`] registry and [`light`](Socket::light) composes those
/// on the plan's net, so a new socket is **one match arm in
/// [`SocketPlan::for_type`] + one `impl Socket`** — it cannot drift out of step with its
/// own plan, and it cannot fake a fixture it has no provisioner for.
pub trait Socket {
    /// Which bulb type this socket accepts.
    fn accepts(&self, ty: BuildThingType) -> bool;
    /// The composed fixture PLAN for `mode` (what the pane renders + [`light`](Self::light)
    /// provisions).
    fn plan(&self, mode: Mode) -> SocketPlan;

    /// The socket's stable name.
    fn name(&self, mode: Mode) -> &'static str {
        self.plan(mode).socket_name
    }

    /// The concrete [`Fixture`] per kind in the plan, in dependency order. The default
    /// walks the plan through [`fixture_for_kind`], which is what keeps the topology the
    /// pane draws and the deployment [`light`](Self::light) produces identical.
    fn fixtures(&self, mode: Mode) -> Vec<Box<dyn Fixture>> {
        self.plan(mode)
            .fixtures
            .iter()
            .map(|&k| fixture_for_kind(k))
            .collect()
    }

    /// How many node consoles the pane grids for this socket.
    fn consoles(&self, mode: Mode) -> usize {
        self.plan(mode).consoles
    }

    /// The [`Probe`]s that assert against this socket's lit nodes. Every socket gets the
    /// app-agnostic `APP-ALIVE` probe; a socket with more to say (e.g. the k8s socket's
    /// `nodes-ready` / `workload-healthy`) overrides.
    ///
    /// `AppAliveProbe` bridges the `app_heartbeat` crate, which only exists behind
    /// `sockets`. With the feature off there is no heartbeat client to read a console
    /// with, so the honest default is NO probe — never a stub that returns a pass,
    /// which is the false green this whole module's evidence classifier exists to
    /// refuse. Everything else about a socket — its plan, fixtures, topology and the
    /// `nodes-ready` / `workload-healthy` probes — is dependency-free and always on.
    fn probes(&self) -> Vec<Box<dyn Probe>> {
        #[cfg(feature = "sockets")]
        {
            vec![Box::new(AppAliveProbe::default())]
        }
        #[cfg(not(feature = "sockets"))]
        {
            Vec::new()
        }
    }

    /// **Light the socket** — realize every fixture in the plan on the plan's net and
    /// return the [`SocketDeployment`], each fixture at its light-up health
    /// (`Ready`/`NotApplicable`/`NotImplemented`/`Down`). This never claims a fixture is
    /// *serving*: call [`SocketDeployment::confirm`] with a booted node's console to
    /// upgrade to `Serving` from real runtime evidence.
    fn light(&self, mode: Mode) -> SocketDeployment {
        SocketDeployment::compose(&self.plan(mode), &self.fixtures(mode))
    }
}

/// **The type→socket registry.** The pane reads a bulb's [`BuildThingType`] and calls this
/// to get the socket to fire — no per-cell config. Adding a socket = impl [`Socket`] + one
/// arm here (+ its plan arm in [`SocketPlan::for_type`]); zero pane changes, the same
/// pattern as `nornir_build_thing::ContainerBackend`.
pub fn socket_for(ty: BuildThingType) -> Box<dyn Socket> {
    match ty {
        BuildThingType::TunnrAppliance => Box::new(TunnrTestSocket),
        BuildThingType::K8sWorkload => Box::new(K8sSocket),
        // Everything else is the degenerate socket today's single-boot already is.
        other => Box::new(SingleBootSocket(other)),
    }
}

/// Every [`BuildThingType`] the registry knows — so a test can sweep the whole table and
/// a new type cannot be added without a socket answering for it.
pub const ALL_BUILD_THING_TYPES: [BuildThingType; 7] = [
    BuildThingType::Exe,
    BuildThingType::Console,
    BuildThingType::Kvm,
    BuildThingType::BootableIso,
    BuildThingType::TunnrAppliance,
    BuildThingType::Container,
    BuildThingType::K8sWorkload,
];

/// The **degenerate socket** — today's single boot (1 node, no shared net, no extra
/// fixtures) behind the trait. `exe`/`console`/`kvm`/`bootable-iso`/`container` all light
/// through this, unchanged: the generalization is not a rewrite of the korp matrix.
#[derive(Debug, Clone, Copy)]
pub struct SingleBootSocket(pub BuildThingType);

impl Socket for SingleBootSocket {
    fn accepts(&self, ty: BuildThingType) -> bool {
        ty == self.0
    }
    fn plan(&self, mode: Mode) -> SocketPlan {
        SocketPlan::for_type(self.0, mode)
    }
}

/// The `tunnr-appliance` socket: composes `[TestNet, Dhcp, Dns, WireGuardPeer,
/// NoCloudSeed, KvmBoot]`. In `Mode::Test` it opens the net + surfaces the WireGuard keys
/// panel; `Mode::Prod` is the same composition, hardened — and, because the only
/// [`NetworkBackend`] that exists is test-grade, `Prod` **refuses** rather than serving
/// the Test fixtures under a Prod label (see [`SocketDeployment::refusals`]).
#[derive(Debug, Default, Clone, Copy)]
pub struct TunnrTestSocket;

impl Socket for TunnrTestSocket {
    fn accepts(&self, ty: BuildThingType) -> bool {
        ty == BuildThingType::TunnrAppliance
    }
    fn plan(&self, mode: Mode) -> SocketPlan {
        SocketPlan::for_type(BuildThingType::TunnrAppliance, mode)
    }
}

/// The `k8s-workload` socket — the **playground**: a control-plane + workers + CNI + a
/// registry on a shared net, a console per node, and cluster-level probes
/// ([`NodesReadyProbe`], [`WorkloadHealthyProbe`]).
///
/// **What is real today:** the composition, the topology (each node's endpoint), the
/// probes (they read a real `kubectl get nodes` / rollout console and are red-when-wrong),
/// and Skidbladnir's existing k8s limbs — `k8s::KubernetesRenderer` (manifests as data),
/// `k8s_apply::K8sApplier` (server-side apply/scale/wait against an **existing** cluster)
/// and `unfold::talos::MachineConfig` + `talos_apid` (machineconfig + apid push).
///
/// **What is NOT built:** nothing *boots a cluster node* for this socket. So the
/// control-plane / worker / CNI / registry fixtures light as
/// [`FixtureHealth::NotImplemented`] with the exact reason, and
/// [`SocketDeployment::all_ok`] is `false` — the pane shows the gap instead of a dead
/// "deploy" button. See `.nornir/kubernetes-target-design.md`, `k8s-unfold.md`,
/// `talos-unfold.md`.
#[derive(Debug, Default, Clone, Copy)]
pub struct K8sSocket;

impl Socket for K8sSocket {
    fn accepts(&self, ty: BuildThingType) -> bool {
        ty == BuildThingType::K8sWorkload
    }
    fn plan(&self, mode: Mode) -> SocketPlan {
        SocketPlan::for_type(BuildThingType::K8sWorkload, mode)
    }
    fn probes(&self) -> Vec<Box<dyn Probe>> {
        // The two cluster probes are pure console classifiers and always compile; the
        // app-alive bridge needs `app_heartbeat` (feature `sockets`). See the note on
        // `Socket::probes`.
        #[allow(unused_mut)]
        let mut p: Vec<Box<dyn Probe>> = vec![
            Box::new(NodesReadyProbe { expected_nodes: 3 }),
            Box::new(WorkloadHealthyProbe),
        ];
        #[cfg(feature = "sockets")]
        p.push(Box::new(AppAliveProbe::default()));
        p
    }
}

// ── Live provisioning: light the socket's fixtures + confirm health from a node ──────

/// How a fixture is **realized** at light-up — honest about the runtime, never a fake.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FixtureRuntime {
    /// Served by the TestNet's QEMU user-mode (slirp) stack — the `-netdev user` VALUE that
    /// realizes it (slirp has a built-in DHCP server + DNS forwarder). No separate process.
    SlirpBuiltin {
        /// The `-netdev` value (`user,id=…,net=…,dhcpstart=…,dns=…`).
        netdev_arg: String,
    },
    /// A tunnr **payload appliance** (e.g. `wg-appliance`) — the config it boots with.
    Payload {
        /// The payload name.
        payload: String,
        /// A human summary of the config it boots with.
        config: String,
    },
    /// Realized by the Build Thing's **own** boot/host machinery — the single-boot path
    /// that already exists (QEMU/OVMF for a `KvmBoot`, the process host + pty for an
    /// `exe`, the `ContainerBackend` for a container). No extra service is provisioned.
    Host {
        /// A human summary of what runs it.
        detail: String,
    },
    /// **No provisioner exists** for this fixture yet — carries WHY. A socket composing
    /// one of these cannot light: the fixture reports
    /// [`FixtureHealth::NotImplemented`], never `Ready`. This is how the design's
    /// declared-but-unbuilt fixtures (the k8s control-plane, the workers, the CNI) stay
    /// visible in the topology WITHOUT the pane growing a dead button that pretends to
    /// deploy them.
    Unimplemented {
        /// What is missing, concretely.
        why: String,
    },
}

impl FixtureRuntime {
    /// Whether this runtime is a **test-grade fake** — something that stands in for a real
    /// service well enough to test against, but is not the service.
    ///
    /// [`SlirpBuiltin`](Self::SlirpBuiltin) is the case that matters: QEMU's user-mode net
    /// is an in-process, unroutable NAT with a toy DHCP server and a DNS forwarder bolted
    /// on. It is a superb TEST fixture and it is **not** a network anyone deploys onto.
    /// A tunnr [`Payload`](Self::Payload) (real boringtun) and the
    /// [`Host`](Self::Host) boot path are the real things, so they are not test-grade;
    /// [`Unimplemented`](Self::Unimplemented) serves nothing at all, so it fakes nothing.
    ///
    /// This is the predicate [`Mode::Prod`] fails closed on — see
    /// [`SocketDeployment::compose`].
    pub fn is_test_grade(&self) -> bool {
        matches!(self, FixtureRuntime::SlirpBuiltin { .. })
    }

    /// A short stable tag for the pane / a report.
    pub fn as_str(&self) -> &'static str {
        match self {
            FixtureRuntime::SlirpBuiltin { .. } => "slirp-builtin",
            FixtureRuntime::Payload { .. } => "tunnr-payload",
            FixtureRuntime::Host { .. } => "host",
            FixtureRuntime::Unimplemented { .. } => "unimplemented",
        }
    }
}

/// A fixture's health across the socket lifecycle. `ok()` = not a hard failure.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FixtureHealth {
    /// Provisioned + its runtime is present/valid; it will serve once the net is up.
    Ready,
    /// Runtime-**CONFIRMED** from observed evidence (a DHCP lease seen, a WG handshake) —
    /// set by [`SocketDeployment::confirm`], never claimed at light-up.
    Serving,
    /// Disabled in this mode by design (e.g. DHCP off in a hardened `Prod` net).
    NotApplicable,
    /// A required fixture could not provision — carries why (never a silent skip).
    Down(String),
    /// **Designed but not built** — no provisioner exists for this fixture yet (carries
    /// why). Distinct from [`Down`](Self::Down) (which is a real runtime failure) so the
    /// pane can say "not implemented" instead of rendering a button that does nothing.
    /// Fail-closed: [`ok`](Self::ok) is `false`, so a socket composing one never reports
    /// itself healthy.
    NotImplemented(String),
}

impl FixtureHealth {
    /// Whether this is a non-failing state (`Ready`/`Serving`/`NotApplicable`).
    /// `Down` and `NotImplemented` are both failures — an unbuilt fixture is never green.
    pub fn ok(&self) -> bool {
        !matches!(
            self,
            FixtureHealth::Down(_) | FixtureHealth::NotImplemented(_)
        )
    }
    /// Stable label for the pane / a report.
    pub fn as_str(&self) -> &str {
        match self {
            FixtureHealth::Ready => "ready",
            FixtureHealth::Serving => "serving",
            FixtureHealth::NotApplicable => "n/a",
            FixtureHealth::Down(_) => "down",
            FixtureHealth::NotImplemented(_) => "not-implemented",
        }
    }
    /// The human reason a fixture is `Down`/`NotImplemented`, if it is.
    pub fn why(&self) -> Option<&str> {
        match self {
            FixtureHealth::Down(w) | FixtureHealth::NotImplemented(w) => Some(w),
            _ => None,
        }
    }
}

/// One **lit fixture**: its kind, how it is realized, and its health.
#[derive(Debug, Clone)]
pub struct LitFixture {
    /// Which fixture.
    pub kind: FixtureKind,
    /// How it is realized (slirp built-in / a tunnr payload).
    pub runtime: FixtureRuntime,
    /// Its current health.
    pub health: FixtureHealth,
}

/// The **socket lit up**: the net it runs on + every fixture realized on it. The demo
/// object the all-cells robot test asserts (fixtures healthy) after a FAT korp boot.
#[derive(Debug, Clone)]
pub struct SocketDeployment {
    /// The socket's stable name (`tunnr-test-socket`).
    pub socket_name: String,
    /// The mode it was lit in.
    pub mode: Mode,
    /// The net all fixtures run on — `None` for the degenerate single-boot socket, which
    /// composes no shared net (an absent net is `None`, never an invented empty one).
    pub net: Option<NetSpec>,
    /// The fixtures realized on the net.
    pub fixtures: Vec<LitFixture>,
    /// The **WireGuard keys panel** the pane surfaces in `Mode::Test` when the socket runs a
    /// WireGuard peer (list/see/add pubkeys, watch the handshake go green). `None` for a
    /// hardened `Prod` net or a socket with no WireGuard — no keys panel is shown. Starts
    /// empty (the operator adds keys via the pane); [`confirm`](Self::confirm) marks the
    /// handshake from the live boot console (auto-fetch), so the panel fills itself.
    pub wg_panel: Option<WgKeyPanel>,
}

impl SocketDeployment {
    /// **Compose** a deployment from a socket's `plan` and its concrete `fixtures`:
    /// realize each fixture on the plan's net and record its light-up health. The one
    /// place a `SocketDeployment` is built, so every socket lights the same way — and so
    /// the deployment's fixture rows are exactly the plan's fixture kinds, in order.
    pub fn compose(plan: &SocketPlan, fixtures: &[Box<dyn Fixture>]) -> SocketDeployment {
        let net = plan.net();
        let lit: Vec<LitFixture> = fixtures
            .iter()
            .map(|f| {
                let runtime = f.runtime(net.as_ref(), plan.mode);
                let mut health = f.light_health(net.as_ref(), plan.mode);
                // ── THE MODE BOUNDARY, FAIL-CLOSED ──────────────────────────────────
                // A Prod socket must NEVER silently serve a test fixture. If a fixture
                // would come up `Ready`/`Serving` on a test-grade runtime, Prod refuses
                // it — `Down` with the reason — instead of shipping the Test fixture
                // under a Prod label. `NotApplicable` and `NotImplemented` are left
                // alone: they serve nothing, so they fake nothing.
                if !plan.mode.is_test()
                    && runtime.is_test_grade()
                    && matches!(health, FixtureHealth::Ready | FixtureHealth::Serving)
                {
                    health = FixtureHealth::Down(prod_refusal_reason(f.kind(), &runtime));
                }
                LitFixture {
                    kind: f.kind(),
                    runtime,
                    health,
                }
            })
            .collect();
        // The WireGuard keys panel is a Test-mode surface (the operator adds pubkeys +
        // watches the handshake); a hardened Prod net shows none. Empty at light-up — it
        // fills from the live boot via `confirm`, and the pane's "add a key" drives
        // `add_wg_peer`.
        let has_wg = lit.iter().any(|f| f.kind == FixtureKind::WireGuardPeer);
        let wg_panel = (plan.mode.is_test() && has_wg).then(WgKeyPanel::new);
        SocketDeployment {
            socket_name: plan.socket_name.to_string(),
            mode: plan.mode,
            net,
            fixtures: lit,
            wg_panel,
        }
    }

    /// The QEMU user-mode `-netdev` VALUE this deployment's net is realized by — what a
    /// consumer (the all-cells korp boot) attaches so the appliance lands on the socket's
    /// net with slirp serving DHCP + DNS. `None` for a socket with no shared net.
    pub fn qemu_netdev_arg(&self) -> Option<String> {
        self.net.as_ref().map(|n| n.qemu_user_netdev("n0"))
    }

    /// Whether every fixture is in a non-failing state (`Ready`/`Serving`/`NotApplicable`).
    /// A `NotImplemented` fixture makes this `false` — an unbuilt fixture is never green.
    pub fn all_ok(&self) -> bool {
        self.fixtures.iter().all(|f| f.health.ok())
    }

    /// Every fixture this deployment **refuses to serve**, with why — the `Down` /
    /// `NotImplemented` rows. What the pane shows in place of a dead button.
    pub fn refusals(&self) -> Vec<(FixtureKind, String)> {
        self.fixtures
            .iter()
            .filter_map(|f| f.health.why().map(|w| (f.kind, w.to_string())))
            .collect()
    }

    /// **Tear the deployment down.** Consumes it (the design's `teardown(self)`): the
    /// fixtures are released and the keys panel — which holds operator-entered peer
    /// pubkeys — goes with it. Returns the kinds that were torn down, in reverse
    /// dependency order (the bulb's own boot first, its net last).
    pub fn teardown(mut self) -> Vec<FixtureKind> {
        self.wg_panel = None;
        let mut kinds: Vec<FixtureKind> = self.fixtures.drain(..).map(|f| f.kind).collect();
        kinds.reverse();
        kinds
    }

    /// The lit fixture of `kind`, if present.
    pub fn fixture(&self, kind: FixtureKind) -> Option<&LitFixture> {
        self.fixtures.iter().find(|f| f.kind == kind)
    }

    /// **Confirm runtime health** from a booted node's captured `console` — upgrade a
    /// `Ready` fixture to `Serving` ONLY on real evidence: DHCP → a client leased an address
    /// in the pool (slirp DHCPACK); DNS → served alongside DHCP by the same slirp stack;
    /// WireGuard → the `wg-appliance` produced a handshake / reported alive. Never downgrades
    /// and never invents evidence — a fixture with no observed proof stays `Ready`.
    pub fn confirm(&mut self, console: &str) {
        let dhcp = self.net.as_ref().map_or_else(
            || Evidence::Unknown("this socket composes no shared net".to_string()),
            |n| n.dhcp_evidence(console),
        );
        let wg = wg_evidence(console);
        for f in &mut self.fixtures {
            let evidence = match f.kind {
                FixtureKind::Dhcp => dhcp.clone(),
                // slirp serves DHCP + DNS together, so a lease proves the WHOLE stack —
                // but only when DNS actually IS that same slirp stack. A DNS fixture run
                // as its own payload must show its own evidence; borrowing the DHCP
                // fixture's proof would be exactly the fake green this layer forbids.
                FixtureKind::Dns => dns_evidence(
                    console,
                    matches!(f.runtime, FixtureRuntime::SlirpBuiltin { .. }),
                    &dhcp,
                ),
                FixtureKind::WireGuardPeer => wg.clone(),
                _ => Evidence::Unknown("this fixture reports no console evidence".to_string()),
            };
            match evidence {
                // A fixture the boot shows FAILING goes Down, carrying the line that
                // proves it — and it does so even from `Serving`, because a tunnel that
                // dropped or a lease that was NAK'd is not still serving. Failure always
                // wins over success evidence elsewhere on the console: fail-closed.
                Evidence::Failed(line) => {
                    if matches!(f.health, FixtureHealth::Ready | FixtureHealth::Serving) {
                        f.health = FixtureHealth::Down(format!(
                            "the boot console shows {} did not come up: {line}",
                            f.kind.as_str()
                        ));
                    }
                }
                Evidence::Served => {
                    // The mode boundary again, on the evidence path: evidence gathered off
                    // a test-grade runtime is TEST evidence. In Prod it can never promote a
                    // fixture — otherwise a Prod socket would go green on a slirp lease.
                    let prod_refuses = !self.mode.is_test() && f.runtime.is_test_grade();
                    if !prod_refuses && f.health == FixtureHealth::Ready {
                        f.health = FixtureHealth::Serving;
                    }
                }
                // Unobserved is NOT healthy and NOT failed — it stays exactly as it was.
                Evidence::Unknown(_) => {}
            }
        }
        // Auto-fetch the keys panel from the boot: a handshake on the single-tunnel Test net
        // marks the configured peers Established (the pane's indicator goes green). Honest:
        // only on real handshake evidence, and only peers the operator already added.
        if let Some(panel) = &mut self.wg_panel {
            let keys: Vec<String> = panel.peers.iter().map(|p| p.public_key.clone()).collect();
            match &wg {
                Evidence::Served => {
                    // Age comes from a `wg show` capture when one is present; a plain boot
                    // console has no age signal, so the observation itself is the age (0).
                    let age = parse_handshake_age_secs(console).unwrap_or(0);
                    for k in keys {
                        panel.mark_handshake(
                            &k,
                            Handshake::Established {
                                last_handshake_secs: age,
                            },
                        );
                    }
                }
                // The tunnel is proven down. A peer that was up is now Lost — leaving it
                // green while the fixture goes Down would be the same fake green one level
                // in. A peer that never connected stays NeverConnected: nothing was lost.
                Evidence::Failed(_) => {
                    for k in keys {
                        let was = panel
                            .peers
                            .iter()
                            .find(|p| p.public_key == k)
                            .map(|p| p.handshake);
                        if !matches!(was, Some(Handshake::NeverConnected) | None) {
                            panel.mark_handshake(&k, Handshake::Lost);
                        }
                    }
                }
                // Only an attempt observed: reflect that the peer is mid-handshake rather
                // than claiming either outcome — but never downgrade one already up.
                Evidence::Unknown(_) => {
                    if first_line_matching(console, WG_ATTEMPT_MARKERS).is_some() {
                        for k in keys {
                            let was = panel
                                .peers
                                .iter()
                                .find(|p| p.public_key == k)
                                .map(|p| p.handshake);
                            if matches!(was, Some(Handshake::NeverConnected)) {
                                panel.mark_handshake(&k, Handshake::Handshaking);
                            }
                        }
                    }
                }
            }
        }
    }

    /// Add a WireGuard peer to the keys panel (the pane's "add a key" action). Returns
    /// `Err(NotApplicable-as-BadBase64?)` — no: returns the validation error, or a clear
    /// error if this deployment has no keys panel (a Prod / no-WireGuard socket).
    #[cfg(feature = "sockets")]
    pub fn add_wg_peer(
        &mut self,
        base64_pubkey: &str,
        endpoint: Option<String>,
        allowed_ips: Vec<String>,
    ) -> Result<(), WgKeyError> {
        match &mut self.wg_panel {
            Some(panel) => panel.add_peer(base64_pubkey, endpoint, allowed_ips),
            // No keys panel on this socket/mode — validate the key so the caller still gets a
            // fail-loud verdict, then report it cannot be added here (not a silent drop).
            None => {
                validate_wg_pubkey(base64_pubkey)?;
                Err(WgKeyError::Duplicate) // closest existing variant: "not accepted here"
            }
        }
    }

    /// A JSON surface of the whole lit socket for the pane to render (fixtures + their
    /// health + the WireGuard keys panel). Auto-fetch-friendly: the pane reads THIS after
    /// `light` + each `confirm`, never a dead empty panel.
    pub fn json(&self) -> serde_json::Value {
        serde_json::json!({
            "socket": self.socket_name,
            "mode": self.mode.as_str(),
            "net": self.net.as_ref().map(|n| serde_json::json!({
                "subnet": n.subnet_cidr, "gateway": n.gateway, "open": n.open,
            })),
            "all_ok": self.all_ok(),
            "fixtures": self.fixtures.iter().map(|f| serde_json::json!({
                "kind": f.kind.as_str(),
                "health": f.health.as_str(),
                "runtime": f.runtime.as_str(),
                // WHY a fixture is not serving — the pane renders this instead of a
                // button that pretends the fixture is there.
                "why": f.health.why(),
            })).collect::<Vec<_>>(),
            "refusals": self.refusals().iter().map(|(k, w)| serde_json::json!({
                "kind": k.as_str(), "why": w,
            })).collect::<Vec<_>>(),
            "wg_panel": self.wg_panel.as_ref().map(|p| p.json()),
        })
    }
}

// ── Concrete fixtures on the TunnrTestSocket ─────────────────────────────────────────

/// A **fixture** — one infra dependency inside a socket. This increment ships the
/// descriptor + config seam: [`plan`](Fixture::plan) (the topology row) and each
/// fixture's typed CONFIG derived from the socket's [`NetSpec`]. The live
/// `provision(&NetSpec) -> Handle` that launches the fixture as a tunnr payload is the
/// next increment — a fixture never claims a running service it has not provisioned.
pub trait Fixture {
    /// Which fixture this is.
    fn kind(&self) -> FixtureKind;
    /// The topology row for `net`/`mode` (endpoint + human detail). `net` is `None` for a
    /// socket with no shared net (the degenerate single-boot socket).
    fn plan(&self, net: Option<&NetSpec>, _mode: Mode) -> FixturePlan {
        FixturePlan::for_kind(self.kind(), net)
    }
    /// How this fixture is REALIZED on `net` in `mode` (honest runtime). Default: the
    /// TestNet's slirp built-in (correct for DHCP/DNS) — and, with **no** net, a fixture
    /// that needs one cannot be realized at all, so it reports `Unimplemented` rather than
    /// inventing a net. Host/boot fixtures override to [`FixtureRuntime::Host`]; the
    /// WireGuard fixture overrides to its tunnr payload.
    fn runtime(&self, net: Option<&NetSpec>, _mode: Mode) -> FixtureRuntime {
        match net {
            Some(n) => FixtureRuntime::SlirpBuiltin {
                netdev_arg: n.qemu_user_netdev("n0"),
            },
            None => FixtureRuntime::Unimplemented {
                why: format!(
                    "{} is a net service but this socket composes no shared net",
                    self.kind().as_str()
                ),
            },
        }
    }
    /// The fixture's health at LIGHT-UP — `Ready` (provisioned + its runtime is present),
    /// `NotApplicable` (disabled in this mode by design), `Down` (could not provision), or
    /// `NotImplemented` (no provisioner exists yet). NEVER `Serving`: that needs runtime
    /// evidence and is set by [`SocketDeployment::confirm`] from a booted node's console —
    /// a fixture never claims a service it has not been observed to provide.
    ///
    /// The default derives honesty from the runtime: a fixture whose runtime is
    /// [`Unimplemented`](FixtureRuntime::Unimplemented) is `NotImplemented`, never `Ready`.
    fn light_health(&self, net: Option<&NetSpec>, mode: Mode) -> FixtureHealth {
        default_light_health_of(self.runtime(net, mode))
    }
}

/// The default light-up health policy, exposed so an override can delegate to it: a
/// fixture with no provisioner is `NotImplemented`, anything realized is `Ready` (never
/// `Serving` — that needs observed evidence).
pub fn default_light_health_of(runtime: FixtureRuntime) -> FixtureHealth {
    match runtime {
        FixtureRuntime::Unimplemented { why } => FixtureHealth::NotImplemented(why),
        _ => FixtureHealth::Ready,
    }
}

/// **The fixture registry.** One concrete [`Fixture`] per [`FixtureKind`] — the seam that
/// keeps a socket's PLAN (the kinds [`SocketPlan::for_type`] composes) and its lit
/// DEPLOYMENT in lockstep. `Socket::fixtures` maps the plan through this, so adding a
/// fixture to a socket's composition can never silently produce a deployment that is
/// missing it. Adding a fixture = impl [`Fixture`] + one arm here.
pub fn fixture_for_kind(kind: FixtureKind) -> Box<dyn Fixture> {
    use FixtureKind::*;
    match kind {
        Dhcp => Box::new(DhcpFixture),
        Dns => Box::new(DnsFixture),
        WireGuardPeer => Box::new(WireGuardPeerFixture),
        TestNet => Box::new(TestNetFixture),
        NoCloudSeed => Box::new(NoCloudSeedFixture),
        // Realized by the Build Thing's own existing boot/host machinery.
        KvmBoot => Box::new(HostFixture {
            kind: KvmBoot,
            detail: "QEMU/OVMF boot of the bulb",
        }),
        ProcessHost => Box::new(HostFixture {
            kind: ProcessHost,
            detail: "process host + pty",
        }),
        Pty => Box::new(HostFixture {
            kind: Pty,
            detail: "pty/tty",
        }),
        ContainerRuntime => Box::new(HostFixture {
            kind: ContainerRuntime,
            detail: "ContainerBackend",
        }),
        // Designed, not built. Each says exactly what is missing (see `k8s-unfold.md` /
        // `talos-unfold.md`): the renderer + apid push exist, no socket fixture LAUNCHES a
        // cluster node yet.
        K8sControlPlane => Box::new(UnimplementedFixture {
            kind: K8sControlPlane,
            why: "no in-VM control-plane provisioner: unfold::talos::MachineConfig renders \
                  the machineconfig and talos_apid pushes it, but nothing boots the node. \
                  TO FIX: boot a Talos/k3s node on this socket's net (machine::kvm already \
                  boots VMs) and hand talos_apid its endpoint — both ends exist, the \
                  launcher between them does not",
        }),
        K8sWorker => Box::new(UnimplementedFixture {
            kind: K8sWorker,
            why: "no in-VM worker provisioner (same seam as the control-plane fixture). \
                  TO FIX: the same launcher, with MachineConfig::worker(..) and a join \
                  token instead of a bootstrap",
        }),
        Cni => Box::new(UnimplementedFixture {
            kind: Cni,
            why: "no CNI fixture: k8s_apply applies manifests to an EXISTING cluster, it \
                  does not install a CNI into one this socket built. TO FIX: once a \
                  control-plane fixture exists, apply the CNI manifests through \
                  k8s_apply::K8sApplier like any other workload",
        }),
        OciRegistry => Box::new(UnimplementedFixture {
            kind: OciRegistry,
            why: "no in-socket registry: federation::oci talks to an existing registry, it \
                  does not serve one. TO FIX: run a registry as a tunnr payload on the \
                  socket's net, or point the cluster at an external one via \
                  MachineConfig::with_mirror",
        }),
        MinioS3 => Box::new(UnimplementedFixture {
            kind: MinioS3,
            why: "no in-socket object store fixture. TO FIX: run MinIO as a tunnr payload \
                  on the socket's net, or point consumers at an external S3 endpoint",
        }),
        Nat => Box::new(UnimplementedFixture {
            kind: Nat,
            why: "no explicit NAT fixture: slirp's user-net already NATs the Test net. \
                  TO FIX: only needed once a non-slirp NetworkBackend exists, which is \
                  also what Prod is waiting on",
        }),
    }
}

/// A fixture realized by the Build Thing's **own** boot/host machinery — the single-boot
/// path that already ships. It provisions nothing extra, so it is honestly `Ready`.
#[derive(Debug, Clone, Copy)]
pub struct HostFixture {
    /// Which host/boot fixture.
    pub kind: FixtureKind,
    /// What runs it.
    pub detail: &'static str,
}

impl Fixture for HostFixture {
    fn kind(&self) -> FixtureKind {
        self.kind
    }
    fn runtime(&self, _net: Option<&NetSpec>, _mode: Mode) -> FixtureRuntime {
        FixtureRuntime::Host {
            detail: self.detail.to_string(),
        }
    }
}

/// A fixture the design declares but **nothing provisions yet**. Always
/// [`FixtureHealth::NotImplemented`] — it appears in the topology (so the gap is visible)
/// and it drags [`SocketDeployment::all_ok`] to `false` (so the gap is never green).
#[derive(Debug, Clone, Copy)]
pub struct UnimplementedFixture {
    /// Which fixture.
    pub kind: FixtureKind,
    /// What is missing.
    pub why: &'static str,
}

impl Fixture for UnimplementedFixture {
    fn kind(&self) -> FixtureKind {
        self.kind
    }
    fn runtime(&self, _net: Option<&NetSpec>, _mode: Mode) -> FixtureRuntime {
        FixtureRuntime::Unimplemented {
            why: self.why.to_string(),
        }
    }
}

/// The **shared net itself** as a fixture row — realized by the socket's
/// [`NetworkBackend`]. It serves no address of its own; it IS the address space.
#[derive(Debug, Default, Clone, Copy)]
pub struct TestNetFixture;

impl Fixture for TestNetFixture {
    fn kind(&self) -> FixtureKind {
        FixtureKind::TestNet
    }
    fn runtime(&self, net: Option<&NetSpec>, _mode: Mode) -> FixtureRuntime {
        match net {
            Some(n) => FixtureRuntime::SlirpBuiltin {
                netdev_arg: n.qemu_user_netdev("n0"),
            },
            None => FixtureRuntime::Unimplemented {
                why: "the socket composes a TestNet but no NetworkBackend produced a spec"
                    .to_string(),
            },
        }
    }
}

/// The **NoCloud/metadata seed** fixture — Skidbladnir already authors this (pure-Rust
/// FAT, the `kvm` feature). In `Mode::Test` it is pre-filled and auth-less by design; a
/// hardened `Prod` seed carries real credentials, which this socket layer does not mint,
/// so in `Prod` it is honestly `NotImplemented` rather than a seeded no-auth image.
#[derive(Debug, Default, Clone, Copy)]
pub struct NoCloudSeedFixture;

impl Fixture for NoCloudSeedFixture {
    fn kind(&self) -> FixtureKind {
        FixtureKind::NoCloudSeed
    }
    fn runtime(&self, _net: Option<&NetSpec>, mode: Mode) -> FixtureRuntime {
        match mode {
            Mode::Test => FixtureRuntime::Host {
                detail: "pre-filled auth-less NoCloud seed (pure-Rust FAT author)".to_string(),
            },
            Mode::Prod => FixtureRuntime::Unimplemented {
                why: "a Prod NoCloud seed must carry real credentials; this socket layer \
                      mints none, and it will not ship the Test seed's auth-less image. \
                      TO FIX: give NoCloudSeedFixture a credential source — Skidbladnir \
                      already owns pki and openbao, so wire one of those in"
                    .to_string(),
            },
        }
    }
}

/// The DHCP config a [`DhcpFixture`] serves on a socket's net.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DhcpConfig {
    /// The subnet it serves.
    pub subnet_cidr: String,
    /// The router/gateway option handed to clients.
    pub gateway: String,
    /// The address pool `(first, last)`, or `None` in a hardened net (static only).
    pub range: Option<(String, String)>,
    /// DNS servers handed to clients.
    pub dns: Vec<String>,
    /// Lease time in seconds.
    pub lease_secs: u32,
}

/// A **DHCP server fixture** — a tiny tunnr payload that hands out the socket's net pool.
#[derive(Debug, Default, Clone, Copy)]
pub struct DhcpFixture;

impl DhcpFixture {
    /// The DHCP config for `net`: the pool + gateway + DNS it would serve. In a hardened
    /// (`Prod`) net with no pool, DHCP is effectively off (static/seeded addressing).
    pub fn config(&self, net: &NetSpec) -> DhcpConfig {
        DhcpConfig {
            subnet_cidr: net.subnet_cidr.clone(),
            gateway: net.gateway.clone(),
            range: net.dhcp_range.clone(),
            dns: net.dns.clone(),
            lease_secs: 3600,
        }
    }
    /// Whether this fixture actually serves leases on `net` (a pool is present).
    pub fn serves_leases(&self, net: &NetSpec) -> bool {
        net.dhcp_range.is_some()
    }
}

impl Fixture for DhcpFixture {
    fn kind(&self) -> FixtureKind {
        FixtureKind::Dhcp
    }
    /// DHCP is served by slirp's built-in server ONLY when the net has a pool; a hardened
    /// (`Prod`) net has none, so the fixture is `NotApplicable` (static/seeded addressing).
    fn light_health(&self, net: Option<&NetSpec>, _mode: Mode) -> FixtureHealth {
        match net {
            // No pool → nothing is served, so nothing is faked: honestly not applicable.
            Some(n) if !self.serves_leases(n) => FixtureHealth::NotApplicable,
            Some(_) => FixtureHealth::Ready,
            None => FixtureHealth::NotImplemented(
                "DHCP needs a shared net; this socket composes none".to_string(),
            ),
        }
    }
}

/// A **DNS resolver fixture** — a tiny tunnr payload resolving names on the socket's net.
#[derive(Debug, Default, Clone, Copy)]
pub struct DnsFixture;

impl Fixture for DnsFixture {
    fn kind(&self) -> FixtureKind {
        FixtureKind::Dns
    }
    /// A resolver with **no resolver address** on the net resolves nothing — that is a
    /// `Down` fixture, not a green one (a fixture that did not come up says so).
    fn light_health(&self, net: Option<&NetSpec>, mode: Mode) -> FixtureHealth {
        match net {
            Some(n) if n.dns.is_empty() => FixtureHealth::Down(
                "the net declares no resolver address — DNS would resolve nothing".to_string(),
            ),
            _ => default_light_health_of(self.runtime(net, mode)),
        }
    }
}

/// The WireGuard peer config a [`WireGuardPeerFixture`] brings up on a socket's net.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WgPeerConfig {
    /// The appliance's WireGuard endpoint (`gateway:51820`) on the net.
    pub endpoint: String,
    /// The UDP listen port.
    pub listen_port: u16,
    /// The CIDRs routed through the tunnel (the whole test subnet in `Test`).
    pub allowed_ips: Vec<String>,
}

/// A **WireGuard peer fixture** — the `wg-appliance` tunnr payload, bringing the tunnel
/// up on the socket's net. In `Mode::Test` its keys are managed through a [`WgKeyPanel`]
/// surfaced in the pane (list/add pubkeys, watch the handshake go green).
#[derive(Debug, Default, Clone, Copy)]
pub struct WireGuardPeerFixture;

impl WireGuardPeerFixture {
    /// The standard WireGuard UDP port.
    pub const LISTEN_PORT: u16 = 51820;

    /// The WireGuard peer config for `net`: the appliance's endpoint on the gateway + the
    /// routed CIDRs (the whole subnet in `Test`; a `Prod` net routes its declared CIDRs).
    pub fn config(&self, net: &NetSpec) -> WgPeerConfig {
        WgPeerConfig {
            endpoint: format!("{}:{}", net.gateway, Self::LISTEN_PORT),
            listen_port: Self::LISTEN_PORT,
            allowed_ips: vec![net.subnet_cidr.clone()],
        }
    }

    /// A fresh [`WgKeyPanel`] for this fixture (empty — the runtime fills the appliance's
    /// own key + peers as the tunnel comes up; the pane's "add a key" drives it).
    pub fn new_key_panel(&self) -> WgKeyPanel {
        WgKeyPanel::new()
    }
}

impl Fixture for WireGuardPeerFixture {
    fn kind(&self) -> FixtureKind {
        FixtureKind::WireGuardPeer
    }
    /// The WireGuard peer is the tunnr **`wg-appliance`** payload (real boringtun) — not a
    /// slirp built-in. Its config (endpoint + routed CIDRs) is derived from the net; with
    /// no net there is no endpoint to listen on, so it reports unbuilt rather than
    /// inventing one.
    fn runtime(&self, net: Option<&NetSpec>, _mode: Mode) -> FixtureRuntime {
        let Some(net) = net else {
            return FixtureRuntime::Unimplemented {
                why: "the WireGuard peer needs a net to take its endpoint from".to_string(),
            };
        };
        let cfg = self.config(net);
        FixtureRuntime::Payload {
            payload: "wg-appliance".to_string(),
            config: format!(
                "endpoint={} allowed_ips={}",
                cfg.endpoint,
                cfg.allowed_ips.join(",")
            ),
        }
    }
}

// ── WireGuard keys panel (the Mode::Test tunnr-appliance UI surface) ──────────────────

/// A rejected WireGuard key (bad base64 or wrong length) — validation is fail-loud, a
/// malformed key never silently enters the panel.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WgKeyError {
    /// Not valid standard base64.
    BadBase64,
    /// Decoded, but not the 32 bytes a Curve25519 WireGuard key is.
    WrongLength(usize),
    /// The peer public key is already in the panel.
    Duplicate,
}

impl std::fmt::Display for WgKeyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            WgKeyError::BadBase64 => write!(f, "not valid base64"),
            WgKeyError::WrongLength(n) => write!(f, "decoded to {n} bytes, expected 32"),
            WgKeyError::Duplicate => write!(f, "peer public key already present"),
        }
    }
}
impl std::error::Error for WgKeyError {}

/// Validate a WireGuard public key: standard base64 decoding to exactly 32 bytes
/// (Curve25519). Returns the raw key bytes on success.
///
/// GATED on `sockets` because it is one of only THREE items in this module that
/// touch a feature dependency (`base64`). The rest of the module — the registry,
/// the plan/topology model, the whole evidence classifier, the key PANEL as data —
/// is pure and compiles in the lean core, which is why it no longer sits behind the
/// gate with this function.
#[cfg(feature = "sockets")]
pub fn validate_wg_pubkey(base64_key: &str) -> Result<[u8; 32], WgKeyError> {
    use base64::Engine;
    let raw = base64::engine::general_purpose::STANDARD
        .decode(base64_key.trim())
        .map_err(|_| WgKeyError::BadBase64)?;
    if raw.len() != 32 {
        return Err(WgKeyError::WrongLength(raw.len()));
    }
    let mut k = [0u8; 32];
    k.copy_from_slice(&raw);
    Ok(k)
}

/// The live handshake state of a WireGuard peer — what the panel's "watch it go green"
/// indicator reads.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Handshake {
    /// No handshake yet.
    NeverConnected,
    /// A handshake is in flight (an initiation was sent; nothing has answered yet).
    Handshaking,
    /// Established; `last_handshake_secs` = seconds since the last successful handshake.
    /// **Age matters** — see [`STALE_AFTER_SECS`](Self::STALE_AFTER_SECS).
    Established { last_handshake_secs: u64 },
    /// It **was** established and the tunnel has since been observed to fail or expire.
    /// Distinct from `NeverConnected`: something worked and then stopped, which is a
    /// different thing to show an operator than "this never came up".
    Lost,
}

impl Handshake {
    /// After this many seconds without a handshake, a WireGuard session is dead: it is
    /// WireGuard's own `REJECT_AFTER_TIME` (boringtun 0.7.1 `src/noise/timers.rs:20`),
    /// the point at which the protocol refuses to use the session's keys. A peer whose
    /// last handshake is older than this is **not up**, however green it looked before.
    pub const STALE_AFTER_SECS: u64 = 180;

    /// Whether the peer is up (the green indicator) — established **and fresh**. A
    /// handshake older than [`STALE_AFTER_SECS`](Self::STALE_AFTER_SECS) is a dead
    /// session, so it does not count.
    pub fn is_up(self) -> bool {
        matches!(self, Handshake::Established { last_handshake_secs }
            if last_handshake_secs <= Self::STALE_AFTER_SECS)
    }

    /// Whether this is an established session that has gone **stale** (past
    /// `REJECT_AFTER_TIME`) — up once, unusable now.
    pub fn is_stale(self) -> bool {
        matches!(self, Handshake::Established { last_handshake_secs }
            if last_handshake_secs > Self::STALE_AFTER_SECS)
    }

    /// Stable label for the pane / JSON surface.
    pub fn as_str(self) -> &'static str {
        match self {
            Handshake::NeverConnected => "never",
            Handshake::Handshaking => "handshaking",
            Handshake::Established { .. } if self.is_stale() => "stale",
            Handshake::Established { .. } => "established",
            Handshake::Lost => "lost",
        }
    }
}

/// The age of the most recent WireGuard handshake, in seconds, if `console` carries one.
///
/// The **only** place this signal exists is `wg(8)`'s `wg show` output —
/// `latest handshake: 6 minutes, 12 seconds ago`. A plain boot console does not report it:
/// boringtun's `Received handshake_response` says a handshake happened, not how long ago.
/// So [`SocketDeployment::confirm`] uses this when a `wg show` capture is present and
/// otherwise records the observation as fresh (age 0) — see its docs; it does **not**
/// invent a timeout where no age signal exists.
pub fn parse_handshake_age_secs(console: &str) -> Option<u64> {
    let low = console.to_ascii_lowercase();
    let idx = low.find("latest handshake:")?;
    let tail = &low[idx + "latest handshake:".len()..];
    let line = tail.lines().next()?;
    // "6 minutes, 12 seconds ago" / "42 seconds ago" / "1 hour, 3 minutes ago"
    let mut total: u64 = 0;
    let mut saw_unit = false;
    let toks: Vec<&str> = line
        .split(|c: char| !c.is_ascii_alphanumeric())
        .filter(|t| !t.is_empty())
        .collect();
    for pair in toks.windows(2) {
        let Ok(n) = pair[0].parse::<u64>() else {
            continue;
        };
        let mult = match pair[1] {
            u if u.starts_with("second") => 1,
            u if u.starts_with("minute") => 60,
            u if u.starts_with("hour") => 3600,
            u if u.starts_with("day") => 86_400,
            _ => continue,
        };
        total += n * mult;
        saw_unit = true;
    }
    saw_unit.then_some(total)
}

/// One WireGuard peer row in the keys panel.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WgPeer {
    /// The peer's base64 public key (validated to 32 bytes on insert).
    pub public_key: String,
    /// Optional `host:port` endpoint.
    pub endpoint: Option<String>,
    /// Allowed IPs (CIDRs) routed to this peer.
    pub allowed_ips: Vec<String>,
    /// The live handshake state.
    pub handshake: Handshake,
}

/// The **WireGuard keys panel** the `Mode::Test` `tunnr-appliance` socket surfaces:
/// the appliance's own public key, the peer list, add-a-peer (validated), and per-peer
/// handshake state. Pure data + lifecycle — the crypto/runtime is tunnr's `wg-appliance`
/// payload (dogfood); Skidbladnir MANAGES the keys.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WgKeyPanel {
    /// The appliance's own public key (base64), once the WireGuard fixture is up.
    pub own_public_key: Option<String>,
    /// The configured peers.
    pub peers: Vec<WgPeer>,
}

impl WgKeyPanel {
    pub fn new() -> Self {
        WgKeyPanel::default()
    }

    /// Record the appliance's own public key (validated). The WireGuard fixture reports
    /// it once its interface is up. Gated: validation is `base64`.
    #[cfg(feature = "sockets")]
    pub fn set_own_public_key(&mut self, base64_key: &str) -> Result<(), WgKeyError> {
        validate_wg_pubkey(base64_key)?;
        self.own_public_key = Some(base64_key.trim().to_string());
        Ok(())
    }

    /// **Add a peer** by public key (the panel's "add a key" action). Validates the key
    /// (base64 → 32 bytes) and rejects a duplicate. Starts `NeverConnected`.
    /// Gated: validation is `base64`.
    #[cfg(feature = "sockets")]
    pub fn add_peer(
        &mut self,
        base64_pubkey: &str,
        endpoint: Option<String>,
        allowed_ips: Vec<String>,
    ) -> Result<(), WgKeyError> {
        validate_wg_pubkey(base64_pubkey)?;
        let key = base64_pubkey.trim().to_string();
        if self.peers.iter().any(|p| p.public_key == key) {
            return Err(WgKeyError::Duplicate);
        }
        self.peers.push(WgPeer {
            public_key: key,
            endpoint,
            allowed_ips,
            handshake: Handshake::NeverConnected,
        });
        Ok(())
    }

    /// Update a peer's handshake state (the runtime feeds this; the UI watches it go
    /// green). No-op for an unknown peer.
    pub fn mark_handshake(&mut self, base64_pubkey: &str, hs: Handshake) {
        let key = base64_pubkey.trim();
        if let Some(p) = self.peers.iter_mut().find(|p| p.public_key == key) {
            p.handshake = hs;
        }
    }

    /// How many peers have an established handshake (the green count).
    pub fn established_count(&self) -> usize {
        self.peers.iter().filter(|p| p.handshake.is_up()).count()
    }

    /// The set of distinct allowed-IP CIDRs across all peers (the routed test net).
    pub fn routed_cidrs(&self) -> BTreeSet<String> {
        self.peers
            .iter()
            .flat_map(|p| p.allowed_ips.iter().cloned())
            .collect()
    }

    /// The JSON surface the pane renders: the appliance's own key, each peer (key +
    /// endpoint + allowed-IPs + handshake state + up), and the green count.
    pub fn json(&self) -> serde_json::Value {
        serde_json::json!({
            "own_public_key": self.own_public_key,
            "peers": self.peers.iter().map(|p| serde_json::json!({
                "public_key": p.public_key,
                "endpoint": p.endpoint,
                "allowed_ips": p.allowed_ips,
                "handshake": p.handshake.as_str(),
                "up": p.handshake.is_up(),
            })).collect::<Vec<_>>(),
            "established_count": self.established_count(),
            "routed_cidrs": self.routed_cidrs().into_iter().collect::<Vec<_>>(),
        })
    }
}

// ── Probes: assert / suck data out of a lit node ─────────────────────────────────────

/// A probe's verdict over one node's observable output.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProbeVerdict {
    /// The probe's name.
    pub probe: String,
    /// Whether the probe passed.
    pub passed: bool,
    /// A human reason.
    pub detail: String,
}

/// A **probe** — asserts against (or sucks data out of) a lit node. In the live socket it
/// reasons over a running [`Deployment`]; today its observable is the node's console text
/// (the serial/screendump stream the Build Thing already captures), which is the seam the
/// pane feeds. Adding a probe = impl this trait + register (registry pattern).
pub trait Probe {
    /// The probe's stable name.
    fn name(&self) -> &str;
    /// Run the probe over a node's captured console output.
    fn run(&self, console: &str) -> ProbeVerdict;
}

/// The **app-alive probe** — bridges the app-agnostic
/// [`app_heartbeat`](app_heartbeat) `APP-ALIVE` contract into the socket [`Probe`] seam.
/// It reads whatever manifest the app self-reported on the console (never hardcoding an
/// app) and calls the whole node healthy iff it deployed, is beating, and every declared
/// function passed. The per-function roster is exposed via [`report`](Self::report) so the
/// pane drives ONE robot UI test per declared function (brief §3c).
#[cfg(feature = "sockets")]
#[derive(Debug, Clone, Copy)]
pub struct AppAliveProbe {
    /// How many `APP-ALIVE` beats a healthy run is expected to show (liveness cutoff).
    pub expected_beats: usize,
}

#[cfg(feature = "sockets")]
impl Default for AppAliveProbe {
    fn default() -> Self {
        AppAliveProbe { expected_beats: 1 }
    }
}

#[cfg(feature = "sockets")]
impl AppAliveProbe {
    /// The full app-agnostic heartbeat report (deploy_ok + beats + per-function verdicts)
    /// — the pane iterates its `functions` to render/drive one test each.
    pub fn report(&self, console: &str) -> app_heartbeat::HeartbeatReport {
        app_heartbeat::HeartbeatProbe::evaluate(console)
    }
}

#[cfg(feature = "sockets")]
impl Probe for AppAliveProbe {
    fn name(&self) -> &str {
        "app-alive"
    }
    fn run(&self, console: &str) -> ProbeVerdict {
        let report = self.report(console);
        let liveness = report.liveness(self.expected_beats);
        let passed = report.healthy() && liveness == app_heartbeat::Liveness::Alive;
        let detail = match report.app.as_deref() {
            None => "no APP-ALIVE line — the app never reported ready".to_string(),
            Some(app) => format!(
                "{app}: deploy_ok={} liveness={liveness:?} functions {}/{} passed",
                report.deploy_ok,
                report.passing(),
                report.functions.len(),
            ),
        };
        ProbeVerdict {
            probe: self.name().to_string(),
            passed,
            detail,
        }
    }
}

/// The **nodes-ready probe** for the k8s socket: every expected node must appear `Ready`.
/// It reads a `kubectl get nodes`-shaped roster off the control-plane console (the
/// observable the pane already captures) — no `kubectl` is shelled out, Skidbladnir's
/// charter is zero-shell; `k8s_apply::K8sApplier` is the API-side counterpart.
///
/// Fail-closed: an empty roster, a missing node, or ANY node in a non-`Ready` state
/// (`NotReady`, `SchedulingDisabled`, `Unknown`) fails. Silence is never success.
#[derive(Debug, Clone, Copy)]
pub struct NodesReadyProbe {
    /// How many nodes the socket's plan expects to join.
    pub expected_nodes: usize,
}

/// One node's line as the probe read it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeStatus {
    /// The node name.
    pub name: String,
    /// The status column verbatim (`Ready`, `NotReady`, `Ready,SchedulingDisabled`…).
    pub status: String,
}

impl NodeStatus {
    /// Ready means exactly `Ready` — `Ready,SchedulingDisabled` is NOT a usable node.
    pub fn is_ready(&self) -> bool {
        self.status == "Ready"
    }
}

impl NodesReadyProbe {
    /// Parse the node roster out of a `kubectl get nodes`-shaped table: `NAME STATUS
    /// ROLES AGE VERSION`. The header row is skipped; anything without at least two
    /// columns is not a node line.
    pub fn roster(console: &str) -> Vec<NodeStatus> {
        console
            .lines()
            .filter_map(|line| {
                let mut cols = line.split_whitespace();
                let (name, status) = (cols.next()?, cols.next()?);
                if name == "NAME" || name.starts_with('[') {
                    return None;
                }
                // A node status column is one of the kubelet's condition words.
                let head = status.split(',').next().unwrap_or(status);
                matches!(
                    head,
                    "Ready" | "NotReady" | "Unknown" | "SchedulingDisabled"
                )
                .then(|| NodeStatus {
                    name: name.to_string(),
                    status: status.to_string(),
                })
            })
            .collect()
    }
}

impl Probe for NodesReadyProbe {
    fn name(&self) -> &str {
        "nodes-ready"
    }
    fn run(&self, console: &str) -> ProbeVerdict {
        let roster = Self::roster(console);
        let ready = roster.iter().filter(|n| n.is_ready()).count();
        let passed = !roster.is_empty() && ready == self.expected_nodes;
        let detail = if roster.is_empty() {
            "no node roster on the console — the cluster never reported its nodes".to_string()
        } else {
            let bad: Vec<&str> = roster
                .iter()
                .filter(|n| !n.is_ready())
                .map(|n| n.name.as_str())
                .collect();
            format!(
                "{ready}/{} node(s) Ready of {} expected{}",
                roster.len(),
                self.expected_nodes,
                if bad.is_empty() {
                    String::new()
                } else {
                    format!("; not ready: {bad:?}")
                }
            )
        };
        ProbeVerdict {
            probe: self.name().to_string(),
            passed,
            detail,
        }
    }
}

/// The **workload-healthy probe** for the k8s socket: the deployed workload's rollout must
/// have completed with every replica available. It reads the rollout line
/// `deployment "x" successfully rolled out` or a `kubectl get deploy`-shaped
/// `READY n/m` column — the same fact `k8s_apply::K8sApplier::wait_ready` polls
/// (`status.ready_replicas` → desired), from the console side.
///
/// Fail-closed: `n/m` with `n < m` fails, and a console with no rollout evidence at all
/// fails (a workload nobody saw come up is not a healthy workload).
#[derive(Debug, Default, Clone, Copy)]
pub struct WorkloadHealthyProbe;

impl WorkloadHealthyProbe {
    /// The `(ready, desired)` replica pairs found in a `READY n/m` column, if any.
    pub fn replica_pairs(console: &str) -> Vec<(u32, u32)> {
        console
            .split_whitespace()
            .filter_map(|tok| {
                let (a, b) = tok.split_once('/')?;
                Some((a.parse::<u32>().ok()?, b.parse::<u32>().ok()?))
            })
            .collect()
    }
}

impl Probe for WorkloadHealthyProbe {
    fn name(&self) -> &str {
        "workload-healthy"
    }
    fn run(&self, console: &str) -> ProbeVerdict {
        let rolled_out = console.contains("successfully rolled out");
        let pairs = Self::replica_pairs(console);
        let short: Vec<(u32, u32)> = pairs.iter().copied().filter(|(r, d)| r < d).collect();
        // Any under-replicated workload fails even if some other line says "rolled out".
        let passed =
            short.is_empty() && (rolled_out || pairs.iter().any(|(r, d)| r == d && *d > 0));
        let detail = if !short.is_empty() {
            format!("under-replicated: {short:?} (ready/desired)")
        } else if passed {
            format!(
                "rollout complete ({} replica column(s) at desired)",
                pairs.len()
            )
        } else {
            "no rollout evidence on the console — the workload never reported ready".to_string()
        };
        ProbeVerdict {
            probe: self.name().to_string(),
            passed,
            detail,
        }
    }
}

/// **Real console transcripts**, kept as fixtures so the evidence classifier is tested
/// against what the tools actually print rather than against the happy path someone
/// remembered. Every one names where it came from.
///
/// The DHCP transcripts are **live captures taken on t14s (2026-07-27)**: busybox udhcpc
/// 1.37.0 against `dnsmasq` 2.9x over a veth pair inside an unprivileged user+network
/// namespace (`unshare -Urn`, no root). The exact commands are in the doc comment of each
/// constant. The WireGuard transcripts are transcribed from **boringtun 0.7.1's source**
/// (`src/noise/timers.rs`, `src/noise/errors.rs`) — this host has no `wg` binary and no
/// WireGuard interface, so those are the tool's own message strings rather than a capture,
/// and they are labelled as such.
#[cfg(test)]
pub(crate) mod transcripts {
    /// **Live capture.** `busybox udhcpc -i veth1 -f -q -t 4 -T 2` against
    /// `dnsmasq --dhcp-range=10.9.0.100,10.9.0.200,1h` on the peer veth. Exit 0.
    ///
    /// Note line 4: `broadcasting select for 10.9.0.157` carries a real pool address and is
    /// only the DHCPREQUEST. It is the line that broke the old "any subnet address" check.
    pub const UDHCPC_SUCCESS: &str = "\
udhcpc: started, v1.37.0
udhcpc: broadcasting discover
udhcpc: broadcasting discover
udhcpc: broadcasting select for 10.9.0.157, server 10.9.0.1
udhcpc: lease of 10.9.0.157 obtained from 10.9.0.1, lease time 3600
";

    /// **Live capture.** The same client with **no DHCP server** on the link, `-n` (exit if
    /// no lease). Exit 1.
    pub const UDHCPC_NO_SERVER: &str = "\
udhcpc: started, v1.37.0
udhcpc: broadcasting discover
udhcpc: broadcasting discover
udhcpc: no lease, failing
";

    /// **Live capture.** Same, with `-b` (background if no lease) instead of `-n`.
    pub const UDHCPC_NO_LEASE_BACKGROUND: &str = "\
udhcpc: started, v1.37.0
udhcpc: broadcasting discover
udhcpc: broadcasting discover
udhcpc: no lease, forking to background
";

    /// **Live capture, truncated mid-exchange** (the client was SIGTERMed while still
    /// discovering). The partial case: real activity, no verdict either way.
    pub const UDHCPC_PARTIAL: &str = "\
udhcpc: started, v1.37.0
udhcpc: broadcasting discover
udhcpc: broadcasting discover
udhcpc: received SIGTERM
";

    /// **Live capture**, dnsmasq's own `--log-dhcp` server-side view of the successful
    /// exchange above (Swedish locale strings elided; the DHCP verbs are locale-independent).
    pub const DNSMASQ_SERVER_SIDE: &str = "\
dnsmasq-dhcp: DHCP, IP range 10.9.0.100 -- 10.9.0.200, lease time 1h
dnsmasq-dhcp: 2564937773 available DHCP range: 10.9.0.100 -- 10.9.0.200
dnsmasq-dhcp: 2564937773 DHCPDISCOVER(veth0) 92:ef:fc:a2:d9:9c
dnsmasq-dhcp: 2564937773 DHCPOFFER(veth0) 10.9.0.157 92:ef:fc:a2:d9:9c
";

    /// **Message strings from busybox 1.37.0's binary** (`strings /usr/bin/busybox`), for
    /// the failure modes that need a server to reproduce: a NAK, and a held lease going
    /// away. Format placeholders filled in with plausible values.
    pub const UDHCPC_NAK: &str = "\
udhcpc: broadcasting renew
udhcpc: received DHCP NAK (5)
";
    /// **Message strings from busybox 1.37.0's binary.** A lease that WAS held and is gone.
    pub const UDHCPC_LEASE_LOST: &str = "\
udhcpc: lease of 10.9.0.157 obtained from 10.9.0.1, lease time 3600
udhcpc: lease lost, entering init state
";

    /// **boringtun 0.7.1 message strings** (`src/noise/mod.rs` tracing). The initiator's
    /// happy path: it sends an initiation and *receives the response* — that response is
    /// the only thing that means the session is up.
    pub const BORINGTUN_HANDSHAKE_OK: &str = "\
Sending handshake_initiation
Received handshake_response
";

    /// **boringtun 0.7.1 message strings** (`src/noise/timers.rs:234`). The initiation is
    /// being retried because nothing answered. Contains the word "handshake" and is a
    /// FAILURE — this is the line the old bare substring check called success.
    pub const BORINGTUN_REKEY_TIMEOUT: &str = "\
Sending handshake_initiation
HANDSHAKE(REKEY_TIMEOUT)
Sending handshake_initiation
HANDSHAKE(REKEY_TIMEOUT)
";

    /// **boringtun 0.7.1 message strings** (`src/noise/timers.rs:156,209`). A session that
    /// was established and has now expired.
    pub const BORINGTUN_SESSION_EXPIRED: &str = "\
Received handshake_response
SESSION_EXPIRED(REJECT_AFTER_TIME)
CONNECTION_EXPIRED(REJECT_AFTER_TIME * 3)
";

    /// **boringtun 0.7.1**: only an initiation sent, nothing back yet. The partial case.
    pub const BORINGTUN_ATTEMPT_ONLY: &str = "Sending handshake_initiation\n";

    /// **`wg(8)` `wg show` output format.** The only place an age signal exists.
    pub const WG_SHOW_FRESH: &str = "\
peer: xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
  endpoint: 10.9.0.1:51820
  latest handshake: 42 seconds ago
  transfer: 1.85 KiB received, 2.31 KiB sent
";

    /// **`wg(8)` `wg show`** with a handshake older than WireGuard's `REJECT_AFTER_TIME`
    /// (180 s, boringtun `src/noise/timers.rs:20`) — the session is dead.
    pub const WG_SHOW_STALE: &str = "\
peer: xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
  endpoint: 10.9.0.1:51820
  latest handshake: 6 minutes, 12 seconds ago
  transfer: 1.85 KiB received, 2.31 KiB sent
";
}

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

    // A valid 32-byte Curve25519 key, base64 (from tunnr wg-appliance's own test vector).
    // Both keys feed `validate_wg_pubkey`, so they live with the gated tests.
    #[cfg(feature = "sockets")]
    const PEER_A: &str = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
    // A second distinct valid 32-byte key (bytes 0..32, base64) — 44 chars.
    #[cfg(feature = "sockets")]
    const PEER_B: &str = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=";

    #[test]
    fn tunnr_appliance_socket_composes_the_five_fixtures_plus_the_boot() {
        // RED if the registry stops composing the tunnr-appliance socket per the design.
        let plan = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Test);
        assert_eq!(plan.socket_name, "tunnr-test-socket");
        for f in [
            FixtureKind::TestNet,
            FixtureKind::Dhcp,
            FixtureKind::Dns,
            FixtureKind::WireGuardPeer,
            FixtureKind::KvmBoot,
        ] {
            assert!(
                plan.fixtures.contains(&f),
                "tunnr-appliance socket includes {}",
                f.as_str()
            );
        }
        assert!(plan.has_wireguard());
        assert!(
            plan.shows_wg_panel,
            "Test-mode tunnr-appliance shows the WG keys panel"
        );
        // The bulb's own boot is provisioned AFTER the net services are up.
        let net_i = plan
            .fixtures
            .iter()
            .position(|f| *f == FixtureKind::TestNet)
            .unwrap();
        let boot_i = plan
            .fixtures
            .iter()
            .position(|f| *f == FixtureKind::KvmBoot)
            .unwrap();
        assert!(
            net_i < boot_i,
            "the test net comes up before the appliance boots"
        );
    }

    #[test]
    fn prod_mode_is_the_same_composition_without_the_keys_panel() {
        let test = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Test);
        let prod = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Prod);
        assert_eq!(test.fixtures, prod.fixtures, "same socket, two modes");
        assert!(
            test.shows_wg_panel && !prod.shows_wg_panel,
            "the keys panel is a Test-mode surface"
        );
    }

    #[test]
    fn existing_kvm_and_iso_types_are_the_degenerate_single_boot_socket() {
        // The generalization must NOT change what the korp matrix already does.
        for ty in [BuildThingType::Kvm, BuildThingType::BootableIso] {
            let plan = SocketPlan::for_type(ty, Mode::Test);
            assert_eq!(
                plan.fixtures,
                vec![FixtureKind::KvmBoot],
                "{}: single-boot socket",
                ty.as_str()
            );
            assert_eq!(plan.consoles, 1);
            assert!(!plan.shows_wg_panel);
        }
    }

    #[test]
    fn the_k8s_socket_names_every_unbuilt_fixture_instead_of_a_dead_deploy_button() {
        // The k8s playground is DESIGNED but nothing boots a cluster node for it. The
        // socket must say exactly that, per fixture, and must NOT report itself healthy.
        // RED if a k8s fixture is ever quietly marked Ready without a provisioner, and RED
        // if one gains a provisioner without this list being updated.
        let dep = socket_for(BuildThingType::K8sWorkload).light(Mode::Test);
        assert!(
            !dep.all_ok(),
            "no provisioner exists — the socket cannot be healthy"
        );

        let refused: Vec<FixtureKind> = dep.refusals().into_iter().map(|(k, _)| k).collect();
        for k in [
            FixtureKind::K8sControlPlane,
            FixtureKind::K8sWorker,
            FixtureKind::Cni,
            FixtureKind::OciRegistry,
        ] {
            assert!(
                refused.contains(&k),
                "{} must declare itself unbuilt",
                k.as_str()
            );
            let h = &dep.fixture(k).unwrap().health;
            assert!(
                matches!(h, FixtureHealth::NotImplemented(_)),
                "{}: {h:?}",
                k.as_str()
            );
            assert!(
                h.why().unwrap().len() > 20,
                "{} must say WHY, usefully",
                k.as_str()
            );
        }
        // The reason points at the REAL seam, so the gap is actionable, not just a TODO.
        let cp = dep
            .fixture(FixtureKind::K8sControlPlane)
            .unwrap()
            .health
            .why()
            .unwrap();
        assert!(
            cp.contains("MachineConfig") && cp.contains("nothing boots"),
            "{cp}"
        );

        // The pane still gets a full topology + the reasons to render.
        let js = dep.json();
        assert_eq!(js["all_ok"], false);
        // control-plane + BOTH workers + cni + registry: every unbuilt node gets its own
        // row, so a 2-worker cluster does not hide one worker behind the other.
        assert_eq!(js["refusals"].as_array().unwrap().len(), 5);
        assert_eq!(js["fixtures"].as_array().unwrap().len(), dep.fixtures.len());
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn the_k8s_socket_carries_the_cluster_probes_not_just_app_alive() {
        let names: Vec<String> = socket_for(BuildThingType::K8sWorkload)
            .probes()
            .iter()
            .map(|p| p.name().to_string())
            .collect();
        for want in ["nodes-ready", "workload-healthy", "app-alive"] {
            assert!(
                names.contains(&want.to_string()),
                "k8s socket runs {want}: {names:?}"
            );
        }
        // A plain single-boot socket does NOT pretend to have cluster probes.
        let kvm: Vec<String> = socket_for(BuildThingType::Kvm)
            .probes()
            .iter()
            .map(|p| p.name().to_string())
            .collect();
        assert_eq!(kvm, vec!["app-alive".to_string()]);
    }

    #[test]
    fn nodes_ready_probe_is_fail_closed_on_silence_and_on_a_half_ready_cluster() {
        let probe = NodesReadyProbe { expected_nodes: 3 };

        // Silence is NOT success — a cluster that never reported its nodes fails.
        let v = probe.run("booting…\nno kubelet yet\n");
        assert!(!v.passed, "an empty roster must fail");
        assert!(v.detail.contains("never reported"), "{}", v.detail);

        // A full, healthy roster passes (header skipped, console timestamps ignored).
        let ok = "\
[    3.11] cluster came up
NAME    STATUS   ROLES           AGE   VERSION
cp1     Ready    control-plane   2m    v1.31.0
w1      Ready    <none>          1m    v1.31.0
w2      Ready    <none>          1m    v1.31.0
";
        assert!(probe.run(ok).passed, "{}", probe.run(ok).detail);
        assert_eq!(
            NodesReadyProbe::roster(ok).len(),
            3,
            "header row is not a node"
        );

        // One node NotReady → RED, and the verdict names the node.
        let half = ok.replace("w2      Ready", "w2      NotReady");
        let v = probe.run(&half);
        assert!(!v.passed, "a NotReady node fails the cluster");
        assert!(v.detail.contains("w2"), "{}", v.detail);

        // A node short of the expected count → RED (a worker that never joined).
        let two = "NAME STATUS\ncp1 Ready\nw1 Ready\n";
        assert!(!probe.run(two).passed, "2 of 3 expected nodes is not ready");
    }

    #[test]
    fn scheduling_disabled_is_not_ready() {
        // `Ready,SchedulingDisabled` is a cordoned node: it will run nothing new. RED if
        // the probe ever accepts it by prefix-matching "Ready".
        let roster = NodesReadyProbe::roster("NAME STATUS\nw1 Ready,SchedulingDisabled\n");
        assert_eq!(roster.len(), 1, "it is still a node on the roster");
        assert!(
            !roster[0].is_ready(),
            "a cordoned node is not a usable node"
        );
        assert!(
            !NodesReadyProbe { expected_nodes: 1 }
                .run("NAME STATUS\nw1 Ready,SchedulingDisabled\n")
                .passed
        );
    }

    #[test]
    fn workload_healthy_probe_refuses_a_rolled_out_claim_that_is_under_replicated() {
        // The fake-green shape for a workload: a console that SAYS it rolled out while a
        // replica column says 1/3. The claim must not win over the count.
        let lying = "\
deployment \"korp\" successfully rolled out
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
korp   1/3     3            1           40s
";
        let v = WorkloadHealthyProbe.run(lying);
        assert!(!v.passed, "under-replication beats a rollout claim");
        assert!(v.detail.contains("under-replicated"), "{}", v.detail);

        // Genuinely healthy: the claim AND the count agree.
        let good = lying.replace("1/3     3            1", "3/3     3            3");
        assert!(
            WorkloadHealthyProbe.run(&good).passed,
            "{}",
            WorkloadHealthyProbe.run(&good).detail
        );

        // No rollout evidence at all → RED (a workload nobody saw come up).
        let v = WorkloadHealthyProbe.run("booting…\nnothing deployed\n");
        assert!(!v.passed);
        assert!(v.detail.contains("never reported ready"), "{}", v.detail);

        // A CIDR is not a replica count — the parser must not read 10.9.0.0/24 as 10/24.
        assert!(WorkloadHealthyProbe::replica_pairs("route via 10.9.0.0/24\n").is_empty());
    }

    #[test]
    fn k8s_socket_is_a_multi_node_console_grid() {
        let plan = SocketPlan::for_type(BuildThingType::K8sWorkload, Mode::Test);
        assert!(plan.consoles > 1, "a k8s workload lights a multi-node grid");
        assert!(plan.fixtures.contains(&FixtureKind::K8sControlPlane));
        assert!(plan.fixtures.contains(&FixtureKind::Cni));
    }

    #[test]
    fn tunnr_test_socket_accepts_only_the_tunnr_appliance() {
        let s = TunnrTestSocket;
        assert!(s.accepts(BuildThingType::TunnrAppliance));
        assert!(!s.accepts(BuildThingType::Kvm));
        assert_eq!(
            s.plan(Mode::Test),
            SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Test)
        );
    }

    #[test]
    fn build_thing_kind_strings_map_to_types() {
        assert_eq!(
            BuildThingType::from_kind("iso"),
            Some(BuildThingType::BootableIso)
        );
        assert_eq!(BuildThingType::from_kind("vm"), Some(BuildThingType::Kvm));
        assert_eq!(
            BuildThingType::from_kind("tunnr-appliance"),
            Some(BuildThingType::TunnrAppliance)
        );
        assert_eq!(BuildThingType::from_kind("nonsense"), None);
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn wg_pubkey_validation_accepts_32_bytes_and_rejects_junk() {
        assert!(
            validate_wg_pubkey(PEER_A).is_ok(),
            "a real 32-byte key validates"
        );
        assert_eq!(
            validate_wg_pubkey("not base64!!!"),
            Err(WgKeyError::BadBase64)
        );
        // "AAAA" decodes to 3 bytes → wrong length.
        assert_eq!(validate_wg_pubkey("AAAA"), Err(WgKeyError::WrongLength(3)));
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn keys_panel_adds_validates_dedups_and_watches_the_handshake_go_green() {
        let mut panel = WgKeyPanel::new();
        panel.set_own_public_key(PEER_A).expect("own key");
        assert_eq!(panel.own_public_key.as_deref(), Some(PEER_A));

        // Add a peer with an endpoint + allowed IPs.
        panel
            .add_peer(
                PEER_B,
                Some("10.9.0.1:51820".into()),
                vec!["10.9.0.0/24".into()],
            )
            .expect("add valid peer");
        assert_eq!(panel.peers.len(), 1);
        assert_eq!(panel.peers[0].handshake, Handshake::NeverConnected);
        assert_eq!(panel.established_count(), 0);

        // A duplicate is rejected; junk is rejected — the panel never holds a bad key.
        assert_eq!(
            panel.add_peer(PEER_B, None, vec![]),
            Err(WgKeyError::Duplicate),
            "same pubkey twice is rejected"
        );
        assert_eq!(
            panel.add_peer("not base64!!!", None, vec![]),
            Err(WgKeyError::BadBase64)
        );
        assert_eq!(
            panel.peers.len(),
            1,
            "no bad/duplicate peer entered the panel"
        );

        // Watch the handshake go green.
        panel.mark_handshake(
            PEER_B,
            Handshake::Established {
                last_handshake_secs: 2,
            },
        );
        assert!(panel.peers[0].handshake.is_up());
        assert_eq!(
            panel.established_count(),
            1,
            "the peer's handshake went green"
        );
        assert!(panel.routed_cidrs().contains("10.9.0.0/24"));
    }

    #[test]
    fn tunnr_appliance_socket_runs_an_open_test_net_with_dhcp_in_test_mode() {
        let plan = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Test);
        let net = plan.net().expect("tunnr-appliance composes a TestNet");
        assert_eq!(net.subnet_cidr, "10.9.0.0/24");
        assert!(net.open, "Test net is open + inspectable");
        assert!(net.dhcp_range.is_some(), "Test hands out DHCP");
        assert!(!net.dns.is_empty());
    }

    #[test]
    fn prod_mode_closes_the_net_and_drops_dhcp() {
        // RED if Prod stops hardening the net (same subnet, closed, no DHCP).
        let net = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Prod)
            .net()
            .expect("same socket composes the net in Prod");
        assert!(!net.open, "Prod net is closed");
        assert!(net.dhcp_range.is_none(), "Prod is static/seeded — no DHCP");
        assert_eq!(
            net.subnet_cidr, "10.9.0.0/24",
            "same subnet, hardened posture"
        );
    }

    #[test]
    fn a_bare_single_boot_socket_has_no_shared_net() {
        // kvm/iso (the korp matrix) need no shared net → None (not an empty net).
        assert!(
            SocketPlan::for_type(BuildThingType::Kvm, Mode::Test)
                .net()
                .is_none()
        );
        assert!(
            SocketPlan::for_type(BuildThingType::BootableIso, Mode::Test)
                .net()
                .is_none()
        );
    }

    #[test]
    fn tunnr_appliance_topology_gives_net_services_their_endpoints() {
        let plan = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Test);
        let topo = plan.topology();
        let find = |k: FixtureKind| topo.iter().find(|f| f.kind == k).cloned().unwrap();
        // net services sit on the gateway with their well-known ports.
        assert_eq!(
            find(FixtureKind::Dns).endpoint.as_deref(),
            Some("10.9.0.1:53")
        );
        assert_eq!(
            find(FixtureKind::WireGuardPeer).endpoint.as_deref(),
            Some("10.9.0.1:51820")
        );
        assert!(
            find(FixtureKind::Dhcp).detail.contains("DHCP pool"),
            "Test dhcp pool shown"
        );
        // the bulb's own boot serves no address.
        assert!(find(FixtureKind::KvmBoot).endpoint.is_none());
        // one plan per composed fixture.
        assert_eq!(topo.len(), plan.fixtures.len());
    }

    #[test]
    fn prod_topology_shows_dhcp_disabled() {
        let plan = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Prod);
        let dhcp = plan
            .topology()
            .into_iter()
            .find(|f| f.kind == FixtureKind::Dhcp)
            .unwrap();
        assert!(
            dhcp.detail.contains("disabled"),
            "Prod dhcp is off: {}",
            dhcp.detail
        );
    }

    #[test]
    fn a_single_boot_socket_topology_is_just_the_boot() {
        let topo = SocketPlan::for_type(BuildThingType::Kvm, Mode::Test).topology();
        assert_eq!(topo.len(), 1);
        assert_eq!(topo[0].kind, FixtureKind::KvmBoot);
        assert!(
            topo[0].endpoint.is_none(),
            "a bare boot serves no net endpoint"
        );
    }

    #[test]
    fn test_net_backend_is_mode_aware() {
        let tn = TestNet::default();
        assert_eq!(tn.name(), "test-net");
        assert!(tn.spec(Mode::Test).open && tn.spec(Mode::Test).dhcp_range.is_some());
        assert!(!tn.spec(Mode::Prod).open && tn.spec(Mode::Prod).dhcp_range.is_none());
    }

    #[test]
    fn tunnr_test_socket_composes_the_concrete_dhcp_dns_wireguard_fixtures() {
        let fixtures = TunnrTestSocket.fixtures(Mode::Test);
        let kinds: Vec<FixtureKind> = fixtures.iter().map(|f| f.kind()).collect();
        for k in [
            FixtureKind::Dhcp,
            FixtureKind::Dns,
            FixtureKind::WireGuardPeer,
        ] {
            assert!(
                kinds.contains(&k),
                "the tunnr socket composes {}",
                k.as_str()
            );
        }
    }

    #[test]
    fn every_socket_lights_exactly_the_fixtures_its_plan_composes() {
        // THE ownership invariant. The pane draws `plan.topology()` and reads
        // `light().fixtures` — if a socket's fixture list drifts from its plan (as the
        // tunnr socket's hand-written 3-of-6 list did), the pane shows a topology whose
        // rows have no deployment behind them. RED if any socket's plan and deployment
        // disagree, for every registered bulb type, in both modes.
        for ty in ALL_BUILD_THING_TYPES {
            for mode in [Mode::Test, Mode::Prod] {
                let socket = socket_for(ty);
                let plan = socket.plan(mode);
                let dep = socket.light(mode);
                let lit: Vec<FixtureKind> = dep.fixtures.iter().map(|f| f.kind).collect();
                assert_eq!(
                    lit,
                    plan.fixtures,
                    "{} in {}: the lit deployment must be exactly the planned fixtures, in order",
                    ty.as_str(),
                    mode.as_str()
                );
                assert_eq!(dep.socket_name, plan.socket_name);
                assert_eq!(dep.fixtures.len(), plan.topology().len());
            }
        }
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn the_registry_fires_a_socket_that_accepts_the_bulb_for_every_type() {
        // RED if a new BuildThingType is added without a socket answering for it, or if a
        // socket is registered under the wrong type.
        for ty in ALL_BUILD_THING_TYPES {
            let s = socket_for(ty);
            assert!(
                s.accepts(ty),
                "the registry's socket for {} must accept it",
                ty.as_str()
            );
            assert_eq!(s.plan(Mode::Test), SocketPlan::for_type(ty, Mode::Test));
            assert_eq!(
                s.consoles(Mode::Test),
                SocketPlan::for_type(ty, Mode::Test).consoles
            );
            assert!(
                !s.probes().is_empty(),
                "every socket carries at least the app-alive probe"
            );
        }
        // The specialised sockets are the ones the design names; everything else is the
        // degenerate single boot.
        assert_eq!(
            socket_for(BuildThingType::TunnrAppliance).name(Mode::Test),
            "tunnr-test-socket"
        );
        assert_eq!(
            socket_for(BuildThingType::K8sWorkload).name(Mode::Test),
            "k8s-socket"
        );
        assert_eq!(
            socket_for(BuildThingType::Kvm).name(Mode::Test),
            "kvm-socket"
        );
    }

    #[test]
    fn a_single_boot_socket_lights_with_no_shared_net() {
        // The degenerate socket: 1 node, no fixtures beyond its own boot, and NO net —
        // `None`, never an invented empty NetSpec. RED if it grows a fake net.
        let dep = socket_for(BuildThingType::Kvm).light(Mode::Test);
        assert!(dep.net.is_none(), "a bare boot composes no shared net");
        assert!(
            dep.qemu_netdev_arg().is_none(),
            "no net → no netdev arg to attach"
        );
        assert!(dep.json()["net"].is_null());
        assert_eq!(dep.fixtures.len(), 1);
        assert!(matches!(
            dep.fixtures[0].runtime,
            FixtureRuntime::Host { .. }
        ));
        assert!(dep.all_ok(), "today's single boot still lights clean");
        assert!(dep.wg_panel.is_none());
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn teardown_consumes_the_deployment_and_takes_the_keys_with_it() {
        // The design's `teardown(self)`: reverse dependency order, and the operator's
        // entered peer keys do not outlive the socket.
        let mut dep = TunnrTestSocket.light(Mode::Test);
        dep.add_wg_peer(PEER_A, None, vec![]).expect("add a key");
        assert_eq!(dep.wg_panel.as_ref().unwrap().peers.len(), 1);
        let order = dep.teardown();
        // The bulb's own boot goes first, the net it rode on goes last.
        assert_eq!(order.first(), Some(&FixtureKind::KvmBoot));
        assert_eq!(order.last(), Some(&FixtureKind::TestNet));
    }

    #[test]
    fn dhcp_fixture_serves_the_test_pool_and_goes_static_in_prod() {
        let net_test = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Test)
            .net()
            .unwrap();
        let cfg = DhcpFixture.config(&net_test);
        assert_eq!(cfg.gateway, "10.9.0.1");
        assert_eq!(cfg.range, Some(("10.9.0.100".into(), "10.9.0.200".into())));
        assert!(cfg.dns.contains(&"10.9.0.1".to_string()));
        assert!(
            DhcpFixture.serves_leases(&net_test),
            "Test net hands out leases"
        );

        let net_prod = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Prod)
            .net()
            .unwrap();
        assert!(
            DhcpFixture.config(&net_prod).range.is_none(),
            "Prod is static — no pool"
        );
        assert!(
            !DhcpFixture.serves_leases(&net_prod),
            "Prod hands out no leases"
        );
    }

    #[test]
    fn wireguard_peer_fixture_endpoint_and_keys_panel() {
        let net = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Test)
            .net()
            .unwrap();
        let cfg = WireGuardPeerFixture.config(&net);
        assert_eq!(
            cfg.endpoint, "10.9.0.1:51820",
            "the appliance's WG endpoint on the gateway"
        );
        assert_eq!(cfg.listen_port, 51820);
        assert_eq!(
            cfg.allowed_ips,
            vec!["10.9.0.0/24".to_string()],
            "routes the test subnet"
        );
        // The fixture hands the pane a fresh keys panel (the runtime fills it live).
        let panel = WireGuardPeerFixture.new_key_panel();
        assert!(panel.own_public_key.is_none() && panel.peers.is_empty());
    }

    #[test]
    fn fixture_plan_matches_the_socket_topology_row() {
        // A fixture's plan() agrees with the socket topology's row for its kind.
        let net = SocketPlan::for_type(BuildThingType::TunnrAppliance, Mode::Test)
            .net()
            .unwrap();
        let wg = WireGuardPeerFixture.plan(Some(&net), Mode::Test);
        assert_eq!(wg.kind, FixtureKind::WireGuardPeer);
        assert_eq!(wg.endpoint.as_deref(), Some("10.9.0.1:51820"));
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn app_alive_probe_bridges_the_heartbeat_contract_app_agnostically() {
        use app_heartbeat::{AppFunction, AppHeartbeat};
        // A synthetic (non-korp) app self-reports on the console; the socket probe reads
        // WHATEVER it emitted — no app is hardcoded.
        let hb = AppHeartbeat::ready("acme-widget", "1.0").with_function(
            AppFunction::new("greet", "prints a greeting").expecting(["ACME: hello"]),
        );
        let console = format!("boot…\n{}\nACME: hello\n", hb.to_line());

        let probe = AppAliveProbe::default();
        let verdict = probe.run(&console);
        assert_eq!(verdict.probe, "app-alive");
        assert!(
            verdict.passed,
            "deployed + beating + function proven: {}",
            verdict.detail
        );

        // The per-function roster the pane drives one robot UI test each over.
        let report = probe.report(&console);
        assert_eq!(report.app.as_deref(), Some("acme-widget"));
        assert_eq!(report.functions.len(), 1);
        assert!(report.functions[0].passed);
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn app_alive_probe_fails_when_no_app_reported() {
        // RED if a node with no APP-ALIVE line is ever called healthy.
        let verdict = AppAliveProbe::default().run("boot…\nsome noise, no manifest\n");
        assert!(!verdict.passed);
        assert!(verdict.detail.contains("never reported ready"));
    }

    #[test]
    fn lighting_test_mode_provisions_dhcp_dns_via_slirp_and_wg_via_payload() {
        // The TunnrTestSocket lights up: DHCP + DNS realized by slirp's built-ins (the
        // netdev carries the pool + resolver), WireGuard by the wg-appliance payload.
        let dep = TunnrTestSocket.light(Mode::Test);
        assert_eq!(dep.socket_name, "tunnr-test-socket");
        assert!(
            dep.all_ok(),
            "every fixture provisions OK in Test mode: {:?}",
            dep.refusals()
        );

        // DHCP + DNS ride the same slirp netdev; the arg realizes the pool + resolver.
        let arg = dep
            .qemu_netdev_arg()
            .expect("the tunnr socket runs on a shared net");
        assert!(
            arg.contains("net=10.9.0.0/24"),
            "netdev carries the subnet: {arg}"
        );
        assert!(
            arg.contains("dhcpstart=10.9.0.100"),
            "slirp serves the DHCP pool: {arg}"
        );
        assert!(
            arg.contains("dns=10.9.0.1"),
            "slirp forwards DNS at the gateway: {arg}"
        );
        assert!(
            arg.contains("restrict=off"),
            "Test net is OPEN (inspectable): {arg}"
        );

        let dhcp = dep.fixture(FixtureKind::Dhcp).unwrap();
        assert_eq!(
            dhcp.health,
            FixtureHealth::Ready,
            "DHCP is provisioned (pool present)"
        );
        assert!(
            matches!(dhcp.runtime, FixtureRuntime::SlirpBuiltin { .. }),
            "DHCP = slirp built-in"
        );
        assert_eq!(
            dep.fixture(FixtureKind::Dns).unwrap().health,
            FixtureHealth::Ready
        );

        // WireGuard is the tunnr wg-appliance payload (real boringtun), not a slirp built-in.
        let wg = dep.fixture(FixtureKind::WireGuardPeer).unwrap();
        match &wg.runtime {
            FixtureRuntime::Payload { payload, config } => {
                assert_eq!(payload, "wg-appliance");
                assert!(
                    config.contains("endpoint=10.9.0.1:51820"),
                    "wg endpoint on the gateway: {config}"
                );
            }
            other => panic!("WireGuard fixture must be a tunnr payload, got {other:?}"),
        }
    }

    #[test]
    fn lighting_prod_mode_disables_dhcp_and_hardens_the_net() {
        // A socket is a DEPLOYMENT system: Prod = the same composition, hardened. DHCP is
        // NotApplicable (static/seeded addressing) and the net is closed.
        let dep = TunnrTestSocket.light(Mode::Prod);
        assert_eq!(
            dep.fixture(FixtureKind::Dhcp).unwrap().health,
            FixtureHealth::NotApplicable,
            "hardened Prod net serves no DHCP pool"
        );
        let arg = dep.qemu_netdev_arg().expect("same socket, same net");
        assert!(arg.contains("restrict=on"), "Prod net is CLOSED: {arg}");
        assert!(
            !arg.contains("dhcpstart="),
            "no DHCP pool in a hardened net: {arg}"
        );
    }

    #[test]
    fn prod_refuses_every_test_grade_fixture_instead_of_serving_it() {
        // THE Mode boundary, fail-closed. The only NetworkBackend that exists is
        // slirp/user-net — a test-grade fake. So a Prod tunnr socket REFUSES the fixtures
        // that would ride it (the net itself, the resolver) rather than shipping the Test
        // fixture under a Prod label, and it is NOT all_ok. RED the moment Prod starts
        // quietly serving a test fixture.
        let dep = TunnrTestSocket.light(Mode::Prod);
        assert!(
            !dep.all_ok(),
            "a Prod socket on a test-grade net must not report healthy"
        );

        let refused: Vec<FixtureKind> = dep.refusals().into_iter().map(|(k, _)| k).collect();
        assert!(
            refused.contains(&FixtureKind::TestNet),
            "the slirp net itself is refused"
        );
        assert!(
            refused.contains(&FixtureKind::Dns),
            "a slirp DNS forwarder is not a Prod resolver"
        );
        for (kind, why) in dep.refusals() {
            assert!(
                !why.is_empty(),
                "{} must say WHY it is refused",
                kind.as_str()
            );
        }
        // The DNS refusal names the mode + the offending runtime, so the pane can explain.
        let dns_why = dep
            .fixture(FixtureKind::Dns)
            .unwrap()
            .health
            .why()
            .unwrap()
            .to_string();
        assert!(
            dns_why.contains("Prod") && dns_why.contains("slirp"),
            "{dns_why}"
        );

        // Real things are NOT refused: the wg-appliance payload is real boringtun and the
        // bulb's own boot is the real boot path. Prod hardens, it does not blanket-fail.
        assert_eq!(
            dep.fixture(FixtureKind::WireGuardPeer).unwrap().health,
            FixtureHealth::Ready
        );
        assert_eq!(
            dep.fixture(FixtureKind::KvmBoot).unwrap().health,
            FixtureHealth::Ready
        );
        // And DHCP, which serves nothing in Prod, is NotApplicable — not a refusal.
        assert!(
            !refused.contains(&FixtureKind::Dhcp),
            "an off fixture fakes nothing"
        );
    }

    #[test]
    fn prod_refuses_a_test_grade_fixture_even_when_the_boot_proves_it_served() {
        // The nastiest fake-green path: real evidence off a TEST-grade runtime. A slirp
        // lease genuinely happened — and it still must not turn a Prod fixture green,
        // because what served it was the test fake. RED if confirm() ever promotes one.
        let mut prod = TunnrTestSocket.light(Mode::Prod);
        let mut test = TunnrTestSocket.light(Mode::Test);
        let console = "udhcpc: lease of 10.9.0.137 obtained from 10.9.0.1\n\
                       wg-appliance: Received handshake_response\n";
        prod.confirm(console);
        test.confirm(console);

        // Same console, same fixture: green in Test, refused in Prod.
        assert_eq!(
            test.fixture(FixtureKind::Dns).unwrap().health,
            FixtureHealth::Serving
        );
        assert!(
            matches!(
                prod.fixture(FixtureKind::Dns).unwrap().health,
                FixtureHealth::Down(_)
            ),
            "a slirp lease is TEST evidence — it cannot make a Prod fixture serving"
        );
        // The real payload fixture still confirms in Prod: this is a boundary, not a wall.
        assert_eq!(
            prod.fixture(FixtureKind::WireGuardPeer).unwrap().health,
            FixtureHealth::Serving,
            "real boringtun handshake evidence confirms in Prod too"
        );
    }

    #[test]
    fn prod_will_not_ship_the_tests_auth_less_nocloud_seed() {
        // The Test seed is pre-filled and auth-less by design. Prod must not inherit it;
        // this layer mints no real credentials, so it says so rather than seeding one.
        let test_seed = TunnrTestSocket.light(Mode::Test);
        assert_eq!(
            test_seed.fixture(FixtureKind::NoCloudSeed).unwrap().health,
            FixtureHealth::Ready
        );

        let prod_seed = TunnrTestSocket.light(Mode::Prod);
        let h = &prod_seed.fixture(FixtureKind::NoCloudSeed).unwrap().health;
        assert!(matches!(h, FixtureHealth::NotImplemented(_)), "got {h:?}");
        assert!(h.why().unwrap().contains("credentials"), "{h:?}");
    }

    #[test]
    fn a_new_network_backend_must_claim_production_fitness_explicitly() {
        // Fail-closed default: a backend is test-grade until it says otherwise, so
        // forgetting to think about Prod can never produce a Prod-labelled test net.
        struct Bare;
        impl NetworkBackend for Bare {
            fn name(&self) -> &str {
                "bare"
            }
            fn spec(&self, mode: Mode) -> NetSpec {
                TestNet::default().spec(mode)
            }
        }
        assert!(
            Bare.is_test_grade(),
            "a backend that never claims production IS test-grade"
        );
        assert!(
            TestNet::default().is_test_grade(),
            "slirp/user-net is a test fixture"
        );
    }

    #[test]
    fn confirm_upgrades_fixtures_to_serving_only_on_real_evidence() {
        // Ready → Serving requires OBSERVED runtime proof off a booted node's console.
        let mut dep = TunnrTestSocket.light(Mode::Test);
        // No evidence yet → nothing is claimed as serving (a fixture never overclaims).
        dep.confirm("booting…\nnet coming up, tunnel not established\n");
        assert_eq!(
            dep.fixture(FixtureKind::Dhcp).unwrap().health,
            FixtureHealth::Ready
        );
        assert_eq!(
            dep.fixture(FixtureKind::WireGuardPeer).unwrap().health,
            FixtureHealth::Ready
        );

        // A real DHCPACK lease in the pool + a WireGuard handshake → upgrade to Serving.
        let console = "\
udhcpc: broadcasting DISCOVER
udhcpc: lease of 10.9.0.137 obtained from 10.9.0.1
wg-appliance: Received handshake_response
";
        dep.confirm(console);
        assert_eq!(
            dep.fixture(FixtureKind::Dhcp).unwrap().health,
            FixtureHealth::Serving,
            "a client leased 10.9.0.137 from the pool → DHCP is serving"
        );
        assert_eq!(
            dep.fixture(FixtureKind::Dns).unwrap().health,
            FixtureHealth::Serving,
            "the same slirp stack serves DNS once a lease is seen"
        );
        assert_eq!(
            dep.fixture(FixtureKind::WireGuardPeer).unwrap().health,
            FixtureHealth::Serving,
            "a boringtun handshake → the WireGuard peer is serving"
        );
        assert!(dep.all_ok());
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn korp_fat_boot_lights_the_tunnr_socket_and_confirms_its_fixtures_serving() {
        // (owner, 3e) korp+sidecar is THE canonical app that drives tunnr: its FAT boot
        // lands on the TunnrTestSocket's net (slirp DHCP lease), brings the WireGuard peer
        // up (handshake), and self-reports APP-ALIVE with its container caps. Driving that
        // captured console through the socket proves the fixtures SERVING + korp deployed —
        // the integration the all-cells board asserts, at the data level.
        let mut dep = TunnrTestSocket.light(Mode::Test);
        assert!(dep.all_ok(), "the socket lights up clean");

        let korp_console = "\
[    0.42] udhcpc: sending discover
[    0.51] udhcpc: lease of 10.9.0.142 obtained from 10.9.0.1, lease time 3600
[    0.63] wg-appliance: Received handshake_response from 10.9.0.1:51820
[    1.10] korp-appliance: warehouse open, flightsql listening
APP-ALIVE {\"app\":\"korp\",\"version\":\"appliance-fat\",\"alive\":true,\"boot_ms\":1100,\
\"functions\":[{\"name\":\"warehouse\"},{\"name\":\"falkordb-sidecar\"},{\"name\":\"flightsql\"}],\
\"endpoints\":{\"flightsql\":\"grpc://10.9.0.142:50051\",\"falkordb\":\"10.9.0.143:6379\"},\"gpu\":\"virtio-gpu\"}
";
        // The socket confirms its fixtures from the REAL boot evidence.
        dep.confirm(korp_console);
        assert_eq!(
            dep.fixture(FixtureKind::Dhcp).unwrap().health,
            FixtureHealth::Serving,
            "korp leased an IP → DHCP serving"
        );
        assert_eq!(
            dep.fixture(FixtureKind::Dns).unwrap().health,
            FixtureHealth::Serving,
            "slirp DNS serving alongside"
        );
        assert_eq!(
            dep.fixture(FixtureKind::WireGuardPeer).unwrap().health,
            FixtureHealth::Serving,
            "wg handshake → peer serving"
        );
        assert!(
            dep.all_ok(),
            "every fixture healthy after a real korp FAT boot"
        );

        // korp self-reported alive with its container caps — the app-agnostic probe reads it.
        let report = AppAliveProbe::default().report(korp_console);
        assert_eq!(
            report.app.as_deref(),
            Some("korp"),
            "korp is the reporting app"
        );
        assert!(report.deploy_ok, "korp reached RUN/DEPLOY OK");
        let fns: Vec<&str> = report.functions.iter().map(|f| f.name.as_str()).collect();
        assert!(
            fns.contains(&"falkordb-sidecar"),
            "korp's FAT boot reports its sidecar capability: {fns:?}"
        );
    }

    #[test]
    fn lighting_test_mode_opens_a_wireguard_keys_panel_prod_shows_none() {
        // Mode::Test surfaces the WG keys panel (operator adds keys, watches handshake); a
        // hardened Prod net shows none. Starts empty — it fills from the live boot.
        let test = TunnrTestSocket.light(Mode::Test);
        let panel = test
            .wg_panel
            .as_ref()
            .expect("Test mode opens the WG keys panel");
        assert!(
            panel.own_public_key.is_none() && panel.peers.is_empty(),
            "the panel starts empty"
        );
        assert!(
            test.json()["wg_panel"].is_object(),
            "the pane's JSON surface carries the panel"
        );

        assert!(
            TunnrTestSocket.light(Mode::Prod).wg_panel.is_none(),
            "Prod shows no keys panel"
        );
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn add_a_peer_then_a_boot_handshake_marks_it_established_in_the_panel() {
        // The pane's "add a key" → add_wg_peer (validated); then the live boot's handshake
        // (via confirm) turns the peer's indicator green — the whole panel lifecycle.
        let mut dep = TunnrTestSocket.light(Mode::Test);
        dep.add_wg_peer(
            PEER_A,
            Some("10.9.0.1:51820".into()),
            vec!["10.9.0.0/24".into()],
        )
        .expect("a valid pubkey is accepted");
        // A duplicate is rejected fail-loud.
        assert_eq!(
            dep.add_wg_peer(PEER_A, None, vec![]),
            Err(WgKeyError::Duplicate)
        );
        // A malformed key never enters the panel.
        assert_eq!(
            dep.add_wg_peer("not-base64!!", None, vec![]),
            Err(WgKeyError::BadBase64)
        );

        // Before the boot: the peer is present but not yet up.
        assert_eq!(dep.wg_panel.as_ref().unwrap().established_count(), 0);

        // The live wg-appliance boot produces a handshake → the panel goes green.
        dep.confirm("wg-appliance: Received handshake_response\n");
        let panel = dep.wg_panel.as_ref().unwrap();
        assert_eq!(
            panel.established_count(),
            1,
            "the added peer's handshake is now established"
        );
        let js = dep.json();
        assert_eq!(
            js["wg_panel"]["peers"][0]["up"], true,
            "the pane surface shows the peer green"
        );
        assert_eq!(js["wg_panel"]["established_count"], 1);
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn add_wg_peer_on_a_prod_socket_validates_but_reports_no_panel() {
        // A Prod (no keys panel) socket still validates the key fail-loud, never a silent drop.
        let mut prod = TunnrTestSocket.light(Mode::Prod);
        assert!(prod.wg_panel.is_none());
        assert_eq!(
            prod.add_wg_peer("bad!!", None, vec![]),
            Err(WgKeyError::BadBase64)
        );
        assert!(
            prod.add_wg_peer(PEER_A, None, vec![]).is_err(),
            "a valid key still can't be added w/o a panel"
        );
    }

    #[test]
    fn no_success_marker_can_hide_inside_a_failure_or_attempt_marker() {
        // THE structural guard for this whole bug class. Every fake-green found so far was
        // a success marker that is a substring of something that means the opposite. This
        // test makes that impossible to reintroduce by inspection rather than by luck:
        // no success marker may occur inside any failure/attempt marker, in either
        // direction. RED the moment someone adds a loose marker like bare "handshake".
        let checks: [(&str, &[&str], &[&str]); 2] = [
            ("wireguard", WG_COMPLETION_MARKERS, WG_FAILURE_MARKERS),
            ("dhcp", DHCP_BOUND_MARKERS, DHCP_FAILURE_MARKERS),
        ];
        for (what, success, bad) in checks {
            for s in success {
                for b in bad {
                    assert!(
                        !b.contains(s),
                        "{what}: failure marker {b:?} contains success marker {s:?}"
                    );
                    assert!(
                        !s.contains(b),
                        "{what}: success marker {s:?} contains failure marker {b:?}"
                    );
                }
            }
        }
        for s in WG_COMPLETION_MARKERS {
            for a in WG_ATTEMPT_MARKERS {
                assert!(
                    !a.contains(s),
                    "wireguard: attempt {a:?} contains completion {s:?}"
                );
                assert!(
                    !s.contains(a),
                    "wireguard: completion {s:?} contains attempt {a:?}"
                );
            }
        }
        // The DHCP bound markers must not appear in an attempt line either — but here the
        // guard is different: attempts legitimately mention leases ("sending renew"), so
        // the real protection is that a bound marker AND a pool address are both required.
        // Assert that pairing directly, on the real capture.
        let net = TestNet::default().spec(Mode::Test);
        let select_only = "udhcpc: broadcasting select for 10.9.0.157, server 10.9.0.1\n";
        assert!(
            !net.saw_dhcp_lease(select_only),
            "a DHCPREQUEST is not a lease"
        );
    }

    #[test]
    fn the_real_udhcpc_capture_classifies_correctly_line_for_line() {
        // Driven by LIVE captures (busybox udhcpc 1.37.0 vs dnsmasq over veth in an
        // unprivileged netns, t14s 2026-07-27) — see `transcripts`.
        let net = TestNet::default().spec(Mode::Test);

        // Success: a bound lease of a pool address.
        assert_eq!(
            net.dhcp_evidence(transcripts::UDHCPC_SUCCESS),
            Evidence::Served
        );
        assert_eq!(
            net.leased_address(transcripts::UDHCPC_SUCCESS),
            Some("10.9.0.157".parse().unwrap())
        );

        // Failure, both real variants.
        for t in [
            transcripts::UDHCPC_NO_SERVER,
            transcripts::UDHCPC_NO_LEASE_BACKGROUND,
        ] {
            match net.dhcp_evidence(t) {
                Evidence::Failed(l) => assert!(l.contains("no lease"), "{l}"),
                other => panic!("a client that got no lease must be Failed, got {other:?}"),
            }
        }

        // Partial: it tried, it was cut off. Unknown — and it SAYS it only saw an attempt.
        match net.dhcp_evidence(transcripts::UDHCPC_PARTIAL) {
            Evidence::Unknown(w) => {
                assert!(w.contains("attempt") && w.contains("10.9.0.100"), "{w}");
            }
            other => panic!("a truncated exchange is Unknown, got {other:?}"),
        }

        // Silence.
        assert_eq!(
            net.dhcp_evidence("booting…\n"),
            Evidence::Unknown("no DHCP activity on the console".to_string())
        );

        // A NAK and a lost lease are failures, from busybox's own message strings.
        assert!(matches!(
            net.dhcp_evidence(transcripts::UDHCPC_NAK),
            Evidence::Failed(_)
        ));
        match net.dhcp_evidence(transcripts::UDHCPC_LEASE_LOST) {
            // The lease was real and is GONE — failure must beat the earlier success line.
            Evidence::Failed(l) => assert!(l.contains("lease lost"), "{l}"),
            other => panic!("a lost lease is a failure, got {other:?}"),
        }
    }

    #[test]
    fn a_dhcprequest_is_not_a_lease_and_neither_is_an_out_of_pool_address() {
        // The two halves of the DHCP rule, each isolated. RED if either is dropped.
        let net = TestNet::default().spec(Mode::Test);

        // (a) A pool address WITHOUT a bound marker — the real `broadcasting select` line.
        assert!(
            net.leased_address("udhcpc: broadcasting select for 10.9.0.157, server 10.9.0.1\n")
                .is_none()
        );

        // (b) A bound marker with an address OUTSIDE the pool (.100–.200). A static or
        //     server-side address is not a lease from this pool.
        assert!(
            net.leased_address("dhclient: bound to 10.9.0.55 -- renewal in 1800 seconds\n")
                .is_none()
        );
        assert!(net.in_dhcp_pool("10.9.0.157".parse().unwrap()));
        assert!(
            !net.in_dhcp_pool("10.9.0.55".parse().unwrap()),
            ".55 is below the pool"
        );
        assert!(
            !net.in_dhcp_pool("10.9.0.1".parse().unwrap()),
            "the gateway is never a lease"
        );

        // (c) Both together — the ISC dhclient wording, in-pool.
        assert!(
            net.leased_address("dhclient: bound to 10.9.0.157 -- renewal in 1800 seconds\n")
                .is_some()
        );

        // A Prod net has no pool at all, so nothing can be leased from one.
        let prod = TestNet::default().spec(Mode::Prod);
        assert!(prod.leased_address(transcripts::UDHCPC_SUCCESS).is_none());
    }

    #[test]
    fn addresses_are_parsed_without_swallowing_versions_or_lease_times() {
        // The scanner must not turn "v1.37.0" or "lease time 3600" into an address.
        let found = addresses_in("udhcpc: started, v1.37.0 lease time 3600 from 10.9.0.1,");
        assert_eq!(
            found,
            vec!["10.9.0.1".parse::<std::net::Ipv4Addr>().unwrap()]
        );
    }

    #[test]
    fn a_handshake_attempt_is_not_a_completed_handshake() {
        // boringtun logs `Sending handshake_initiation` on every ATTEMPT. An initiation
        // nobody answered is a tunnel that never came up. RED if a bare "handshake"
        // substring check ever comes back.
        match wg_evidence(transcripts::BORINGTUN_ATTEMPT_ONLY) {
            Evidence::Unknown(w) => assert!(w.contains("never observed to complete"), "{w}"),
            other => panic!("an unanswered initiation is Unknown, got {other:?}"),
        }
        // The completion is what counts.
        assert_eq!(
            wg_evidence(transcripts::BORINGTUN_HANDSHAKE_OK),
            Evidence::Served
        );
        // And the retry WARN — which literally contains "HANDSHAKE" — is a FAILURE.
        match wg_evidence(transcripts::BORINGTUN_REKEY_TIMEOUT) {
            Evidence::Failed(l) => assert!(l.to_lowercase().contains("rekey_timeout"), "{l}"),
            other => panic!("a rekey timeout is a failure, got {other:?}"),
        }
        // An expired session beats the earlier successful response line.
        match wg_evidence(transcripts::BORINGTUN_SESSION_EXPIRED) {
            Evidence::Failed(l) => assert!(l.to_lowercase().contains("expired"), "{l}"),
            other => panic!("an expired session is a failure, got {other:?}"),
        }
        assert_eq!(
            wg_evidence("booting…\n"),
            Evidence::Unknown("no WireGuard activity on the console".to_string())
        );
    }

    #[test]
    fn every_refusal_and_every_gap_names_what_would_fix_it() {
        // A reason that only says "test-grade" or "not implemented" leaves an operator
        // stuck. Every Down/NotImplemented reason across every registered socket, in both
        // modes, must carry a concrete remedy. RED if a new refusal ships as a dead end.
        let mut seen = 0;
        for ty in ALL_BUILD_THING_TYPES {
            for mode in [Mode::Test, Mode::Prod] {
                let dep = socket_for(ty).light(mode);
                for (kind, why) in dep.refusals() {
                    assert!(
                        why.contains(REMEDY_MARKER),
                        "{}/{}: {} refuses without saying how to fix it: {why}",
                        ty.as_str(),
                        mode.as_str(),
                        kind.as_str()
                    );
                    // The remedy must be more than the marker itself.
                    let remedy = why.split(REMEDY_MARKER).nth(1).unwrap_or("").trim();
                    assert!(
                        remedy.len() > 30,
                        "{}: remedy too thin: {remedy:?}",
                        kind.as_str()
                    );
                    seen += 1;
                }
            }
        }
        assert!(
            seen >= 10,
            "the sweep must actually cover the known gaps, saw {seen}"
        );
    }

    #[test]
    fn the_prod_net_refusal_names_the_one_change_that_would_fix_it() {
        // The single most important remedy in the system: everything slirp serves is
        // waiting on one thing, and the refusal says so by name.
        let dep = TunnrTestSocket.light(Mode::Prod);
        let why = dep
            .fixture(FixtureKind::TestNet)
            .unwrap()
            .health
            .why()
            .unwrap();
        assert!(why.contains("NetworkBackend"), "{why}");
        assert!(
            why.contains("is_test_grade"),
            "it names the exact predicate: {why}"
        );
        // And the Prod seed points at the credential sources this crate already owns.
        let seed = dep
            .fixture(FixtureKind::NoCloudSeed)
            .unwrap()
            .health
            .why()
            .unwrap();
        assert!(seed.contains("openbao") || seed.contains("pki"), "{seed}");
    }

    #[test]
    fn a_stale_handshake_is_not_a_live_peer() {
        // WireGuard's REJECT_AFTER_TIME (180 s) is the protocol's own cutoff: past it the
        // session's keys are refused. A peer whose last handshake is older than that is
        // dead however green it looked. RED if `is_up` goes back to ignoring age.
        assert!(
            Handshake::Established {
                last_handshake_secs: 42
            }
            .is_up()
        );
        assert!(
            Handshake::Established {
                last_handshake_secs: 180
            }
            .is_up(),
            "exactly at the limit"
        );
        let stale = Handshake::Established {
            last_handshake_secs: 372,
        };
        assert!(!stale.is_up(), "past REJECT_AFTER_TIME the session is dead");
        assert!(stale.is_stale());
        assert_eq!(
            stale.as_str(),
            "stale",
            "the pane must distinguish stale from established"
        );

        // The age signal is real and comes from `wg show`.
        assert_eq!(
            parse_handshake_age_secs(transcripts::WG_SHOW_FRESH),
            Some(42)
        );
        assert_eq!(
            parse_handshake_age_secs(transcripts::WG_SHOW_STALE),
            Some(6 * 60 + 12)
        );
        // A boot console carries NO age signal — we must not invent one.
        assert_eq!(
            parse_handshake_age_secs(transcripts::BORINGTUN_HANDSHAKE_OK),
            None
        );
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn a_wg_show_capture_ages_the_panel_and_a_stale_peer_goes_dark() {
        // End to end: the operator adds a key, a fresh `wg show` marks it green, and a
        // later STALE `wg show` takes the green away without anything "failing".
        let mut dep = TunnrTestSocket.light(Mode::Test);
        dep.add_wg_peer(PEER_A, None, vec!["10.9.0.0/24".into()])
            .unwrap();

        dep.confirm(transcripts::WG_SHOW_FRESH);
        assert_eq!(
            dep.wg_panel.as_ref().unwrap().established_count(),
            1,
            "fresh handshake is green"
        );
        assert_eq!(
            dep.json()["wg_panel"]["peers"][0]["handshake"],
            "established"
        );

        dep.confirm(transcripts::WG_SHOW_STALE);
        let panel = dep.wg_panel.as_ref().unwrap();
        assert_eq!(
            panel.established_count(),
            0,
            "a stale session is not an established peer"
        );
        assert_eq!(dep.json()["wg_panel"]["peers"][0]["handshake"], "stale");
        assert_eq!(dep.json()["wg_panel"]["peers"][0]["up"], false);
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn the_peer_panel_follows_the_tunnel_down_instead_of_staying_green() {
        // The fake green one level in: the FIXTURE goes Down on failure evidence, but the
        // PEER row used to stay Established because confirm only ever marked peers up.
        // RED if a peer is left green while the tunnel it rides is proven dead.
        let mut dep = TunnrTestSocket.light(Mode::Test);
        dep.add_wg_peer(PEER_A, None, vec![]).unwrap();
        dep.confirm(transcripts::BORINGTUN_HANDSHAKE_OK);
        assert_eq!(dep.wg_panel.as_ref().unwrap().established_count(), 1);

        dep.confirm(transcripts::BORINGTUN_SESSION_EXPIRED);
        let panel = dep.wg_panel.as_ref().unwrap();
        assert_eq!(
            panel.peers[0].handshake,
            Handshake::Lost,
            "a peer that was up is now Lost"
        );
        assert!(!panel.peers[0].handshake.is_up());
        assert_eq!(panel.established_count(), 0);
        // Lost is NOT the same as never — the operator can tell "it broke" from "it never
        // worked", which is the whole point of the distinction.
        assert_ne!(panel.peers[0].handshake, Handshake::NeverConnected);
        assert_eq!(dep.json()["wg_panel"]["peers"][0]["handshake"], "lost");
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn a_peer_that_never_connected_stays_never_not_lost() {
        // Nothing was lost if nothing ever worked. RED if a failure blanket-marks peers.
        let mut dep = TunnrTestSocket.light(Mode::Test);
        dep.add_wg_peer(PEER_A, None, vec![]).unwrap();
        dep.confirm(transcripts::BORINGTUN_REKEY_TIMEOUT);
        assert_eq!(
            dep.wg_panel.as_ref().unwrap().peers[0].handshake,
            Handshake::NeverConnected,
            "a peer that never came up did not 'lose' anything"
        );
    }

    #[cfg(feature = "sockets")]
    #[test]
    fn an_unanswered_initiation_shows_the_peer_handshaking_not_established() {
        // The partial case at peer level: an attempt moves the row to Handshaking — honest
        // about activity — and never to Established.
        let mut dep = TunnrTestSocket.light(Mode::Test);
        dep.add_wg_peer(PEER_A, None, vec![]).unwrap();
        dep.confirm(transcripts::BORINGTUN_ATTEMPT_ONLY);
        let panel = dep.wg_panel.as_ref().unwrap();
        assert_eq!(panel.peers[0].handshake, Handshake::Handshaking);
        assert!(!panel.peers[0].handshake.is_up(), "handshaking is not up");
        assert_eq!(panel.established_count(), 0);
    }

    #[test]
    fn the_dnsmasq_server_side_view_also_proves_the_offer_but_not_the_bind() {
        // The server's own log (live capture) shows DISCOVER + OFFER. An OFFER is not a
        // bind — the client still has to REQUEST and be ACKed. So the server-side view
        // alone must NOT be read as a served lease.
        let net = TestNet::default().spec(Mode::Test);
        assert!(
            !matches!(
                net.dhcp_evidence(transcripts::DNSMASQ_SERVER_SIDE),
                Evidence::Served
            ),
            "a DHCPOFFER is not proof the client bound the address"
        );
    }

    #[test]
    fn a_failed_wireguard_handshake_is_not_a_handshake() {
        // REGRESSION. boringtun/wg-quick's FAILURE message is
        // "Handshake did not complete after 5 seconds" — it contains the word "handshake",
        // and the substring check that promoted the peer to Serving read it as success.
        // A tunnel that never came up reported green. RED if that ever returns.
        let mut dep = TunnrTestSocket.light(Mode::Test);
        dep.confirm("wg: Handshake did not complete after 5 seconds, retrying\n");

        let wg = &dep.fixture(FixtureKind::WireGuardPeer).unwrap().health;
        assert!(
            matches!(wg, FixtureHealth::Down(_)),
            "a failed handshake must be Down, got {wg:?}"
        );
        assert!(
            wg.why().unwrap().contains("did not complete"),
            "it quotes the proof: {wg:?}"
        );
        assert!(!dep.all_ok(), "a socket with a dead tunnel is not healthy");
        assert_eq!(
            wg_evidence("wg: Handshake did not complete after 5 seconds\n"),
            Evidence::Failed("wg: Handshake did not complete after 5 seconds".to_string())
        );
    }

    #[test]
    fn a_dhcp_client_that_fell_back_to_a_static_address_never_leased() {
        // REGRESSION. `saw_dhcp_lease` matches any subnet address that is not the gateway,
        // so "udhcpc: no lease, failing over to static 10.9.0.55" — a client that got NO
        // lease — read as a lease and turned DHCP (and, riding it, DNS) green.
        let mut dep = TunnrTestSocket.light(Mode::Test);
        dep.confirm("udhcpc: no lease, failing over to static 10.9.0.55\n");

        let dhcp = &dep.fixture(FixtureKind::Dhcp).unwrap().health;
        assert!(
            matches!(dhcp, FixtureHealth::Down(_)),
            "no lease must be Down, got {dhcp:?}"
        );
        assert!(dhcp.why().unwrap().contains("no lease"), "{dhcp:?}");
        // And DNS must not inherit a green it only ever had by riding DHCP's evidence.
        assert_ne!(
            dep.fixture(FixtureKind::Dns).unwrap().health,
            FixtureHealth::Serving,
            "DNS cannot be serving off a lease that never happened"
        );
    }

    #[test]
    fn failure_evidence_beats_success_evidence_anywhere_on_the_console() {
        // Fail-closed ordering: a console that shows a lease AND then shows the tunnel
        // dropping must not average out to healthy. Failure wins, and it wins even from
        // Serving — a tunnel that dropped is not still serving.
        let mut dep = TunnrTestSocket.light(Mode::Test);
        dep.confirm(
            "udhcpc: lease of 10.9.0.137 obtained from 10.9.0.1\n\
                     wg-appliance: Received handshake_response\n",
        );
        assert_eq!(
            dep.fixture(FixtureKind::WireGuardPeer).unwrap().health,
            FixtureHealth::Serving
        );

        // Later in the same boot, it dies.
        dep.confirm(
            "wg-appliance: Received handshake_response\n\
                     wg: tunnel down, peer unreachable\n",
        );
        let wg = &dep.fixture(FixtureKind::WireGuardPeer).unwrap().health;
        assert!(
            matches!(wg, FixtureHealth::Down(_)),
            "Serving must be revocable, got {wg:?}"
        );
    }

    #[test]
    fn a_silent_console_leaves_a_fixture_unobserved_not_healthy_and_not_failed() {
        // The third state that keeps the other two honest: nothing observed changes
        // nothing. RED if silence ever starts counting as either verdict.
        let net = TestNet::default().spec(Mode::Test);
        assert!(matches!(
            net.dhcp_evidence("booting…\nnothing to report\n"),
            Evidence::Unknown(_)
        ));
        assert!(matches!(wg_evidence("booting…\n"), Evidence::Unknown(_)));

        let mut dep = TunnrTestSocket.light(Mode::Test);
        let before: Vec<FixtureHealth> = dep.fixtures.iter().map(|f| f.health.clone()).collect();
        dep.confirm("booting…\nsome unrelated noise\n");
        let after: Vec<FixtureHealth> = dep.fixtures.iter().map(|f| f.health.clone()).collect();
        assert_eq!(
            before, after,
            "an uninformative console changes no fixture's health"
        );
    }

    #[test]
    fn a_dns_payload_may_not_borrow_the_dhcp_fixtures_proof() {
        // DNS goes green off a DHCP lease ONLY because slirp serves both. Say the resolver
        // is its own payload and the rule must not apply — it needs its own evidence.
        let dhcp = Evidence::Served;
        assert_eq!(
            dns_evidence("udhcpc: lease of 10.9.0.137\n", true, &dhcp),
            Evidence::Served
        );
        assert!(
            matches!(
                dns_evidence("udhcpc: lease of 10.9.0.137\n", false, &dhcp),
                Evidence::Unknown(_)
            ),
            "a standalone resolver is not proven by someone else's DHCP lease"
        );
        // Its own failure is still its own failure, slirp or not.
        assert!(matches!(
            dns_evidence(
                "resolve: Temporary failure in name resolution\n",
                true,
                &dhcp
            ),
            Evidence::Failed(_)
        ));
    }

    #[test]
    fn a_resolver_with_no_nameserver_on_the_net_is_down_not_ready() {
        // A DNS fixture that could not possibly resolve must say so at LIGHT-UP, before any
        // console exists. RED if an unserviceable resolver ever lights green.
        let mut net = TestNet::default().spec(Mode::Test);
        net.dns.clear();
        let h = DnsFixture.light_health(Some(&net), Mode::Test);
        assert!(matches!(h, FixtureHealth::Down(_)), "got {h:?}");
        assert!(h.why().unwrap().contains("resolve nothing"), "{h:?}");
    }

    #[test]
    fn saw_dhcp_lease_needs_a_bound_line_not_merely_an_address() {
        // RED if `saw_dhcp_lease` ever goes back to "any subnet address that is not the
        // gateway". It now means what its name always claimed: a BOUND lease from the pool.
        let net = TestNet::default().spec(Mode::Test);
        assert!(
            !net.saw_dhcp_lease("router at 10.9.0.1 up\n"),
            "the gateway is not a lease"
        );
        // An interface that HAS an address proves nothing about how it got it — it could be
        // static, or a fallback. This line used to count as a lease; it must not.
        assert!(
            !net.saw_dhcp_lease("inet 10.9.0.152/24\n"),
            "a configured address is not evidence a DHCP server handed it out"
        );
        // The real thing, from the live udhcpc capture.
        assert!(net.saw_dhcp_lease(transcripts::UDHCPC_SUCCESS));
    }
}