#[path = "common/harness.rs"]
mod harness;
use harness::TestHarness;
use macrame::graph::{EdgeAssertion, TraversalBuilder};
use macrame::{BranchId, ConceptUpsert, Database, DbError};
use std::time::Duration;
const TS: &str = "2026-01-01T00:00:00.000000Z";
const TS2: &str = "2026-02-01T00:00:00.000000Z";
const TS3: &str = "2026-03-01T00:00:00.000000Z";
const NOW: &str = "2026-06-01T00:00:00.000000Z";
async fn seed(db: &Database) {
for id in ["a", "b", "c", "d"] {
db.upsert_concept(ConceptUpsert::new(id, "N").valid_from(TS))
.await
.unwrap();
}
}
fn edge(source: &str, target: &str, from: &str, to: Option<&str>) -> EdgeAssertion {
let e = EdgeAssertion::new(source, target, "KNOWS").valid_from(from);
match to {
Some(t) => e.valid_to(t),
None => e,
}
}
async fn seen(db: &Database, branch: Option<&BranchId>) -> Vec<(String, String)> {
let mut out =
macrame::temporal::query_as_of_edges_on(db.read_conn(), NOW, branch.map(BranchId::as_str))
.await
.unwrap()
.into_iter()
.map(|(s, t, _, _, _)| (s, t))
.collect::<Vec<_>>();
out.sort();
out
}
async fn rows_on(db: &Database, branch: &str) -> i64 {
db.read_conn()
.query(
"SELECT COUNT(*) FROM links WHERE branch_id = ?1",
libsql::params![branch],
)
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap()
}
async fn forked(h: &TestHarness) -> (Database, BranchId) {
let db = h.db_with_fake_clock().await;
seed(&db).await;
(db, BranchId::new("alt").unwrap())
}
#[tokio::test]
async fn an_edge_asserted_on_a_branch_is_invisible_to_the_trunk() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.assert_edge(edge("a", "b", TS, None)).await.unwrap();
h.advance(Duration::from_secs(60));
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.assert_edge(edge("c", "d", TS, None).on_branch(alt.clone()))
.await
.unwrap();
assert_eq!(
seen(&db, Some(&alt)).await,
vec![("a".into(), "b".into()), ("c".into(), "d".into())]
);
assert_eq!(seen(&db, None).await, vec![("a".into(), "b".into())]);
assert_eq!(rows_on(&db, "alt").await, 1);
assert_eq!(rows_on(&db, "main").await, 1);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_branch_supersedes_an_inherited_edge_by_writing_beside_it() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.assert_edge(edge("a", "b", TS, None).weight(1.0))
.await
.unwrap();
h.advance(Duration::from_secs(60));
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.assert_edge(edge("a", "b", TS, None).weight(9.0).on_branch(alt.clone()))
.await
.unwrap();
let two: i64 = db
.read_conn()
.query(
"SELECT COUNT(*) FROM links_current WHERE source_id='a' AND target_id='b'",
(),
)
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(two, 2, "the parent's row must survive the branch's write");
let g = db
.load_subgraph_with(
&TraversalBuilder::new("a").on_branch(alt.clone()),
NOW,
1_000,
)
.await
.unwrap();
let w = g
.out_edges("a")
.iter()
.find(|e| e.node(&g) == "b")
.map(|e| e.weight())
.unwrap();
assert_eq!(w, 9.0);
let trunk = db
.load_subgraph_with(&TraversalBuilder::new("a"), NOW, 1_000)
.await
.unwrap();
assert_eq!(
trunk
.out_edges("a")
.iter()
.find(|e| e.node(&trunk) == "b")
.unwrap()
.weight(),
1.0,
"the trunk must not learn what the branch believes"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn retiring_an_inherited_edge_shadows_it_and_leaves_the_parent_alone() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.assert_edge(edge("a", "b", TS, None)).await.unwrap();
db.assert_edge(edge("b", "c", TS, None)).await.unwrap();
h.advance(Duration::from_secs(60));
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.retire_edge_on("a", "b", "KNOWS", TS, TS3, alt.clone())
.await
.unwrap();
assert_eq!(seen(&db, Some(&alt)).await, vec![("b".into(), "c".into())]);
assert_eq!(
seen(&db, None).await,
vec![("a".into(), "b".into()), ("b".into(), "c".into())]
);
assert_eq!(rows_on(&db, "alt").await, 1);
let parent_open: i64 = db
.read_conn()
.query(
"SELECT COUNT(*) FROM links_current WHERE branch_id='main' \
AND source_id='a' AND valid_to = '9999-12-31T23:59:59.999999Z'",
(),
)
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(parent_open, 1);
db.close().await.unwrap();
}
#[tokio::test]
async fn retiring_what_a_lineage_cannot_see_is_not_found() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.assert_edge(edge("a", "b", TS, None)).await.unwrap();
let err = db
.retire_edge_on("a", "b", "KNOWS", TS, TS3, alt.clone())
.await
.unwrap_err();
assert!(matches!(err, DbError::NotFound(_)), "got {err:?}");
db.close().await.unwrap();
}
#[tokio::test]
async fn a_branch_may_not_overlap_an_interval_it_inherited() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.assert_edge(edge("a", "b", TS, Some(TS3))).await.unwrap();
h.advance(Duration::from_secs(60));
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
let err = db
.assert_edge(edge("a", "b", TS2, Some(NOW)).on_branch(alt.clone()))
.await
.unwrap_err();
let DbError::OverlappingInterval { overlap } = err else {
panic!("expected an overlap, got {err:?}");
};
assert_eq!(overlap.existing_from, TS);
assert_eq!(overlap.existing_to, TS3);
db.close().await.unwrap();
}
#[tokio::test]
async fn the_trunk_is_not_refused_for_overlapping_what_a_branch_believes() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.assert_edge(edge("a", "b", TS, Some(TS3)).on_branch(alt.clone()))
.await
.unwrap();
db.assert_edge(edge("a", "b", TS2, Some(NOW)))
.await
.expect("the trunk must not be refused for a belief it cannot see");
assert_eq!(rows_on(&db, "main").await, 1);
assert_eq!(rows_on(&db, "alt").await, 1);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_branch_may_overlap_a_sibling_it_shares_no_ancestry_with() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
let other = BranchId::new("other").unwrap();
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.fork(other.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.assert_edge(edge("a", "b", TS, Some(TS3)).on_branch(alt.clone()))
.await
.unwrap();
db.assert_edge(edge("a", "b", TS2, Some(NOW)).on_branch(other.clone()))
.await
.expect("siblings share no visibility");
db.close().await.unwrap();
}
#[tokio::test]
async fn a_branch_may_still_re_assert_at_the_same_valid_from() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.assert_edge(edge("a", "b", TS, Some(TS3))).await.unwrap();
h.advance(Duration::from_secs(60));
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.assert_edge(edge("a", "b", TS, Some(NOW)).on_branch(alt.clone()))
.await
.expect("same valid_from is re-assertion, not overlap");
db.close().await.unwrap();
}
#[tokio::test]
async fn the_guard_sees_a_pre_fork_interval_the_projection_no_longer_holds() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.assert_edge(edge("a", "b", TS, Some(TS3))).await.unwrap();
h.advance(Duration::from_secs(60));
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.assert_edge(edge("a", "b", TS, Some(TS3)).weight(4.0))
.await
.unwrap();
let err = db
.assert_edge(edge("a", "b", TS2, Some(NOW)).on_branch(alt.clone()))
.await
.unwrap_err();
let DbError::OverlappingInterval { overlap } = err else {
panic!("the fold arm did not find the pre-fork interval: {err:?}");
};
assert_eq!(overlap.existing_from, TS);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_batch_contradicts_itself_within_a_lineage_and_not_across_two() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
let clash = vec![
edge("a", "b", TS, Some(NOW)).on_branch(alt.clone()),
edge("a", "b", TS2, Some(NOW)).on_branch(alt.clone()),
];
assert!(matches!(
db.write_bulk_atomic(clash).await,
Err(DbError::OverlappingInterval { .. })
));
let split = vec![
edge("a", "b", TS, Some(NOW)).on_branch(alt.clone()),
edge("a", "b", TS2, Some(NOW)),
];
assert_eq!(db.write_bulk_atomic(split).await.unwrap(), 2);
assert_eq!(rows_on(&db, "alt").await, 1);
assert_eq!(rows_on(&db, "main").await, 1);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_bulk_import_lands_each_edge_on_the_lineage_it_names() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
let edges = vec![
edge("a", "b", TS, None).on_branch(alt.clone()),
edge("b", "c", TS, None).on_branch(alt.clone()),
edge("c", "d", TS, None),
];
assert_eq!(db.bulk_import(edges).await.unwrap(), 3);
assert_eq!(rows_on(&db, "alt").await, 2);
assert_eq!(rows_on(&db, "main").await, 1);
db.close().await.unwrap();
}
#[tokio::test]
async fn every_write_refuses_an_unregistered_lineage_by_name() {
let h = TestHarness::new();
let (db, _) = forked(&h).await;
let ghost = BranchId::new("ghost").unwrap();
let unknown = |e: DbError| match e {
DbError::UnknownBranch(b) => b,
other => panic!("expected UnknownBranch, got {other:?}"),
};
assert_eq!(
unknown(
db.assert_edge(edge("a", "b", TS, None).on_branch(ghost.clone()))
.await
.unwrap_err()
),
"ghost"
);
assert_eq!(
unknown(
db.retire_edge_on("a", "b", "KNOWS", TS, TS3, ghost.clone())
.await
.unwrap_err()
),
"ghost"
);
assert_eq!(
unknown(
db.upsert_concept(
ConceptUpsert::new("z", "Z")
.valid_from(TS)
.on_branch(ghost.clone())
)
.await
.unwrap_err()
),
"ghost"
);
assert_eq!(
unknown(
db.write_bulk_atomic(vec![edge("a", "b", TS, None).on_branch(ghost.clone())])
.await
.unwrap_err()
),
"ghost"
);
assert_eq!(rows_on(&db, "main").await, 0);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_branch_mints_a_new_concept_and_may_not_restate_an_inherited_one() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.upsert_concept(
ConceptUpsert::new("mine", "Mine")
.valid_from(TS)
.on_branch(alt.clone()),
)
.await
.unwrap();
let err = db
.upsert_concept(
ConceptUpsert::new("a", "Renamed")
.valid_from(TS)
.on_branch(alt.clone()),
)
.await
.unwrap_err();
let DbError::CrossLineage {
id,
held_by,
attempted,
} = err
else {
panic!("expected CrossLineage, got {err:?}");
};
assert_eq!(
(id.as_str(), held_by.as_str(), attempted.as_str()),
("a", "main", "alt")
);
db.close().await.unwrap();
}
#[tokio::test]
async fn re_upserting_a_trunk_concept_from_the_trunk_is_still_a_plain_update() {
let h = TestHarness::new();
let (db, alt) = forked(&h).await;
db.fork(alt.clone(), BranchId::main()).await.unwrap();
h.advance(Duration::from_secs(60));
db.upsert_concept(ConceptUpsert::new("a", "Renamed").valid_from(TS2))
.await
.unwrap();
let title: String = db
.read_conn()
.query("SELECT title FROM concepts WHERE id='a'", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(title, "Renamed");
db.close().await.unwrap();
}