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/warehousehealth.rs

//! **The dwarves Mímir "Warehouse Health" backoffice pane.**
//!
//! Under the Mímir menu: EVERY warehouse table with its REAL numbers —
//! **row count · on-disk byte size · last-populated snapshot** — not green boxes.
//! The point is to make a *hollow Mímir* impossible to miss: after the 2026-07-24
//! repopulate that silently skipped vector/embedding indexing, this pane shows
//! `embeddings` / `embedding_snapshots` at **0 rows, 0 B, never** in RED, next to
//! `symbol_facts` at its real 150 MB — so the gap is visible at a glance.
//!
//! ## The seam (offline, host-agnostic — same shape as the Benches pane)
//! The numbers come from nornir's `Warehouse.TableStats` RPC (row count + byte
//! size + last snapshot, read cheaply off each table's Iceberg snapshot summary)
//! serialized to a per-workspace JSON file the host drops beside the other nornir
//! run artifacts. This pane reads that file as DATA — no gRPC, no nornir dep (the
//! POD is defined here).
//!
//! ## The fixture is gone (2026-09-10, the eighth pane)
//! A missing stats file used to mount `health_fixture()` — a mirror of the live
//! nordisk warehouse of 2026-07-24, real table names at real sizes, `symbol_facts`
//! at ~150 MB. `state_json`'s `source` said `"fixture"` beside it.
//!
//! This is the pane that suffered most from having one. Its stated job, three
//! paragraphs up, is to make a **hollow Mímir impossible to miss** — and with no
//! stats file it drew a *populated* warehouse: a COMPLETE banner, filled tables,
//! plausible byte sizes. The one pane built to show you an empty warehouse was the
//! one that invented a full one. A green COMPLETE chip IS a claim of health,
//! whatever the caption underneath says.
//!
//! `bf692d9` ("seven panes stop drawing invented data") did not cover this pane —
//! its seven were Status, Forge, Coderoom, Binary, Fleet, Profile and Saga. Now:
//! no file ⇒ zero tables, no banner, and the shared
//! [`facett_core::empty::EmptyState`] NAMING the stats file it wanted.

use std::path::{Path, PathBuf};

use facett_core::empty::EmptyState;
use facett_core::{Facet, Panel};

/// The stats feed, named the way an operator would go look it up.
const HEALTH_FEED: &str = "nornir Warehouse.TableStats JSON";

/// One warehouse table's health — the POD the pane renders. Mirrors nornir's
/// `WarehouseTableStat` proto message / `warehouse::iceberg::TableStat` field for
/// field, so the host can serialize the RPC result straight into the seam file.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct TableHealthRow {
    pub table: String,
    /// Row count from the snapshot summary; `-1` = unknown, `0` = never written.
    pub row_count: i64,
    /// On-disk byte size (`total-files-size`); `-1` = unknown.
    pub byte_size: i64,
    /// Last-populated snapshot timestamp (epoch ms); `0` = never populated.
    pub last_snapshot_ms: i64,
    /// `row_count<=0 && data_files<=0`.
    pub empty: bool,
    /// On the documented empty-until-triggered allowlist (an empty read is fine).
    pub allowlisted: bool,
    /// Why it is legitimately empty (if allowlisted).
    #[serde(default)]
    pub reason: String,
}

impl TableHealthRow {
    /// An empty, NON-allowlisted expected table — a silent-skip regression (RED).
    pub fn is_regression(&self) -> bool {
        self.empty && !self.allowlisted
    }
}

/// The full health document — the per-table rows plus the completeness roll-up
/// (`Warehouse.TableStats` returns exactly this shape).
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct WarehouseHealthDoc {
    pub stats: Vec<TableHealthRow>,
    /// No empty regression outside the allowlist.
    pub complete: bool,
    /// Expected index types that filled ZERO rows (the silent skips).
    #[serde(default)]
    pub empty_regressions: Vec<String>,
    /// One-line human roll-up.
    #[serde(default)]
    pub summary: String,
}

