macrame-db 0.17.0

A Bitemporal Graph Ledger on libSQL · Embedded knowledge database
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
//! The archive predicates and the lineage the folds already had (0.14.12, W12.12, D-229).
//!
//! v12 gave `links_current` a primary key ending in `branch_id` and gave the four
//! folds in `temporal::replay` a partition ending in `branch_id`. It did not give
//! either archive predicate one, because both spell their own SQL and neither is
//! built from the fold — the same shape as
//! [D-227](../docs/architecture/s13-decision-register.md#d-227), one layer down.
//!
//! A link's `entity_id` is `source|target|type|valid_from` and carries no lineage,
//! deliberately: re-keying it would have split every edge's history in two at the
//! rung. So "a later assertion for the same interval key" matched **across**
//! lineages, and one branch writing at an ancestor's key made the ancestor's own
//! open, current row archivable.
//!
//! **The reason this file exists rather than four assertions appended somewhere.**
//! `audit_current` returns 0 across the defect. `links_current` is honestly
//! re-derived from a `links` table that has been wrongly pruned, so the projection
//! *is* the image of the ledger and Doctrine VI's check has nothing to say. The
//! only way to see it is to ask what a lineage can still reach, before and after.

#[path = "common/harness.rs"]
mod harness;

use std::time::Duration;

use harness::TestHarness;
use macrame::graph::{EdgeAssertion, TraversalBuilder};
use macrame::integrity::audit_current;
use macrame::{BranchId, ConceptUpsert, Database};

const EPOCH: &str = "1970-01-01T00:00:00.000000Z";
const OPEN: &str = "9999-12-31T23:59:59.999999Z";
const T1: &str = "1970-01-02T00:00:00.000000Z";
const T2: &str = "1970-01-03T00:00:00.000000Z";
const LATE: &str = "2999-01-01T00:00:00.000000Z";
const STEP: Duration = Duration::from_secs(3_600);

/// `a → b → c` on the trunk, with the clock moved on so the fork point is after
/// every write and a cutoff of `LATE` is after everything.
async fn seed(h: &TestHarness) -> Database {
    let db = h.db_with_fake_clock().await;
    db.write_concepts(
        ["a", "b", "c"]
            .iter()
            .map(|n| ConceptUpsert::new(*n, "n").valid_from(EPOCH))
            .collect(),
    )
    .await
    .unwrap();
    db.bulk_import(vec![
        EdgeAssertion::new("a", "b", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN),
        EdgeAssertion::new("b", "c", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN),
    ])
    .await
    .unwrap();
    h.advance(STEP);
    db
}

fn id(name: &str) -> BranchId {
    BranchId::new(name).unwrap()
}

/// What one lineage can still reach from `a` at `ts`, in order.
///
/// The instant is a parameter because a retirement's whole content is which side
/// of `valid_to` the reader stands on: at `EPOCH` a branch that retired an edge
/// over `[EPOCH, T1)` still sees it, and the case would pass while measuring
/// nothing.
async fn reached_at(db: &Database, branch: Option<&str>, ts: &str) -> Vec<String> {
    let mut b = TraversalBuilder::new("a");
    if let Some(n) = branch {
        b = b.on_branch(id(n));
    }
    let mut v = b.execute_ids(db.read_conn(), ts).await.unwrap();
    v.sort();
    v
}

async fn reached(db: &Database, branch: Option<&str>) -> Vec<String> {
    reached_at(db, branch, EPOCH).await
}

/// Every surviving `links` row at one key, as `"branch valid_to"`.
///
/// A count cannot answer this file's question. A retirement writes a new row and
/// thereby supersedes the lineage's own open one, so an archive after one
/// retirement legitimately takes a row — and "one row went cold" is true both
/// when the right one did and when the shadow did.
async fn rows_at(db: &Database, source: &str, target: &str) -> Vec<String> {
    let mut rows = db
        .read_conn()
        .query(
            "SELECT branch_id || ' ' || valid_to FROM links \
             WHERE source_id = ?1 AND target_id = ?2 ORDER BY branch_id, valid_to",
            libsql::params![source, target],
        )
        .await
        .unwrap();
    let mut out = Vec::new();
    while let Some(r) = rows.next().await.unwrap() {
        out.push(r.get::<String>(0).unwrap());
    }
    out
}

