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

nordisk/dwarves

crates/dwarves-embed/src/holgerbuild.rs

//! **The holger build-MATRIX pane** — the CI forge cockpit's "which of holger's
//! deployment executables are wired to build" (separation-spec item **B2**).
//!
//! holger declares one product list; dwarves builds the products a holger checkout builds
//! (`holger`, `holger-downstream`, `holger-cli`, each one crate and one row). dwarves
//! ORCHESTRATES that build; the **`ResolveHolgerBuild`** RPC resolves the plan against holger's
//! declared build-space (`.nornir/build-variants.toml`) WITHOUT compiling holger — each
//! tier mapped onto its build target (bin · package · materialized `cargo` argv) plus
//! which resolve and whether the plan is complete. This pane renders that MATRIX so a
//! forge operator sees the build landscape (crates/tiers × targets × status) in the
//! dwarves UI.
//!
//! The reusable `<guest>-embed` adapter shape (like [`crate::ForgePanel`] /
//! [`crate::JobsFleetPanel`]): a **thin tonic gRPC client** fetches the reply from a
//! running `dwarves-server` over the [`ResolveHolgerBuild`](crate::pb) RPC, a **pure**
//! mapping projects the wire tiers onto rows, and the matrix renders on the SHARED
//! [`facett_table::Table`] (L5 — do NOT twin a table renderer; the pane just formats each
//! tier to strings). NO `holger`/`nornir` dep (that would be a dependency cycle — nornir
//! already depends on dwarves) and NO dwarves `server` feature.
//!
//! ## Honest states — never a silent blank
//! Every non-render outcome is a DISTINCT, named state (folded into `state_json` for the
//! status line + robot assertions):
//! - **`loaded`** — the reply carried tiers; the matrix renders (with per-tier resolved
//!   status + the plan's `complete` verdict).
//! - **`no_build_space`** — connected, but holger declares no build-space (`found=false`)
//!   — an honest empty, distinct from offline.
//! - **`empty`** — connected, `found=true`, yet zero tiers (a degenerate reply).
//! - **`unreachable`** — could not connect to `dwarves-server`.
//! - **`auth_failed`** — the server rejected the bearer (Unauthenticated/PermissionDenied).
//! - **`not_served`** — the server does not expose `ResolveHolgerBuild` (Unimplemented).
//! - **`error`** — any other RPC failure (the message surfaced for the tooltip).
//!
//! Auto-polls on an interval like the sibling panes, and drives headless over the
//! `state_json`/`update_json` seam (the channel a `nornir-robotui` scenario + a thin gRPC
//! client use) — so the pane is robot-tested without pixels.

use std::time::Duration;

use facett_core::{Facet, Panel};
use facett_table::Table;

use crate::pb;

/// Default seconds between live auto-poll re-resolves of the build matrix. A plan/wiring
/// check is cheap on the server, but it changes rarely — a calmer interval than Forge.
pub const DEFAULT_BUILD_POLL_INTERVAL_S: f64 = 5.0;

/// The pane's default holger checkout when the request leaves `holger_root` empty: the
/// SERVER resolves it (`$DWARVES_HOLGER_REPO`, else `../holger`). The pane sends empty
/// unless a host pins one, so the resolution stays server-side (it has the checkout).
pub const HOLGER_ROOT_ENV: &str = "DWARVES_HOLGER_REPO";

// ── the wire → domain projection (pure, unit-testable) ────────────────────────

/// One resolved (or unwired) holger deployment tier — the domain projection of a wire
/// [`pb::HolgerTier`]. A plain serde row so `state_json` carries the whole matrix for a
/// robot to assert, and so [`tier_cells`] formats it to the shared table's string cells.
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
pub struct TierRow {
    /// The row/variant name (`holger`|`holger-downstream`|`holger-cli`).
    pub tier: String,
    /// The pipeline step id (`build-<tier>`).
    pub step: String,
    /// True iff the tier names a build target holger's build-space declares.
    pub resolved: bool,
    /// The tier's executable name (empty when unwired).
    pub bin: String,
    /// The tier's cargo package (empty when unwired).
    pub package: String,
    /// The materialized `cargo build …` argv (empty when unwired).
    pub command_line: String,
}

/// **Pure** map the wire tiers onto the matrix rows (order preserved). Unit-testable with
/// stub rows (no server).
pub fn tiers_from_wire(rows: &[pb::HolgerTier]) -> Vec<TierRow> {
    rows.iter()
        .map(|t| TierRow {
            tier: t.tier.clone(),
            step: t.step.clone(),
            resolved: t.resolved,
            bin: t.bin.clone(),
            package: t.package.clone(),
            command_line: t.command_line.clone(),
        })
        .collect()
}