/// Sanitize a workspace name to a filesystem-safe token — the SAME rule the other
/// dwarves panes use, kept in step.
fn safe(name: &str) -> String {
    name.trim()
        .chars()
        .map(|c| {
            if c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-') {
                c
            } else {
                '_'
            }
        })
        .collect()
}

/// The **warehouse-health JSON** a host writes for a named workspace by
/// serializing nornir's `Warehouse.TableStats` result. Empty name ⇒ a shared
/// default. MUST match the host's emit path so the pane sees that workspace's
/// numbers.
pub fn workspace_health_path(workspace: &str) -> PathBuf {
    let name = safe(workspace);
    let file = if name.is_empty() {
        "dwarves-warehouse-health.json".to_string()
    } else {
        format!("dwarves-warehouse-health-{name}.json")
    };
    std::env::temp_dir().join(file)
}

/// Human byte size (`0 B` / `1.2 KB` / `150.0 MB`); `-1` ⇒ `?`.
pub fn human_bytes(n: i64) -> String {
    if n < 0 {
        return "?".to_string();
    }
    let n = n as f64;
    const KB: f64 = 1024.0;
    if n < KB {
        format!("{n:.0} B")
    } else if n < KB * KB {
        format!("{:.1} KB", n / KB)
    } else if n < KB * KB * KB {
        format!("{:.1} MB", n / (KB * KB))
    } else {
        format!("{:.1} GB", n / (KB * KB * KB))
    }
}

/// Human last-populated: `never` for 0, else an epoch-ms date. No chrono dep — a
/// plain `<epoch>ms` marker keeps the pane leaf-light; the host can localize.
pub fn human_snapshot(ms: i64) -> String {
    if ms <= 0 {
        "never".to_string()
    } else {
        // seconds since epoch, good enough for an at-a-glance recency marker.
        format!("@{}s", ms / 1000)
    }
}

/// **The Warehouse Health pane.** Holds the health doc bound to one workspace.
pub struct WarehouseHealthView {
    doc: WarehouseHealthDoc,
    workspace: String,
    /// Whether the numbers came from a real run file.
    from_run: bool,
    /// Set when there are no table stats to show — the NAMED empty state (which file,
    /// why), drawn in place of the banner and the table. This replaced the fixture.
    empty: Option<EmptyState>,
}

impl WarehouseHealthView {
    /// Build for `workspace` by reading its health JSON. No file ⇒ the named empty
    /// state, never an invented warehouse.
    pub fn for_workspace(workspace: &str) -> Self {
        Self::from_path(&workspace_health_path(workspace), workspace)
    }

    /// Build from an explicit path (the helper the tab + tests drive).
    /// Missing/unreadable ⇒ zero tables + the NAMED empty state; never panics.
    pub fn from_path(path: &Path, workspace: &str) -> Self {
        let parsed = std::fs::read_to_string(path)
            .ok()
            .and_then(|s| serde_json::from_str::<WarehouseHealthDoc>(&s).ok());
        let from_run = parsed.is_some();
        let ws = if workspace.trim().is_empty() {
            "no workspace selected".to_string()
        } else {
            format!("workspace `{workspace}`")
        };
        // `not-configured` (no readable stats file) is kept apart from `no-rows` (a
        // file that parsed but lists no tables) — the two mean different things to an
        // operator, and only the second is a statement about the warehouse.
        let empty = match &parsed {
            None => Some(
                EmptyState::not_configured(
                    HEALTH_FEED,
                    format!("{} is missing or unreadable for {ws}", path.display()),
                )
                .with_hint("populate the warehouse, then drop the TableStats JSON beside it"),
            ),
            Some(d) if d.stats.is_empty() => Some(
                EmptyState::no_rows(
                    HEALTH_FEED,
                    format!("{} lists no tables for {ws}", path.display()),
                )
                .with_hint("re-run `Warehouse.TableStats`"),
            ),
            Some(_) => None,
        };
        Self {
            doc: parsed.unwrap_or_default(),
            workspace: workspace.to_string(),
            from_run,
            empty,
        }
    }

