use fathomdb_engine::{Engine, PreparedWrite};
use fathomdb_schema::SQLITE_SUFFIX;
use tempfile::TempDir;
fn db_path(dir: &TempDir, name: &str) -> std::path::PathBuf {
dir.path().join(format!("{name}{SQLITE_SUFFIX}"))
}
fn node_write(kind: &str, body: &str, logical_id: &str) -> PreparedWrite {
PreparedWrite::Node {
kind: kind.to_string(),
body: body.to_string(),
source_id: None,
logical_id: Some(logical_id.to_string()),
}
}
fn edge_with_t_invalid(from: &str, to: &str, logical_id: &str, t_invalid: &str) -> PreparedWrite {
PreparedWrite::Edge {
kind: "link".to_string(),
from: from.to_string(),
to: to.to_string(),
source_id: None,
logical_id: Some(logical_id.to_string()),
body: Some(format!("{from} links to {to}")),
t_valid: None,
t_invalid: Some(t_invalid.to_string()),
confidence: None,
extractor_model_id: None,
temporal_fallback: None,
}
}
fn live_edge(from: &str, to: &str, logical_id: &str) -> PreparedWrite {
PreparedWrite::Edge {
kind: "link".to_string(),
from: from.to_string(),
to: to.to_string(),
source_id: None,
logical_id: Some(logical_id.to_string()),
body: Some(format!("{from} links to {to}")),
t_valid: None,
t_invalid: None,
confidence: None,
extractor_model_id: None,
temporal_fallback: None,
}
}
fn temporal_fallback_edge(from: &str, to: &str, logical_id: &str) -> PreparedWrite {
PreparedWrite::Edge {
kind: "link".to_string(),
from: from.to_string(),
to: to.to_string(),
source_id: None,
logical_id: Some(logical_id.to_string()),
body: Some(format!("{from} links to {to}")),
t_valid: Some("2024-01-01T00:00:00Z".to_string()),
t_invalid: None,
confidence: None,
extractor_model_id: None,
temporal_fallback: Some(true),
}
}
#[allow(dead_code)]
fn write_inline_stub(dir: &TempDir, doc_id: &str, result_json: &str) -> String {
use std::io::Write as _;
let src = format!(
"import json,sys\nRES='{}'\n\
for line in sys.stdin:\n \
line=line.strip()\n \
if not line: continue\n \
msg=json.loads(line)\n \
t=msg.get('type')\n \
if t=='hello': print(json.dumps({{'protocol':'fathomdb.extract.v1','type':'ready','schema_version':1,'model':'stub','max_docs_per_request':1}}),flush=True)\n \
elif t=='extract':\n \
r=json.loads(RES)\n \
r['request_id']=msg.get('request_id')\n \
print(json.dumps(r),flush=True)\n",
result_json.replace('\'', "\\'"),
);
let path = dir.path().join(format!("stub_{doc_id}.py"));
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(src.as_bytes()).unwrap();
path.to_string_lossy().to_string()
}
#[test]
fn graph_arm_drops_invalidated_edges() {
let dir = TempDir::new().unwrap();
let opened = Engine::open(db_path(&dir, "invalidated_edge")).expect("open");
opened
.engine
.write(&[
node_write("doc", "alice anchor text for search", "alice"),
node_write("doc", "bob target node unreachable via dead edge", "bob"),
edge_with_t_invalid("alice", "bob", "edge-ab", "2000-01-01T00:00:00Z"),
])
.expect("write");
let result = opened
.engine
.search_reranked("alice anchor", None, 0, true, 0.3, 0)
.expect("search with graph arm");
let bob_in_results = result.results.iter().any(|h| h.body.contains("bob target"));
assert!(
!bob_in_results,
"graph arm must NOT surface bob via an expired edge (t_invalid=2000); \
got results: {:?}",
result.results.iter().map(|h| &h.body).collect::<Vec<_>>()
);
opened.engine.close().unwrap();
}
#[test]
fn graph_arm_temporal_fallback_excluded_or_downweighted() {
let dir = TempDir::new().unwrap();
let opened = Engine::open(db_path(&dir, "temporal_fallback_edge")).expect("open");
opened
.engine
.write(&[
node_write("doc", "carol anchor text for search", "carol"),
node_write("doc", "dave reachable via live edge", "dave"),
node_write("doc", "eve unreachable via fallback edge", "eve"),
live_edge("carol", "dave", "edge-carol-dave"),
temporal_fallback_edge("carol", "eve", "edge-carol-eve"),
])
.expect("write");
let result = opened
.engine
.search_reranked("anchor text", None, 0, true, 0.3, 0)
.expect("search with graph arm");
let bodies: Vec<&str> = result.results.iter().map(|h| h.body.as_str()).collect();
assert!(
result.results.iter().any(|h| h.body.contains("dave reachable")),
"dave (reachable via live edge) must appear in graph arm results; got: {bodies:?}"
);
assert!(
!result.results.iter().any(|h| h.body.contains("eve unreachable")),
"eve must NOT appear (only edge has temporal_fallback=true → excluded from BFS); \
got: {bodies:?}"
);
opened.engine.close().unwrap();
}
#[test]
fn graph_arm_disabled_is_byte_identical_to_baseline() {
let dir = TempDir::new().unwrap();
let opened = Engine::open(db_path(&dir, "baseline_parity")).expect("open");
opened
.engine
.write(&[
node_write("doc", "baseline search doc alpha", "n1"),
node_write("doc", "baseline search doc beta", "n2"),
node_write("doc", "baseline search doc gamma", "n3"),
live_edge("n1", "n2", "e12"),
live_edge("n2", "n3", "e23"),
])
.expect("write");
let without_arm = opened
.engine
.search_reranked("baseline search", None, 0, false, 0.3, 0)
.expect("search without graph arm");
let with_arm = opened
.engine
.search_reranked("baseline search", None, 0, true, 0.3, 0)
.expect("search with graph arm");
let classic = opened.engine.search("baseline search").expect("classic search");
assert_eq!(
without_arm.results, classic.results,
"use_graph_arm=false must produce byte-identical results to Engine::search()"
);
let _ = with_arm;
opened.engine.close().unwrap();
}
#[test]
fn graph_arm_factoid_recall_cdf_artifact_pinned() {
let cdf_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../../../dev/plans/runs/IR-C-recall-cdf.json");
assert!(
cdf_path.exists(),
"IR-C-recall-cdf.json must exist at {}: run Slice 5 to generate it",
cdf_path.display()
);
let content = std::fs::read_to_string(&cdf_path).expect("read IR-C-recall-cdf.json");
let json: serde_json::Value =
serde_json::from_str(&content).expect("IR-C-recall-cdf.json must be valid JSON");
let recall_cdf =
json["recall_cdf"].as_array().expect("IR-C-recall-cdf.json must have a 'recall_cdf' array");
let entry = recall_cdf.iter().find(|e| {
e["arm"].as_str() == Some("rrf_fused")
&& e["query_class"].as_str() == Some("exact_fact")
&& e["k"].as_u64() == Some(200)
});
let entry = entry.expect(
"IR-C-recall-cdf.json must contain an entry with \
arm='rrf_fused', query_class='exact_fact', k=200",
);
let found_at_k = entry["found_at_k"].as_f64().expect("found_at_k must be a number");
assert!(
found_at_k >= 0.9695,
"factoid Recall@K=200 rrf_fused must be >= 0.9695 (got {found_at_k:.4}); \
this is the no-regress floor from Slice 5"
);
println!(
"graph_arm_factoid_recall_cdf_artifact_pinned: found_at_k={found_at_k:.4} >= 0.9695 OK"
);
}