/// The build-matrix table's column headers (stable order — the `state_json` shape a deck
/// / render-matrix reads).
pub const MATRIX_COLUMNS: [&str; 5] = ["tier", "status", "bin", "package", "cargo"];

/// Format one tier as the shared table's string cells (aligned with [`MATRIX_COLUMNS`]).
/// An unwired tier reads `unwired` with a `—` placeholder for the empty target fields, so
/// a dropped/renamed executable is VISIBLE in the grid, never a blank row.
fn tier_cells(t: &TierRow) -> Vec<String> {
    let status = if t.resolved { "resolved" } else { "unwired" };
    let dash = |s: &str| {
        if s.is_empty() {
            "—".to_string()
        } else {
            s.to_string()
        }
    };
    vec![
        t.tier.clone(),
        status.to_string(),
        dash(&t.bin),
        dash(&t.package),
        dash(&t.command_line),
    ]
}

/// Build the SHARED [`Table`] from the matrix rows: the header columns + one string-cell
/// row per tier, keyed on the tier name (a stable row id — a re-sort keeps identity). The
/// REUSED renderer (L5); the pane owns only the string formatting.
fn table_from_tiers(tiers: &[TierRow]) -> Table {
    let mut t = Table::new(
        "holger build matrix",
        MATRIX_COLUMNS.iter().map(|c| c.to_string()).collect(),
    );
    for tier in tiers {
        t.push_row(tier_cells(tier));
    }
    t.with_row_ids(tiers.iter().map(|r| r.tier.clone()).collect())
}

// ── thin gRPC fetch (with the honest-state error classification) ──────────────

/// A build-matrix fetch failure, classified into the pane's HONEST states. The pane maps
/// each variant onto a distinct status (never a generic "offline"): a dead server is
/// `Unreachable`, a rejected bearer is `AuthFailed`, a server that does not expose the RPC
/// is `NotServed`, anything else is `Rpc`.
#[derive(Clone, Debug)]
pub enum BuildErr {
    /// Could not connect to `dwarves-server` (dead port / bad url / timeout).
    Unreachable(String),
    /// The server rejected the bearer (gRPC Unauthenticated / PermissionDenied).
    AuthFailed(String),
    /// The server does not expose `ResolveHolgerBuild` (gRPC Unimplemented).
    NotServed(String),
    /// Any other RPC failure (the message is surfaced for the tooltip).
    Rpc(String),
}

impl BuildErr {
    /// The state key this failure maps to (mirrors [`Conn::key`]).
    fn key(&self) -> &'static str {
        match self {
            BuildErr::Unreachable(_) => "unreachable",
            BuildErr::AuthFailed(_) => "auth_failed",
            BuildErr::NotServed(_) => "not_served",
            BuildErr::Rpc(_) => "error",
        }
    }
    /// The human message (for the hover tooltip + tests).
    pub fn message(&self) -> &str {
        match self {
            BuildErr::Unreachable(m)
            | BuildErr::AuthFailed(m)
            | BuildErr::NotServed(m)
            | BuildErr::Rpc(m) => m,
        }
    }
    /// Classify a tonic RPC `Status` into the honest error kinds.
    fn from_status(s: &tonic::Status) -> Self {
        use tonic::Code;
        let msg = format!("{}: {}", s.code(), s.message());
        match s.code() {
            Code::Unauthenticated | Code::PermissionDenied => BuildErr::AuthFailed(msg),
            Code::Unimplemented => BuildErr::NotServed(msg),
            _ => BuildErr::Rpc(msg),
        }
    }
}

/// Normalize an endpoint: prefix `http://` if the scheme is missing.
fn norm_endpoint(endpoint: &str) -> String {
    if endpoint.starts_with("http") {
        endpoint.to_string()
    } else {
        format!("http://{endpoint}")
    }
}

