#[path = "common/harness.rs"]
mod harness;
#[path = "common/v7_schema.rs"]
mod v7_schema;
use harness::TestHarness;
use macrame::error::DbError;
use macrame::schema::ddl;
use macrame::schema::migrations::{self, SCHEMA_VERSION};
use v7_schema::seeded_v7;
const TS: &str = "2026-01-01T00:00:00.000000Z";
async fn connect(harness: &TestHarness) -> libsql::Connection {
libsql::Builder::new_local(&harness.db_path)
.build()
.await
.unwrap()
.connect()
.unwrap()
}
async fn user_version(conn: &libsql::Connection) -> u32 {
conn.query("PRAGMA user_version", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap()
}
fn refusal_reason(err: DbError) -> String {
match err {
DbError::Migration { to, reason } => {
assert!(
to <= SCHEMA_VERSION,
"a refusal cannot name a version above the ladder's top: {to}"
);
reason
}
other => panic!("expected DbError::Migration, got {other:?}"),
}
}
#[tokio::test]
async fn fresh_database_reaches_the_baseline_version() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
assert_eq!(user_version(&conn).await, SCHEMA_VERSION);
conn.execute(
"INSERT INTO concepts (id, title, valid_from, recorded_at) \
VALUES ('c1', 'T', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
(),
)
.await
.expect_err("second-precision timestamps must be rejected at v2");
}
#[tokio::test]
async fn run_is_idempotent_and_preserves_data() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
conn.execute(
"INSERT INTO concepts (id, title, valid_from, recorded_at) \
VALUES ('c1', 'T', '2026-01-01T00:00:00.000000Z', '2026-01-01T00:00:00.000000Z')",
(),
)
.await
.unwrap();
migrations::run(&conn).await.unwrap();
assert_eq!(user_version(&conn).await, SCHEMA_VERSION);
let surviving: i64 = conn
.query("SELECT COUNT(*) FROM concepts", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(surviving, 1, "second run must not disturb existing rows");
}
#[tokio::test]
async fn refuses_a_database_from_a_newer_build() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
conn.execute(&format!("PRAGMA user_version = {}", SCHEMA_VERSION + 7), ())
.await
.unwrap();
let reason = refusal_reason(migrations::run(&conn).await.unwrap_err());
assert!(
reason.contains(&format!("v{}", SCHEMA_VERSION + 7)),
"refusal should name the version found: {reason}"
);
}
#[tokio::test]
async fn refuses_a_pre_canonical_v1_database() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
conn.execute("PRAGMA user_version = 1", ()).await.unwrap();
let reason = refusal_reason(migrations::run(&conn).await.unwrap_err());
assert!(
reason.contains("v1") && reason.contains("no migration path"),
"refusal should identify the legacy schema and say there is no path: {reason}"
);
}
#[tokio::test]
async fn refuses_an_unstamped_database_that_is_not_empty() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
conn.execute("CREATE TABLE somebody_elses_data (x)", ())
.await
.unwrap();
let reason = refusal_reason(migrations::run(&conn).await.unwrap_err());
assert!(
reason.contains("unrelated"),
"refusal should explain what it is protecting: {reason}"
);
assert_eq!(
user_version(&conn).await,
0,
"a refused open must not stamp the file"
);
}
#[tokio::test]
async fn a_refused_run_leaves_no_partial_schema() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
conn.execute("CREATE TABLE somebody_elses_data (x)", ())
.await
.unwrap();
let _ = migrations::run(&conn).await.unwrap_err();
let macrame_objects: i64 = conn
.query(
"SELECT COUNT(*) FROM sqlite_master WHERE name IN \
('concepts', 'links', 'links_current', 'transaction_log')",
(),
)
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(macrame_objects, 0);
}
#[tokio::test]
async fn the_baseline_leaves_every_declared_object_behind() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
let mut rows = conn
.query(
"SELECT type, name FROM sqlite_master \
WHERE type IN ('table','trigger','index') AND name NOT LIKE 'sqlite_%'",
(),
)
.await
.unwrap();
let mut present: Vec<(String, String)> = Vec::new();
while let Some(row) = rows.next().await.unwrap() {
present.push((row.get(0).unwrap(), row.get(1).unwrap()));
}
let has = |kind: &str, name: &str| {
present
.iter()
.any(|(k, n)| k == kind && n.eq_ignore_ascii_case(name))
};
for table in [
"concepts",
"links",
"links_current",
"transaction_log",
"analytics_annotations",
] {
assert!(has("table", table), "missing table {table}: {present:?}");
}
let triggers = present.iter().filter(|(k, _)| k == "trigger").count();
assert_eq!(
triggers,
ddl::CREATE_TRIGGERS.len(),
"trigger count drifted from CREATE_TRIGGERS: {present:?}"
);
let indices = present.iter().filter(|(k, _)| k == "index").count();
assert_eq!(
indices,
ddl::CREATE_INDICES.len(),
"index count drifted from CREATE_INDICES: {present:?}"
);
}
#[tokio::test]
async fn a_v2_database_climbs_to_v3_and_gains_the_annotations_table() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
conn.execute(ddl::CREATE_CONCEPTS_TABLE, ()).await.unwrap();
conn.execute(ddl::CREATE_LINKS_TABLE, ()).await.unwrap();
conn.execute(ddl::CREATE_LINKS_CURRENT_TABLE, ())
.await
.unwrap();
conn.execute(ddl::CREATE_TRANSACTION_LOG_TABLE, ())
.await
.unwrap();
for index_ddl in ddl::CREATE_INDICES {
conn.execute(index_ddl, ()).await.unwrap();
}
for trigger_ddl in ddl::CREATE_TRIGGERS {
conn.execute(trigger_ddl, ()).await.unwrap();
}
conn.execute("PRAGMA user_version = 2", ()).await.unwrap();
migrations::run(&conn).await.unwrap();
let version: u32 = conn
.query("PRAGMA user_version", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(version, SCHEMA_VERSION);
conn.query(
"SELECT concept_id, label, value, computed_at FROM analytics_annotations",
(),
)
.await
.expect("the rung must create analytics_annotations");
}
#[tokio::test]
async fn a_v5_database_climbs_to_v6_and_gains_the_open_interval_index() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
conn.execute("DROP INDEX idx_lc_open_interval", ())
.await
.unwrap();
conn.execute("PRAGMA user_version = 5", ()).await.unwrap();
migrations::run(&conn).await.unwrap();
assert_eq!(user_version(&conn).await, SCHEMA_VERSION);
let found: i64 = conn
.query(
"SELECT COUNT(*) FROM sqlite_master \
WHERE type = 'index' AND name = 'idx_lc_open_interval'",
(),
)
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(found, 1, "the rung must create idx_lc_open_interval");
}
#[tokio::test]
async fn a_v10_database_climbs_to_v11_and_the_archive_read_stops_scanning_links() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
conn.execute("DROP INDEX idx_links_recorded_at", ())
.await
.unwrap();
conn.execute("DROP INDEX idx_links_target", ())
.await
.unwrap();
conn.execute("PRAGMA user_version = 10", ()).await.unwrap();
const ARCHIVING_READ: &str = "SELECT source_id FROM links WHERE recorded_at < ?1 AND (valid_to <> '9999-12-31T23:59:59.999999Z' AND valid_to <= ?1)";
let before = plan_string(&conn, ARCHIVING_READ).await;
assert!(
before.contains("SCAN links"),
"the fixture is not starting from the v10 plan — expected a full scan of `links`, got: {before}"
);
migrations::run(&conn).await.unwrap();
assert_eq!(user_version(&conn).await, SCHEMA_VERSION);
for name in ["idx_links_recorded_at", "idx_links_target"] {
let found: i64 = conn
.query(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name = ?1",
libsql::params![name],
)
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(found, 1, "the rung must create {name}");
}
let after = plan_string(&conn, ARCHIVING_READ).await;
assert!(
after.contains("SEARCH links USING INDEX idx_links_recorded_at"),
"the rung created the index and the planner did not take it. An index with no reader is an index write per ledger insert, forever (D-089). Plan: {after}"
);
}
async fn plan_string(conn: &libsql::Connection, sql: &str) -> String {
let mut rows = conn
.query(&format!("EXPLAIN QUERY PLAN {sql}"), ())
.await
.unwrap();
let mut lines = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
lines.push(r.get::<String>(3).unwrap_or_default());
}
lines.join(" | ")
}
#[test]
fn a_version_bump_must_bring_its_own_rung_test() {
assert_eq!(
SCHEMA_VERSION, 11,
"SCHEMA_VERSION moved. Add a test for the new rung — one that starts \
from a database at the previous version and asserts what the rung is \
*for*, not merely that `run` reached the top."
);
}
#[tokio::test]
async fn a_v6_database_climbs_to_v7_and_gains_the_weight_check() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
for stmt in [
"ALTER TABLE links RENAME TO links_old",
"CREATE TABLE links (
source_id TEXT NOT NULL REFERENCES concepts(id),
target_id TEXT NOT NULL REFERENCES concepts(id),
edge_type TEXT NOT NULL,
valid_from TEXT NOT NULL,
recorded_at TEXT NOT NULL,
valid_to TEXT NOT NULL DEFAULT '9999-12-31T23:59:59.999999Z',
weight REAL NOT NULL DEFAULT 1.0,
properties TEXT NOT NULL DEFAULT '{}',
PRIMARY KEY (source_id, target_id, edge_type, valid_from, recorded_at)
)",
"DROP TABLE links_old",
] {
conn.execute(stmt, ()).await.unwrap();
}
for trigger_ddl in ddl::CREATE_TRIGGERS {
conn.execute(trigger_ddl, ()).await.unwrap();
}
conn.execute("PRAGMA user_version = 6", ()).await.unwrap();
for id in ["c0", "c1"] {
conn.execute(
"INSERT INTO concepts (id, title, valid_from, recorded_at) \
VALUES (?1, 'N', ?2, ?2)",
libsql::params![id, TS],
)
.await
.unwrap();
}
for (etype, weight) in [("A", 1.0), ("B", 0.0), ("C", 2.5)] {
conn.execute(
"INSERT INTO links (source_id, target_id, edge_type, valid_from, valid_to, \
weight, properties, recorded_at) \
VALUES ('c0','c1',?1,?2,'9999-12-31T23:59:59.999999Z',?3,'{}',?2)",
libsql::params![etype, TS, weight],
)
.await
.unwrap();
}
migrations::run(&conn).await.unwrap();
assert_eq!(user_version(&conn).await, SCHEMA_VERSION);
let rows: i64 = conn
.query("SELECT COUNT(*) FROM links", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(rows, 3, "the rebuild lost rows");
let triggers: i64 = conn
.query(
"SELECT COUNT(*) FROM sqlite_master \
WHERE type = 'trigger' AND tbl_name = 'links'",
(),
)
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(
triggers, 4,
"DROP TABLE took the triggers with it and the rung did not put them back"
);
for (label, weight) in [("negative", "-1.0"), ("text", "'abc'")] {
let refused = conn
.execute(
&format!(
"INSERT INTO links (source_id, target_id, edge_type, valid_from, \
valid_to, weight, properties, recorded_at) \
VALUES ('c0','c1','Z','{TS}','9999-12-31T23:59:59.999999Z',\
{weight},'{{}}','{TS}')"
),
(),
)
.await;
assert!(refused.is_err(), "a {label} weight survived the rung");
}
}
async fn count(conn: &libsql::Connection, sql: &str) -> i64 {
conn.query(sql, ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap()
}
#[tokio::test]
async fn a_v7_database_climbs_to_v8_and_gains_rowid_pk() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
conn.execute("PRAGMA foreign_keys = ON", ()).await.unwrap();
seeded_v7(&conn, &["c0", "c1", "c2", "c3"]).await;
let rowids_before = ids_by_rowid(&conn, "rowid").await;
assert_eq!(rowids_before.len(), 4);
migrations::run(&conn).await.unwrap();
assert_eq!(user_version(&conn).await, SCHEMA_VERSION);
assert_eq!(count(&conn, "SELECT COUNT(*) FROM concepts").await, 4);
assert_eq!(count(&conn, "SELECT COUNT(*) FROM links").await, 3);
assert_eq!(
ids_by_rowid(&conn, "rowid_pk").await,
rowids_before,
"the rebuild renumbered the concepts; the FTS index is keyed on these"
);
assert_eq!(
count(&conn, "SELECT COUNT(*) FROM pragma_foreign_key_check").await,
0,
"the rung committed a foreign-key violation"
);
assert!(
conn.execute(
"INSERT INTO links (source_id, target_id, edge_type, valid_from, valid_to, \
weight, properties, recorded_at) \
VALUES ('c0','nobody','KNOWS',?1,'9999-12-31T23:59:59.999999Z',1.0,'{}',?1)",
libsql::params![TS],
)
.await
.is_err(),
"foreign keys were not restored after the rung"
);
assert_eq!(
count(
&conn,
"SELECT COUNT(*) FROM concepts_fts WHERE concepts_fts MATCH 'findable'"
)
.await,
4,
"the FTS index did not survive the re-keying"
);
let indices = index_names(&conn).await;
for gone in ["idx_annotations_label", "idx_lc_tgt_active"] {
assert!(!indices.contains(&gone.to_string()), "{gone} survived v8");
}
assert_eq!(
indices.len(),
ddl::CREATE_INDICES.len(),
"v8 left a different index set than CREATE_INDICES declares: {indices:?}"
);
}
#[tokio::test]
async fn the_v8_rung_needs_the_suspension_and_links_rows_prove_it() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
conn.execute("PRAGMA foreign_keys = ON", ()).await.unwrap();
seeded_v7(&conn, &["c0", "c1"]).await;
conn.execute("BEGIN IMMEDIATE", ()).await.unwrap();
let dropped = conn.execute("DROP TABLE concepts", ()).await;
let _ = conn.execute("ROLLBACK", ()).await;
assert!(
dropped.is_err(),
"`DROP TABLE concepts` succeeded with foreign keys enforced, so \
`suspends_foreign_keys` is buying nothing on this engine. Either the \
engine changed or the fixture lost its `links` rows — check the latter \
first, because a `concepts` with no inbound rows makes this pass \
vacuously."
);
}
#[tokio::test]
async fn a_v7_database_with_a_pre_existing_orphan_is_refused() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
conn.execute("PRAGMA foreign_keys = OFF", ()).await.unwrap();
seeded_v7(&conn, &["c0", "c1"]).await;
conn.execute(
"INSERT INTO links (source_id, target_id, edge_type, valid_from, valid_to, \
weight, properties, recorded_at) \
VALUES ('c0','ghost','KNOWS',?1,'9999-12-31T23:59:59.999999Z',1.0,'{}',?1)",
libsql::params![TS],
)
.await
.unwrap();
let reason = refusal_reason(migrations::run(&conn).await.unwrap_err());
assert!(
reason.contains("suspended foreign keys and left a violation") && reason.contains("links"),
"the refusal should name the check and the table, not merely fail: {reason}"
);
assert_eq!(
user_version(&conn).await,
7,
"a refused rung must leave the database honestly at its old version"
);
}
async fn ids_by_rowid(conn: &libsql::Connection, col: &str) -> Vec<(i64, String)> {
let mut rows = conn
.query(
&format!("SELECT {col}, id FROM concepts ORDER BY {col}"),
(),
)
.await
.unwrap();
let mut out = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
out.push((r.get(0).unwrap(), r.get(1).unwrap()));
}
out
}
async fn index_names(conn: &libsql::Connection) -> Vec<String> {
let mut rows = conn
.query(
"SELECT name FROM sqlite_master WHERE type = 'index' \
AND name NOT LIKE 'sqlite_%' ORDER BY name",
(),
)
.await
.unwrap();
let mut out = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
out.push(r.get(0).unwrap());
}
out
}
#[tokio::test]
async fn a_negative_weight_already_stored_blocks_the_v7_rung_with_an_explanation() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
for stmt in [
"ALTER TABLE links RENAME TO links_old",
"CREATE TABLE links (
source_id TEXT NOT NULL REFERENCES concepts(id),
target_id TEXT NOT NULL REFERENCES concepts(id),
edge_type TEXT NOT NULL,
valid_from TEXT NOT NULL,
recorded_at TEXT NOT NULL,
valid_to TEXT NOT NULL DEFAULT '9999-12-31T23:59:59.999999Z',
weight REAL NOT NULL DEFAULT 1.0,
properties TEXT NOT NULL DEFAULT '{}',
PRIMARY KEY (source_id, target_id, edge_type, valid_from, recorded_at)
)",
"DROP TABLE links_old",
] {
conn.execute(stmt, ()).await.unwrap();
}
conn.execute("PRAGMA user_version = 6", ()).await.unwrap();
for id in ["c0", "c1"] {
conn.execute(
"INSERT INTO concepts (id, title, valid_from, recorded_at) \
VALUES (?1, 'N', ?2, ?2)",
libsql::params![id, TS],
)
.await
.unwrap();
}
conn.execute(
"INSERT INTO links (source_id, target_id, edge_type, valid_from, valid_to, \
weight, properties, recorded_at) \
VALUES ('c0','c1','NEG',?1,'9999-12-31T23:59:59.999999Z',-1.5,'{}',?1)",
libsql::params![TS],
)
.await
.unwrap();
let err = migrations::run(&conn)
.await
.expect_err("the rung cannot represent this row and must say so");
let msg = err.to_string();
assert!(
msg.contains("c0 -> c1") && msg.contains("Doctrine III"),
"the refusal must name a row and why it will not choose: {msg}"
);
assert_eq!(user_version(&conn).await, 6);
let rows: i64 = conn
.query("SELECT COUNT(*) FROM links", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(rows, 1, "the failed rung was not clean");
}
#[tokio::test]
async fn the_single_open_probe_seeks_rather_than_scans() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
let probe = "SELECT 1 FROM links_current \
WHERE source_id = ?1 AND target_id = ?2 AND edge_type = ?3 \
AND valid_from <> ?4 AND valid_to = ?5";
let mut rows = conn
.query(&format!("EXPLAIN QUERY PLAN {probe}"), ())
.await
.unwrap();
let mut plan = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
plan.push(r.get::<String>(3).unwrap());
}
let step = plan.join(" | ");
assert!(
step.contains("idx_lc_open_interval"),
"the probe is not using its own index: {step}"
);
assert!(
step.contains("source_id=? AND target_id=? AND edge_type=?"),
"the probe binds fewer columns than the index offers, so it still scans: {step}"
);
}
#[tokio::test]
async fn the_overlap_guard_seeks_on_all_three_equality_columns() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
let probe = "SELECT valid_from, valid_to FROM links_current \
WHERE source_id = ?1 AND target_id = ?2 AND edge_type = ?3 \
AND valid_from <> ?4";
let mut rows = conn
.query(&format!("EXPLAIN QUERY PLAN {probe}"), ())
.await
.unwrap();
let mut plan = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
plan.push(r.get::<String>(3).unwrap());
}
let step = plan.join(" | ");
assert!(
step.contains("idx_lc_open_interval"),
"the overlap guard is not using the index added for it: {step}"
);
assert!(
step.contains("source_id=? AND target_id=? AND edge_type=?"),
"the guard binds fewer columns than the index offers, so it scans the \
source's out-degree — this is D-059's defect in D-060's guard: {step}"
);
}
#[tokio::test]
async fn the_filtered_subgraph_walk_stays_on_the_traversal_index() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
let step = |edge_filter: &str| {
format!(
"SELECT l.target_id FROM links_current l \
WHERE l.source_id = ?1 \
AND l.valid_from <= ?3 AND ?3 < l.valid_to \
AND l.weight >= ?4{edge_filter}"
)
};
for (label, sql) in [
("unfiltered", step("")),
("edge-type filtered", step(" AND l.edge_type IN (?5)")),
] {
let mut rows = conn
.query(&format!("EXPLAIN QUERY PLAN {sql}"), ())
.await
.unwrap();
let mut plan = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
plan.push(r.get::<String>(3).unwrap());
}
let step = plan.join(" | ");
assert!(
step.contains("idx_lc_traversal_cover"),
"{label}: the filtered walk left its index: {step}"
);
assert!(
step.contains("COVERING INDEX"),
"{label}: the walk is no longer index-only: {step}"
);
}
}
#[test]
fn the_open_interval_probe_matches_the_trigger() {
let trigger = ddl::CREATE_TRIGGERS
.iter()
.find(|t| t.contains("trg_links_single_open"))
.expect("trg_links_single_open must exist");
let flat = trigger.split_whitespace().collect::<Vec<_>>().join(" ");
for clause in [
"source_id = NEW.source_id",
"target_id = NEW.target_id",
"edge_type = NEW.edge_type",
"valid_from <> NEW.valid_from",
"valid_to = '9999-12-31T23:59:59.999999Z'",
] {
assert!(
flat.contains(clause),
"the trigger no longer contains {clause:?}; \
the_single_open_probe_seeks_rather_than_scans models a stale query:\n{flat}"
);
}
}
#[tokio::test]
async fn the_traversal_walks_inside_the_index_with_and_without_an_edge_type_filter() {
use macrame::graph::TraversalBuilder;
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
let plan_of = |sql: String| {
let conn = conn.clone();
async move {
let sql = sql.trim().trim_end_matches(';').to_string();
let mut rows = conn
.query(&format!("EXPLAIN QUERY PLAN {sql}"), ())
.await
.unwrap();
let mut lines = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
lines.push(r.get::<String>(3).unwrap());
}
lines
}
};
for (label, sql) in [
(
"unfiltered",
TraversalBuilder::new("A").max_depth(3).build_sql(),
),
(
"edge-type filtered",
TraversalBuilder::new("A")
.max_depth(3)
.edge_types(vec!["CITES".into()])
.build_sql(),
),
] {
let plan = plan_of(sql).await;
let step = plan
.iter()
.find(|l| l.contains(" l ") || l.ends_with(" l"))
.unwrap_or_else(|| panic!("{label}: no plan line for links_current: {plan:?}"));
assert!(
step.contains("COVERING INDEX idx_lc_traversal_cover"),
"{label}: recursive step is not index-only: {step}"
);
assert!(
step.contains("valid_from<?"),
"{label}: the valid-time window is not in the index seek, only a \
filter over the whole source slice — check the column order: {step}"
);
}
}
#[tokio::test]
async fn the_subsumed_source_index_is_gone() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
let n: i64 = conn
.query(
"SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name='idx_lc_src_active'",
(),
)
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(n, 0, "idx_lc_src_active is subsumed and must not survive");
}
#[tokio::test]
async fn the_shipped_traversal_cte_stays_on_the_covering_index() {
use macrame::graph::TraversalBuilder;
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
for (label, builder) in [
("unfiltered", TraversalBuilder::new("a").max_depth(3)),
(
"edge-type filtered",
TraversalBuilder::new("a")
.max_depth(3)
.edge_types(vec!["CITES".to_string()]),
),
] {
let sql = builder.build_sql();
let mut rows = conn
.query(&format!("EXPLAIN QUERY PLAN {sql}"), ())
.await
.unwrap();
let mut plan = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
plan.push(r.get::<String>(3).unwrap());
}
let plan = plan.join(" | ");
assert!(
plan.contains("idx_lc_traversal_cover"),
"{label}: the walk left its index: {plan}"
);
assert!(
plan.contains("COVERING INDEX"),
"{label}: the walk is no longer index-only: {plan}"
);
}
}
const CONCEPTS_GUARD_V8: &str = "
CREATE TRIGGER trg_concepts_guard_delete
BEFORE DELETE ON concepts
BEGIN
SELECT RAISE(ABORT, 'macrame: concepts are never physically archived (D-022)');
END;
";
async fn downgrade_guard_to_v8(conn: &libsql::Connection) {
conn.execute("DROP TRIGGER trg_concepts_guard_delete", ())
.await
.unwrap();
conn.execute(CONCEPTS_GUARD_V8, ()).await.unwrap();
conn.execute("PRAGMA user_version = 8", ()).await.unwrap();
}
async fn guard_sql(conn: &libsql::Connection) -> String {
conn.query(
"SELECT sql FROM sqlite_master WHERE type = 'trigger' \
AND name = 'trg_concepts_guard_delete'",
(),
)
.await
.unwrap()
.next()
.await
.unwrap()
.expect("the concepts delete guard should exist")
.get(0)
.unwrap()
}
#[tokio::test]
async fn a_v8_database_climbs_past_v9_and_the_concepts_guard_becomes_conditional() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
downgrade_guard_to_v8(&conn).await;
assert!(
guard_sql(&conn).await.contains("never physically archived"),
"the fixture did not start from the v8 guard"
);
migrations::run(&conn).await.unwrap();
assert_eq!(user_version(&conn).await, SCHEMA_VERSION);
let sql = guard_sql(&conn).await;
assert!(
sql.contains(ddl::ARCHIVE_SESSION_MARKER),
"the guard is not marker-gated after the rung: {sql}"
);
assert!(
!sql.contains("never physically archived"),
"the v8 body survived the rung: {sql}"
);
}
#[tokio::test]
async fn re_issuing_the_baseline_guard_keeps_the_v8_body() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
downgrade_guard_to_v8(&conn).await;
conn.execute(ddl::CREATE_CONCEPTS_GUARD_DELETE, ())
.await
.unwrap();
let sql = guard_sql(&conn).await;
assert!(
sql.contains("never physically archived"),
"IF NOT EXISTS replaced the body — D-126's premise no longer holds and \
the v8 → v9 rung may be unnecessary: {sql}"
);
}
#[tokio::test]
async fn a_v9_stamp_over_an_ungated_guard_is_refused() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
conn.execute("DROP TRIGGER trg_concepts_guard_delete", ())
.await
.unwrap();
conn.execute(CONCEPTS_GUARD_V8, ()).await.unwrap();
let reason = refusal_reason(migrations::run(&conn).await.unwrap_err());
assert!(
reason.contains("trg_concepts_guard_delete") && reason.contains("archive-session"),
"the refusal should name the guard and what it lacks: {reason}"
);
}
#[tokio::test]
async fn a_v7_database_climbs_all_the_way_to_the_top_with_a_working_guard() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
seeded_v7(&conn, &["c1", "c2"]).await;
conn.execute("PRAGMA user_version = 7", ()).await.unwrap();
migrations::run(&conn).await.unwrap();
assert_eq!(user_version(&conn).await, SCHEMA_VERSION);
let res = conn
.execute("DELETE FROM concepts WHERE id = 'c1'", ())
.await;
assert!(
res.is_err(),
"an ad-hoc concept delete must still be refused"
);
conn.execute(
&format!("CREATE TABLE {} (x)", ddl::ARCHIVE_SESSION_MARKER),
(),
)
.await
.unwrap();
conn.execute(
"DELETE FROM links WHERE source_id = 'c1' OR target_id = 'c1'",
(),
)
.await
.unwrap();
conn.execute("DELETE FROM concepts WHERE id = 'c1'", ())
.await
.expect("a concept delete inside an archive session must be permitted at v9");
conn.execute(&format!("DROP TABLE {}", ddl::ARCHIVE_SESSION_MARKER), ())
.await
.unwrap();
}
const CONCEPTS_LOG_INSERT_V9_FIXTURE: &str = "
CREATE TRIGGER trg_concepts_log_insert
AFTER INSERT ON concepts
BEGIN
INSERT INTO transaction_log (table_name, entity_id, operation, payload, recorded_at)
VALUES ('concepts', NEW.id, 'I',
json_object('v', 2, 'title', NEW.title, 'content', NEW.content,
'valid_from', NEW.valid_from, 'valid_to', NEW.valid_to,
'retired', NEW.retired,
'embedding_model', NEW.embedding_model),
NEW.recorded_at);
END;
";
async fn log_insert_sql(conn: &libsql::Connection) -> String {
conn.query(
"SELECT sql FROM sqlite_master WHERE type = 'trigger' \
AND name = 'trg_concepts_log_insert'",
(),
)
.await
.unwrap()
.next()
.await
.unwrap()
.expect("the concepts insert log trigger should exist")
.get(0)
.unwrap()
}
#[tokio::test]
async fn a_v9_database_climbs_to_v10_and_the_insert_log_becomes_marker_gated() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
conn.execute("DROP TRIGGER trg_concepts_log_insert", ())
.await
.unwrap();
conn.execute(CONCEPTS_LOG_INSERT_V9_FIXTURE, ())
.await
.unwrap();
conn.execute("PRAGMA user_version = 9", ()).await.unwrap();
assert!(
!log_insert_sql(&conn)
.await
.contains(ddl::ARCHIVE_SESSION_MARKER),
"the fixture did not start from the v9 trigger"
);
migrations::run(&conn).await.unwrap();
assert_eq!(user_version(&conn).await, SCHEMA_VERSION);
assert!(
log_insert_sql(&conn)
.await
.contains(ddl::ARCHIVE_SESSION_MARKER),
"the insert log trigger is not marker-gated after the rung"
);
}
#[tokio::test]
async fn an_insert_inside_a_session_writes_no_log_row_and_outside_one_still_does() {
let harness = TestHarness::new();
let conn = connect(&harness).await;
migrations::run(&conn).await.unwrap();
let log_rows = |conn: libsql::Connection| async move {
conn.query("SELECT COUNT(*) FROM transaction_log", ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get::<i64>(0)
.unwrap()
};
conn.execute(
"INSERT INTO concepts (id, title, valid_from, recorded_at) \
VALUES ('outside', 'T', ?1, ?1)",
libsql::params![TS],
)
.await
.unwrap();
assert_eq!(
log_rows(conn.clone()).await,
1,
"an ordinary concept insert must still be logged"
);
conn.execute(
&format!("CREATE TABLE {} (x)", ddl::ARCHIVE_SESSION_MARKER),
(),
)
.await
.unwrap();
conn.execute(
"INSERT INTO concepts (id, title, valid_from, recorded_at) \
VALUES ('inside', 'T', ?1, ?1)",
libsql::params![TS],
)
.await
.unwrap();
conn.execute(&format!("DROP TABLE {}", ddl::ARCHIVE_SESSION_MARKER), ())
.await
.unwrap();
assert_eq!(
log_rows(conn.clone()).await,
1,
"an insert inside an archive session wrote a transaction_log row; \
rehydration is a move back and mints no transaction-time facts"
);
}