#[path = "common/harness.rs"]
mod harness;
const ARCHIVED_AT: &str = "2026-07-30T12:00:00.000000Z";
use std::collections::BTreeSet;
use std::future::Future;
use harness::TestHarness;
use macrame::error::DbError;
use macrame::integrity::{audit_current, rebuild_current};
use macrame::schema::migrations;
use proptest::prelude::*;
const SENTINEL: &str = "9999-12-31T23:59:59.999999Z";
const TS: [&str; 5] = [
"2026-01-01T00:00:00.000000Z",
"2026-02-01T00:00:00.000000Z",
"2026-03-01T00:00:00.000000Z",
"2026-04-01T00:00:00.000000Z",
"2026-05-01T00:00:00.000000Z",
];
const NODES: [&str; 2] = ["c0", "c1"];
const TYPES: [&str; 2] = ["A", "B"];
const WEIGHTS: [f64; 3] = [0.5, 1.0, 2.0];
static RT: std::sync::OnceLock<tokio::runtime::Runtime> = std::sync::OnceLock::new();
fn block_on<F: Future>(f: F) -> F::Output {
RT.get_or_init(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
})
.block_on(f)
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
struct Row {
source_id: String,
target_id: String,
edge_type: String,
valid_from: String,
valid_to: String,
weight_bits: u64,
properties: String,
recorded_at: String,
}
impl Row {
fn key(&self) -> (&str, &str, &str, &str) {
(
&self.source_id,
&self.target_id,
&self.edge_type,
&self.valid_from,
)
}
}
fn projection(links: &[Row]) -> BTreeSet<Row> {
let mut best: Vec<Row> = Vec::new();
for row in links {
match best.iter_mut().find(|b| b.key() == row.key()) {
Some(b) if row.recorded_at > b.recorded_at => *b = row.clone(),
Some(_) => {}
None => best.push(row.clone()),
}
}
best.into_iter().collect()
}
fn expected_drift(links: &[Row], current: &[Row]) -> usize {
let projected = projection(links);
let materialized: BTreeSet<Row> = current.iter().cloned().collect();
materialized.difference(&projected).count() + projected.difference(&materialized).count()
}
async fn read_rows(conn: &libsql::Connection, table: &str) -> Vec<Row> {
let mut rows = conn
.query(
&format!(
"SELECT source_id, target_id, edge_type, valid_from, valid_to, \
weight, properties, recorded_at FROM {table}"
),
(),
)
.await
.unwrap();
let mut out = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
let weight: f64 = r.get(5).unwrap();
out.push(Row {
source_id: r.get(0).unwrap(),
target_id: r.get(1).unwrap(),
edge_type: r.get(2).unwrap(),
valid_from: r.get(3).unwrap(),
valid_to: r.get(4).unwrap(),
weight_bits: weight.to_bits(),
properties: r.get(6).unwrap(),
recorded_at: r.get(7).unwrap(),
});
}
out
}
async fn audit_count(conn: &libsql::Connection) -> usize {
match audit_current(conn).await {
Ok(n) => n,
Err(DbError::CurrentDrift { n }) => n,
Err(e) => panic!("audit failed for a reason other than drift: {e:?}"),
}
}
#[derive(Debug, Clone, Copy)]
struct Assert {
src: usize,
tgt: usize,
etype: usize,
valid_from: usize,
valid_to: Option<usize>,
weight: usize,
recorded_at: usize,
}
fn assert_strategy() -> impl Strategy<Value = Assert> {
(
0..NODES.len(),
0..NODES.len(),
0..TYPES.len(),
0..3usize, prop::option::of(2..5usize), 0..WEIGHTS.len(),
0..TS.len(),
)
.prop_map(
|(src, tgt, etype, valid_from, valid_to, weight, recorded_at)| Assert {
src,
tgt,
etype,
valid_from,
valid_to,
weight,
recorded_at,
},
)
}
#[derive(Debug, Clone, Copy)]
enum Corruption {
Drop(usize),
Stale(usize),
Ghost(usize),
}
fn corruption_strategy() -> impl Strategy<Value = Corruption> {
prop_oneof![
(0..8usize).prop_map(Corruption::Drop),
(0..8usize).prop_map(Corruption::Stale),
(0..3usize).prop_map(Corruption::Ghost),
]
}
async fn fresh(harness: &TestHarness) -> (libsql::Database, libsql::Connection) {
let db = libsql::Builder::new_local(&harness.db_path)
.build()
.await
.unwrap();
let conn = db.connect().unwrap();
migrations::run(&conn).await.unwrap();
for id in NODES {
conn.execute(
"INSERT INTO concepts (id, title, valid_from, recorded_at) VALUES (?1, 'N', ?2, ?2)",
libsql::params![id, TS[0]],
)
.await
.unwrap();
}
(db, conn)
}
async fn apply(conn: &libsql::Connection, history: &[Assert]) {
for a in history {
let _ = conn
.execute(
"INSERT INTO links (source_id, target_id, edge_type, valid_from, valid_to, \
weight, properties, recorded_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, '{}', ?7)",
libsql::params![
NODES[a.src],
NODES[a.tgt],
TYPES[a.etype],
TS[a.valid_from],
a.valid_to.map_or(SENTINEL, |i| TS[i]),
WEIGHTS[a.weight],
TS[a.recorded_at],
],
)
.await;
}
}
async fn corrupt(conn: &libsql::Connection, damage: &[Corruption]) {
for c in damage {
match *c {
Corruption::Drop(n) | Corruption::Stale(n) => {
let current = read_rows(conn, "links_current").await;
if current.is_empty() {
continue;
}
let victim = ¤t[n % current.len()];
let sql = match c {
Corruption::Drop(_) => {
"DELETE FROM links_current WHERE source_id = ?1 AND target_id = ?2 \
AND edge_type = ?3 AND valid_from = ?4"
}
_ => {
"UPDATE links_current SET weight = 99.0 WHERE source_id = ?1 \
AND target_id = ?2 AND edge_type = ?3 AND valid_from = ?4"
}
};
conn.execute(
sql,
libsql::params![
victim.source_id.clone(),
victim.target_id.clone(),
victim.edge_type.clone(),
victim.valid_from.clone()
],
)
.await
.unwrap();
}
Corruption::Ghost(n) => {
let _ = conn
.execute(
"INSERT OR IGNORE INTO links_current (source_id, target_id, edge_type, \
valid_from, valid_to, weight, properties, recorded_at) \
VALUES ('c0', 'c1', 'GHOST', ?1, ?2, 7.0, '{}', ?2)",
libsql::params![TS[n % 3], TS[0]],
)
.await;
}
}
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 32, ..ProptestConfig::default() })]
#[test]
fn the_sync_trigger_keeps_current_equal_to_the_projection(
history in prop::collection::vec(assert_strategy(), 0..12)
) {
block_on(async {
let harness = TestHarness::new();
let (_db, conn) = fresh(&harness).await;
apply(&conn, &history).await;
let links = read_rows(&conn, "links").await;
let current = read_rows(&conn, "links_current").await;
prop_assert_eq!(
expected_drift(&links, ¤t), 0,
"the AFTER INSERT trigger did not maintain Doctrine VI"
);
prop_assert_eq!(audit_count(&conn).await, 0);
Ok(())
})?;
}
#[test]
fn the_audit_count_equals_the_model(
history in prop::collection::vec(assert_strategy(), 0..12),
damage in prop::collection::vec(corruption_strategy(), 0..5),
) {
block_on(async {
let harness = TestHarness::new();
let (_db, conn) = fresh(&harness).await;
apply(&conn, &history).await;
corrupt(&conn, &damage).await;
let links = read_rows(&conn, "links").await;
let current = read_rows(&conn, "links_current").await;
prop_assert_eq!(
audit_count(&conn).await,
expected_drift(&links, ¤t),
"audit disagreed with the model\nlinks: {:#?}\ncurrent: {:#?}",
links, current
);
Ok(())
})?;
}
#[test]
fn rebuild_restores_the_projection_from_any_damage(
history in prop::collection::vec(assert_strategy(), 0..12),
damage in prop::collection::vec(corruption_strategy(), 0..5),
) {
block_on(async {
let harness = TestHarness::new();
let (_db, conn) = fresh(&harness).await;
apply(&conn, &history).await;
corrupt(&conn, &damage).await;
let links = read_rows(&conn, "links").await;
let report = rebuild_current(&conn).await.unwrap();
let rebuilt = read_rows(&conn, "links_current").await;
let expected = projection(&links);
prop_assert_eq!(rebuilt.iter().cloned().collect::<BTreeSet<_>>(), expected.clone());
prop_assert_eq!(report.rows_rebuilt, expected.len());
prop_assert_eq!(report.drift_after, 0);
prop_assert_eq!(audit_count(&conn).await, 0);
let second = rebuild_current(&conn).await.unwrap();
prop_assert_eq!(second, report);
prop_assert_eq!(read_rows(&conn, "links_current").await, rebuilt);
Ok(())
})?;
}
#[test]
fn archiving_leaves_the_ledger_auditable(
history in prop::collection::vec(assert_strategy(), 0..12),
cutoff in 0..TS.len(),
) {
block_on(async {
let harness = TestHarness::new();
let (_db, conn) = fresh(&harness).await;
apply(&conn, &history).await;
prop_assume!(audit_count(&conn).await == 0);
let cold = harness.temp_dir.path().join("cold.db");
macrame::temporal::archive(&conn, TS[cutoff], ARCHIVED_AT, &cold).await.unwrap();
let links = read_rows(&conn, "links").await;
let current = read_rows(&conn, "links_current").await;
prop_assert_eq!(
audit_count(&conn).await, 0,
"archive left drift behind\ncutoff: {}\nlinks: {:#?}\ncurrent: {:#?}",
TS[cutoff], links, current
);
Ok(())
})?;
}
}
const OLD_WALK: &str = r#"
WITH RECURSIVE walk(node_id, depth, path) AS (
SELECT ?1, 0, '/' || CAST(?1 AS BLOB) || '/'
UNION ALL
SELECT l.target_id, w.depth + 1, w.path || CAST(l.target_id AS BLOB) || '/'
FROM walk w
JOIN links_current l ON l.source_id = w.node_id
WHERE w.depth < ?2
AND l.valid_from <= ?3 AND ?3 < l.valid_to
AND l.weight >= ?4
AND INSTR(w.path, '/' || CAST(l.target_id AS BLOB) || '/') = 0
)"#;
const NEW_WALK: &str = r#"
WITH RECURSIVE walk(node_id, depth) AS (
SELECT ?1, 0
UNION
SELECT l.target_id, w.depth + 1
FROM walk w
JOIN links_current l ON l.source_id = w.node_id
WHERE w.depth < ?2
AND l.valid_from <= ?3 AND ?3 < l.valid_to
AND l.weight >= ?4
)"#;
const IDS_PROJECTION: &str = r#"
SELECT DISTINCT w.node_id
FROM walk w JOIN concepts c ON c.id = w.node_id
WHERE c.retired = 0
ORDER BY w.node_id;"#;
const EDGES_PROJECTION: &str = r#"
SELECT DISTINCT l.source_id, l.target_id, l.edge_type
FROM walk w
JOIN links_current l ON l.source_id = w.node_id
WHERE l.valid_from <= ?3 AND ?3 < l.valid_to
AND l.weight >= ?4
ORDER BY l.source_id, l.target_id, l.edge_type;"#;
const TNODES: [&str; 5] = ["t0", "t1", "t2", "t3", "t4"];
#[derive(Debug, Clone)]
struct TEdge {
src: usize,
tgt: usize,
etype: usize,
expired: bool,
}
fn tedge_strategy() -> impl Strategy<Value = TEdge> {
(
0..TNODES.len(),
0..TNODES.len(),
0..TYPES.len(),
prop::bool::weighted(0.2),
)
.prop_map(|(src, tgt, etype, expired)| TEdge {
src,
tgt,
etype,
expired,
})
}
async fn fresh_traversal(harness: &TestHarness) -> (libsql::Database, libsql::Connection) {
let db = libsql::Builder::new_local(&harness.db_path)
.build()
.await
.unwrap();
let conn = db.connect().unwrap();
migrations::run(&conn).await.unwrap();
for id in TNODES {
conn.execute(
"INSERT INTO concepts (id, title, valid_from, recorded_at) VALUES (?1, 'N', ?2, ?2)",
libsql::params![id, TS[0]],
)
.await
.unwrap();
}
(db, conn)
}
async fn apply_edges(conn: &libsql::Connection, edges: &[TEdge]) {
for e in edges {
let _ = conn
.execute(
"INSERT INTO links (source_id, target_id, edge_type, valid_from, valid_to, \
weight, properties, recorded_at) VALUES (?1, ?2, ?3, ?4, ?5, 1.0, '{}', ?4)",
libsql::params![
TNODES[e.src],
TNODES[e.tgt],
TYPES[e.etype],
TS[0],
if e.expired { TS[2] } else { SENTINEL },
],
)
.await;
}
}
async fn rows_of(conn: &libsql::Connection, sql: &str, depth: usize) -> Vec<Vec<String>> {
let mut rows = conn
.query(
sql,
libsql::params![TNODES[0], depth as i64, TS[3], f64::NEG_INFINITY],
)
.await
.unwrap();
let mut out = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
let mut cols = Vec::new();
for i in 0.. {
match r.get::<String>(i) {
Ok(v) => cols.push(v),
Err(_) => break,
}
}
out.push(cols);
}
out
}
proptest! {
#![proptest_config(ProptestConfig { cases: 32, ..ProptestConfig::default() })]
#[test]
fn the_traversal_rewrite_returns_what_the_simple_path_form_returned(
edges in prop::collection::vec(tedge_strategy(), 0..10),
depth in 1usize..5,
) {
block_on(async {
let harness = TestHarness::new();
let (_db, conn) = fresh_traversal(&harness).await;
apply_edges(&conn, &edges).await;
let old_ids = rows_of(&conn, &format!("{OLD_WALK}{IDS_PROJECTION}"), depth).await;
let new_ids = rows_of(&conn, &format!("{NEW_WALK}{IDS_PROJECTION}"), depth).await;
prop_assert_eq!(&old_ids, &new_ids, "node sets diverged at depth {}", depth);
let old_edges = rows_of(&conn, &format!("{OLD_WALK}{EDGES_PROJECTION}"), depth).await;
let new_edges = rows_of(&conn, &format!("{NEW_WALK}{EDGES_PROJECTION}"), depth).await;
prop_assert_eq!(&old_edges, &new_edges, "edge sets diverged at depth {}", depth);
let shipped = macrame::graph::TraversalBuilder::new(TNODES[0])
.max_depth(depth)
.min_weight(f64::NEG_INFINITY)
.build_sql();
let shipped_ids = rows_of(&conn, &shipped, depth).await;
prop_assert_eq!(&old_ids, &shipped_ids, "build_sql diverged at depth {}", depth);
Ok(())
})?;
}
}