/// How many rows each ledger table still holds, as the archive's own footprint.
async fn counts(db: &Database) -> (i64, i64) {
    let conn = db.read_conn();
    let one = |sql: &'static str| {
        let conn = conn.clone();
        async move {
            conn.query(sql, ())
                .await
                .unwrap()
                .next()
                .await
                .unwrap()
                .unwrap()
                .get::<i64>(0)
                .unwrap()
        }
    };
    (
        one("SELECT COUNT(*) FROM links").await,
        one("SELECT COUNT(*) FROM transaction_log WHERE table_name = 'links'").await,
    )
}

// ───────────────────────────────────────────────────────────────────────────
// The defect, in both directions
// ───────────────────────────────────────────────────────────────────────────

/// A branch asserting at the trunk's edge key must not archive the trunk's row.
///
/// The measurement that named the release. Before the repair the trunk reached
/// `a` alone after one archive, having lost an edge it still currently believed,
/// because a *different lineage* had written at the same interval key.
#[tokio::test]
async fn a_branch_writing_at_the_trunks_key_does_not_archive_the_trunks_belief() {
    let h = TestHarness::new();
    let db = seed(&h).await;
    let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
    h.advance(STEP);

    db.assert_edge(
        EdgeAssertion::new("a", "b", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN)
            .weight(2.0)
            .on_branch(alt.id.clone()),
    )
    .await
    .unwrap();
    h.advance(STEP);

    assert_eq!(reached(&db, None).await, ["a", "b", "c"]);
    db.archive(LATE).await.unwrap();

    assert_eq!(
        reached(&db, None).await,
        ["a", "b", "c"],
        "the trunk lost an edge it still believed, because a branch disagreed with it"
    );
    assert_eq!(reached(&db, Some("alt")).await, ["a", "b", "c"]);

    db.close().await.unwrap();
}

/// And the other way round: the trunk writing after a fork must not archive the
/// branch's own row.
///
/// Both directions matter and only one of them is the motivating story. The
/// predicate compares `recorded_at`, so whichever lineage wrote *second* was the
/// one that pruned the other — which means an abandoned branch could delete the
/// trunk's history simply by having been written to last.
#[tokio::test]
async fn the_trunk_writing_after_a_fork_does_not_archive_the_branchs_belief() {
    let h = TestHarness::new();
    let db = seed(&h).await;
    let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
    h.advance(STEP);

    db.assert_edge(
        EdgeAssertion::new("a", "b", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN)
            .weight(2.0)
            .on_branch(alt.id.clone()),
    )
    .await
    .unwrap();
    h.advance(STEP);

    // The trunk moves last, so *its* row is the newer one at this key.
    db.assert_edge(
        EdgeAssertion::new("a", "b", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN)
            .weight(3.0),
    )
    .await
    .unwrap();
    h.advance(STEP);

    db.archive(LATE).await.unwrap();

    assert_eq!(
        reached(&db, Some("alt")).await,
        ["a", "b", "c"],
        "the branch lost an edge because the trunk wrote at the same key after it forked"
    );
    assert_eq!(reached(&db, None).await, ["a", "b", "c"]);

    db.close().await.unwrap();
}