    /// Build directly from a fetched doc (a host that already called the RPC).
    pub fn from_doc(doc: WarehouseHealthDoc, workspace: &str) -> Self {
        let empty = doc.stats.is_empty().then(|| {
            EmptyState::no_rows(HEALTH_FEED, "the fetched TableStats reply lists no tables")
                .with_hint("re-run `Warehouse.TableStats`")
        });
        Self {
            doc,
            workspace: workspace.to_string(),
            from_run: true,
            empty,
        }
    }

    /// Re-bind to a new workspace (the selector broadcast).
    pub fn bind(&mut self, workspace: &str) {
        *self = Self::for_workspace(workspace);
    }

    pub fn workspace(&self) -> &str {
        &self.workspace
    }

    fn filled_count(&self) -> usize {
        self.doc.stats.iter().filter(|r| !r.empty).count()
    }
    fn regression_count(&self) -> usize {
        self.doc.stats.iter().filter(|r| r.is_regression()).count()
    }
}

impl Facet for WarehouseHealthView {
    fn title(&self) -> &str {
        "Warehouse Health"
    }

    fn ui(&mut self, ui: &mut egui::Ui) {
        ui.horizontal(|ui| {
            if self.workspace.is_empty() {
                ui.label("Warehouse Health: no workspace selected");
            } else {
                ui.label(format!("Warehouse Health: {}", self.workspace));
            }
            ui.separator();
            ui.label(if self.from_run {
                "● live stats"
            } else {
                "○ no stats"
            });
        });

        if let Some(e) = &self.empty {
            // No stats ⇒ no COMPLETE banner, no table rows, no byte sizes. A pane built
            // to make a hollow warehouse visible must not paint a full one.
            e.ui(ui);
            return;
        }

        // Honest completeness banner — RED when an index type silently stopped filling.
        if self.doc.complete && self.regression_count() == 0 {
            ui.colored_label(
                egui::Color32::from_rgb(90, 200, 120),
                format!("● COMPLETE — {} table(s) filled", self.filled_count()),
            );
        } else {
            ui.colored_label(
                egui::Color32::from_rgb(224, 90, 90),
                format!(
                    "● INCOMPLETE — {} index type(s) filled ZERO rows: {}",
                    self.regression_count(),
                    self.doc.empty_regressions.join(", ")
                ),
            );
        }
        ui.label(&self.doc.summary);
        ui.separator();

        egui::ScrollArea::vertical()
            .auto_shrink([false, false])
            .show(ui, |ui| {
                egui::Grid::new("warehouse-health-grid")
                    .striped(true)
                    .spacing([14.0, 3.0])
                    .show(ui, |ui| {
                        for h in ["table", "rows", "size", "last populated", "state"] {
                            ui.label(egui::RichText::new(h).strong());
                        }
                        ui.end_row();
                        for r in &self.doc.stats {
                            ui.label(&r.table);
                            ui.label(if r.row_count < 0 {
                                "?".to_string()
                            } else {
                                r.row_count.to_string()
                            });
                            ui.label(human_bytes(r.byte_size));
                            ui.label(human_snapshot(r.last_snapshot_ms));
                            let (txt, col) = if r.is_regression() {
                                ("REGRESSION (empty)", egui::Color32::from_rgb(224, 90, 90))
                            } else if r.empty && r.allowlisted {
                                ("empty (expected)", egui::Color32::GRAY)
                            } else {
                                ("filled", egui::Color32::from_rgb(90, 200, 120))
                            };
                            ui.label(egui::RichText::new(txt).color(col)).on_hover_text(
                                if r.reason.is_empty() {
                                    ""
                                } else {
                                    r.reason.as_str()
                                },
                            );
                            ui.end_row();
                        }
                    });
            });
    }