/// Resolve holger's 3-tier build matrix from `dwarves-server` at `endpoint` over the thin
/// `ResolveHolgerBuild` gRPC (Bearer `token`, empty ⇒ no real auth). `holger_root` is
/// resolved on the SERVER's filesystem (empty ⇒ the server's default). Runs on a private
/// current-thread runtime so it is safe from the synchronous egui loop; a 3s connect
/// timeout keeps a dead server from wedging the UI thread. Failures are classified into
/// the honest [`BuildErr`] states — never a panic, never a silent blank.
pub fn fetch_holger_build(
    endpoint: &str,
    token: &str,
    holger_root: &str,
) -> Result<pb::HolgerBuildReply, BuildErr> {
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .map_err(|e| BuildErr::Unreachable(format!("build tokio runtime: {e}")))?;
    let endpoint = norm_endpoint(endpoint);
    let token = token.to_string();
    let holger_root = holger_root.to_string();
    rt.block_on(async move {
        // The ONE shared client bearer-builder (Law #1) — no per-site
        // `format!("Bearer …").parse()` twin. `None` ⇒ open (empty token).
        let bearer = nornir_service::client::bearer(&token)
            .map_err(|e| BuildErr::AuthFailed(format!("parse bearer token: {e}")))?;
        let channel = tonic::transport::Channel::from_shared(endpoint.clone())
            .map_err(|e| BuildErr::Unreachable(format!("invalid server url `{endpoint}`: {e}")))?
            .connect_timeout(Duration::from_secs(3))
            .timeout(crate::READ_TIMEOUT)
            .connect()
            .await
            .map_err(|e| {
                BuildErr::Unreachable(format!("connect to dwarves-server at {endpoint}: {e}"))
            })?;
        let mut client = pb::dwarf_client::DwarfClient::with_interceptor(
            channel,
            move |mut req: tonic::Request<()>| {
                if let Some(b) = &bearer {
                    req.metadata_mut().insert("authorization", b.clone());
                }
                Ok(req)
            },
        );
        let reply = client
            .resolve_holger_build(pb::HolgerBuildRequest { holger_root })
            .await
            .map_err(|s| BuildErr::from_status(&s))?
            .into_inner();
        Ok(reply)
    })
}

// ── the pane ──────────────────────────────────────────────────────────────────

/// The outcome of the last resolve — drives the status line + `state_json`. Every variant
/// is a DISTINCT honest state; there is no silent-blank fallthrough.
#[derive(Clone, Debug, PartialEq, Eq)]
enum Conn {
    /// Never resolved yet.
    Idle,
    /// The reply carried tiers; the matrix renders.
    Loaded,
    /// Connected, but holger declares no build-space (`found=false`) — an honest empty.
    NoBuildSpace,
    /// Connected, `found=true`, yet zero tiers (a degenerate reply).
    Empty,
    /// Could not connect.
    Unreachable,
    /// The bearer was rejected.
    AuthFailed,
    /// The server does not expose the RPC.
    NotServed,
    /// Any other RPC failure.
    Error,
}

impl Conn {
    fn key(&self) -> &'static str {
        match self {
            Conn::Idle => "idle",
            Conn::Loaded => "loaded",
            Conn::NoBuildSpace => "no_build_space",
            Conn::Empty => "empty",
            Conn::Unreachable => "unreachable",
            Conn::AuthFailed => "auth_failed",
            Conn::NotServed => "not_served",
            Conn::Error => "error",
        }
    }
    /// True for the states that render nothing (so `ui` shows the honest empty message).
    fn is_error_like(&self) -> bool {
        !matches!(self, Conn::Loaded)
    }
}

/// The holger build-matrix cockpit pane. A [`Facet`] (hence a [`Panel`] via the blanket
/// impl) a host mounts in a tab.
pub struct HolgerBuildPanel {
    endpoint: String,
    token: String,
    /// The holger checkout the SERVER resolves against (empty ⇒ the server's default).
    holger_root: String,
    /// The rendered matrix rows (the domain projection surfaced in `state_json`).
    tiers: Vec<TierRow>,
    /// The SHARED table the matrix renders on (rebuilt on each successful resolve).
    table: Table,
    conn: Conn,
    /// The plan's completeness verdict + counts from the last successful resolve.
    complete: bool,
    resolved: u32,
    missing: u32,
    found: bool,
    /// The last error's display text (folded into `state_json` for the tooltip + tests).
    error: Option<String>,
    auto_poll: bool,
    poll_interval_s: f64,
    last_poll_s: f64,
}