/// Archiving a branch's closed shadow row must not resurrect what it retired.
///
/// The second defect, and a different one: not supersession matching across
/// lineages but "a closed interval is history", which is true of a lineage
/// holding the only row at its key and false of a shadow. A branch retires an
/// inherited edge by writing its **own** closed row at the ancestor's key — the
/// only cross-lineage retirement Doctrine III permits. Archive that row and the
/// ancestor's open row wins the resolution again.
///
/// Measured before the repair: the branch reached `c` at `T2` after the archive,
/// having stopped believing it before the archive. A maintenance operation that
/// mints no assertions restored a belief the ledger had superseded.
#[tokio::test]
async fn archiving_a_shadow_row_does_not_resurrect_what_the_branch_retired() {
    let h = TestHarness::new();
    let db = seed(&h).await;
    let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
    h.advance(STEP);

    db.retire_edge_on("b", "c", "LEADSTO", EPOCH, T1, alt.id.clone())
        .await
        .unwrap();
    h.advance(STEP);

    assert_eq!(reached_at(&db, Some("alt"), T2).await, ["a", "b"]);
    assert_eq!(reached_at(&db, None, T2).await, ["a", "b", "c"]);

    db.archive(LATE).await.unwrap();

    assert_eq!(
        reached_at(&db, Some("alt"), T2).await,
        ["a", "b"],
        "the archive un-retired an edge -- a belief resurrected by an operation \
         that asserts nothing"
    );
    assert_eq!(
        reached_at(&db, None, T2).await,
        ["a", "b", "c"],
        "and the ancestor's own row was never the branch's to affect"
    );

    db.close().await.unwrap();
}

/// The ancestor's row is held back too, and that is the conservative half.
///
/// Strictly, only a row whose *ancestor* still holds the key must stay; the arm
/// stands down for **any** second lineage at the key, in both directions. That
/// is deliberate — ancestry would mean resolving the chain for every branch in
/// an operation that takes no branch parameter, and "still holds it after this
/// session" is self-referential. Leaving rows hot costs bytes and is never
/// wrong. This pins the cost so it is a measured choice rather than a surprise.
#[tokio::test]
async fn a_key_two_lineages_hold_keeps_its_closed_intervals_hot() {
    let h = TestHarness::new();
    let db = seed(&h).await;
    let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
    h.advance(STEP);

    // Both lineages close the same key. Neither row may go cold while the other
    // is hot, even though the trunk's would be archivable on its own.
    db.retire_edge_on("b", "c", "LEADSTO", EPOCH, T1, alt.id.clone())
        .await
        .unwrap();
    h.advance(STEP);
    db.retire_edge("b", "c", "LEADSTO", EPOCH, T1)
        .await
        .unwrap();
    h.advance(STEP);

    let report = db.archive(LATE).await.unwrap();

    // **Nothing goes cold, and this number moved at 0.15.26** ([D-269]). It read
    // 1 until then: retiring on the trunk superseded the trunk's own open row,
    // and the supersession arm took it. That arm now asks *when* as well as
    // whose, and `alt` forked between the open row and the retirement — so the
    // open row is the one `alt`'s cutoff still points at, and taking it would
    // have changed what a branch believes. The assertion below is the reason
    // the number is allowed to move: the answers are what they were.
    //
    // [D-269]: ../docs/architecture/s13-decision-register.md#d-269
    assert_eq!(report.links_archived, 0);
    assert_eq!(
        rows_at(&db, "b", "c").await,
        [
            format!("alt {T1}"),
            format!("main {T1}"),
            format!("main {OPEN}")
        ],
        "both closed rows stay hot: two lineages hold the key, so the \
         closed-interval arm stands down for both. The trunk's open row is the \
         third and is held for the other reason ([D-269]): `alt` forked after it \
         and reads it still"
    );
    assert_eq!(reached_at(&db, None, T2).await, ["a", "b"]);
    assert_eq!(reached_at(&db, Some("alt"), T2).await, ["a", "b"]);
    // At an instant inside the retired interval both lineages still believe the
    // edge, which is the reading the held row is being held for.
    assert_eq!(reached_at(&db, Some("alt"), EPOCH).await, ["a", "b", "c"]);

    db.close().await.unwrap();
}

