src/mimir_lens/sources.rs
//! **Provenance-carrying table set for the Mimir lenses** — the anti-silent-empty
//! record. LIFTED from `dwarves::mimir` (mod.rs); the fold logic is unchanged.
//!
//! An empty ranked list and an unfed table must NEVER draw the same. Every lens
//! response carries [`evidence_note`], which names missing feeds by table.
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use super::rows::Table;
/// One warehouse table, plus **which root it was read from** — provenance is a
/// column, not a footnote.
#[derive(Debug, Clone, Default)]
pub struct SourcedTable {
pub table: Table,
/// The warehouse root this table's rows came from (`""` when absent everywhere).
pub root: String,
}
/// The tables a lens reads, merged across one or more warehouse roots.
///
/// Merging is per TABLE, not per row: the first root that has rows for a table
/// wins, and the winning root is recorded. (Since the §2a fix unified the two
/// warehouse roots, nornir reads a single root — but the LIST shape is kept so a
/// consumer that still reads a default root alongside a workspace one stays
/// traceable.)
#[derive(Debug, Clone, Default)]
pub struct Sources {
tables: BTreeMap<String, SourcedTable>,
/// Every root that was consulted, in order.
pub roots: Vec<String>,
}
impl Sources {
/// Merge a root's tables in. Later roots only fill tables earlier roots left empty.
pub fn add_root(&mut self, root: impl Into<String>, tables: BTreeMap<String, Table>) {
let root = root.into();
self.roots.push(root.clone());
for (name, t) in tables {
let slot = self.tables.entry(name).or_default();
if slot.table.is_empty() && !t.is_empty() {
slot.table = t;
slot.root = root.clone();
}
}
}
pub fn get(&self, table: &str) -> &Table {
static EMPTY: std::sync::OnceLock<Table> = std::sync::OnceLock::new();
self.tables
.get(table)
.map(|s| &s.table)
.unwrap_or_else(|| EMPTY.get_or_init(Table::default))
}
/// Which root a table's rows came from (`""` ⇒ the table is empty everywhere).
pub fn root_of(&self, table: &str) -> &str {
self.tables
.get(table)
.map(|s| s.root.as_str())
.unwrap_or("")
}
/// The honest feed record for a set of tables: rows read and the root they came from.
pub fn feeds(&self, want: &[&str]) -> Vec<Feed> {
want.iter()
.map(|t| Feed {
table: (*t).to_string(),
rows: self.get(t).rows.len(),
root: self.root_of(t).to_string(),
missing: self.get(t).is_empty(),
})
.collect()
}
}
/// What one table actually yielded — the anti-silent-empty record, with provenance.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Feed {
pub table: String,
pub rows: usize,
/// The warehouse root the rows came from.
pub root: String,
pub missing: bool,
}
/// A one-line statement of what the evidence supports, rendered verbatim by every
/// front door so no consumer can present a thin result as a strong one.
pub fn evidence_note(feeds: &[Feed], what: &str) -> String {
let missing: Vec<&str> = feeds
.iter()
.filter(|f| f.missing)
.map(|f| f.table.as_str())
.collect();
if missing.is_empty() {
return format!("{what}: every input table carried rows.");
}
format!(
"{what}: MISSING FEEDS — {} carried NO rows. This is a missing producer, not an \
absence of findings; any count below is a LOWER bound.",
missing.join(", ")
)
}
#[cfg(test)]
mod tests {
use super::*;
fn tbl(n: usize) -> Table {
Table::new(
vec!["a".into()],
(0..n).map(|i| vec![i.to_string()]).collect(),
)
}
#[test]
fn a_table_absent_everywhere_is_a_missing_feed_not_a_clean_result() {
let mut s = Sources::default();
s.add_root("/only", [("call_edges".to_string(), tbl(2))].into());
assert!(s.get("scip_occurrences").is_empty());
assert_eq!(s.root_of("scip_occurrences"), "");
let feeds = s.feeds(&["call_edges", "scip_occurrences"]);
assert!(!feeds[0].missing && feeds[0].rows == 2);
assert!(feeds[1].missing);
let note = evidence_note(&feeds, "zombie");
assert!(note.contains("MISSING FEEDS"), "{note}");
assert!(note.contains("scip_occurrences"), "{note}");
assert!(note.contains("LOWER bound"), "{note}");
}
}