#[path = "common/harness.rs"]
mod harness;
use harness::TestHarness;
use macrame::graph::EdgeAssertion;
use macrame::{Annotation, ConceptUpsert, Database};
const T0: &str = "2026-01-01T00:00:00.000000Z";
const T1: &str = "2026-02-01T00:00:00.000000Z";
const OPEN: &str = "9999-12-31T23:59:59.999999Z";
const CUTOFF: &str = "2099-01-01T00:00:00.000000Z";
const ARCHIVED_AT: &str = "2099-01-02T00:00:00.000000Z";
async fn seeded(harness: &TestHarness) -> Database {
let db = Database::open(&harness.db_path).await.unwrap();
for (id, retired, valid_to) in [
("gone", true, T1),
("live", true, T1),
("expired", false, T1),
("retired_open", true, OPEN),
("target_only", true, T1),
("other", false, T1),
] {
db.upsert_concept(
ConceptUpsert::new(id, "Title")
.content(format!("searchable body of {id}"))
.valid_from(T0)
.valid_to(valid_to)
.retired(retired),
)
.await
.unwrap();
}
db.assert_edge(EdgeAssertion::new("live", "other", "KNOWS").valid_from(T0))
.await
.unwrap();
db.assert_edge(EdgeAssertion::new("live", "target_only", "KNOWS").valid_from(T0))
.await
.unwrap();
db
}
async fn count(db: &Database, sql: &str) -> i64 {
db.read_conn()
.query(sql, ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap()
}
#[tokio::test]
async fn an_archivable_concept_moves_and_the_others_stay() {
let harness = TestHarness::new();
let db = seeded(&harness).await;
let report = db.archive(CUTOFF).await.unwrap();
assert_eq!(
report.concepts_archived, 1,
"exactly `gone` should have crossed"
);
assert_eq!(
count(&db, "SELECT COUNT(*) FROM concepts WHERE id = 'gone'").await,
0,
"the archived concept is still in the hot table"
);
for id in ["live", "expired", "retired_open", "target_only", "other"] {
assert_eq!(
count(
&db,
&format!("SELECT COUNT(*) FROM concepts WHERE id = '{id}'")
)
.await,
1,
"{id} should not have been archived"
);
}
db.close().await.unwrap();
}
#[tokio::test]
async fn the_cold_row_carries_every_column_the_hot_row_had() {
let harness = TestHarness::new();
let db = seeded(&harness).await;
let hot: Vec<String> = {
let mut rows = db
.read_conn()
.query(
"SELECT id, title, content, valid_from, valid_to, recorded_at, retired \
FROM concepts WHERE id = 'gone'",
(),
)
.await
.unwrap();
let row = rows.next().await.unwrap().unwrap();
(0..7).map(|i| format!("{:?}", row.get_value(i).unwrap())).collect()
};
db.archive(CUTOFF).await.unwrap();
let conn = db.read_conn();
conn.execute(
"ATTACH DATABASE ?1 AS cold",
libsql::params![db.archive_path().to_string_lossy().as_ref()],
)
.await
.unwrap();
let cold: Vec<String> = {
let mut rows = conn
.query(
"SELECT id, title, content, valid_from, valid_to, recorded_at, retired \
FROM cold.concepts WHERE id = 'gone'",
(),
)
.await
.unwrap();
let row = rows
.next()
.await
.unwrap()
.expect("the archived concept should be in cold.concepts");
(0..7).map(|i| format!("{:?}", row.get_value(i).unwrap())).collect()
};
conn.execute("DETACH DATABASE cold", ()).await.unwrap();
assert_eq!(hot, cold, "the cold row is not the hot row it replaced");
assert!(
cold[2].contains("searchable body of gone"),
"content did not cross: {:?}",
cold[2]
);
db.close().await.unwrap();
}
#[tokio::test]
async fn the_search_index_does_not_keep_an_archived_concept() {
let harness = TestHarness::new();
let db = seeded(&harness).await;
assert_eq!(
count(
&db,
"SELECT COUNT(*) FROM concepts_fts WHERE concepts_fts MATCH 'gone'"
)
.await,
1,
"the fixture should be findable before the archive"
);
db.archive(CUTOFF).await.unwrap();
assert_eq!(
count(
&db,
"SELECT COUNT(*) FROM concepts_fts WHERE concepts_fts MATCH 'gone'"
)
.await,
0,
"the search index still matches a concept that is no longer hot"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn reconstruct_gives_the_same_answer_across_an_archive() {
let harness = TestHarness::new();
let db = seeded(&harness).await;
let before = db.reconstruct(ARCHIVED_AT).await.unwrap();
db.archive(CUTOFF).await.unwrap();
let after = db.reconstruct(ARCHIVED_AT).await.unwrap();
assert_eq!(
before.concepts, after.concepts,
"archiving a concept changed what the ledger says was believed — the \
fold is not log-driven after all, and C2 step 4 is real work"
);
assert_eq!(before.edges, after.edges, "the edge set moved too");
db.close().await.unwrap();
}
#[tokio::test]
async fn derived_rows_are_disposed_of_rather_than_archived() {
let harness = TestHarness::new();
let db = seeded(&harness).await;
db.write_analytics_annotations(vec![Annotation::new("gone", "louvain.community", "7")])
.await
.unwrap();
assert_eq!(
count(
&db,
"SELECT COUNT(*) FROM analytics_annotations WHERE concept_id = 'gone'"
)
.await,
1
);
let report = db.archive(CUTOFF).await.unwrap();
assert_eq!(report.concepts_archived, 1);
assert_eq!(
count(
&db,
"SELECT COUNT(*) FROM analytics_annotations WHERE concept_id = 'gone'"
)
.await,
0,
"the annotation outlived the concept it annotates"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn vacuum_after_an_archive_leaves_the_sparse_numbering_and_the_index_alone() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
for i in 0..8 {
let archivable = i % 2 == 0;
db.upsert_concept(
ConceptUpsert::new(format!("c{i}"), "Title")
.content(format!("searchable body {i}"))
.valid_from(T0)
.valid_to(if archivable { T1 } else { OPEN })
.retired(archivable),
)
.await
.unwrap();
}
let report = db.archive(CUTOFF).await.unwrap();
assert_eq!(report.concepts_archived, 4, "four concepts should have left");
let survivors = |db: &Database| {
let conn = db.read_conn().clone();
async move {
let mut rows = conn
.query("SELECT rowid_pk, id FROM concepts ORDER BY rowid_pk", ())
.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 = survivors(&db).await;
assert_eq!(before.len(), 4);
assert!(
before.windows(2).any(|w| w[1].0 - w[0].0 > 1),
"the archive did not leave a gap, so this test proves nothing: {before:?}"
);
db.raw()
.connect()
.unwrap()
.execute("VACUUM", ())
.await
.unwrap();
assert_eq!(
survivors(&db).await,
before,
"VACUUM renumbered a rowid_pk left sparse by an archive — concepts_fts \
is keyed on this column and now points at the wrong rows"
);
for i in 0..8 {
let matched = count(
&db,
&format!("SELECT COUNT(*) FROM concepts_fts WHERE concepts_fts MATCH 'body AND {i}'"),
)
.await;
assert_eq!(
matched,
i64::from(i % 2 != 0),
"concept c{i} is findable in the index but should not be, or vice versa"
);
}
db.close().await.unwrap();
}
async fn archived_then_rehydrated(db: &Database) -> macrame::temporal::RehydrateReport {
db.archive(CUTOFF).await.unwrap();
db.rehydrate(&["gone"]).await.unwrap()
}
#[tokio::test]
async fn reconstruct_is_bit_identical_across_archive_and_rehydrate() {
let control = TestHarness::new();
let control_db = seeded(&control).await;
let expected = control_db.reconstruct(ARCHIVED_AT).await.unwrap();
control_db.close().await.unwrap();
let harness = TestHarness::new();
let db = seeded(&harness).await;
let report = archived_then_rehydrated(&db).await;
assert_eq!(report.concepts_rehydrated, 1);
let actual = db.reconstruct(ARCHIVED_AT).await.unwrap();
assert_eq!(
expected.concepts, actual.concepts,
"the round trip changed what the ledger says was believed"
);
assert_eq!(expected.edges, actual.edges);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_rehydrated_concept_is_usable() {
let harness = TestHarness::new();
let db = seeded(&harness).await;
archived_then_rehydrated(&db).await;
{
let row = db
.read_conn()
.query(
"SELECT title, content, valid_from, valid_to, recorded_at, retired FROM concepts WHERE id = 'gone'",
(),
)
.await
.unwrap()
.next()
.await
.unwrap()
.expect("the rehydrated concept is not in the hot table");
assert_eq!(row.get::<String>(0).unwrap(), "Title");
assert_eq!(row.get::<String>(1).unwrap(), "searchable body of gone");
assert_eq!(row.get::<String>(2).unwrap(), T0);
assert_eq!(row.get::<String>(3).unwrap(), T1);
assert_eq!(row.get::<i64>(5).unwrap(), 1, "retired did not survive");
}
assert_eq!(
count(
&db,
"SELECT COUNT(*) FROM concepts_fts WHERE concepts_fts MATCH 'gone'"
)
.await,
1,
"the rehydrated concept is not in the search index"
);
db.upsert_concept(
ConceptUpsert::new("gone", "Title After Rehydration")
.content("searchable body of gone")
.valid_from(T0)
.valid_to(T1)
.retired(true),
)
.await
.unwrap();
let again = db.rehydrate(&["gone"]).await.unwrap();
assert_eq!(
again.concepts_rehydrated, 0,
"the concept is still in cold.concepts after being rehydrated"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn rehydration_writes_no_log_rows_and_cannot_resurrect_a_retirement() {
let harness = TestHarness::new();
let db = seeded(&harness).await;
let before = count(&db, "SELECT COUNT(*) FROM transaction_log").await;
archived_then_rehydrated(&db).await;
let after = count(&db, "SELECT COUNT(*) FROM transaction_log").await;
assert_eq!(
before, after,
"rehydration wrote transaction_log rows; it is a physical move and mints \
no transaction-time facts"
);
let state = db.reconstruct(ARCHIVED_AT).await.unwrap();
assert!(
!state.concepts.contains_key("gone"),
"the rehydrated concept came back un-retired — a new 'I' outranked its \
own retirement in the fold, which is what the v10 rung prevents"
);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_claimed_rowid_forces_a_reassignment_and_the_index_follows() {
let harness = TestHarness::new();
let db = seeded(&harness).await;
let old_rowid: i64 = db
.read_conn()
.query("SELECT rowid_pk FROM concepts WHERE id = 'gone'", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
db.archive(CUTOFF).await.unwrap();
db.raw()
.connect()
.unwrap()
.execute(
"INSERT INTO concepts (rowid_pk, id, title, content, valid_from, \
valid_to, recorded_at) VALUES (?1, 'squatter', 'Squatter', \
'searchable body of squatter', ?2, ?3, ?2)",
libsql::params![old_rowid, T0, OPEN],
)
.await
.unwrap();
let report = db.rehydrate(&["gone"]).await.unwrap();
assert_eq!(report.concepts_rehydrated, 1);
assert_eq!(
report.rowids_reassigned, 1,
"the collision path was not taken, so this test proves nothing"
);
let rowids: Vec<i64> = {
let mut rows = db
.read_conn()
.query(
"SELECT rowid_pk FROM concepts WHERE id IN ('gone', 'squatter') \
ORDER BY id",
(),
)
.await
.unwrap();
let mut v = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
v.push(r.get(0).unwrap());
}
v
};
assert_eq!(rowids.len(), 2);
assert_ne!(rowids[0], rowids[1], "both concepts hold the same rowid_pk");
for term in ["gone", "squatter"] {
assert_eq!(
count(
&db,
&format!("SELECT COUNT(*) FROM concepts_fts WHERE concepts_fts MATCH '{term}'")
)
.await,
1,
"the search index does not describe {term} exactly once"
);
}
db.close().await.unwrap();
}