/// A key only one lineage holds still sends its closed intervals cold — and
/// **"one lineage holds it" has to be true of the whole key**, not of one row
/// of it ([D-269], 0.15.26).
///
/// The arm the repair narrows is the one the cold file exists for, so the
/// narrowing has to stop where shadowing stops. That was D-229's sentence and
/// it was half of one. Shadowing is not the only thing that pins a row: a fork
/// point is a `recorded_at` a descendant reads its ancestors through, so a
/// branch goes on believing the row that was newest when it forked. This
/// fixture used to close the seed's own `a → b` — a key with a pre-fork row on
/// it — and assert both later rows cold, which took `alt`'s inheritance with
/// them and is the defect D-269 fixes.
///
/// So the key here is one that **did not exist when `alt` forked**. `a → c` is
/// written and retired entirely after the fork, `alt` never writes at it and
/// never inherited anything at it, and the archive behaves exactly as D-229
/// said it should: both rows cold.
///
/// [D-269]: ../docs/architecture/s13-decision-register.md#d-269
#[tokio::test]
async fn a_key_one_lineage_holds_still_archives_its_closed_interval() {
    let h = TestHarness::new();
    let db = seed(&h).await;
    let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
    h.advance(STEP);

    // Born after the fork: `alt` inherits nothing at this key, so nothing of
    // `alt`'s resolves to either row below.
    db.assert_edge(
        EdgeAssertion::new("a", "c", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN),
    )
    .await
    .unwrap();
    h.advance(STEP);

    // The branch writes at a *different* key, so `a -> c` is the trunk's alone.
    db.assert_edge(
        EdgeAssertion::new("b", "c", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN)
            .weight(2.0)
            .on_branch(alt.id.clone()),
    )
    .await
    .unwrap();
    h.advance(STEP);

    db.retire_edge("a", "c", "LEADSTO", EPOCH, T1)
        .await
        .unwrap();
    h.advance(STEP);

    let report = db.archive(LATE).await.unwrap();

    // Two rows, and both belong in the cold file: the open row the retirement
    // superseded, and the closed row the retirement wrote. Nobody's shadow and
    // nobody's inheritance.
    assert_eq!(report.links_archived, 2);
    assert_eq!(
        rows_at(&db, "a", "c").await,
        [] as [String; 0],
        "the trunk's closed `a -> c` is nobody's shadow and belongs in the cold file"
    );
    assert_eq!(
        rows_at(&db, "b", "c").await.len(),
        2,
        "and the key two lineages do hold is untouched by this session"
    );

    db.close().await.unwrap();
}

/// **A fork pins the row its cutoff points at, with nobody writing on it.**
///
/// The case `lineage_property_tests.rs` found on its first run against a clean
/// tree, minimised by hand ([D-269]). Every operation is on the trunk: `alt`
/// forks, and then `a → b` is restated — the same key, the same interval, a new
/// row, which is what an idempotent importer writes on every run. The old row is
/// superseded *on its own lineage*, so D-229's clause had nothing to say, and
/// one ordinary `archive` took it.
///
/// It is the only row `alt` can resolve `a → b` to. The trunk kept reaching `b`
/// and the branch stopped, and no branch wrote anything anywhere in the history.
/// That is what separates this from D-229, which needed two lineages disagreeing
/// at a shared key: this needs one lineage agreeing with an older version of
/// itself while somebody else is pinned to it.
///
/// [D-269]: ../docs/architecture/s13-decision-register.md#d-269
#[tokio::test]
async fn a_fork_keeps_believing_the_row_the_trunk_restated_over() {
    let h = TestHarness::new();
    let db = seed(&h).await;
    let before = reached_at(&db, None, T2).await;

    db.fork(id("alt"), BranchId::main()).await.unwrap();
    h.advance(STEP);

    // Idempotent restatement: same key, same interval, later `recorded_at`.
    db.assert_edge(
        EdgeAssertion::new("a", "b", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN),
    )
    .await
    .unwrap();
    h.advance(STEP);

    assert_eq!(reached_at(&db, Some("alt"), T2).await, before);
    let report = db.archive(LATE).await.unwrap();

    assert_eq!(
        reached_at(&db, Some("alt"), T2).await,
        before,
        "the branch believes what it believed; an archive is a move, not a \
         retirement (report: {report:?})"
    );
    assert_eq!(
        reached_at(&db, None, T2).await,
        before,
        "and the trunk, which was never at risk, is the control"
    );

    db.close().await.unwrap();
}

