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

nordisk/nornir

src/mimir_lens/room_feed/built.rs

//! # What Build Thing runs filed — the binary lenses' ONE input
//!
//! Owner, 2026-10-17: **Build Thing runs feed the binary lenses, and the
//! reader never links.** Builder 2 (`code_room::products`) files every
//! artifact a successful Build Thing run made into the workspace's Mímir:
//!
//! * the artifact node + BUILT_FROM edges under the pseudo repo
//!   `@build-thing`, its provenance (variant, features, commits, toolchain,
//!   builder, time, sha256) as the filing's `bin_ingest_runs.ledger`;
//! * the binary half — `binary_facts` / `section_sizes` / `symbol_sizes` in
//!   the warehouse, keyed by the artifact's content sha256 (only for bytes
//!   readable on the server's box).
//!
//! The lenses that read linked symbols read THAT, here: `generic_cost` (the
//! latest filed artifact of each variant), `binary_drift` (successive filings
//! of one variant compared), `zombie_features`' bytes. Nothing reads
//! `<builds>/mimir_v2/artifacts/<member>/<sha>/` (the room's binary hook): no
//! producer writes there, by design, and a lens that looked there answered
//! `empty_gap` for ever.
//!
//! No filing yet is said plainly — "no Build Thing run has filed an artifact
//! for X yet: ⚒ Build it" — never an empty list.

use std::collections::{BTreeMap, BTreeSet};

use mimir_ingest::PRODUCTS_REPO;
use nornir_introspect::size::SymbolSize;
use serde_json::Value;

use super::{Basis, Query, Warehouse};
use crate::binscan::BinaryReport;
use crate::code_room::{Reach, Refusal, State};
use crate::mimir_lens as lens;
use crate::mimir_lens::binary_drift::Filing;

type Answered = Result<(Basis, Value), Refusal>;

/// Where the filings are read from, for `feeds`.
const FILINGS: &str = "bin_ingest_runs:@build-thing";

/// What a binary lens says when nothing is filed for what it was asked — the
/// gap of its answer (`empty_gap`), never an empty list on its own.
pub fn no_build(what: &str) -> String {
    format!(
        "no Build Thing run has filed an artifact for {what} yet: ⚒ Build it — dwarves' ⚒ Build Thing pane, `dwarves buildthing <repo> --variant <variant>` \
         or a Runa `Build` step files it with this server (builder 2), and the binary lenses read what it filed"
    )
}

/// What the request names: `repo` = the recipe's repo or the built package.
fn wanted(q: &Query<'_>) -> String {
    if q.repo.is_empty() { "this workspace".to_string() } else { format!("`{}`", q.repo) }
}

fn wants(q: &Query<'_>, f: &Filing) -> bool {
    q.repo.is_empty() || f.repo == q.repo || f.package.as_deref() == Some(q.repo)
}