impl HolgerBuildPanel {
    /// Build the pane for a `dwarves-server` `endpoint` and eagerly do the first resolve.
    /// `holger_root` pins the server-side checkout (empty ⇒ the server's default). Offline
    /// / no build-space ⇒ the honest states, never a panic.
    pub fn new(
        endpoint: impl Into<String>,
        token: impl Into<String>,
        holger_root: impl Into<String>,
    ) -> Self {
        let mut p = Self {
            endpoint: endpoint.into(),
            token: token.into(),
            holger_root: holger_root.into(),
            tiers: Vec::new(),
            table: table_from_tiers(&[]),
            conn: Conn::Idle,
            complete: false,
            resolved: 0,
            missing: 0,
            found: false,
            error: None,
            auto_poll: true,
            poll_interval_s: DEFAULT_BUILD_POLL_INTERVAL_S,
            last_poll_s: 0.0,
        };
        p.refresh();
        p
    }

    /// True when auto-poll is on AND at least one interval has elapsed. Pure →
    /// unit-testable without a clock.
    pub fn poll_due(&self, now_s: f64) -> bool {
        self.auto_poll && (now_s - self.last_poll_s) >= self.poll_interval_s
    }

    /// Turn live auto-poll on/off (the ● live / ⏸ paused toggle).
    pub fn set_auto_poll(&mut self, on: bool) {
        self.auto_poll = on;
    }

    /// (Re-)resolve the build matrix and apply the outcome.
    pub fn refresh(&mut self) {
        let outcome = fetch_holger_build(&self.endpoint, &self.token, &self.holger_root);
        self.apply_fetch(outcome);
    }

    /// Fold a [`fetch_holger_build`] outcome into the pane's rendered state. Split from
    /// [`refresh`] so the transitions drive headless in tests (the same `state_json` seam a
    /// robot reads) without a live server. Each outcome lands in a DISTINCT honest state.
    pub fn apply_fetch(&mut self, outcome: Result<pb::HolgerBuildReply, BuildErr>) {
        match outcome {
            Ok(reply) => {
                self.tiers = tiers_from_wire(&reply.tiers);
                self.table = table_from_tiers(&self.tiers);
                self.complete = reply.complete;
                self.resolved = reply.resolved;
                self.missing = reply.missing;
                self.found = reply.found;
                self.error = None;
                self.conn = if !reply.found {
                    // holger declares no build-space — an honest empty, not offline.
                    Conn::NoBuildSpace
                } else if self.tiers.is_empty() {
                    // found but zero tiers (degenerate) — still honest, not a blank.
                    Conn::Empty
                } else {
                    Conn::Loaded
                };
            }
            Err(e) => {
                // A failed resolve keeps the last-good matrix out of the way (cleared) and
                // lands in the classified honest state — the tooltip carries the detail.
                self.tiers = Vec::new();
                self.table = table_from_tiers(&[]);
                self.complete = false;
                self.resolved = 0;
                self.missing = 0;
                self.found = false;
                self.error = Some(e.message().to_string());
                self.conn = match e {
                    BuildErr::Unreachable(_) => Conn::Unreachable,
                    BuildErr::AuthFailed(_) => Conn::AuthFailed,
                    BuildErr::NotServed(_) => Conn::NotServed,
                    BuildErr::Rpc(_) => Conn::Error,
                };
                debug_assert_eq!(self.conn.key(), e.key());
            }
        }
    }

    /// The honest one-line status message the header shows for the current state.
    fn status_line(&self) -> String {
        match self.conn {
            Conn::Idle => "resolving…".to_string(),
            Conn::Loaded => {
                let verdict = if self.complete {
                    "complete"
                } else {
                    "INCOMPLETE"
                };
                // PLAIN NUMBERS (no thousands separators) — a nordisk law.
                format!(
                    "{} tiers · {} resolved / {} unwired · plan {verdict}",
                    self.tiers.len(),
                    self.resolved,
                    self.missing
                )
            }
            Conn::NoBuildSpace => format!(
                "● connected to {} · holger declares no build-space",
                self.endpoint
            ),
            Conn::Empty => "connected · the build-space declares no tiers".to_string(),
            Conn::Unreachable => {
                format!(
                    "build matrix unavailable — cannot reach dwarves-server at {}",
                    self.endpoint
                )
            }
            Conn::AuthFailed => {
                "auth failed — the dwarves-server rejected the bearer token".to_string()
            }
            Conn::NotServed => {
                "not served — this dwarves-server does not expose ResolveHolgerBuild".to_string()
            }
            Conn::Error => "resolve failed — see the error detail".to_string(),
        }
    }