/// **And a held row must not resurrect the row that closed it** ([D-269]).
///
/// The second shape the generator found, and it exists only because of the
/// first repair. `alt` forks after the seed wrote `a → b` open, so that open row
/// is pinned and stays hot. The trunk then retires the edge, writing a closed
/// row at the same key — and the closed-interval arm was willing to take it,
/// because no *other lineage* holds the key. With the older row still hot, the
/// trunk's resolution falls back to it: the edge the trunk had retired comes
/// back, restored by an operation that mints no assertions.
///
/// That is exactly D-229's resurrection, reached through D-269's own repair,
/// which is why the fix is two clauses and not one. Worth stating plainly: the
/// first clause on its own is a regression, and only a generator that keeps
/// checking after every op would have said so.
///
/// [D-269]: ../docs/architecture/s13-decision-register.md#d-269
#[tokio::test]
async fn the_row_a_fork_pins_does_not_resurrect_the_retirement_over_it() {
    let h = TestHarness::new();
    let db = seed(&h).await;

    db.fork(id("alt"), BranchId::main()).await.unwrap();
    h.advance(STEP);

    db.retire_edge("a", "b", "LEADSTO", EPOCH, T1)
        .await
        .unwrap();
    h.advance(STEP);

    let before = reached_at(&db, None, T2).await;
    assert_eq!(before, ["a"], "the trunk retired its way out of the graph");

    let report = db.archive(LATE).await.unwrap();

    assert_eq!(
        reached_at(&db, None, T2).await,
        before,
        "the retirement stands: archiving the closed row would let the open row          the fork pinned win again (report: {report:?})"
    );
    assert_eq!(
        reached_at(&db, Some("alt"), T2).await,
        ["a", "b", "c"],
        "and `alt`, which forked before the retirement, never stopped believing it"
    );

    db.close().await.unwrap();
}

// ───────────────────────────────────────────────────────────────────────────
// What the repair must not have turned off
// ───────────────────────────────────────────────────────────────────────────

/// Same-lineage supersession still goes cold, on the trunk and on a branch alike.
///
/// A narrowing clause is worth nothing if it narrows to nothing. Each lineage
/// writes twice at one key, so each has exactly one superseded row, and the
/// archive must take both.
#[tokio::test]
async fn a_lineage_still_supersedes_itself() {
    let h = TestHarness::new();
    let db = seed(&h).await;
    let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
    h.advance(STEP);

    for (weight, branch) in [(2.0, None), (3.0, Some(alt.id.clone()))] {
        let mut e = EdgeAssertion::new("a", "b", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN)
            .weight(weight);
        if let Some(b) = branch {
            e = e.on_branch(b);
        }
        db.assert_edge(e).await.unwrap();
        h.advance(STEP);
    }
    // A third generation on each, so each lineage supersedes itself once.
    db.assert_edge(
        EdgeAssertion::new("a", "b", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN)
            .weight(4.0),
    )
    .await
    .unwrap();
    h.advance(STEP);
    db.assert_edge(
        EdgeAssertion::new("a", "b", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN)
            .weight(5.0)
            .on_branch(alt.id.clone()),
    )
    .await
    .unwrap();
    h.advance(STEP);

    let (links_before, _) = counts(&db).await;
    let report = db.archive(LATE).await.unwrap();
    let (links_after, _) = counts(&db).await;

    assert!(
        report.links_archived >= 2,
        "each lineage superseded itself once and both should have gone cold, got {}",
        report.links_archived
    );
    assert_eq!(links_before - links_after, report.links_archived as i64);
    assert_eq!(
        reached(&db, None).await,
        ["a", "b", "c"],
        "and current belief is untouched on both"
    );
    assert_eq!(reached(&db, Some("alt")).await, ["a", "b", "c"]);

    db.close().await.unwrap();
}

