#[path = "common/harness.rs"]
mod harness;
use harness::TestHarness;
use macrame::graph::AttributeMode;
use macrame::graph::EdgeAssertion;
use macrame::graph::{dijkstra, k_core, louvain, scc};
use macrame::schema::migrations;
use macrame::temporal::hydrate_attributes;
use macrame::{ConceptUpsert, Database};
const T0: &str = "2026-01-01T00:00:00.000000Z";
const T1: &str = "2026-02-01T00:00:00.000000Z";
const T2: &str = "2026-03-01T00:00:00.000000Z";
const NOW: &str = "2026-12-01T00:00:00.000000Z";
const OPEN: &str = "9999-12-31T23:59:59.999999Z";
#[tokio::test]
async fn embedding_model_survives_every_temporal_read() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
db.upsert_concept(
ConceptUpsert::new("c1", "Titled")
.content("Body")
.embedding_model("nomic_v1")
.valid_from(T0),
)
.await
.unwrap();
let live: Option<String> = db
.read_conn()
.query("SELECT embedding_model FROM concepts WHERE id = 'c1'", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.ok();
assert_eq!(live.as_deref(), Some("nomic_v1"), "live column");
let state = db.reconstruct(NOW).await.unwrap();
assert_eq!(
state.concepts["c1"].embedding_model.as_deref(),
Some("nomic_v1"),
"reconstruct() must not lose the model"
);
let at_time = hydrate_attributes(
db.read_conn(),
&["c1".to_string()],
NOW,
AttributeMode::AtTime,
)
.await
.unwrap();
assert_eq!(
at_time[0].embedding_model.as_deref(),
Some("nomic_v1"),
"AttributeMode::AtTime must not lose the model"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_v1_concept_payload_still_folds() {
let harness = TestHarness::new();
let db = libsql::Builder::new_local(&harness.db_path)
.build()
.await
.unwrap();
let conn = db.connect().unwrap();
migrations::run(&conn).await.unwrap();
conn.execute(
"INSERT INTO transaction_log (table_name, entity_id, operation, payload, recorded_at) \
VALUES ('concepts', 'old', 'I', \
json_object('v', 1, 'title', 'Old', 'content', 'Body', \
'valid_from', ?1, 'valid_to', ?2, 'retired', 0), ?1)",
libsql::params![T0, OPEN],
)
.await
.unwrap();
let state = macrame::temporal::reconstruct(&conn, NOW, None, None)
.await
.unwrap();
let c = state
.concepts
.get("old")
.expect("v1 payload must still fold");
assert_eq!(c.title, "Old");
assert_eq!(c.embedding_model, None, "absent, not an error");
}
#[test]
fn the_trigger_payload_version_matches_the_reader_ceiling() {
let concept_triggers: Vec<&&str> = macrame::schema::ddl::CREATE_TRIGGERS
.iter()
.filter(|t| t.contains("INSERT INTO transaction_log") && t.contains("'concepts'"))
.collect();
assert_eq!(concept_triggers.len(), 2, "insert and update");
for t in concept_triggers {
assert!(
t.contains("'v', 2"),
"concept log trigger writes a payload version the reader does not expect:\n{t}"
);
assert!(
t.contains("'embedding_model'"),
"defect V: the payload omits embedding_model again:\n{t}"
);
}
}
#[tokio::test]
async fn a_concept_id_shaped_like_an_edge_key_is_refused() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let colliding_id = format!("a|b|KNOWS|{T0}");
let err = db
.upsert_concept(ConceptUpsert::new(colliding_id, "Collides").valid_from(T0))
.await
.expect_err("an id carrying the log's delimiter must be refused");
assert!(
matches!(err, macrame::DbError::InvalidId { .. }),
"and refused as invalid, not as missing — defect J: got {err:?}"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_concept_whose_id_looks_like_an_edge_key_survives_reconstruction() {
let harness = TestHarness::new();
let db = libsql::Builder::new_local(&harness.db_path)
.build()
.await
.unwrap();
let conn = db.connect().unwrap();
migrations::run(&conn).await.unwrap();
let colliding_id = format!("a|b|KNOWS|{T0}");
conn.execute(
"INSERT INTO transaction_log (table_name, entity_id, operation, payload, recorded_at) \
VALUES ('concepts', ?1, 'I', \
json_object('v', 2, 'title', 'Collides', 'content', '', \
'valid_from', ?2, 'valid_to', ?3, 'retired', 0, \
'embedding_model', null), ?2)",
libsql::params![colliding_id.as_str(), T0, OPEN],
)
.await
.unwrap();
conn.execute(
"INSERT INTO transaction_log (table_name, entity_id, operation, payload, recorded_at) \
VALUES ('links', ?1, 'I', \
json_object('v', 1, 'source_id', 'a', 'target_id', 'b', \
'edge_type', 'KNOWS', 'valid_from', ?2, 'valid_to', ?3, \
'weight', 1.0, 'properties', json('{}')), ?2)",
libsql::params![colliding_id.as_str(), T0, OPEN],
)
.await
.unwrap();
let state = macrame::temporal::reconstruct(&conn, NOW, None, None)
.await
.unwrap();
assert!(
state.concepts.contains_key(&colliding_id),
"defect W: the concept was conflated with the edge and dropped"
);
assert_eq!(state.concepts[&colliding_id].title, "Collides");
assert!(
state
.edges
.iter()
.any(|e| e.0 == "a" && e.1 == "b" && e.2 == "KNOWS"),
"and the edge is still there too"
);
}
#[tokio::test]
async fn a_short_id_inside_a_longer_one_does_not_prune_the_walk() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["start", "abc", "b"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
db.assert_edge(EdgeAssertion::new("start", "abc", "KNOWS").valid_from(T0))
.await
.unwrap();
db.assert_edge(EdgeAssertion::new("abc", "b", "KNOWS").valid_from(T0))
.await
.unwrap();
let graph = db.load_subgraph("start", 3, NOW, 1 << 20).await.unwrap();
assert!(
graph.nodes.contains_key("b"),
"`b` was pruned because `abc` contains it: {:?}",
graph.nodes.keys().collect::<Vec<_>>()
);
db.close().await.unwrap();
}
#[tokio::test]
async fn all_three_readers_agree_a_retired_concept_is_not_visible() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
db.upsert_concept(ConceptUpsert::new("c1", "Live").valid_from(T0))
.await
.unwrap();
db.upsert_concept(
ConceptUpsert::new("c1", "Live")
.valid_from(T0)
.retired(true),
)
.await
.unwrap();
let ids = vec!["c1".to_string()];
let current = hydrate_attributes(db.read_conn(), &ids, NOW, AttributeMode::Current)
.await
.unwrap();
assert!(
current.is_empty(),
"Current: retired concept is not visible"
);
let at_time = hydrate_attributes(db.read_conn(), &ids, NOW, AttributeMode::AtTime)
.await
.unwrap();
assert!(
at_time.is_empty(),
"defect AB: AtTime returned a concept retired before ts"
);
let state = db.reconstruct(NOW).await.unwrap();
assert!(
!state.concepts.contains_key("c1"),
"reconstruct: retired concept is not visible"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn at_time_before_the_retirement_still_sees_the_concept() {
let harness = TestHarness::new();
let db = libsql::Builder::new_local(&harness.db_path)
.build()
.await
.unwrap();
let conn = db.connect().unwrap();
migrations::run(&conn).await.unwrap();
conn.execute(
"INSERT INTO concepts (id, title, content, valid_from, recorded_at) \
VALUES ('c1', 'Live', '', ?1, ?1)",
libsql::params![T0],
)
.await
.unwrap();
conn.execute(
"UPDATE concepts SET retired = 1, recorded_at = ?1 WHERE id = 'c1'",
libsql::params![T2],
)
.await
.unwrap();
let ids = vec!["c1".to_string()];
let before = hydrate_attributes(&conn, &ids, T1, AttributeMode::AtTime)
.await
.unwrap();
assert_eq!(
before.len(),
1,
"as believed at T1 the concept was not yet retired"
);
let after = hydrate_attributes(&conn, &ids, NOW, AttributeMode::AtTime)
.await
.unwrap();
assert!(after.is_empty(), "and by NOW it is");
}
async fn graph_with_a_retired_neighbour(
harness: &TestHarness,
) -> (Database, macrame::graph::Subgraph) {
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["a", "b", "c"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
db.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from(T0)
.valid_to(OPEN)
.weight(1.0),
)
.await
.unwrap();
db.assert_edge(
EdgeAssertion::new("a", "c", "KNOWS")
.valid_from(T0)
.valid_to(OPEN)
.weight(1.0),
)
.await
.unwrap();
db.upsert_concept(ConceptUpsert::new("c", "c").valid_from(T0).retired(true))
.await
.unwrap();
let graph = db.load_subgraph("a", 3, NOW, 1 << 20).await.unwrap();
(db, graph)
}
#[tokio::test]
async fn a_retired_neighbour_leaves_no_dangling_adjacency() {
let harness = TestHarness::new();
let (db, graph) = graph_with_a_retired_neighbour(&harness).await;
assert!(
graph.is_closed(),
"every adjacency endpoint must be a hydrated node"
);
assert!(!graph.nodes.contains_key("c"), "the retired node is absent");
assert_eq!(graph.edge_count(), 1, "and so is the edge into it");
let comm = louvain(&graph);
assert_eq!(comm.len(), graph.nodes.len());
assert!(comm.keys().all(|k| graph.nodes.contains_key(k)));
let components = scc(&graph);
for component in &components {
for node in component {
assert!(
graph.nodes.contains_key(node),
"scc returned a phantom component member {node:?}"
);
}
}
let core = k_core(&graph, 2);
assert!(
core.is_empty(),
"one edge cannot put anything in the 2-core; got {core:?}"
);
let dist = dijkstra(&graph, "a");
for node in dist.keys() {
assert!(
graph.nodes.contains_key(node),
"dijkstra reached {node:?}, which the caller cannot look up"
);
}
db.close().await.unwrap();
}
#[tokio::test]
async fn retiring_the_start_node_yields_an_empty_graph() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["a", "b"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
db.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from(T0)
.valid_to(OPEN)
.weight(1.0),
)
.await
.unwrap();
db.upsert_concept(ConceptUpsert::new("a", "a").valid_from(T0).retired(true))
.await
.unwrap();
let graph = db.load_subgraph("a", 3, NOW, 1 << 20).await.unwrap();
assert!(graph.is_closed());
assert!(!graph.nodes.contains_key("a"));
assert_eq!(graph.edge_count(), 0, "no edge can survive its source");
db.close().await.unwrap();
}
#[tokio::test]
async fn load_subgraph_totals_agree_with_the_derivation() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for i in 0..40 {
db.upsert_concept(
ConceptUpsert::new(format!("n{i:03}"), format!("Node {i}"))
.content("some content of a plausible length")
.valid_from(T0),
)
.await
.unwrap();
}
for i in 0..39 {
db.assert_edge(
EdgeAssertion::new(format!("n{i:03}"), format!("n{:03}", i + 1), "KNOWS")
.valid_from(T0)
.valid_to(OPEN)
.weight(1.0),
)
.await
.unwrap();
}
let graph = db.load_subgraph("n000", 50, NOW, 1 << 20).await.unwrap();
assert_eq!(graph.nodes.len(), 40);
assert!(graph.is_closed());
let budget = graph.estimated_bytes();
let refused = db.load_subgraph("n000", 50, NOW, budget / 2).await;
assert!(
matches!(refused, Err(macrame::DbError::SubgraphTooLarge { .. })),
"half the graph's own size must not fit"
);
db.load_subgraph("n000", 50, NOW, budget)
.await
.expect("a graph must fit a budget equal to its own estimated_bytes()");
db.close().await.unwrap();
}
#[tokio::test]
async fn hydrate_spans_more_than_one_chunk() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let n = macrame::util::limits::HYDRATE_CHUNK + 50;
let ids: Vec<String> = (0..n).map(|i| format!("n{i:04}")).collect();
for id in &ids {
db.upsert_concept(ConceptUpsert::new(id.clone(), id.clone()).valid_from(T0))
.await
.unwrap();
}
let attrs = hydrate_attributes(db.read_conn(), &ids, NOW, AttributeMode::Current)
.await
.unwrap();
assert_eq!(attrs.len(), n, "every node comes back");
let returned: Vec<&str> = attrs.iter().map(|a| a.id.as_str()).collect();
let expected: Vec<&str> = ids.iter().map(String::as_str).collect();
assert_eq!(returned, expected, "results follow node_ids order");
db.close().await.unwrap();
}
#[tokio::test]
async fn deleting_a_link_outside_an_archive_session_is_a_typed_violation() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["a", "b"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
db.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from(T0)
.valid_to(OPEN)
.weight(1.0),
)
.await
.unwrap();
let err = db
.raw()
.connect()
.unwrap()
.execute("DELETE FROM links WHERE source_id = 'a'", ())
.await
.expect_err("the delete guard must refuse this");
let typed = macrame::error::classify(
db.read_conn(),
err,
macrame::error::WriteOp::Delete { table: "links" },
)
.await;
assert!(
matches!(typed, macrame::DbError::ArchiveViolation { ref table } if table == "links"),
"defect AC: expected ArchiveViolation, got {typed:?}"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn overlapping_closed_intervals_are_refused() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["a", "b"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
let jan = "2026-01-01T00:00:00.000000Z";
let mar = "2026-03-01T00:00:00.000000Z";
let jun = "2026-06-01T00:00:00.000000Z";
let sep = "2026-09-01T00:00:00.000000Z";
let apr = "2026-04-01T00:00:00.000000Z";
db.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from(jan)
.valid_to(jun),
)
.await
.unwrap();
let err = db
.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from(mar)
.valid_to(sep),
)
.await
.expect_err("defect AA: [Mar, Sep) overlaps [Jan, Jun)");
assert!(
matches!(err, macrame::DbError::OverlappingInterval { .. }),
"got {err:?}"
);
let edges = macrame::temporal::query_as_of_edges(db.read_conn(), apr)
.await
.unwrap();
assert_eq!(edges.len(), 1, "one relationship, one edge at any instant");
db.close().await.unwrap();
}
#[tokio::test]
async fn abutting_intervals_are_accepted() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["a", "b"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
let jan = "2026-01-01T00:00:00.000000Z";
let jun = "2026-06-01T00:00:00.000000Z";
let sep = "2026-09-01T00:00:00.000000Z";
db.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from(jan)
.valid_to(jun),
)
.await
.unwrap();
db.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from(jun)
.valid_to(sep),
)
.await
.expect("half-open intervals that abut do not overlap");
db.close().await.unwrap();
}
#[tokio::test]
async fn an_open_interval_overlapping_a_closed_one_is_refused() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["a", "b"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
db.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from("2026-01-01T00:00:00.000000Z")
.valid_to("2026-06-01T00:00:00.000000Z"),
)
.await
.unwrap();
let err = db
.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS").valid_from("2026-03-01T00:00:00.000000Z"),
)
.await
.expect_err("an open interval starting inside a closed one overlaps it");
assert!(
matches!(err, macrame::DbError::OverlappingInterval { .. }),
"got {err:?}"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn two_open_intervals_remain_the_specific_violation() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["a", "b"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
db.assert_edge(EdgeAssertion::new("a", "b", "KNOWS").valid_from(T0))
.await
.unwrap();
let err = db
.assert_edge(EdgeAssertion::new("a", "b", "KNOWS").valid_from(T1))
.await
.unwrap_err();
assert!(
matches!(err, macrame::DbError::SingleOpenViolation { .. }),
"the storage guard must keep this case: got {err:?}"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_batch_carrying_its_own_overlap_is_refused_whole() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["a", "b"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
let err = db
.write_bulk_atomic(vec![
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from("2026-01-01T00:00:00.000000Z")
.valid_to("2026-06-01T00:00:00.000000Z"),
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from("2026-03-01T00:00:00.000000Z")
.valid_to("2026-09-01T00:00:00.000000Z"),
])
.await
.expect_err("the batch overlaps itself");
assert!(
matches!(err, macrame::DbError::OverlappingInterval { .. }),
"got {err:?}"
);
let n: i64 = db
.read_conn()
.query("SELECT COUNT(*) FROM links", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(n, 0, "nothing may land from a batch that is refused");
db.close().await.unwrap();
}
#[test]
fn the_overlap_arithmetic_has_a_production_caller() {
use macrame::temporal::Interval;
let a = Interval::new("2026-01-01T00:00:00.000000Z", "2026-06-01T00:00:00.000000Z");
let b = Interval::new("2026-03-01T00:00:00.000000Z", "2026-09-01T00:00:00.000000Z");
let c = Interval::new("2026-06-01T00:00:00.000000Z", "2026-09-01T00:00:00.000000Z");
assert!(a.overlaps(&b));
assert!(
!a.overlaps(&c),
"half-open intervals that abut do not overlap"
);
}
#[tokio::test]
async fn an_injected_clock_stamps_the_transaction_time() {
let harness = TestHarness::new();
let db = harness.db_with_fake_clock().await;
db.upsert_concept(ConceptUpsert::new("c1", "First").valid_from(T0))
.await
.unwrap();
let stamp: String = db
.read_conn()
.query("SELECT recorded_at FROM concepts WHERE id = 'c1'", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert!(
stamp.starts_with("1970-01-01T00:00:00"),
"the fake clock starts at the epoch; got {stamp}"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn transaction_time_follows_the_injected_clock() {
let harness = TestHarness::starting_at(
std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_767_225_600),
);
let db = harness.db_with_fake_clock().await;
db.upsert_concept(ConceptUpsert::new("c1", "Monday").valid_from(T0))
.await
.unwrap();
harness.advance(std::time::Duration::from_secs(172_800));
db.upsert_concept(ConceptUpsert::new("c1", "Wednesday").valid_from(T0))
.await
.unwrap();
let mut rows = db
.read_conn()
.query(
"SELECT recorded_at FROM transaction_log \
WHERE table_name = 'concepts' AND entity_id = 'c1' ORDER BY seq_id",
(),
)
.await
.unwrap();
let mut stamps = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
stamps.push(r.get::<String>(0).unwrap());
}
assert_eq!(stamps.len(), 2);
assert!(stamps[0] < stamps[1], "{stamps:?}");
assert!(stamps[0].starts_with("2026-01-01"), "{stamps:?}");
assert!(stamps[1].starts_with("2026-01-03"), "{stamps:?}");
db.close().await.unwrap();
}
#[tokio::test]
async fn an_injected_clock_is_floored_against_an_existing_database() {
let harness = TestHarness::new();
let db = Database::open_with_cadence(&harness.db_path, None)
.await
.unwrap();
db.upsert_concept(ConceptUpsert::new("c1", "Existing").valid_from(T0))
.await
.unwrap();
db.close().await.unwrap();
let reopened = harness.db_with_fake_clock().await;
reopened
.upsert_concept(ConceptUpsert::new("c1", "Updated").valid_from(T0))
.await
.expect("a floored clock must not trip the monotonicity guard");
let stamps: Vec<String> = {
let mut rows = reopened
.read_conn()
.query(
"SELECT recorded_at FROM transaction_log \
WHERE table_name = 'concepts' ORDER BY seq_id",
(),
)
.await
.unwrap();
let mut v = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
v.push(r.get::<String>(0).unwrap());
}
v
};
assert_eq!(stamps.len(), 2);
assert!(
stamps[0] < stamps[1],
"the second session's stamp must be above the first's: {stamps:?}"
);
reopened.close().await.unwrap();
}
async fn mixed_graph(db: &Database) {
for id in ["hub", "a", "b", "c", "d"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
for (target, ty, w) in [
("a", "CITES", 1.0),
("b", "CITES", 0.2),
("c", "KNOWS", 1.0),
("d", "KNOWS", 0.2),
] {
db.assert_edge(
EdgeAssertion::new("hub", target, ty)
.valid_from(T0)
.weight(w),
)
.await
.unwrap();
}
}
#[tokio::test]
async fn load_subgraph_with_filters_the_returned_edges_not_only_the_walk() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
mixed_graph(&db).await;
let graph = db
.load_subgraph_with(
¯ame::graph::TraversalBuilder::new("hub")
.max_depth(3)
.edge_types(vec!["CITES".into()]),
NOW,
1 << 20,
)
.await
.unwrap();
let types: Vec<&str> = graph
.out_edges("hub")
.iter()
.map(|e| e.edge_type.as_str())
.collect();
assert_eq!(
types,
["CITES", "CITES"],
"only the asked-for type: {types:?}"
);
assert!(graph.is_closed());
assert!(
!graph.nodes.contains_key("c") && !graph.nodes.contains_key("d"),
"KNOWS-only neighbours are not reached either: {:?}",
graph.nodes.keys().collect::<Vec<_>>()
);
db.close().await.unwrap();
}
#[tokio::test]
async fn load_subgraph_with_honours_min_weight() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
mixed_graph(&db).await;
let graph = db
.load_subgraph_with(
¯ame::graph::TraversalBuilder::new("hub")
.max_depth(3)
.min_weight(0.5),
NOW,
1 << 20,
)
.await
.unwrap();
assert_eq!(graph.edge_count(), 2, "only the weight-1.0 edges survive");
assert!(graph.out_edges("hub").iter().all(|e| e.weight >= 0.5));
assert!(graph.is_closed());
db.close().await.unwrap();
}
#[tokio::test]
async fn a_filtered_load_fits_a_budget_the_unfiltered_one_exceeds() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
db.upsert_concept(ConceptUpsert::new("hub", "hub").valid_from(T0))
.await
.unwrap();
for i in 0..60 {
let id = format!("n{i:03}");
db.upsert_concept(ConceptUpsert::new(&id, &id).valid_from(T0))
.await
.unwrap();
let ty = if i % 2 == 0 { "CITES" } else { "KNOWS" };
db.assert_edge(EdgeAssertion::new("hub", &id, ty).valid_from(T0))
.await
.unwrap();
}
let filtered = db
.load_subgraph_with(
¯ame::graph::TraversalBuilder::new("hub")
.max_depth(1)
.edge_types(vec!["CITES".into()]),
NOW,
1 << 20,
)
.await
.unwrap();
let budget = filtered.estimated_bytes();
assert!(
matches!(
db.load_subgraph("hub", 1, NOW, budget).await,
Err(macrame::DbError::SubgraphTooLarge { .. })
),
"the unfiltered load must exceed a budget sized for the filtered one"
);
assert_eq!(
db.load_subgraph_with(
¯ame::graph::TraversalBuilder::new("hub")
.max_depth(1)
.edge_types(vec!["CITES".into()]),
NOW,
budget,
)
.await
.unwrap()
.edge_count(),
30,
"and the filtered one fits it"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn the_unfiltered_loader_still_returns_everything() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
mixed_graph(&db).await;
let graph = db.load_subgraph("hub", 3, NOW, 1 << 20).await.unwrap();
assert_eq!(
graph.edge_count(),
4,
"all four edges, both types, both weights"
);
assert_eq!(graph.nodes.len(), 5);
db.close().await.unwrap();
}
#[tokio::test]
async fn vacuum_does_not_disturb_the_fts_index() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for i in 0..20 {
db.upsert_concept(
ConceptUpsert::new(format!("c{i:03}"), format!("Title {i}"))
.content("searchable body text")
.valid_from(T0),
)
.await
.unwrap();
}
for i in [3usize, 7, 11] {
db.upsert_concept(
ConceptUpsert::new(format!("c{i:03}"), "Updated")
.content("searchable body text")
.valid_from(T0),
)
.await
.unwrap();
}
let rowids = |db: &Database| {
let conn = db.read_conn().clone();
async move {
let mut rows = conn
.query("SELECT rowid, id FROM concepts ORDER BY rowid", ())
.await
.unwrap();
let mut v = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
v.push((r.get::<i64>(0).unwrap(), r.get::<String>(1).unwrap()));
}
v
}
};
let before = rowids(&db).await;
assert!(
before
.iter()
.enumerate()
.all(|(i, (r, _))| *r == i as i64 + 1),
"rowids must be dense for the argument to hold: {before:?}"
);
assert_eq!(
hits(&db).await,
20,
"every concept is indexed to begin with"
);
db.raw()
.connect()
.unwrap()
.execute("VACUUM", ())
.await
.unwrap();
assert_eq!(rowids(&db).await, before, "VACUUM renumbered the rowids");
assert_eq!(
hits(&db).await,
20,
"the index must still find every concept after VACUUM"
);
db.close().await.unwrap();
}
async fn hits(db: &Database) -> i64 {
db.read_conn()
.query(
"SELECT COUNT(*) FROM concepts_fts WHERE concepts_fts MATCH 'searchable'",
(),
)
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap()
}
#[tokio::test]
async fn an_emptied_fts_index_still_passes_integrity_check() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for i in 0..10 {
db.upsert_concept(
ConceptUpsert::new(format!("c{i:03}"), format!("Title {i}"))
.content("searchable body text")
.valid_from(T0),
)
.await
.unwrap();
}
assert_eq!(hits(&db).await, 10);
let raw = db.raw().connect().unwrap();
raw.execute(
"INSERT INTO concepts_fts (concepts_fts) VALUES ('delete-all')",
(),
)
.await
.unwrap();
assert_eq!(
hits(&db).await,
0,
"the index is now empty — genuinely stale"
);
let checked = raw
.execute(macrame::schema::ddl::VERIFY_CONCEPTS_FTS, ())
.await;
assert!(
checked.is_ok(),
"integrity-check now detects content desync — verify_fts() can be built \
(D-071 says why it was not): {checked:?}"
);
db.rebuild_fts().await.unwrap();
assert_eq!(hits(&db).await, 10, "rebuild_fts restores the index");
db.close().await.unwrap();
}
#[tokio::test]
async fn a_delete_row_in_the_log_is_refused_as_corruption() {
let harness = TestHarness::new();
let db = libsql::Builder::new_local(&harness.db_path)
.build()
.await
.unwrap();
let conn = db.connect().unwrap();
migrations::run(&conn).await.unwrap();
conn.execute(
"INSERT INTO transaction_log (table_name, entity_id, operation, payload, recorded_at) \
VALUES ('concepts', 'c1', 'D', '{}', ?1)",
libsql::params![T0],
)
.await
.unwrap();
let err = macrame::temporal::reconstruct(&conn, NOW, None, None)
.await
.expect_err("a 'D' row must not fold as a tombstone");
match err {
macrame::DbError::ReplayCorrupt { seq, reason } => {
assert!(
seq > 0,
"the error must name the offending row, got seq {seq}"
);
assert!(
reason.contains("Doctrine V"),
"the refusal should say which rule it enforces: {reason}"
);
}
other => panic!("expected ReplayCorrupt, got {other:?}"),
}
}
#[tokio::test]
async fn a_delete_row_for_a_link_is_refused_too() {
let harness = TestHarness::new();
let db = libsql::Builder::new_local(&harness.db_path)
.build()
.await
.unwrap();
let conn = db.connect().unwrap();
migrations::run(&conn).await.unwrap();
conn.execute(
"INSERT INTO transaction_log (table_name, entity_id, operation, payload, recorded_at) \
VALUES ('links', 'a|b|KNOWS|x', 'D', '{}', ?1)",
libsql::params![T0],
)
.await
.unwrap();
let err = macrame::temporal::reconstruct(&conn, NOW, None, None)
.await
.unwrap_err();
assert!(
matches!(err, macrame::DbError::ReplayCorrupt { ref reason, .. } if reason.contains("links")),
"got {err:?}"
);
}
#[tokio::test]
async fn retirement_still_removes_a_concept_from_a_composed_fold() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
db.upsert_concept(ConceptUpsert::new("keep", "Keep").valid_from(T0))
.await
.unwrap();
db.upsert_concept(ConceptUpsert::new("gone", "Gone").valid_from(T0))
.await
.unwrap();
db.upsert_concept(
ConceptUpsert::new("gone", "Gone")
.valid_from(T0)
.retired(true),
)
.await
.unwrap();
let state = db.reconstruct(NOW).await.unwrap();
assert!(state.concepts.contains_key("keep"));
assert!(
!state.concepts.contains_key("gone"),
"retirement is the mechanism that removes a concept, and it still works"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_retired_edge_is_superseded_rather_than_removed() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["a", "b"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
db.assert_edge(EdgeAssertion::new("a", "b", "KNOWS").valid_from(T0))
.await
.unwrap();
db.retire_edge("a", "b", "KNOWS", T0, T2).await.unwrap();
let state = db.reconstruct(NOW).await.unwrap();
let edges: Vec<_> = state
.edges
.iter()
.filter(|e| e.0 == "a" && e.1 == "b")
.collect();
assert_eq!(edges.len(), 1, "one interval key, one edge: {edges:?}");
assert_eq!(edges[0].4, T2, "and it is closed, not absent");
db.close().await.unwrap();
}
#[tokio::test]
async fn an_upgraded_database_is_re_anchored_at_open() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for i in 0..5 {
db.upsert_concept(ConceptUpsert::new(format!("c{i}"), "T").valid_from(T0))
.await
.unwrap();
}
let snaps_dir = db.snapshots_dir().to_path_buf();
db.close().await.unwrap();
let count = |dir: &std::path::Path| {
std::fs::read_dir(dir)
.map(|d| d.flatten().count())
.unwrap_or(0)
};
assert!(count(&snaps_dir) > 0, "close() must leave an anchor");
for entry in std::fs::read_dir(&snaps_dir).unwrap().flatten() {
std::fs::remove_file(entry.path()).unwrap();
}
assert_eq!(count(&snaps_dir), 0);
{
let raw = libsql::Builder::new_local(&harness.db_path)
.build()
.await
.unwrap();
let conn = raw.connect().unwrap();
conn.execute("DROP INDEX IF EXISTS idx_lc_open_interval", ())
.await
.unwrap();
conn.execute("PRAGMA user_version = 5", ()).await.unwrap();
}
let reopened = Database::open(&harness.db_path).await.unwrap();
assert_eq!(
reopened.schema_version(),
macrame::schema::migrations::SCHEMA_VERSION,
"the rung must have run"
);
assert!(
count(&snaps_dir) > 0,
"an upgraded database must be re-anchored at open, or the next \
reconstruct folds from genesis"
);
reopened.close().await.unwrap();
}
#[tokio::test]
async fn a_fresh_database_is_not_re_anchored() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
assert_eq!(
count_snapshots(&db),
0,
"opening a new database must not write a snapshot"
);
db.close().await.unwrap();
}
fn count_snapshots(db: &Database) -> usize {
std::fs::read_dir(db.snapshots_dir())
.map(|d| d.flatten().count())
.unwrap_or(0)
}
#[tokio::test]
async fn a_legal_archive_is_unaffected_by_the_classified_deletes() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for id in ["a", "b"] {
db.upsert_concept(ConceptUpsert::new(id, id).valid_from(T0))
.await
.unwrap();
}
db.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from(T0)
.valid_to(T1)
.weight(1.0),
)
.await
.unwrap();
db.assert_edge(
EdgeAssertion::new("a", "b", "KNOWS")
.valid_from(T1)
.valid_to(OPEN)
.weight(2.0),
)
.await
.unwrap();
let report = db.archive("2099-01-01T00:00:00.000000Z").await.unwrap();
assert!(
report.links_archived > 0,
"the fixture must actually archive something, or this proves nothing"
);
db.close().await.unwrap();
}