/// Every artifact Build Thing runs filed with this server, in the order they
/// landed (`rev`). A build landed twice (its record and its rows each queue
/// the landing) is ONE filing, with its last — most complete — provenance.
pub fn filed(reach: &Reach<'_>) -> Result<Vec<Filing>, Refusal> {
    let log = reach.code_room()?.log()?;
    let rows = log
        .block_on(log.bin_runs(PRODUCTS_REPO))
        .map_err(|e| Refusal::new(State::Internal, format!("the Build Thing filings were not read (`bin_ingest_runs` of `{PRODUCTS_REPO}`): {e:#}")))?;
    let mut by_build: BTreeMap<(String, String), Filing> = BTreeMap::new();
    for row in rows {
        let Some(led) = row.ledger.as_deref().and_then(|l| serde_json::from_str::<Value>(l).ok()) else { continue };
        if led.get("builder2").is_none() {
            continue;
        }
        let s = |k: &str| led.get(k).and_then(Value::as_str).unwrap_or("").to_string();
        let art = &led["artifact"];
        let sha256 = art.get("sha256").and_then(Value::as_str).unwrap_or("").to_string();
        if sha256.is_empty() {
            continue;
        }
        let f = Filing {
            repo: s("repo"),
            variant: s("variant"),
            package: led.get("package").and_then(Value::as_str).map(str::to_string),
            file: art.get("file").and_then(Value::as_str).unwrap_or("").to_string(),
            target: format!("{}:{}", s("triple"), s("profile")),
            features: led.get("features").and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_str).map(str::to_string).collect()).unwrap_or_default(),
            owner: row.artifact.clone(),
            bytes: art.get("bytes").and_then(Value::as_u64).unwrap_or(0),
            build_id: s("build_id"),
            built_at: s("built_at"),
            rev: row.rev,
            commits: led.get("commits").and_then(Value::as_object).map(|o| o.iter().filter_map(|(k, v)| v.as_str().map(|v| (k.clone(), v.to_string()))).collect()).unwrap_or_default(),
            toolchain: s("toolchain"),
            builder: s("builder"),
            sha256,
        };
        by_build.insert((f.build_id.clone(), f.sha256.clone()), f);
    }
    let mut out: Vec<Filing> = by_build.into_values().collect();
    out.sort_by(|a, b| (a.rev, &a.built_at, &a.owner).cmp(&(b.rev, &b.built_at, &b.owner)));
    Ok(out)
}

/// The filings of each line, oldest first.
fn lines<'f>(filings: impl IntoIterator<Item = &'f Filing>) -> BTreeMap<(String, String, String, String, String), Vec<&'f Filing>> {
    let mut out: BTreeMap<_, Vec<&Filing>> = BTreeMap::new();
    for f in filings {
        out.entry(f.line()).or_default().push(f);
    }
    out
}

/// One line's name in a sentence: `repo/variant file (target, features)`.
fn line_name(f: &Filing) -> String {
    let feats = if f.features.is_empty() { String::new() } else { format!(", features {}", f.features.join(",")) };
    format!("{}/{} `{}` ({}{feats})", f.repo, f.variant, f.file, f.target)
}

/// The analyses this answer reads, each loaded once.
struct Analyses<'w> {
    wh: Result<&'w crate::warehouse::iceberg::IcebergWarehouse, String>,
    loaded: BTreeMap<String, Option<BinaryReport>>,
    rows: usize,
}

impl<'w> Analyses<'w> {
    fn new(wh: &'w Warehouse<'_>) -> Self {
        Self { wh: wh.handle().map_err(str::to_string), loaded: BTreeMap::new(), rows: 0 }
    }

    /// The analysis of `f`, or `None` with the reason in `basis`.
    fn of(&mut self, f: &Filing, basis: &mut Basis) -> Option<&BinaryReport> {
        if !self.loaded.contains_key(&f.sha256) {
            let r = match &self.wh {
                Err(why) => {
                    basis.gap(format!("the binary facts of the filed artifacts were not read — {why}"));
                    None
                }
                Ok(wh) => match crate::binscan::load_report(wh, &f.sha256) {
                    Ok(Some(r)) => {
                        self.rows += r.symbols.len();
                        Some(r)
                    }
                    Ok(None) => {
                        basis.gap(format!(
                            "{} (build {}, commit {}) was filed but its bytes were not analysed on this server (its file was not readable here): its sizes are ABSENT, not zero",
                            line_name(f),
                            f.build_id,
                            f.commit()
                        ));
                        None
                    }
                    Err(e) => {
                        basis.gap(format!("the analysis of {} (sha256 {}) was not read: {e:#}", line_name(f), &f.sha256[..f.sha256.len().min(16)]));
                        None
                    }
                },
            };
            self.loaded.insert(f.sha256.clone(), r);
        }
        self.loaded.get(&f.sha256).and_then(Option::as_ref)
    }

    fn root(&self) -> String {
        self.wh.as_ref().map(|wh| wh.root().display().to_string()).unwrap_or_default()
    }
}

