use std::path::PathBuf;
use fathomdb_engine::{Engine, ExtractDocument, PreparedWrite, RowKind};
use fathomdb_schema::SQLITE_SUFFIX;
use rusqlite::{params, Connection};
use tempfile::TempDir;
const LEGACY_SOURCE_ID: &str = "_legacy:pre-0.8.20";
fn db_path(dir: &TempDir, name: &str) -> PathBuf {
dir.path().join(format!("{name}{SQLITE_SUFFIX}"))
}
fn count(conn: &Connection, sql: &str) -> i64 {
conn.query_row(sql, [], |row| row.get::<_, i64>(0)).expect("count query")
}
fn fixture_dir() -> PathBuf {
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/slice15_byo_llm")
}
#[test]
fn no_canonical_row_has_null_source_id() {
let dir = TempDir::new().expect("tempdir");
let path = db_path(&dir, "null_provenance");
let opened = Engine::open_without_embedder_for_test(&path).expect("open");
let engine = &opened.engine;
engine
.write(&[PreparedWrite::Node {
kind: "doc".to_string(),
body: "public node body".to_string(),
source_id: fathomdb_engine::SourceId::new("doc-public").expect("test source id"),
logical_id: None,
state: fathomdb_engine::InitialState::Active,
reason: None,
valid_from: None,
valid_until: None,
}])
.expect("public node write");
engine
.write(&[PreparedWrite::Edge {
kind: "mentions".to_string(),
from: "a".to_string(),
to: "b".to_string(),
source_id: fathomdb_engine::SourceId::new("doc-public").expect("test source id"),
logical_id: Some("edge-public".to_string()),
body: Some("public edge body".to_string()),
t_valid: None,
t_invalid: None,
confidence: None,
extractor_model_id: None,
temporal_fallback: None,
}])
.expect("public edge write");
engine
.write_canonical_row_with_kind_for_test("coverage", "coverage row body", RowKind::Coverage)
.expect("engine-derived coverage row");
engine
.write_canonical_row_with_kind_for_test("graph", "graph row body", RowKind::Graph)
.expect("engine-derived graph row");
let conn = Connection::open(&path).expect("open raw");
let null_nodes = count(&conn, "SELECT count(*) FROM canonical_nodes WHERE source_id IS NULL");
let null_edges = count(&conn, "SELECT count(*) FROM canonical_edges WHERE source_id IS NULL");
assert_eq!(
null_nodes, 0,
"every stored canonical_nodes row must carry provenance; \
a NULL source_id row is unreachable by excise_source and therefore un-erasable"
);
assert_eq!(
null_edges, 0,
"every stored canonical_edges row must carry provenance; \
a NULL source_id row is unreachable by excise_source and therefore un-erasable"
);
let engine_rows = count(
&conn,
"SELECT count(*) FROM canonical_nodes WHERE kind IN ('coverage', 'graph') \
AND source_id LIKE '\\_engine:%' ESCAPE '\\'",
);
assert_eq!(engine_rows, 2, "engine-derived rows must take a reserved `_engine:*` provenance");
}
fn seed_pre_0_8_20_database(dir: &TempDir, name: &str) -> PathBuf {
let path = db_path(dir, name);
let conn = Connection::open(&path).expect("open raw");
let steps: Vec<fathomdb_schema::Migration> =
fathomdb_schema::MIGRATIONS.iter().filter(|m| m.step_id <= 20).cloned().collect();
fathomdb_schema::migrate_with_steps(&conn, &steps).expect("migrate to step 20");
conn.execute(
"INSERT INTO canonical_nodes(write_cursor, kind, body, source_id, logical_id)
VALUES(1, 'doc', 'ungoverned legacy body', NULL, NULL)",
params![],
)
.expect("insert ungoverned legacy node");
conn.execute(
"INSERT INTO canonical_nodes(write_cursor, kind, body, source_id, logical_id)
VALUES(2, 'doc', 'governed legacy body', NULL, 'governed-lid-1')",
params![],
)
.expect("insert governed legacy node");
drop(conn);
path
}
#[test]
fn excise_legacy_source_deletes_no_governed_row() {
let dir = TempDir::new().expect("tempdir");
let path = seed_pre_0_8_20_database(&dir, "legacy_excise");
let opened = Engine::open_without_embedder_for_test(&path).expect("open migrates to head");
let engine = &opened.engine;
let report = engine.excise_source(LEGACY_SOURCE_ID).expect("excise legacy source");
assert!(
report.nodes_excised >= 1,
"the backfilled ungoverned legacy row must be excisable by `{LEGACY_SOURCE_ID}`; \
removed {} node(s)",
report.nodes_excised
);
drop(opened);
let conn = Connection::open(&path).expect("open raw");
let ungoverned =
count(&conn, "SELECT count(*) FROM canonical_nodes WHERE body = 'ungoverned legacy body'");
assert_eq!(ungoverned, 0, "the ungoverned legacy row must be gone after excise_source");
let governed =
count(&conn, "SELECT count(*) FROM canonical_nodes WHERE body = 'governed legacy body'");
assert_eq!(
governed, 1,
"TC-11 pin: excise_source('{LEGACY_SOURCE_ID}') must delete NO governed row"
);
let governed_source: Option<String> = conn
.query_row(
"SELECT source_id FROM canonical_nodes WHERE body = 'governed legacy body'",
[],
|row| row.get(0),
)
.expect("governed row still present");
assert_eq!(
governed_source, None,
"TC-11 pin: a governed legacy row keeps NULL source_id and stays purge-addressable \
by logical_id"
);
}
#[test]
fn extractor_omitting_source_doc_id_still_excisable() {
let script = fixture_dir().join("provenance_omitting_harness.py");
assert!(script.exists(), "fixture harness must exist at {}", script.display());
let cmd_strings = ["python3".to_string(), script.to_string_lossy().to_string()];
let cmd_refs: Vec<&str> = cmd_strings.iter().map(|s| s.as_str()).collect();
let dir = TempDir::new().expect("tempdir");
let path = db_path(&dir, "ingest_provenance");
let opened = Engine::open_without_embedder_for_test(&path).expect("open");
let engine = &opened.engine;
let docs = vec![ExtractDocument {
source_doc_id: "caller-doc-1".to_string(),
body: "Alice owns the project".to_string(),
}];
let receipt = engine.ingest_with_extractor(&cmd_refs, &docs).expect("ingest must succeed");
assert!(receipt.nodes_written > 0, "fixture must produce entity rows");
assert!(receipt.edges_written > 0, "fixture must produce edge rows");
{
let conn = Connection::open(&path).expect("open raw");
let bad_nodes = count(
&conn,
"SELECT count(*) FROM canonical_nodes WHERE source_id IS NOT 'caller-doc-1'",
);
assert_eq!(
bad_nodes, 0,
"every ingested node must carry the CALLER's source_doc_id, not the model's echo"
);
let bad_edges = count(
&conn,
"SELECT count(*) FROM canonical_edges WHERE source_id IS NOT 'caller-doc-1'",
);
assert_eq!(
bad_edges, 0,
"every ingested edge must carry the CALLER's source_doc_id, not the model's echo"
);
}
let report = engine.excise_source("caller-doc-1").expect("excise");
assert!(
report.nodes_excised > 0 && report.edges_excised > 0,
"rows ingested from a source_doc_id-omitting extractor must remain excisable \
by the caller's document id (removed {} nodes / {} edges)",
report.nodes_excised,
report.edges_excised
);
drop(opened);
let conn = Connection::open(&path).expect("open raw");
assert_eq!(count(&conn, "SELECT count(*) FROM canonical_nodes"), 0);
assert_eq!(count(&conn, "SELECT count(*) FROM canonical_edges"), 0);
}