crates/nornir-release-core/src/knowledge.rs
//! The call-graph MODEL of the release doctor's SEMANTIC-BLAST analysis.
//!
//! ## The reader itself is not here
//!
//! [`KnowledgeView`], its memoized inverted index, `callers_of` / `callees_of` /
//! `call_path`, and the two-phase snapshot-pushdown [`load_latest`] /
//! [`load_latest_audited`] loader are the ONE definition in the shared
//! `nornir-knowledge-view` edda leaf, which reasons only over the engine-agnostic
//! [`nornir_warehouse_trait::Warehouse`] seam + its POD types + std — so hosting
//! the release core here costs no `nornir` dep (PRE-PROMPT L5: consumed, never
//! twinned).
//!
//! ## What is gone (Mímir v2, 2026-10-13)
//!
//! `load_preferred_merged` and the `scip` shaping helpers — the cross-binary
//! moniker join over `scip_occurrences`, with a `symbol_facts` / `call_edges`
//! fallback — read three tables nornir no longer writes (its syn scan and its
//! `rust-analyzer scip` ingest are deleted; the code graph is the `mimir_v2`
//! lake, which the `Warehouse` seam does not carry). They are deleted with
//! their `scip` feature rather than left to answer from an empty or a stale
//! table. [`crate::semantic_blast::compute`] says so by name
//! ([`crate::semantic_blast::CallGraphNotProduced`]); a producer that holds a
//! call graph builds a [`KnowledgeView`] and calls
//! [`crate::semantic_blast::rank`].
pub use nornir_knowledge_view::{load_latest, KnowledgeView};
#[allow(unused_imports)]
pub(crate) use nornir_knowledge_view::{load_latest_audited, ScanAudit};
#[cfg(test)]
mod tests {
use super::*;
use nornir_warehouse_trait::CallEdgeRow;
fn edge(callee: &str) -> CallEdgeRow {
CallEdgeRow {
crate_name: "demo".into(),
caller_path: "demo::f".into(),
callee_ident: callee.into(),
call_kind: "call".into(),
file: "src/lib.rs".into(),
line: 1,
}
}
fn edge_from(caller: &str, callee: &str) -> CallEdgeRow {
CallEdgeRow {
crate_name: "demo".into(),
caller_path: caller.into(),
callee_ident: callee.into(),
call_kind: "call".into(),
file: "src/lib.rs".into(),
line: 1,
}
}
/// The indexed `callers_of` matches every shape the reverse-lookup depends on:
/// bare method call, path-qualified, and the `::`-boundary decoy (`new` must
/// NOT match `renew`). This is the exact match semantics SEMANTIC-BLAST relies
/// on to attribute a changed symbol to its call sites.
#[test]
fn callers_of_matches_last_segment_and_bare() {
let view = KnowledgeView::new(
vec![],
vec![edge("new"), edge("Arc::new"), edge("Foo::new"), edge("renew"), edge("Foo::make")],
);
let hits: Vec<&str> =
view.callers_of("new").iter().map(|c| c.callee_ident.as_str()).collect();
let ok = hits.contains(&"new")
&& hits.contains(&"Arc::new")
&& hits.contains(&"Foo::new")
&& !hits.contains(&"renew")
&& !hits.contains(&"Foo::make")
&& hits.len() == 3
&& view.callers_of("Arc::new").iter().map(|c| c.callee_ident.as_str()).collect::<Vec<_>>()
== vec!["Arc::new"];
nornir_testmatrix::functional_status(
"release-knowledge-view",
"callers_of_matches_last_segment_and_bare",
ok,
&format!("hits={hits:?}"),
);
assert!(ok, "reverse-lookup match shapes wrong: {hits:?}");
}
/// The memoized-CSR `call_path` BFS walks caller → callee at identifier
/// granularity, normalizing fully-qualified inputs to their last segment.
#[test]
fn call_path_bfs_over_call_edges() {
let view = KnowledgeView::new(
vec![],
vec![
edge_from("a::run", "step"),
edge_from("b::step", "Repo::commit"),
edge_from("a::run", "noop"),
],
);
let ok = view.call_path("run", "commit") == Some(vec!["run".into(), "step".into(), "commit".into()])
&& view.call_path("a::run", "Repo::commit")
== Some(vec!["run".into(), "step".into(), "commit".into()])
&& view.call_path("step", "step") == Some(vec!["step".to_string()])
&& view.call_path("commit", "run").is_none()
&& view.call_path("ghost", "run").is_none();
nornir_testmatrix::functional_status(
"release-knowledge-view",
"call_path_bfs_over_call_edges",
ok,
&format!("run->commit={:?}", view.call_path("run", "commit")),
);
assert!(ok, "call_path BFS wrong");
}
}