    fn state_json(&self) -> serde_json::Value {
        let rows: Vec<serde_json::Value> = self
            .doc
            .stats
            .iter()
            .map(|r| {
                serde_json::json!({
                    "table": r.table,
                    "row_count": r.row_count,
                    "byte_size": r.byte_size,
                    "last_snapshot_ms": r.last_snapshot_ms,
                    "empty": r.empty,
                    "allowlisted": r.allowlisted,
                    "regression": r.is_regression(),
                })
            })
            .collect();
        serde_json::json!({
            "component": "WarehouseHealthView",
            "selected_workspace": self.workspace,
            // `"fixture"` is gone: the only two values now are the real run and the
            // honest empty, and the empty carries its own named block.
            "source": if self.from_run { "workspace-run" } else { "empty" },
            "health_feed": HEALTH_FEED,
            "health_empty": self.empty.as_ref().map(|e| e.state_json()),
            // With no stats there is nothing to be complete ABOUT, so this must not
            // read `true` off an empty doc and paint a green COMPLETE chip.
            "complete": self.empty.is_none() && self.doc.complete && self.regression_count() == 0,
            "table_count": self.doc.stats.len(),
            "filled_count": self.filled_count(),
            "regression_count": self.regression_count(),
            "empty_regressions": self.doc.empty_regressions,
            "summary": self.doc.summary,
            "tables": rows,
        })
    }

    fn update_json(&mut self, msg_json: &str) {
        if let Ok(v) = serde_json::from_str::<serde_json::Value>(msg_json) {
            if let Some(ws) = v.get("set_workspace").and_then(|t| t.as_str()) {
                self.bind(ws);
            }
        }
    }
}

/// The dwarves Mímir **Warehouse Health** tab for a workspace.
pub fn warehouse_health_panel(workspace: &str) -> Box<dyn Panel> {
    Box::new(WarehouseHealthView::for_workspace(workspace))
}

/// Build from an explicit path (a host/CLI that resolved it, or a test).
pub fn warehouse_health_panel_at(path: &Path, workspace: &str) -> Box<dyn Panel> {
    Box::new(WarehouseHealthView::from_path(path, workspace))
}