    /// The status colour for the current state (green live · amber degraded · red down).
    fn status_color(&self) -> egui::Color32 {
        match self.conn {
            Conn::Loaded if self.complete => egui::Color32::from_rgb(120, 190, 120),
            Conn::Loaded => egui::Color32::from_rgb(224, 160, 60), // rendered but incomplete plan
            Conn::NoBuildSpace | Conn::Empty => egui::Color32::from_rgb(150, 150, 150),
            Conn::Idle => egui::Color32::from_rgb(150, 150, 150),
            Conn::Unreachable | Conn::AuthFailed | Conn::NotServed | Conn::Error => {
                egui::Color32::from_rgb(224, 120, 90)
            }
        }
    }
}

impl Facet for HolgerBuildPanel {
    fn title(&self) -> &str {
        "Builds"
    }

    fn ui(&mut self, ui: &mut egui::Ui) {
        // LIVE auto-poll: re-resolve on the interval so a build-space edit shows up.
        let now_s = ui.input(|i| i.time);
        if self.poll_due(now_s) {
            self.refresh();
            self.last_poll_s = now_s;
        }
        if self.auto_poll {
            ui.ctx()
                .request_repaint_after(Duration::from_secs_f32(self.poll_interval_s as f32));
        }
        let mut do_refresh = false;
        ui.horizontal(|ui| {
            if ui
                .button("⟳ Refresh")
                .on_hover_text(format!(
                    "re-resolve holger's build matrix from {}",
                    self.endpoint
                ))
                .clicked()
            {
                do_refresh = true;
            }
            let (glyph, hint) = if self.auto_poll {
                ("● live", "auto-polling — click to pause")
            } else {
                ("⏸ paused", "auto-poll paused — click to resume live")
            };
            if ui.button(glyph).on_hover_text(hint).clicked() {
                self.auto_poll = !self.auto_poll;
                self.last_poll_s = now_s;
            }
            ui.separator();
            let resp = ui.colored_label(self.status_color(), self.status_line());
            if let Some(e) = &self.error {
                resp.on_hover_text(e);
            }
        });
        if do_refresh {
            self.refresh();
            self.last_poll_s = now_s;
        }
        ui.separator();
        // Render the shared matrix table when loaded; else the honest empty message.
        if self.conn.is_error_like() {
            ui.centered_and_justified(|ui| {
                ui.weak(self.status_line());
            });
        } else {
            Facet::ui(&mut self.table, ui);
        }
    }

    fn state_json(&self) -> serde_json::Value {
        serde_json::json!({
            "kind": "holger_build_matrix",
            "endpoint": self.endpoint,
            "holger_root": self.holger_root,
            "status": self.conn.key(),
            // The matrix as data — the whole 3-tier resolution a robot asserts.
            "tiers": self.tiers,
            "tier_count": self.tiers.len(),
            "resolved": self.resolved,
            "missing": self.missing,
            "complete": self.complete,
            "found": self.found,
            "error": self.error,
            // Poll state.
            "auto_poll": self.auto_poll,
            "poll_interval_s": self.poll_interval_s,
            // The shared table's own state (columns + row count) — the reused renderer.
            "table": Facet::state_json(&self.table),
        })
    }

    fn update_json(&mut self, msg_json: &str) {
        // The pane's own control surface (drives it headless from a robot test):
        //   {"refresh": true} / {"poll": true} → re-resolve the build matrix
        //   {"auto_poll": bool}                → toggle live polling
        //   {"holger_root": "…"}               → pin the server-side checkout + re-resolve
        // Anything else is forwarded to the inner table (its sort/select JSON seam).
        if let Ok(v) = serde_json::from_str::<serde_json::Value>(msg_json) {
            if let Some(on) = v.get("auto_poll").and_then(|r| r.as_bool()) {
                self.auto_poll = on;
                return;
            }
            if let Some(root) = v.get("holger_root").and_then(|r| r.as_str()) {
                self.holger_root = root.to_string();
                self.refresh();
                return;
            }
            if v.get("refresh").and_then(|r| r.as_bool()) == Some(true)
                || v.get("poll").and_then(|r| r.as_bool()) == Some(true)
            {
                self.refresh();
                return;
            }
        }
        Facet::update_json(&mut self.table, msg_json);
    }
}

// ── the seam ──────────────────────────────────────────────────────────────────

/// **The holger build-matrix pane as a `Box<dyn Panel>`.** `endpoint` is the
/// `dwarves-server` url (e.g. `http://127.0.0.1:7900`). The bearer comes from the
/// dwarves client chain ([`crate::ClientCredential`]: `$DWARVES_SERVER_TOKEN`, then
/// `~/.dwarves/token`); the server-side holger checkout is pinned from
/// `$DWARVES_HOLGER_REPO` (empty ⇒ the server resolves its own default). The first resolve
/// happens eagerly; offline / no build-space ⇒ an honest state, never a panic.
pub fn holger_build_panel(endpoint: impl Into<String>) -> Box<dyn Panel> {
    let token = crate::ClientCredential::resolve().token;
    let holger_root = std::env::var(HOLGER_ROOT_ENV).unwrap_or_default();
    Box::new(HolgerBuildPanel::new(endpoint.into(), token, holger_root))
}

