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)
}
}