/// A warehouse shaped like the live nordisk one of 2026-07-24 for a TEST: the
/// per-snapshot knowledge tables filled, and the embedding tables EMPTY +
/// non-allowlisted, so the regression detection has something to find.
///
/// **`#[cfg(test)]` since 2026-09-10.** As `pub fn health_fixture()` this was what the
/// SHIPPED pane drew whenever the stats file was missing — a populated warehouse with
/// plausible byte sizes, from the one pane whose entire purpose is to make an EMPTY
/// warehouse impossible to miss.
#[cfg(test)]
pub(crate) fn health_fixture() -> WarehouseHealthDoc {
    let filled = |table: &str, rows: i64, bytes: i64| TableHealthRow {
        table: table.into(),
        row_count: rows,
        byte_size: bytes,
        last_snapshot_ms: 1_753_000_000_000,
        empty: false,
        allowlisted: false,
        reason: String::new(),
    };
    let regression = |table: &str| TableHealthRow {
        table: table.into(),
        row_count: 0,
        byte_size: 0,
        last_snapshot_ms: 0,
        empty: true,
        allowlisted: false,
        reason: String::new(),
    };
    let allowlisted = |table: &str, why: &str| TableHealthRow {
        table: table.into(),
        row_count: 0,
        byte_size: 0,
        last_snapshot_ms: 0,
        empty: true,
        allowlisted: true,
        reason: why.into(),
    };
    let stats = vec![
        filled("symbol_facts", 79_111, 157_005_137),
        filled("call_edges", 537_754, 161_034_340),
        filled("feature_gate_facts", 12_004, 14_893_547),
        filled("test_inventory", 6_233, 10_608_525),
        filled("git_heat_facts", 4_812, 2_631_142),
        filled("scip_occurrences", 331_402, 20_977_681),
        filled("tantivy_index_blobs", 1645, 934_771_098),
        // THE REGRESSION — vector/embedding indexing silently skipped.
        regression("embeddings"),
        regression("embedding_snapshots"),
        regression("embedding_manifest"),
        // legitimately empty until triggered.
        allowlisted("yank_events", "empty means NO crate versions were yanked"),
        allowlisted(
            "test_results",
            "no `nornir test run`/release executed the matrix yet",
        ),
        allowlisted("bench_runs", "filled by `nornir bench`, not by populate"),
    ];
    WarehouseHealthDoc {
        complete: false,
        empty_regressions: vec![
            "embeddings".into(),
            "embedding_snapshots".into(),
            "embedding_manifest".into(),
        ],
        summary: "populate completeness: 7/10 expected tables filled · 3 regression(s) \
                  [embeddings, embedding_snapshots, embedding_manifest] · embeddings MISSING · scip ok"
            .into(),
        stats,
    }
}

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

    /// **THE WAREHOUSE FIXTURE IS GONE** (2026-09-10, the eighth pane — `bf692d9`
    /// covered the other seven). With no stats file the pane used to draw
    /// `health_fixture()`: a mirror of the live nordisk warehouse, `symbol_facts` at
    /// ~150 MB, filled knowledge tables, plausible sizes. The pane whose entire stated
    /// job is to make a HOLLOW Mímir impossible to miss was drawing a full one. Pins:
    ///
    ///   (a) a missing stats file gives ZERO tables, not ten;
    ///   (b) `complete` is NOT true — an empty doc must not paint a green COMPLETE chip;
    ///   (c) the pane carries the NAMED empty state over the stats file, cause
    ///       `not-configured`, and `source` is `empty` rather than `fixture`;
    ///   (d) none of the fixture's invented tables/sizes survives anywhere;
    ///   (e) a file that PARSED but lists no tables is a different cause (`no-rows`).
    ///
    /// RED-when-broken: restore the fallback and (a)/(d) fail.
    #[test]
    fn an_unpopulated_warehouse_names_its_feed_never_a_fixture() {
        let st = warehouse_health_panel_at(
            Path::new("/nonexistent/dwarves/warehouse-health.json"),
            "nordisk",
        )
        .state_json();

        // (a) + (b)
        assert_eq!(st["table_count"], 0, "no stats file ⇒ no tables: {st}");
        assert!(st["tables"].as_array().unwrap().is_empty(), "{st}");
        assert_eq!(
            st["complete"], false,
            "an empty doc is not a COMPLETE warehouse: {st}"
        );

        // (c)
        assert_eq!(
            st["source"], "empty",
            "`fixture` is not a source any more: {st}"
        );
        assert_eq!(
            st["health_empty"]["feed"], HEALTH_FEED,
            "the pane NAMES its feed: {st}"
        );
        assert_eq!(st["health_empty"]["cause"], "not-configured");
        assert!(st["health_empty"]["detail"]
            .as_str()
            .unwrap()
            .contains("warehouse-health.json"));

        // (d) the fixture's invented facts appear NOWHERE.
        let whole = st.to_string();
        for gone in [
            "symbol_facts",
            "tantivy_index_blobs",
            "embedding_manifest",
            "scip ok",
        ] {
            assert!(
                !whole.contains(gone),
                "the deleted fixture's `{gone}` is gone: {whole}"
            );
        }

        // (e) a present-but-tableless doc is read-and-empty, not not-configured.
        let present = WarehouseHealthView::from_doc(WarehouseHealthDoc::default(), "nordisk");
        assert_eq!(
            Facet::state_json(&present)["health_empty"]["cause"],
            "no-rows"
        );
    }

    /// ROBOT SCENARIO — the **Warehouse Health** backoffice pane driven over the
    /// remote seam (`Panel::state_json`): assert the pane shows REAL per-table numbers
    /// AND red-flags the embeddings regression — the whole reason the pane exists.
    /// Emits a testmatrix cell.
    ///
    /// The doc is handed in DIRECTLY (2026-09-10). This used to read it off
    /// `warehouse_health_panel("")`, which only worked because an unfed pane mounted
    /// `health_fixture()` — so the test was asserting the fallback, not the render.
    /// What it is about is what the pane does WITH stats, so it now gets stats.
    #[test]
    fn warehouse_health_pane_shows_real_numbers_and_flags_empty_index_types() {
        let pane: Box<dyn Panel> =
            Box::new(WarehouseHealthView::from_doc(health_fixture(), "nordisk"));
        let st = pane.state_json();

        let tables = st["tables"].as_array().unwrap();
        // real numbers, not green boxes: symbol_facts carries a big row count + size.
        let symbol_facts = tables
            .iter()
            .find(|t| t["table"] == "symbol_facts")
            .unwrap();
        let has_real_numbers = symbol_facts["row_count"].as_i64().unwrap_or(0) > 1000
            && symbol_facts["byte_size"].as_i64().unwrap_or(0) > 1_000_000;
        // the regression is flagged: embeddings empty + regression:true + named in the roll-up.
        let emb = tables.iter().find(|t| t["table"] == "embeddings").unwrap();
        let flags_regression = emb["empty"] == true
            && emb["regression"] == true
            && st["complete"] == false
            && st["regression_count"].as_u64().unwrap_or(0) >= 3
            && st["empty_regressions"]
                .as_array()
                .unwrap()
                .iter()
                .any(|t| t == "embeddings");
        // an allowlisted-empty table is NOT a regression (honest allowlist).
        let yank = tables.iter().find(|t| t["table"] == "yank_events").unwrap();
        let allowlist_ok = yank["empty"] == true && yank["regression"] == false;

        let drivable = st["component"] == "WarehouseHealthView"
            // The doc is handed straight in, so this is a real fetched reply as far as
            // the pane is concerned — `"fixture"` is not a source any more.
            && st["source"] == "workspace-run"
            && st.get("health_empty").map(|v| v.is_null()).unwrap_or(true)
            && has_real_numbers
            && flags_regression
            && allowlist_ok;
        assert!(
            drivable,
            "Warehouse Health pane shows real numbers + flags the empty index types: {st}"
        );

        facett_core::testmatrix::emit(
            "dwarves-embed::warehousehealth",
            "warehouse_health_pane_shows_real_numbers_and_flags_empty_index_types",
            drivable,
            "Warehouse Health backoffice pane (Mímir menu): every table's REAL row count + byte size + \
             last-populated snapshot over Panel::state_json, red-flagging index types that filled ZERO \
             rows outside the empty-until-triggered allowlist (the 2026-07-24 embeddings regression)",
        );
    }

    /// RED-when-broken (the GATE): a workspace with a REAL health file reflects
    /// THOSE numbers, not the fixture — including a COMPLETE warehouse flipping the
    /// banner green.
    #[test]
    fn workspace_file_reflects_over_the_fixture() {
        let ws = "dwarves-wh-health-test-ws";
        let path = workspace_health_path(ws);
        let _ = std::fs::remove_file(&path);

        let doc = WarehouseHealthDoc {
            stats: vec![TableHealthRow {
                table: "symbol_facts".into(),
                row_count: 42,
                byte_size: 4096,
                last_snapshot_ms: 1_753_000_000_000,
                empty: false,
                allowlisted: false,
                reason: String::new(),
            }],
            complete: true,
            empty_regressions: Vec::new(),
            summary: "populate completeness: 1/1 expected tables filled".into(),
        };
        std::fs::write(&path, serde_json::to_string(&doc).unwrap()).unwrap();

        let pane = warehouse_health_panel(ws);
        let st = pane.state_json();
        let ok = st["selected_workspace"] == ws
            && st["source"] == "workspace-run"
            && st["complete"] == true
            && st["regression_count"] == 0
            && st["table_count"] == 1
            && st["tables"][0]["row_count"] == 42;
        assert!(
            ok,
            "the pane reflects the seeded health file, not the fixture: {st}"
        );

        let _ = std::fs::remove_file(&path);
    }

    #[test]
    fn human_bytes_and_snapshot_render() {
        assert_eq!(human_bytes(0), "0 B");
        assert_eq!(human_bytes(-1), "?");
        assert_eq!(human_bytes(1024), "1.0 KB");
        assert!(human_bytes(157_005_137).ends_with("MB"));
        assert_eq!(human_snapshot(0), "never");
        assert!(human_snapshot(1_753_000_000_000).starts_with('@'));
    }
}