/// Like [`holger_build_panel`] but with the token + holger root passed explicitly (for
/// hosts that manage their own credentials rather than reading the environment).
pub fn holger_build_panel_with(
    endpoint: impl Into<String>,
    token: impl Into<String>,
    holger_root: impl Into<String>,
) -> Box<dyn Panel> {
    Box::new(HolgerBuildPanel::new(
        endpoint.into(),
        token.into(),
        holger_root.into(),
    ))
}

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

    /// A full reply over holger's product rows (`holger`, `holger-downstream`,
    /// `holger-cli`), all resolved.
    fn full_reply() -> pb::HolgerBuildReply {
        let tier = |name: &str| pb::HolgerTier {
            tier: name.into(),
            step: format!("build-{name}"),
            resolved: true,
            bin: name.into(),
            package: name.into(),
            command_line: format!("cargo build -p {name} --bin {name} --release"),
        };
        pb::HolgerBuildReply {
            tiers: vec![tier("holger"), tier("holger-downstream"), tier("holger-cli")],
            resolved: 3,
            missing: 0,
            complete: true,
            found: true,
        }
    }

    /// PURE mapping: the wire tiers project onto the matrix rows (order preserved), and an
    /// unwired tier keeps its empty target fields. RED-when-broken: a dropped field mapping
    /// loses the bin/package/status.
    #[test]
    fn wire_tiers_map_to_matrix_rows() {
        let rows = tiers_from_wire(&full_reply().tiers);
        assert_eq!(rows.len(), 3, "one row per tier");
        assert_eq!(rows[0].tier, "holger");
        assert_eq!(rows[0].bin, "holger");
        assert_eq!(rows[0].package, "holger");
        assert!(rows[0].resolved);
        assert!(
            rows[2].command_line.starts_with("cargo build"),
            "the cargo argv crosses"
        );
        assert!(tiers_from_wire(&[]).is_empty(), "no tiers → empty matrix");

        // The shared table gets the columns + one row per tier, keyed on the tier name.
        let t = table_from_tiers(&rows);
        let st = Facet::state_json(&t);
        assert_eq!(st["rows"], 3, "the reused table holds one row per tier");
        assert_eq!(st["columns"][1], "status", "the status column is present");
    }

    /// **The headline live-data oracle — RED-when-empty.** A full 3-tier reply driven
    /// through the real `apply_fetch` path makes the pane render LIVE rows: `loaded`, the
    /// matrix has 3 tiers, the plan is complete, and the reused table holds 3 rows. RED
    /// when the pane is empty/disconnected (the paired negative leg: an unreachable resolve
    /// lands in `unreachable` with zero tiers — the state the oracle rejects). Emits a
    /// testmatrix cell (reuse facett-core → edda).
    #[test]
    fn robot_build_matrix_shows_live_rows_red_when_empty() {
        let mut p = HolgerBuildPanel::new("http://127.0.0.1:0", "", "");
        p.apply_fetch(Ok(full_reply()));
        let st = Facet::state_json(&p);
        let shows_live_rows = st["status"] == "loaded"
            && st["tier_count"] == 3
            && st["resolved"] == 3
            && st["missing"] == 0
            && st["complete"] == true
            && st["found"] == true
            && st["table"]["rows"] == 3
            && st["tiers"][0]["tier"] == "holger"
            && st["tiers"][2]["bin"] == "holger-cli";
        assert!(
            shows_live_rows,
            "the Builds pane must show LIVE tier rows, not a blank pane: {st}"
        );

        // Negative leg — pin the oracle: an unreachable resolve is the zero-tier state the
        // live oracle rejects, so the test can only pass when real data is present.
        let mut down = HolgerBuildPanel::new("http://127.0.0.1:0", "", "");
        down.apply_fetch(Err(BuildErr::Unreachable("connect refused".into())));
        let dst = Facet::state_json(&down);
        let empty_is_red =
            dst["status"] == "unreachable" && dst["tier_count"] == 0 && dst["table"]["rows"] == 0;
        assert!(
            empty_is_red,
            "an unreachable resolve is the zero-tier state the oracle rejects: {dst}"
        );

        facett_core::testmatrix::emit(
            "dwarves-embed",
            "holger_build_matrix_shows_live_rows_red_when_empty",
            shows_live_rows && empty_is_red,
            "the holger build-matrix pane driven through apply_fetch shows LIVE 3-tier rows (loaded + tier_count/resolved/table.rows, plan complete) on the reused facett-table; RED when the pane is empty/disconnected (unreachable, zero tiers)",
        );
    }

    /// RED-when-broken: a build-space missing the `holger-cli` row resolves to an INCOMPLETE
    /// plan — the pane still renders (loaded) but surfaces `complete=false` + the unwired
    /// tier as an `unwired` row, never a false-green. The guard against a silently-dropped
    /// deployment target.
    #[test]
    fn a_dropped_tier_renders_incomplete_and_unwired() {
        let reply = pb::HolgerBuildReply {
            tiers: vec![
                pb::HolgerTier {
                    tier: "holger".into(),
                    step: "build-holger".into(),
                    resolved: true,
                    bin: "holger".into(),
                    package: "holger".into(),
                    command_line: "cargo build …".into(),
                },
                pb::HolgerTier {
                    tier: "holger-downstream".into(),
                    step: "build-holger-downstream".into(),
                    resolved: true,
                    bin: "holger-downstream".into(),
                    package: "holger-downstream".into(),
                    command_line: "cargo build …".into(),
                },
                pb::HolgerTier {
                    tier: "holger-cli".into(),
                    step: "build-holger-cli".into(),
                    resolved: false,
                    bin: String::new(),
                    package: String::new(),
                    command_line: String::new(),
                },
            ],
            resolved: 2,
            missing: 1,
            complete: false,
            found: true,
        };
        let mut p = HolgerBuildPanel::new("http://127.0.0.1:0", "", "");
        p.apply_fetch(Ok(reply));
        let st = Facet::state_json(&p);
        assert_eq!(
            st["status"], "loaded",
            "an incomplete plan still renders, not a blank"
        );
        assert_eq!(
            st["complete"], false,
            "a dropped tier ⇒ complete=false (no false green)"
        );
        assert_eq!(st["missing"], 1);
        let full = st["tiers"]
            .as_array()
            .unwrap()
            .iter()
            .find(|t| t["tier"] == "holger-cli")
            .unwrap();
        assert_eq!(full["resolved"], false, "the `holger-cli` row is unwired");
        assert_eq!(full["bin"], "", "an unwired tier has an empty bin");
        // The cell formatting makes the unwired tier VISIBLE (a `—` placeholder, never blank).
        let cells = tier_cells(
            &tiers_from_wire(&[pb::HolgerTier {
                tier: "holger-cli".into(),
                step: "build-holger-cli".into(),
                resolved: false,
                ..Default::default()
            }])[0],
        );
        assert_eq!(cells[1], "unwired");
        assert_eq!(cells[2], "—", "empty bin renders as the — placeholder");
    }

    /// Every failure lands in its DISTINCT honest state (never a silent blank / a generic
    /// "offline"): connect ⇒ `unreachable`, Unauthenticated ⇒ `auth_failed`, Unimplemented
    /// ⇒ `not_served`, other RPC ⇒ `error`; and `found=false` ⇒ `no_build_space`. The
    /// error text is preserved for the tooltip in each case.
    #[test]
    fn honest_states_are_all_distinct() {
        let drive = |outcome: Result<pb::HolgerBuildReply, BuildErr>| {
            let mut p = HolgerBuildPanel::new("http://127.0.0.1:0", "", "");
            p.apply_fetch(outcome);
            Facet::state_json(&p)
        };

        // The four failure legs — classified from the tonic Code.
        let unreachable = drive(Err(BuildErr::from_status(&tonic::Status::unavailable(
            "no route",
        ))));
        assert_eq!(
            unreachable["status"], "error",
            "generic Unavailable ⇒ error"
        );
        let auth = drive(Err(BuildErr::from_status(&tonic::Status::unauthenticated(
            "bad bearer",
        ))));
        assert_eq!(auth["status"], "auth_failed");
        assert!(
            auth["error"].as_str().unwrap().contains("bad bearer"),
            "the auth error is surfaced"
        );
        let denied = drive(Err(BuildErr::from_status(
            &tonic::Status::permission_denied("nope"),
        )));
        assert_eq!(
            denied["status"], "auth_failed",
            "PermissionDenied ⇒ auth_failed too"
        );
        let not_served = drive(Err(BuildErr::from_status(&tonic::Status::unimplemented(
            "no such method",
        ))));
        assert_eq!(not_served["status"], "not_served");
        let conn = drive(Err(BuildErr::Unreachable("connect refused".into())));
        assert_eq!(conn["status"], "unreachable");

        // found=false ⇒ the honest empty (no_build_space), NOT loaded and NOT offline.
        let empty = drive(Ok(pb::HolgerBuildReply {
            found: false,
            ..Default::default()
        }));
        assert_eq!(empty["status"], "no_build_space");
        assert_eq!(empty["tier_count"], 0);
        assert!(empty["error"].is_null(), "an honest empty is not an error");

        // found=true but zero tiers ⇒ the degenerate `empty` (still not a blank).
        let degenerate = drive(Ok(pb::HolgerBuildReply {
            found: true,
            tiers: vec![],
            ..Default::default()
        }));
        assert_eq!(degenerate["status"], "empty");

        facett_core::testmatrix::emit(
            "dwarves-embed",
            "holger_build_matrix_honest_states",
            unreachable["status"] == "error"
                && auth["status"] == "auth_failed"
                && not_served["status"] == "not_served"
                && conn["status"] == "unreachable"
                && empty["status"] == "no_build_space"
                && degenerate["status"] == "empty",
            "the holger build-matrix pane surfaces every non-render outcome as a DISTINCT honest state: unreachable / auth_failed / not_served / error / no_build_space / empty — never a silent blank",
        );
    }

    /// ROBOT SCENARIO — the pane drives purely over the `state_json`/`update_json` seam
    /// (the channel a `nornir-robotui` scenario + a thin gRPC client use): feed a live
    /// reply, assert the matrix, toggle auto-poll + pin a holger root over the seam, then a
    /// down leg. RED-when-broken: an unreachable resolve blanks the matrix.
    #[test]
    fn robot_pane_drives_over_the_seam() {
        let mut p = HolgerBuildPanel::new("http://127.0.0.1:0", "", "");
        p.apply_fetch(Ok(full_reply()));
        let st = Facet::state_json(&p);
        let loaded = st["status"] == "loaded" && st["tier_count"] == 3 && st["table"]["rows"] == 3;
        assert!(loaded, "live reply reflected on the seam: {st}");

        // Toggle auto-poll off over the seam.
        Facet::update_json(&mut p, r#"{"auto_poll":false}"#);
        let poll_off = Facet::state_json(&p)["auto_poll"] == serde_json::json!(false);

        // Pin a server-side holger root over the seam (re-resolves against the dead port ⇒
        // unreachable, blanking the matrix — the RED-when-broken leg).
        Facet::update_json(&mut p, r#"{"holger_root":"/some/holger"}"#);
        let after = Facet::state_json(&p);
        let pinned = after["holger_root"] == "/some/holger";
        let red = after["status"] == "unreachable" && after["tier_count"] == 0;

        let drivable = loaded && poll_off && pinned && red;
        assert!(drivable, "the Builds pane drives live-reply + auto-poll + holger-root pin + down over the seam: {after}");

        facett_core::testmatrix::emit(
            "dwarves-embed",
            "holger_build_matrix_pane_drivable",
            drivable,
            "the holger build-matrix pane drives over state_json/update_json: a live 3-tier reply renders on the reused table, auto-poll toggles, the server-side holger_root pins + re-resolves; a dead endpoint blanks to unreachable (RED-when-broken)",
        );
    }

    #[test]
    fn poll_due_respects_interval_and_toggle() {
        let p = HolgerBuildPanel::new("http://127.0.0.1:0", "", "");
        assert!(p.auto_poll);
        assert!(!p.poll_due(0.5), "not due before one interval");
        assert!(
            p.poll_due(DEFAULT_BUILD_POLL_INTERVAL_S),
            "due at one interval"
        );
        let mut p = p;
        p.set_auto_poll(false);
        assert!(!p.poll_due(99.0), "never due while paused");
    }

    /// An offline endpoint yields the clean `unreachable` state on construction, never a
    /// panic (the read leg's non-panic discipline).
    #[test]
    fn offline_server_is_a_clean_unreachable_state() {
        let p = HolgerBuildPanel::new("http://127.0.0.1:0", "", "");
        let st = Facet::state_json(&p);
        assert_eq!(st["status"], "unreachable");
        assert_eq!(st["tier_count"], 0);
        assert!(
            st["error"].is_string(),
            "the error is captured for the tooltip"
        );
    }
}