/// `generic_cost` — the monomorphisation tax of the LATEST filed artifact of
/// every line the request names, from its `symbol_sizes`.
pub fn generic_cost(reach: &Reach<'_>, wh: &Warehouse<'_>, q: &Query<'_>) -> Answered {
    let mut basis = Basis::default();
    let all = filed(reach)?;
    basis.fact(FILINGS, all.len(), "lake");
    let lines = lines(all.iter().filter(|f| wants(q, f)));
    if lines.is_empty() {
        basis.gap(no_build(&wanted(q)));
    }
    let mut an = Analyses::new(wh);
    let mut instances = Vec::new();
    let mut artifacts: BTreeMap<String, (String, u64)> = BTreeMap::new();
    let mut read = Vec::new();
    for filings in lines.values() {
        let latest = filings.last().expect("a line has a filing");
        let Some(r) = an.of(latest, &mut basis) else { continue };
        let (mut i, total) = lens::bloat::instances_of(&latest.owner, &r.symbols);
        instances.append(&mut i);
        artifacts.insert(latest.owner.clone(), (latest.repo.clone(), total));
        read.push(format!("{} build {} @ {}", line_name(latest), latest.build_id, latest.commit()));
    }
    basis.feed("symbol_sizes", an.rows, an.root());
    if !read.is_empty() {
        basis.note(format!("read from the latest Build Thing artifact of each variant: {}", read.join("; ")));
    }
    basis.note(
        "an instance = a sized code symbol whose name carries generic arguments (v0 mangling) or, legacy-mangled, one of two or more symbols of one name; \
         an artifact's total is the bytes of ALL its symbols, not the file size",
    );
    let min = q.i64("min_instantiations").max(0) as u32;
    let report = lens::bloat::analyze(&instances, &artifacts, basis.feeds.clone(), min, q.limit);
    let mut v = report.state_json();
    v["source"] = Value::from("build");
    Ok((basis, v))
}

/// `binary_drift` — the same artifact (one line) with two provenances that
/// differ: the last `history` (default 1) successive pairs of distinct bytes
/// of every line the request names, diffed from their analyses.
pub fn binary_drift(reach: &Reach<'_>, wh: &Warehouse<'_>, q: &Query<'_>) -> Answered {
    use lens::binary_drift::{Step, successive};
    let mut basis = Basis::default();
    let all = filed(reach)?;
    basis.fact(FILINGS, all.len(), "lake");
    let lines = lines(all.iter().filter(|f| wants(q, f)));
    if lines.is_empty() {
        basis.gap(no_build(&wanted(q)));
    }
    let history = q.i64("history").max(1) as usize;
    let mut an = Analyses::new(wh);
    let mut steps = Vec::new();
    let mut single = Vec::new();
    let mut latest: Vec<(Filing, Vec<SymbolSize>)> = Vec::new();
    let mut same_bytes = 0usize;
    for filings in lines.values() {
        // the same bytes filed again are no drift: one per run of equal sha256
        let mut distinct: Vec<&Filing> = Vec::new();
        for &f in filings {
            if distinct.last().is_some_and(|l| l.sha256 == f.sha256) {
                same_bytes += 1;
                *distinct.last_mut().expect("checked") = f;
            } else {
                distinct.push(f);
            }
        }
        let newest = *distinct.last().expect("a line has a filing");
        if distinct.len() < 2 {
            single.push(format!("{} — filed once (build {} @ {})", line_name(newest), newest.build_id, newest.commit()));
            continue;
        }
        if let Some(r) = an.of(newest, &mut basis) {
            latest.push((newest.clone(), r.symbols.clone()));
        }
        let pairs: Vec<(&Filing, &Filing)> = distinct.windows(2).map(|w| (w[0], w[1])).collect();
        for (from, to) in pairs.into_iter().rev().take(history) {
            let a = an.of(from, &mut basis).cloned();
            let b = an.of(to, &mut basis);
            let diff = match (a, b) {
                (Some(a), Some(b)) => Some(nornir_introspect::diff::diff_reports(&a, b)),
                _ => None,
            };
            steps.push(Step { from: from.clone(), to: to.clone(), diff });
        }
    }
    basis.feed("symbol_sizes", an.rows, an.root());
    if steps.is_empty() && !single.is_empty() {
        basis.gap(format!(
            "only one Build Thing artifact of each variant asked has been filed ({}): there is nothing to compare it with yet — ⚒ Build it again after a change and its drift shows here",
            single.join("; ")
        ));
    } else if !single.is_empty() {
        basis.note(format!("{} variant(s) filed once, nothing to compare yet: {}", single.len(), single.join("; ")));
    }
    if same_bytes > 0 {
        basis.note(format!("{same_bytes} filing(s) were the same bytes as the one before them (sha256 equal): no drift, not compared"));
    }
    basis.lower_bound("symbol_sizes carry each symbol's size, not its bytes: a change that keeps every size shows only as differing artifact bytes (sha256)");
    basis.gap("binary call edges have no producer (P4): an injected call inside an unchanged symbol is NOT evaluated");
    let report = successive(&steps, &latest, single, basis.feeds.clone(), q.limit);
    Ok((basis, report.state_json()))
}