/// An unbranched database archives exactly what it archived before.
///
/// The clause is `newer.branch_id = links.branch_id`, and every row on a ledger
/// that has never forked carries `'main'`, so it is satisfied by every pair. This
/// says so as a measurement rather than as an argument about defaults.
#[tokio::test]
async fn a_ledger_that_never_forked_archives_what_it_always_did() {
    let h = TestHarness::new();
    let db = seed(&h).await;

    for weight in [2.0, 3.0] {
        db.assert_edge(
            EdgeAssertion::new("a", "b", "LEADSTO")
                .valid_from(EPOCH)
                .valid_to(OPEN)
                .weight(weight),
        )
        .await
        .unwrap();
        h.advance(STEP);
    }

    let report = db.archive(LATE).await.unwrap();

    assert_eq!(
        report.links_archived, 2,
        "two superseded generations, and nothing about lineage in the way"
    );
    assert_eq!(reached(&db, None).await, ["a", "b", "c"]);
    assert_eq!(audit_current(db.read_conn()).await.unwrap(), 0);

    db.close().await.unwrap();
}

// ───────────────────────────────────────────────────────────────────────────
// Why nothing caught it
// ───────────────────────────────────────────────────────────────────────────

/// The drift audit is silent across this defect, by construction.
///
/// This is not a gap in `audit_current` and the test is here to say so. Doctrine
/// VI asks whether `links_current` is the image of `links`; the archive deletes
/// from `links` and then re-derives `links_current` from what survives, so the
/// answer is *yes* whether or not the right rows survived. An audit that could
/// see this would have to compare the ledger against something outside itself.
#[tokio::test]
async fn the_drift_audit_cannot_see_a_wrongly_pruned_ledger() {
    let h = TestHarness::new();
    let db = seed(&h).await;
    let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
    h.advance(STEP);

    db.assert_edge(
        EdgeAssertion::new("a", "b", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN)
            .weight(2.0)
            .on_branch(alt.id.clone()),
    )
    .await
    .unwrap();
    h.advance(STEP);

    assert_eq!(audit_current(db.read_conn()).await.unwrap(), 0);
    db.archive(LATE).await.unwrap();
    assert_eq!(
        audit_current(db.read_conn()).await.unwrap(),
        0,
        "zero before and zero after — the same answer the defect gave"
    );

    db.close().await.unwrap();
}

/// The newest log entry of every fold partition stays hot.
///
/// `LOG_ARCHIVABLE`'s docstring promised "the newest entry per entity", written
/// when the fold partitioned by entity. Since v12 it partitions by
/// `(table_name, entity_id, branch_id)`, so the promise had quietly become one
/// about a coarser unit than the thing it was protecting.
#[tokio::test]
async fn every_lineage_keeps_the_newest_log_entry_for_its_own_key() {
    let h = TestHarness::new();
    let db = seed(&h).await;
    let alt = db.fork(id("alt"), BranchId::main()).await.unwrap();
    h.advance(STEP);

    db.assert_edge(
        EdgeAssertion::new("a", "b", "LEADSTO")
            .valid_from(EPOCH)
            .valid_to(OPEN)
            .weight(2.0)
            .on_branch(alt.id.clone()),
    )
    .await
    .unwrap();
    h.advance(STEP);

    db.archive(LATE).await.unwrap();

    let mut rows = db
        .read_conn()
        .query(
            "SELECT branch_id, COUNT(*) FROM transaction_log \
             WHERE table_name = 'links' AND entity_id = ?1 GROUP BY branch_id ORDER BY branch_id",
            libsql::params![format!("a|b|LEADSTO|{EPOCH}")],
        )
        .await
        .unwrap();
    let mut per_lineage = Vec::new();
    while let Some(r) = rows.next().await.unwrap() {
        per_lineage.push((r.get::<String>(0).unwrap(), r.get::<i64>(1).unwrap()));
    }

    assert_eq!(
        per_lineage,
        [("alt".to_string(), 1), ("main".to_string(), 1)],
        "one surviving entry per lineage, which is one per fold partition"
    );

    db.close().await.unwrap();
}