use super::*;
fn seed_polluted_graph(db: &YantrikDB) {
let conn = db.conn.lock();
let ts = 1_700_000_000.0f64;
for (name, etype, mc) in [
("Pranab", "person", 1412i64),
("Pranab's", "project", 748),
("Hermes", "person", 10),
("Hermes's", "org", 2),
("Don't", "concept", 96),
] {
conn.execute(
"INSERT INTO entities (name, entity_type, mention_count, first_seen, last_seen) \
VALUES (?1, ?2, ?3, ?4, ?4)",
params![name, etype, mc, ts],
)
.unwrap();
}
for (rid, entity) in [
("rid-x", "Pranab"),
("rid-x", "Pranab's"), ("rid-y", "Pranab's"), ] {
conn.execute(
"INSERT INTO memory_entities (memory_rid, entity_name) VALUES (?1, ?2)",
params![rid, entity],
)
.unwrap();
}
}
#[test]
fn possessive_phantoms_fold_into_their_canonicals() {
let db = YantrikDB::new(":memory:", 8).unwrap();
seed_polluted_graph(&db);
db.rebuild_graph_index().unwrap();
{
let conn = db.conn.lock();
let aliases: Vec<(String, String)> = conn
.prepare("SELECT alias, canonical_name FROM entity_aliases ORDER BY alias")
.unwrap()
.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))
.unwrap()
.collect::<std::result::Result<_, _>>()
.unwrap();
assert_eq!(
aliases,
vec![
("Hermes's".to_string(), "Hermes".to_string()),
("Pranab's".to_string(), "Pranab".to_string()),
],
"possessives alias to canonicals; Don't must NOT alias"
);
}
let gi = db.graph_index.read();
let hits = gi.entity_matches_query(&[String::from("pranab")]);
assert_eq!(hits.len(), 1, "{hits:?}");
let (name, etype, mentions) = &hits[0];
assert_eq!(name, "Pranab");
assert_eq!(
etype, "person",
"canonical type wins; 'project' was the misparse"
);
assert_eq!(*mentions, 1412 + 748, "phantom mentions merge additively");
let mems = gi.memories_for_entities(&["Pranab"]);
assert_eq!(
mems.len(),
2,
"rid-x once (deduped) + rid-y (repointed): {mems:?}"
);
assert!(mems.iter().any(|r| r == "rid-x") && mems.iter().any(|r| r == "rid-y"));
drop(gi);
db.rebuild_graph_index().unwrap();
{
let conn = db.conn.lock();
let n: i64 = conn
.query_row("SELECT COUNT(*) FROM entity_aliases", [], |r| r.get(0))
.unwrap();
assert_eq!(n, 2, "migration is idempotent");
}
let gi = db.graph_index.read();
let hits = gi.entity_matches_query(&[String::from("pranab")]);
assert_eq!(hits[0].2, 1412 + 748, "no double-fold on rebuild");
}
#[cfg(feature = "bundled-embedder")]
#[test]
fn claims_lane_surfaces_the_direction_cosine_destroys() {
let db = YantrikDB::with_default(":memory:").unwrap();
let rec = |text: &str| {
db.record_text(
text,
"semantic",
0.5,
0.0,
604800.0,
&serde_json::json!({}),
"default",
0.8,
"general",
"user",
None,
)
.unwrap()
};
for name in ["Pat", "Sam", "Alex", "Jo", "Max", "Kim", "Lee", "Ada"] {
rec(&format!("{name} reports to Taylor."));
}
let target = rec("Taylor reports to Carol.");
{
let conn = db.conn.lock();
conn.execute(
"INSERT INTO entities (name, entity_type, mention_count, first_seen, last_seen) \
VALUES ('Taylor', 'person', 9, 1.0, 1.0)",
[],
)
.unwrap();
conn.execute(
"INSERT INTO claims (claim_id, src, dst, rel_type, weight, created_at, \
source_memory_rid) VALUES ('c1', 'Taylor', 'Carol', 'reports_to', 1.0, 1.0, ?1)",
params![target],
)
.unwrap();
}
db.rebuild_graph_index().unwrap();
let query = "Who does Taylor report to?";
let resp = db
.recall_with_response(
&db.embed(query).unwrap(),
5,
None,
None,
false,
true,
Some(query),
true,
None,
None,
None,
)
.unwrap();
let hit = resp
.results
.iter()
.find(|r| r.rid == target)
.expect("the claim's source record must reach top-5");
assert!(
hit.why_retrieved
.iter()
.any(|w| w.contains("claims_match: Taylor -reports_to-> Carol")),
"direction provenance must be spelled out, got {:?}",
hit.why_retrieved
);
}
#[cfg(feature = "bundled-embedder")]
#[test]
fn recall_is_deterministic_with_claims_in_the_reserve_band() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("det.db");
let db = YantrikDB::with_default(path.to_str().unwrap()).unwrap();
let rec = |text: &str| {
db.record_text(
text,
"semantic",
0.5,
0.0,
604800.0,
&serde_json::json!({}),
"default",
0.8,
"general",
"user",
None,
)
.unwrap()
};
for name in ["Pat", "Sam", "Alex", "Jo", "Max", "Kim", "Lee", "Ada"] {
rec(&format!("{name} reports to Taylor."));
}
let mut claim_rids = Vec::new();
for (i, dst) in ["Carol", "Quinn", "Liam", "Grace"].iter().enumerate() {
let rid = rec(&format!("Taylor reports to {dst} on project {i}."));
claim_rids.push((rid, dst.to_string()));
}
{
let conn = db.conn.lock();
conn.execute(
"INSERT INTO entities (name, entity_type, mention_count, first_seen, last_seen) \
VALUES ('Taylor', 'person', 12, 1.0, 1.0)",
[],
)
.unwrap();
for (i, (rid, dst)) in claim_rids.iter().enumerate() {
conn.execute(
"INSERT INTO claims (claim_id, src, dst, rel_type, weight, created_at, \
source_memory_rid) VALUES (?1, 'Taylor', ?2, 'reports_to', 1.0, ?3, ?4)",
params![format!("c{i}"), dst, 1.0 + i as f64, rid],
)
.unwrap();
}
}
db.rebuild_graph_index().unwrap();
let query = "What is Taylor's role?";
let emb = db.embed(query).unwrap();
let baseline: Vec<String> = db
.recall_with_response(
&emb,
5,
None,
None,
false,
true,
Some(query),
true,
None,
None,
None,
)
.unwrap()
.results
.iter()
.map(|r| r.rid.clone())
.collect();
for run in 1..5 {
let this: Vec<String> = db
.recall_with_response(
&emb,
5,
None,
None,
false,
true,
Some(query),
true,
None,
None,
None,
)
.unwrap()
.results
.iter()
.map(|r| r.rid.clone())
.collect();
assert_eq!(
this, baseline,
"run {run} produced a different ranking from identical inputs"
);
}
db.close().unwrap();
for reopen in 0..5 {
let db = YantrikDB::with_default(path.to_str().unwrap()).unwrap();
let this: Vec<String> = db
.recall_with_response(
&emb,
5,
None,
None,
false,
true,
Some(query),
true,
None,
None,
None,
)
.unwrap()
.results
.iter()
.map(|r| r.rid.clone())
.collect();
db.close().unwrap();
assert_eq!(
this, baseline,
"fresh open {reopen} produced a different ranking from identical bytes"
);
}
}
#[test]
fn fold_reverses_when_alias_rows_are_deleted() {
let db = YantrikDB::new(":memory:", 8).unwrap();
seed_polluted_graph(&db);
db.rebuild_graph_index().unwrap();
{
let conn = db.conn.lock();
conn.execute(
"DELETE FROM entity_aliases WHERE source = 'possessive_migration_v1'",
[],
)
.unwrap();
let rebuilt = crate::graph_index::GraphIndex::build_from_db(&conn).unwrap();
let hits = rebuilt.entity_matches_query(&[String::from("pranab")]);
assert_eq!(
hits[0].2, 1412,
"with aliases gone the phantom un-folds; canonical keeps only its own mentions"
);
}
}