/// The bytes items take in the latest filed artifacts — `zombie_features`'
/// `bytes_in_binary`: by item path (generic arguments and `<>` erased), the
/// largest total over the latest artifact of every line, for the repos those
/// artifacts were built from.
#[derive(Debug, Default)]
pub struct LinkedBytes {
    /// Repos some analysed latest artifact was built from (its commits).
    covered: BTreeSet<String>,
    bytes: BTreeMap<String, u64>,
}

/// The join key of an item path or a symbol's demangled name.
pub fn path_key(name: &str) -> String {
    let erased = lens::binary_drift::erase_generics(name);
    let flat: String = erased.chars().filter(|c| *c != '<' && *c != '>').collect();
    flat.trim().trim_end_matches("::").to_string()
}

impl LinkedBytes {
    /// Read them, saying in `basis` what the bytes rest on (notes only: the
    /// bytes are a column of the lens, not its input).
    pub fn read(reach: &Reach<'_>, wh: &Warehouse<'_>, repos: &[String], basis: &mut Basis) -> Self {
        let mut out = Self::default();
        let all = match filed(reach) {
            Ok(a) => a,
            Err(r) => {
                basis.note(format!("bytes_in_binary: the Build Thing filings were not read — [{}] {}", r.state, r.note));
                return out;
            }
        };
        let lines = lines(all.iter());
        let mut an = Analyses::new(wh);
        let mut scratch = Basis::default();
        for filings in lines.values() {
            let latest = filings.last().expect("a line has a filing");
            let Some(r) = an.of(latest, &mut scratch) else { continue };
            out.covered.extend(latest.commits.keys().cloned());
            let mut per: BTreeMap<String, u64> = BTreeMap::new();
            for s in r.symbols.iter().filter(|s| s.size_bytes > 0) {
                let name = if s.name_demangled_no_generics.is_empty() { &s.name_demangled } else { &s.name_demangled_no_generics };
                *per.entry(path_key(name)).or_default() += s.size_bytes;
            }
            for (k, b) in per {
                let e = out.bytes.entry(k).or_default();
                *e = (*e).max(b);
            }
        }
        let missing: Vec<&str> = repos.iter().filter(|r| !out.covered.contains(*r)).map(String::as_str).collect();
        if !missing.is_empty() {
            basis.note(format!(
                "bytes_in_binary: no Build Thing run has filed an analysed artifact built from {} yet: ⚒ Build it — the bytes stay null there",
                missing.join(", ")
            ));
        }
        if missing.len() < repos.len() {
            basis.note("bytes_in_binary = what the item's symbols take in the latest Build Thing artifact built from its repo (joined by path; the largest over the variants)");
        }
        out
    }

    /// The repo has an analysed filed artifact built from it.
    pub fn covers(&self, repo: &str) -> bool {
        self.covered.contains(repo)
    }

    /// The bytes of `path` (0 = linked by none).
    pub fn of(&self, path: &str) -> u64 {
        self.bytes.get(&path_key(path)).copied().unwrap_or(0)
    }
}