src/code_room/products.rs
//! BUILDER 2 on the server: a finished Build Thing run lands in the
//! workspace's Mímir.
//!
//! The run's record already reaches the server: `Telemetry.SubmitBuildProvenance`
//! files the `BuildProvenance` (warehouse `build_provenance`), and
//! `Telemetry.SubmitBuildInfo` its §f3 rows (warehouse `build_info`), among them
//! the LINEAGE rows the forge adds at filing time ([`COMMIT_PREFIX`],
//! [`DEP_CRATE`], [`REPO_ROOT`]). When the rows of a successful build arrive the
//! server queues ONE jera job (the workspace's `rooms` lane) that
//!
//! 1. reads both back and makes the run's [`ProductFacts`] ([`facts_of`]);
//! 2. writes them through the consumer's seat ([`CodeRoom::land_products`]):
//! artifact node + BUILT_FROM edges + provenance in the binary stream's
//! tables (`mimir_ingest::product`);
//! 3. the BINARY HALF: every artifact readable on this box is analysed exactly
//! as `nornir bin analyze` does and persisted to the same warehouse tables
//! (`binary_facts` / `section_sizes` / `symbol_sizes`), keyed by the content
//! sha256 the artifact node's id carries ([`binary_half`]).
//!
//! The read: `Coderoom.Artifacts` ([`answer`]) — which artifacts were built
//! from crate X / commit Y.
use std::path::{Path, PathBuf};
use anyhow::{Result, bail};
use mimir_ingest::{ArtifactQuery, BinRunRow, ProductArtifact, ProductFacts};
use nornir_build_thing::{BuildInfoRow, BuildProvenance};
use serde_json::{Value, json};
use super::{CodeRoom, Refusal, State};
/// `build_info` provenance row: `commit:<repo>` = the sha the build read of
/// that repo (the recipe's own and every sibling a path dependency lives in).
pub const COMMIT_PREFIX: &str = "commit:";
/// `build_info` dep row name: `name@version` of one crate of the build's
/// dependency closure.
pub const DEP_CRATE: &str = "crate";
/// `build_info` provenance row: the builder's absolute checkout of the recipe
/// repo (artifact paths in the record are relative to it).
pub const REPO_ROOT: &str = "repo_root";
/// Does this batch of `build_info` rows close a SUCCESSFUL build? (its manifest
/// says `outcome = success`): the builds the server lands in Mímir.
pub fn landed_builds(rows: &[BuildInfoRow]) -> Vec<String> {
let mut ids: Vec<String> = rows
.iter()
.filter(|r| r.kind == "provenance" && r.name == "outcome" && r.value == "success")
.map(|r| r.build_id.clone())
.collect();
ids.sort();
ids.dedup();
ids
}
/// The run's facts from its record + its `build_info` rows.
pub fn facts_of(record: &BuildProvenance, info: &[BuildInfoRow]) -> Result<ProductFacts> {
if record.outcome != nornir_build_thing::provenance::Outcome::Success {
bail!("build {} did not succeed: no product to land", record.id);
}
let mine = || info.iter().filter(|r| r.build_id == record.id);
let mut commits: std::collections::BTreeMap<String, String> = mine()
.filter(|r| r.kind == "provenance")
.filter_map(|r| r.name.strip_prefix(COMMIT_PREFIX).map(|repo| (repo.to_string(), r.value.clone())))
.filter(|(_, sha)| !sha.is_empty())
.collect();
if !record.inputs.vcs_rev.is_empty() && record.inputs.vcs_rev != "unknown" {
commits.entry(record.repo.clone()).or_insert_with(|| record.inputs.vcs_rev.clone());
}
let mut crates: Vec<(String, String)> = mine()
.filter(|r| r.kind == "dep" && r.name == DEP_CRATE)
.filter_map(|r| r.value.rsplit_once('@').map(|(n, v)| (n.to_string(), v.to_string())))
.collect();
crates.sort();
crates.dedup();
let artifacts: Vec<ProductArtifact> = record
.artifacts
.iter()
.filter(|a| !a.sha256.is_empty())
.map(|a| ProductArtifact {
file: Path::new(&a.path).file_name().map(|f| f.to_string_lossy().into_owned()).unwrap_or_else(|| a.path.clone()),
bytes: a.bytes,
sha256: a.sha256.clone(),
})
.collect();
if artifacts.is_empty() {
bail!("build {} recorded no artifact", record.id);
}
Ok(ProductFacts {
build_id: record.id.clone(),
repo: record.repo.clone(),
variant: record.variant.name.clone(),
package: record.variant.package.clone(),
features: record.inputs.features.clone(),
profile: record.inputs.profile.clone(),
triple: record.inputs.target.clone().unwrap_or_else(|| record.toolchain.host.clone()),
toolchain: if record.toolchain.rustc.is_empty() { record.toolchain.image.clone().unwrap_or_default() } else { record.toolchain.rustc.clone() },
builder: record.builder.clone(),
built_at: record.built_at.clone(),
recipe: record.build_thing.as_ref().map(ToString::to_string),
commits,
crates,
artifacts,
record: serde_json::to_value(record).ok(),
})
}
/// Where the record's artifacts are on THIS box: `(path, sha256)` of every one
/// that is readable here and still has the recorded bytes (the builder's
/// [`REPO_ROOT`] joins a relative path). A build on another box has none.
pub fn local_artifacts(record: &BuildProvenance, info: &[BuildInfoRow]) -> Vec<(PathBuf, String)> {
let root = info.iter().find(|r| r.build_id == record.id && r.kind == "provenance" && r.name == REPO_ROOT).map(|r| PathBuf::from(&r.value));
record
.artifacts
.iter()
.filter_map(|a| {
let p = Path::new(&a.path);
let p = if p.is_absolute() { p.to_path_buf() } else { root.as_ref()?.join(p) };
let same = std::fs::metadata(&p).is_ok_and(|m| m.is_file() && m.len() == a.bytes);
same.then(|| (p, a.sha256.clone()))
})
.collect()
}
/// The binary half: `nornir bin analyze` over every local artifact, persisted to
/// `binary_facts` / `section_sizes` / `symbol_sizes` under its content sha256
/// (the analysis of bytes that changed since filing is skipped, by name).
pub fn binary_half(wh: &crate::warehouse::iceberg::IcebergWarehouse, record: &BuildProvenance, info: &[BuildInfoRow]) -> Vec<Value> {
let mut out = Vec::new();
for (path, sha) in local_artifacts(record, info) {
let r = (|| -> Result<Value> {
let now = crate::binscan::content_sha256(&path)?;
if now != sha {
bail!("{} changed since the build filed it (sha256 {now}, filed {sha})", path.display());
}
// the same bytes are the same analysis: a build filed twice (its
// record and its rows each queue the landing), or the same bytes
// built again, append no second copy of the rows
if let Some(h) = wh.query_binary_facts(&sha)? {
return Ok(json!({ "artifact": path.display().to_string(), "sha256": sha, "already_analysed": true, "total_bytes": h.total_bytes }));
}
let report = crate::binscan::analyze(&path, None)?;
let snap = crate::binscan::persist_report(wh, &report, &record.repo, &record.inputs.vcs_rev, &sha, &path.display().to_string())?;
Ok(json!({
"artifact": path.display().to_string(),
"sha256": sha,
"snapshot": snap.to_string(),
"total_bytes": report.header.total_bytes,
"sections": report.sections.len(),
"symbols": report.symbols.len(),
"crates": report.crates.len(),
}))
})();
out.push(r.unwrap_or_else(|e| json!({ "artifact": path.display().to_string(), "error": format!("{e:#}") })));
}
out
}
impl CodeRoom {
/// Land one Build Thing run's facts in this workspace's lake, through the
/// consumer's seat (it waits for a run in flight; readers never wait).
pub fn land_products(&self, facts: &ProductFacts) -> Result<Vec<BinRunRow>, Refusal> {
let landed = self.with_consumer(|log, consumer| {
log.block_on(consumer.ingest_products(facts))
.map_err(|e| Refusal::new(State::Internal, format!("code room of `{}`: build {} did not land: {e:#}", self.workspace, facts.build_id)))
})?;
// `Viz.Binary` and the lenses that read the built artifacts list them,
// and a build does not move the room's version: their kept answers are
// of the room without this build
self.answers().forget("Viz.Binary");
for lens in crate::mimir_lens::BUILD_FED {
self.answers().forget(&format!("Mimir.{lens}"));
}
Ok(landed)
}
}
/// **Builder 2's job**, whole: land one finished Build Thing run (its
/// `build_provenance` record + its `build_info` rows) in the workspace's
/// Mímir — the artifact node, BUILT_FROM and provenance through the consumer's
/// seat ([`CodeRoom::land_products`]), then the binary half ([`binary_half`]).
/// This is THE producer of everything the binary lenses read
/// (`generic_cost`, `binary_drift`, `zombie_features`' bytes).
pub fn land_build(room: &CodeRoom, wh: &crate::warehouse::iceberg::IcebergWarehouse, record: &BuildProvenance, rows: &[BuildInfoRow]) -> Result<Value> {
let facts = facts_of(record, rows)?;
let landed = room.land_products(&facts).map_err(|r| anyhow::anyhow!("[{}] {r}", r.state))?;
let binary = binary_half(wh, record, rows);
// the binary half lands after the products: what a lens kept in between
// read the artifact without its sizes
for lens in crate::mimir_lens::BUILD_FED {
room.answers().forget(&format!("Mimir.{lens}"));
}
Ok(json!({
"build_id": record.id,
"repo": facts.repo,
"variant": facts.variant,
"artifacts": landed.iter().map(|r| json!({ "owner": r.artifact, "rev": r.rev, "plus": r.rows_plus, "status": r.status })).collect::<Vec<_>>(),
"crates": facts.crates.len(),
"commits": facts.commits,
"binary_half": binary,
}))
}
/// `Coderoom.Artifacts` — `{ "crate": "name[@version]", "commit": "[repo@]sha",
/// "variant": "…" }` (any subset) → the matching built artifacts. It reads the
/// products, not a room: it answers before any member is consumed (a build can
/// land first), `empty_fact` when nothing matches.
pub fn answer(room: &CodeRoom, req: &Value) -> Result<Value, Refusal> {
let s = |k: &str| req.get(k).and_then(Value::as_str).map(str::trim).filter(|v| !v.is_empty()).map(str::to_string);
let q = ArtifactQuery { krate: s("crate"), commit: s("commit"), variant: s("variant") };
if q.commit.as_deref().is_some_and(|c| c.rsplit('@').next().is_some_and(|sha| sha.len() < 7)) {
return Err(Refusal::new(State::InvalidArgument, "`commit` needs at least 7 hex digits of the sha"));
}
let log = room.log()?;
let hits = log
.block_on(async {
let pinned = log.pin().await?;
mimir_ingest::product::artifacts(&pinned, &q).await
})
.map_err(|e| Refusal::new(State::Internal, format!("read the built artifacts: {e:#}")))?;
Ok(json!({
"surface": "Coderoom.Artifacts",
"state": if hits.is_empty() { "empty_fact" } else { "complete" },
"query": q,
"count": hits.len(),
"artifacts": hits,
"evidence": { "notes": [
"builder 2: every artifact a successful Build Thing run filed with this server, from the binary stream's tables under `@build-thing` (artifact node + BUILT_FROM edges)",
"crates = the build's dependency closure as cargo resolved it; commits = the recipe repo's and every sibling repo a path dependency of the build lives in",
"the binary half (sizes, symbols) is in the warehouse's binary_facts / section_sizes / symbol_sizes under the artifact's sha256",
] },
}))
}
#[cfg(test)]
mod tests {
use super::*;
use nornir_build_thing::build_info::{BuildInfoKind, manifest_rows};
const SIB_SHA: &str = "0123456789abcdef0123456789abcdef01234567";
const APP_SHA: &str = "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678";
/// A successful build of `app` whose one artifact is a real ELF (`/bin/true`
/// copied under the builder's checkout), + the forge's lineage rows.
fn build(root: &Path) -> (BuildProvenance, Vec<BuildInfoRow>) {
let bin = root.join("target/release/app");
std::fs::create_dir_all(bin.parent().unwrap()).unwrap();
std::fs::copy("/bin/true", &bin).unwrap();
let bytes = std::fs::metadata(&bin).unwrap().len();
let sha = crate::binscan::content_sha256(&bin).unwrap();
let record: BuildProvenance = serde_json::from_value(json!({
"schema": "x", "id": "build-7", "repo": "app",
"variant": { "name": "app-server", "package": "app" },
"inputs": { "cargo_args": [], "features": ["tls"], "profile": "release", "vcs_rev": APP_SHA, "vcs_branch": "main", "vcs_dirty": false, "worktree_digest": "clean" },
"toolchain": { "rustc": "rustc 1.92.0", "host": "x86_64-unknown-linux-gnu", "cargo": "cargo 1.92.0" },
"host": { "cores": 8, "memory_bytes": 1 },
"outcome": "success",
"artifacts": [ { "path": "target/release/app", "bytes": bytes, "sha256": sha } ],
"built_at": "2026-10-15T20:00:00Z", "builder": "builder@dwarf",
}))
.unwrap();
let mut rows = manifest_rows(&record, 1);
let row = |k: BuildInfoKind, n: &str, v: &str| BuildInfoRow::new("build-7", 2, k).kv(n, v.to_string());
rows.push(row(BuildInfoKind::Provenance, REPO_ROOT, &root.display().to_string()));
rows.push(row(BuildInfoKind::Provenance, &format!("{COMMIT_PREFIX}app"), APP_SHA));
rows.push(row(BuildInfoKind::Provenance, &format!("{COMMIT_PREFIX}sib"), SIB_SHA));
for c in ["app@0.1.0", "sib@0.2.0", "serde@1.0.200"] {
rows.push(row(BuildInfoKind::Dep, DEP_CRATE, c));
}
// another build's rows in the same batch are not this one's
rows.push(BuildInfoRow::new("build-8", 2, BuildInfoKind::Dep).kv(DEP_CRATE, "tokio@1.0.0"));
(record, rows)
}
#[test]
fn a_filed_build_lands_in_mimir_and_answers_which_artifacts_came_from_a_crate_or_a_commit() {
let tmp = tempfile::tempdir().unwrap();
let (record, rows) = build(&tmp.path().join("checkout"));
assert_eq!(landed_builds(&rows), vec!["build-7".to_string()], "the manifest says success");
let facts = facts_of(&record, &rows).unwrap();
assert_eq!(facts.commits.get("sib").map(String::as_str), Some(SIB_SHA));
assert_eq!(facts.crates.len(), 3, "tokio is build-8's");
assert_eq!((facts.variant.as_str(), facts.triple.as_str(), facts.features.clone()), ("app-server", "x86_64-unknown-linux-gnu", vec!["tls".to_string()]));
let room = CodeRoom::new("ws", &tmp.path().join("builds"));
let landed = room.land_products(&facts).unwrap();
assert_eq!(landed.len(), 1);
assert_eq!(landed[0].repo, mimir_ingest::PRODUCTS_REPO);
let v = answer(&room, &json!({ "crate": "serde" })).unwrap();
assert_eq!((v["state"].as_str(), v["count"].as_u64()), (Some("complete"), Some(1)), "{v}");
let hit = &v["artifacts"][0];
assert_eq!(hit["file"], "app");
assert!(hit["provenance"].as_str().unwrap().contains("build-7"));
assert_eq!(answer(&room, &json!({ "commit": &SIB_SHA[..8] })).unwrap()["count"], 1, "built from the sibling's commit");
assert_eq!(answer(&room, &json!({ "commit": format!("app@{APP_SHA}") })).unwrap()["count"], 1);
assert_eq!(answer(&room, &json!({ "crate": "tokio" })).unwrap()["state"], "empty_fact");
assert_eq!(answer(&room, &json!({ "commit": "0123" })).unwrap_err().state, State::InvalidArgument);
}
/// `Viz.Binary` lists what Build Thing runs filed (`@build-thing`, no
/// consumer offset — the room never sees it): with a room that links no
/// artifact, a filed build is what the Binary pane shows, its sizes and
/// hardening from the server's analysis; before it lands the answer says
/// no build filed one either.
#[test]
fn viz_binary_shows_a_filed_build_when_the_room_links_no_artifact() {
let tmp = tempfile::tempdir().unwrap();
let (record, rows) = build(&tmp.path().join("checkout"));
let ws = crate::code_room::reader::fixture::fresh_tiny();
let wh = crate::warehouse::iceberg::IcebergWarehouse::open(&tmp.path().join("wh")).unwrap();
let reach = crate::code_room::Reach::new(&ws.handle, &wh);
let ask = |p: Value| crate::code_room::reader::wired::answer("Viz.Binary", &reach, &p);
let r = ask(json!({})).unwrap_err();
assert_eq!(r.state, State::NotConsumed, "{}", r.note);
assert!(r.note.contains("no Build Thing run has filed an artifact"), "{}", r.note);
let facts = facts_of(&record, &rows).unwrap();
ws.handle.land_products(&facts).unwrap();
assert_eq!(binary_half(&wh, &record, &rows).len(), 1);
let v = ask(json!({})).unwrap_or_else(|r| panic!("[{}] {}", r.state, r.note));
// partial: the room half (repo `v`, consumed without the binary stream) is the named gap
assert_eq!((v["state"].as_str(), v["source"].as_str()), (Some("partial"), Some("build")), "{v}");
assert_eq!(v["evidence"]["missing"][0]["input"], "binary stream", "{v}");
let arts = v["artifacts"].as_array().unwrap();
assert_eq!(arts.len(), 1, "{v}");
assert_eq!((arts[0]["source"].as_str(), arts[0]["binary"].as_str(), arts[0]["variant"].as_str()), (Some("build"), Some("app"), Some("app-server")));
assert_eq!(arts[0]["sha256"], record.artifacts[0].sha256);
assert!(v["checksec"]["pie"].is_boolean() && v["checksec"]["total_bytes"].as_i64().unwrap_or(0) > 0, "hardening measured: {v}");
assert_eq!(v["artifact"], arts[0]["artifact"]);
// asked by file name, and by a sha prefix
assert_eq!(ask(json!({ "artifact": "app" })).unwrap()["source"], "build");
assert_eq!(ask(json!({ "artifact": &record.artifacts[0].sha256[..12] })).unwrap()["source"], "build");
}
#[test]
fn the_binary_half_is_bin_analyze_under_the_artifacts_sha_and_only_for_bytes_on_this_box() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().join("checkout");
let (record, rows) = build(&root);
let wh = crate::warehouse::iceberg::IcebergWarehouse::open(&tmp.path().join("wh")).unwrap();
let out = binary_half(&wh, &record, &rows);
assert_eq!(out.len(), 1, "{out:?}");
assert!(out[0].get("error").is_none(), "{out:?}");
let sha = &record.artifacts[0].sha256;
let (header, sections, _) = crate::binscan::load_analysis(&wh, sha).unwrap().expect("persisted under its sha");
assert_eq!(header.repo, "app");
assert!(header.total_bytes > 0 && !sections.is_empty());
// a build on another box: nothing here to read, nothing analysed
let elsewhere: Vec<BuildInfoRow> = rows.iter().filter(|r| r.name != REPO_ROOT).cloned().collect();
assert!(local_artifacts(&record, &elsewhere).is_empty());
// bytes that changed since filing are not the artifact
std::fs::write(root.join("target/release/app"), b"other").unwrap();
assert!(local_artifacts(&record, &rows).is_empty(), "size differs: not the filed artifact");
let mut failed = record.clone();
failed.outcome = nornir_build_thing::provenance::Outcome::Failed;
assert!(facts_of(&failed, &rows).is_err());
}
}