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
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
use ;
use ;
use ;
use crateAncestor;
use crate;
use crateNodeAttributes;
/// One lineage's belief about one edge, at the instant a fold asked (§15.2).
///
/// # Why this is a struct and was a five-tuple until 0.14.5
///
/// The tuple had nowhere to put `branch_id`, and that was not a cosmetic
/// shortfall: [D-216](../../docs/architecture/s13-decision-register.md) widened
/// the four SQL folds to partition by `(table_name, entity_id, branch_id)` so
/// two lineages' beliefs about one edge would stay two rows, and then the
/// composition immediately downstream re-collapsed them, because `edge_key`
/// composed `source|target|type|valid_from` and the map it fed had one slot per
/// edge key. The widened partition was handing two rows to a container that
/// could not hold two. That is [D-221](../../docs/architecture/s13-decision-register.md#d-221),
/// and this type is its fix.
///
/// A struct rather than a six-tuple because the next field to arrive should be
/// additive, which is why it is also `#[non_exhaustive]` — the same call
/// [D-207](../../docs/architecture/s13-decision-register.md#d-207) made for
/// `DbError`, one release earlier, for the same reason. Construct these by
/// reading a [`MaterializedState`]; the crate is the only writer.
///
/// **Ordered by the tuple order of its fields**, so a `Vec<EdgeBelief>` sorts to
/// a canonical form and two reconstructions of the same instant are *equal*
/// rather than merely equivalent — a property the snapshot suite compares on.
///
/// # Constructing one
///
/// `#[non_exhaustive]` means no crate but this one may write the literal, and
/// [`save_snapshot`](crate::temporal::save_snapshot) is public and takes a
/// `MaterializedState` — so without a constructor the attribute would not make
/// the next field additive, it would make a public function uncallable. Use
/// [`EdgeBelief::new`], which takes the five fields that were the tuple and
/// defaults the sixth to the trunk, with [`EdgeBelief::on_branch`] for the
/// rest. That is [`EdgeAssertion::new`](crate::graph::EdgeAssertion::new)'s
/// shape, deliberately: the two are the same fact travelling in opposite
/// directions and should not need two idioms.
/// Full materialized state reconstructed from transaction_log replay (§5.5).
/// One lineage's view of a fold, from every lineage's beliefs (review C-10).
///
/// The nearest lineage holding an edge key wins, and a key no visible lineage
/// holds is absent. That is `graph::lineage::visible_cte`'s rule — `ROW_NUMBER() OVER
/// (PARTITION BY the edge key ORDER BY g.dist)`, `rn = 1` — written once more,
/// in Rust, over a value a caller already has (0.15.17, [D-259]).
///
/// # What this is for
///
/// Until this release the rule existed **only** as SQL. A caller holding a
/// [`MaterializedState`] — from [`reconstruct`], or read back from a snapshot —
/// had every lineage's belief in one `Vec` and no function in the crate that
/// would finish the question, so the choice was to re-issue the read through
/// `graph::TraversalBuilder::on_branch` (a different query against the
/// *projection*, not against the state in hand) or to reimplement the rule.
/// Reimplementing it is what review C-10 expected someone to do, and the two
/// copies would have drifted the first time a shape was added.
///
/// # Ties, and why there are none to break in practice
///
/// A fold emits at most one row per `(edge key, lineage)` and an ancestry names
/// each lineage once, so no two candidates for a key share a `dist` and the
/// winner is determined by distance alone. This still breaks ties on the
/// belief's own ordering rather than on iteration order, because it is a public
/// pure function: an input the crate did not build should get an answer that is
/// a function of the input, not of a hash seed.
///
/// # The cutoff is not applied here, and cannot be
///
/// An ancestor's rows are visible to a descendant only up to
/// [`Ancestor::cutoff`], and that is a comparison against the row's
/// `recorded_at` — a column [`EdgeBelief`] does not carry, deliberately, since
/// a belief is *what was believed* and not *when it was written down*. So the
/// cutoff belongs to whatever produced the beliefs: [`reconstruct_on`] applies
/// it by folding each ancestor to its own instant before calling this.
///
/// Handing this the unbounded `edges` of a plain [`reconstruct`] therefore
/// gives the nearest-lineage answer **without** the fork bound — right on a
/// database whose ancestors have not been written to since the fork, and
/// quietly wide on one that has. The doc says so rather than the signature,
/// because a `&[EdgeBelief]` cannot be typed into "already cut".
///
/// [D-259]: ../../docs/architecture/s13-decision-register.md#d-259
/// State at `ts` **as one lineage saw it** (0.15.17, [D-259], review C-10).
///
/// [`reconstruct`] answers a whole-ledger question — *what did the ledger hold
/// at `ts`* — and the ledger held every lineage's belief at once. This answers
/// the narrower one a caller usually means: *what did `branch` hold at `ts`*,
/// with the ancestry resolved and each ancestor bounded at its fork point.
///
/// # How it is assembled
///
/// One fold, with the ancestry bound into it: the `JOIN` against the `lineage`
/// relation drops every lineage the reader cannot see, `recorded_at <=
/// g.cutoff` bounds each ancestor at its own fork point, and [`resolve_beliefs`]
/// then picks the nearest holder of each key. The cut is applied to the
/// window's **input**, which is the part that cannot be done any other way —
/// see `bounded_hot_fold`.
///
/// ## The shape this is not, and the measurement that decided it
///
/// The first version folded once per **distinct effective instant** — `min(ts,
/// cutoff)` for the reader and each ancestor — and kept from each fold the
/// lineages whose instant it was. That form reuses [`reconstruct`] whole,
/// snapshot composition included, and the argument for it was that a fork depth
/// of 1 is two cheap folds where this one is a single expensive one.
///
/// Measured (`examples/reconstruct_on_probe.rs`, 400 concepts), the argument
/// holds at exactly one of the four configurations tried:
///
/// | snapshots | fork depth | fold-per-bound | this |
/// |---|---|---|---|
/// | off | 1 | 5.6 ms | **2.9 ms** |
/// | off | 8 | 24.1 ms | **3.2 ms** |
/// | on | 1 | **2.0 ms** | 2.9 ms |
/// | on | 8 | 7.5 ms | **3.2 ms** |
///
/// Both shapes run in one process against one build, alternating, because the
/// first version of this comparison ran them in two processes against two
/// builds and that is thin evidence for reversing a design. The probe also
/// asserts that the two shapes return the **same edges** before it times them,
/// and counts the snapshot files on disk rather than trusting that asking for a
/// cadence produced one — the whole argument for fold-per-bound rests on
/// composition actually being available.
///
/// The per-bound form is linear in fork depth and this one is flat, so the
/// crossover is at depth 2 with snapshots configured and below depth 1 without
/// them. Losing about 1 ms at the one point where the other shape wins buys a
/// cost that does not depend on how deeply a caller has forked, and one code
/// path instead of two — the same call [D-056]'s guard made earlier in this
/// release for the same reason.
///
/// ## What that costs: no snapshot composition
///
/// A snapshot is a materialised state with no `recorded_at` left in it, so
/// there is nothing for a cutoff to compare against and no way to anchor a
/// bounded fold on one. This therefore folds from genesis every time, which is
/// where the flat ~3 ms comes from — and why, on a database with snapshots
/// configured, this is **4x** [`reconstruct`] rather than 1.2x. The absolute
/// cost is the same in both configurations; it is `reconstruct` that gets
/// faster, not this that gets slower.
///
/// An unforked database never pays any of it: the shape is `Trunk`, there is
/// one lineage and nothing to resolve, and this delegates to [`reconstruct`]
/// unchanged, snapshots and all.
///
/// # Concepts need no distance rule, because the tie cannot happen (0.15.18,
/// [D-260])
///
/// [`MaterializedState::concepts`] is keyed by concept id alone, so once a row
/// is folded there is no lineage left on it to pick a nearest one by. The fold
/// here *is* narrowed — a lineage outside the ancestry contributes nothing, and
/// an ancestor's post-cutoff concept writes are cut like its edges — and that
/// narrowing is all a concept needs, because **two visible lineages cannot both
/// hold one concept id**.
///
/// That is the schema's guarantee and not this function's. `concepts.id` is
/// `NOT NULL UNIQUE` — identity, not identity-per-lineage — and
/// `trg_concepts_cross_lineage` turns the index's refusal into
/// [`DbError::CrossLineage`](crate::DbError::CrossLineage) so it says which
/// rule was broken; `trg_concepts_branch_immutable` stops a concept being moved
/// to another lineage afterwards. A branch therefore **inherits** its parent's
/// concepts and cannot restate them (§15.2, [D-225]), which is the same rule
/// read from the other side.
///
/// The one route past that guard is [`archive_branch`] — it reads the live
/// table, and archiving moves rows out of it — so archiving a lineage and then
/// minting its id on the trunk does leave two lineages' rows for one id in
/// hot-plus-cold history. It still reaches no reader: an archived lineage is
/// gone from `branches`, so it is in nobody's ancestry, so the `JOIN` above
/// drops its rows on **both** arms (the cold one joins the union, not each
/// file). `rehydrate` refuses to bring the concept back while its lineage is
/// forgotten ([D-253]). `examples/concept_lineage_probe.rs` walks all five
/// routes and prints which the database refuses.
///
/// So this is not a resolution the caller must compensate for. It is a rule
/// with nothing to decide, and if `concepts` ever gained per-lineage rows —
/// the overlay design [D-214] defers — it would need one, along with a lineage
/// on the folded row to apply it to.
///
/// Only [`MaterializedState::edges`] gets the distance rule, which is the field
/// review C-10 named and the only one the rule has ever been needed for.
///
/// [D-260]: ../../docs/architecture/s13-decision-register.md#d-260
/// [D-253]: ../../docs/architecture/s13-decision-register.md#d-253
/// [D-225]: ../../docs/architecture/s13-decision-register.md#d-225
/// [D-214]: ../../docs/architecture/s13-decision-register.md#d-214
/// [`archive_branch`]: crate::Database::archive_branch
///
/// # This result is not a snapshot
///
/// It is one lineage's *view*, so it is missing beliefs the ledger holds. Do not
/// pass it to [`save_snapshot`](crate::temporal::save_snapshot): a later
/// [`reconstruct`] anchoring on it would compose a whole-ledger answer on top of
/// a partial base and return the other lineages' rows only where something
/// touched them again. [`seq_anchor`](MaterializedState::seq_anchor) is the
/// highest `seq_id` among the rows this lineage can *see*, which is the honest
/// number for what was folded and is not a licence to anchor on it.
///
/// [D-056]: ../../docs/architecture/s13-decision-register.md#d-056
///
/// # Errors
///
/// [`DbError::UnknownBranch`](crate::DbError::UnknownBranch), naming it, when
/// `branch` is not registered — refused rather than answered for the trunk, for
/// the reason `graph::lineage::Lineages::shape` gives.
///
/// Otherwise the same refusals [`reconstruct`] raises at `ts`, and for the same
/// reasons: reach is decided by `ts` alone, because the cutoffs are a predicate
/// inside one query rather than instants of their own.
///
/// # Where this narrows silently, named rather than left to be found
///
/// An ancestor's inherited row is the *last* one it wrote at or before the fork
/// point, and the fold finds it in the hot log. `LOG_ARCHIVABLE` archives an
/// entry once a later one supersedes it for the same entity — so a pre-fork
/// assertion that the ancestor corrected afterwards is archivable, and once the
/// retention horizon passes the fork point it can be cold. The reader then
/// loses an edge it should have inherited, and **nothing raises**, because `ts`
/// is well inside the hot log and reach was asked about `ts`.
///
/// That is not new and not this function's: it is the same degradation
/// `graph::lineage`'s module docs describe for `links_cut`, reached from the
/// fold side instead of the projection side, and it is bounded to keys an
/// ancestor churned after forking. Passing `archive_path` closes it — the cold
/// arm unions both files before it cuts.
///
/// [D-259]: ../../docs/architecture/s13-decision-register.md#d-259
pub async
/// The newest log payload shape this build writes and the highest it can read.
///
/// Kept beside the folds because they are the only readers, and bumped in step
/// with the `json_object('v', …)` literals in `schema::ddl` — a test asserts the
/// two agree, since nothing else would notice them drifting apart.
pub const PAYLOAD_VERSION: u8 = 2;
/// Every fold partitions on `(table_name, entity_id)`, never `entity_id` alone.
///
/// The two namespaces are not disjoint and nothing makes them so. A link's
/// `entity_id` is the synthetic `source|target|type|valid_from`; a concept's is
/// whatever the caller passed, unvalidated (defect AD). Partitioning on the id
/// alone therefore lets a concept and a link contend for one window, and
/// `ROW_NUMBER() = 1` hands the whole partition to whichever has the greater
/// `seq_id` — so the loser vanishes from the reconstruction while sitting
/// plainly in both `concepts` and `transaction_log`. Silent, and on the read
/// path the ledger exists to make trustworthy.
///
/// Validating identifiers would make the collision unreachable and is the
/// durable fix; this makes it harmless regardless, which is the property worth
/// having at the fold. `table_name` leads the partition because the log is
/// already indexed on `entity_id` and the discriminator is two values wide.
const HOT_FOLD: &str = r#"
SELECT seq_id, table_name, entity_id, operation, payload, branch_id
FROM (
SELECT seq_id, table_name, entity_id, operation, payload, branch_id,
ROW_NUMBER() OVER (PARTITION BY table_name, entity_id, branch_id ORDER BY seq_id DESC) as rn
FROM transaction_log
WHERE recorded_at <= ?1
) WHERE rn = 1
"#;
/// Fold over hot and cold together (§5.5, D-026). Requires `cold` to be ATTACHed.
///
/// The hot entry wins for entities present in both files because its `seq_id` is
/// greater — the same last-writer-wins rule as snapshot composition.
/// The hot fold, narrowed to one lineage's view (0.15.17, [D-259]).
///
/// [`HOT_FOLD`] with the ancestry joined in: the `JOIN` drops every lineage the
/// reader cannot see, and `recorded_at <= g.cutoff` bounds each ancestor at its
/// own fork point. `?1` is the instant, and the ancestry binds from
/// `first_slot` — three placeholders per ancestor, the same block
/// [`crate::graph::lineage::ancestry_values`] emits for the read path.
///
/// **The cutoff is inside the window's input, not applied to its output**, and
/// that is the whole reason this is a fold rather than a filter over
/// [`reconstruct`]'s answer. If an ancestor wrote a row and then superseded it
/// after the fork, the reader must see the *earlier* row — so the cut has to
/// happen before `ROW_NUMBER` picks a winner. A predicate over a finished
/// `MaterializedState` cannot express that, and would return nothing for that
/// key instead of returning the row the lineage actually inherited.
/// [`bounded_hot_fold`] over hot and cold together. Requires `cold` ATTACHed.
///
/// The union is inside the cutoff filter rather than outside it, for the same
/// reason the plain [`cold_fold`] puts `recorded_at <= ?1` there: a row's file
/// is not a fact about its lineage, and a bound applied to one file and not the
/// other would give a different answer depending on when the archive ran.
/// Fold over the hot log *above a snapshot anchor* (§5.5, [D-049]).
///
/// `seq_id > ?2` is an inequality, and deliberately so: the hot log's ids have
/// gaps, so successor arithmetic (`seq_id = :anchor + 1`) would stop at the
/// first one and silently truncate the delta. This is the first anchored fold
/// in the crate, which makes it the first code [D-024]'s rule has ever bound —
/// before this the rule was vacuous, not satisfied.
///
/// **The gaps come from the archive, not from rollbacks** (0.15.19, review
/// C-17). This comment used to name a rolled-back transaction as the source,
/// which is [D-024]'s stated mechanism and is the one thing [D-049] measured
/// and disproved: `sqlite_sequence` is written *inside* the transaction, so a
/// rollback takes the allocation with it and the number is reused. What does
/// leave gaps is `temporal::archive`, which deletes superseded rows from
/// `transaction_log` — scattered through the sequence rather than forming a
/// prefix, which is exactly the shape successor arithmetic cannot walk. Same
/// inequality, and now the same reason the register gives for it; the
/// gap-tolerance test builds its state by deleting a log row inside a session
/// marker, which is the real mechanism and not the retracted one.
///
/// [D-049]: ../../docs/architecture/s13-decision-register.md#d-049
/// [D-024]: ../../docs/architecture/s13-decision-register.md#d-024
const ANCHORED_HOT_FOLD: &str = r#"
SELECT seq_id, table_name, entity_id, operation, payload, branch_id
FROM (
SELECT seq_id, table_name, entity_id, operation, payload, branch_id,
ROW_NUMBER() OVER (PARTITION BY table_name, entity_id, branch_id ORDER BY seq_id DESC) as rn
FROM transaction_log
WHERE recorded_at <= ?1 AND seq_id > ?2
) WHERE rn = 1
"#;
/// Fold over hot **and cold** above a snapshot anchor (§5.5, 0.5.5).
///
/// The union is what lets composition survive an archive. Rows keep their
/// `seq_id` when they move to cold — the cold schema declares a plain `INTEGER
/// PRIMARY KEY` precisely so history is not renumbered — so `seq_id > ?2`
/// partitions the two files consistently and last-writer-wins across them by the
/// same rule the unanchored folds use.
/// Whether an attached cold file predates the lineage column (§15.2, v12).
///
/// Cold files are **read-only media as far as the read path is concerned**.
/// They get moved (D-026), they can sit on a share, and a fold that upgraded
/// one in order to read it would be a write on a path callers have every reason
/// to believe is a read. So the shape is detected and tolerated, never
/// corrected: the archive *writer* upgrades, and only inside its own
/// transaction.
///
/// Detection is column presence rather than a version stamp, because a cold
/// file carries no version anyone can trust — it is a file that has been moved.
/// Ask the attached cold file whether it carries `transaction_log.branch_id`.
///
/// Returns [`ColdLineage::PreV12`] when the pragma cannot be read at all. That
/// is the conservative direction: a fold that guesses "stamped" against a v11
/// file fails with `no such column`, while a fold that guesses "pre-v12"
/// against a v12 file reads rows it can still fold — it would mislabel a
/// branch's rows as trunk, which is why the guess is never made when the pragma
/// answers.
async
/// The winning log rows for one fold, before they are applied to a base state.
///
/// Absence and disappearance are different facts, and a merge is where the
/// difference starts to matter. A full fold from nothing can treat "this entity
/// went away" and "there is no row for it" identically — both end as absence.
/// Composed onto a snapshot they are opposites: a disappearance must *remove*
/// the entity the snapshot carries, and skipping it leaves the snapshot's stale
/// row standing as though nothing had happened. So they are collected rather
/// than dropped, and the full fold applies them to an empty base, which keeps
/// one code path for both cases (D-049).
///
/// **There is one such set, not two (D-072).** It used to carry `edges_gone`
/// beside `concepts_gone`, and both were populated only from the `'D'` branch of
/// [`fold_delta`] — so when that branch became an error, `edges_gone` was left
/// reachable by nothing. Closing one unreachable path by opening another is not
/// a fix, so it went too.
///
/// The asymmetry is real and worth stating, because "concepts can vanish and
/// edges cannot" looks like an oversight until you follow it:
///
/// * A **concept** disappears by being *retired*, which writes a `'U'` row whose
/// payload has `retired = 1`. That is a genuine removal from a composed state
/// and `concepts_gone` carries it.
/// * An **edge** never disappears. It is retired by asserting a successor over
/// the same interval key — same `source|target|type|valid_from`, later
/// `recorded_at` — so the log row is an `'I'` under the *same* `entity_id`, and
/// last-writer-wins in [`Self::apply_to`] replaces the tuple in place. There is
/// nothing to remove because nothing left; the interval simply closed.
///
/// That is Doctrine III showing through: an edge assertion is immutable and
/// superseded, never deleted.
/// Release a `cold` handle left attached by an earlier call (§5.5, D-044).
///
/// Both ATTACH sites pair with an unconditional DETACH on the way out, so in
/// the normal course this finds nothing and the statement fails harmlessly with
/// "no such database: cold". It exists for the case the pairing cannot cover: a
/// panic unwinding between the two, which skips the DETACH no matter which exit
/// path the `Result` would have taken.
///
/// A `Drop` guard is the reflex here and does not work — `execute` is `async`,
/// and a `Drop` impl cannot await, so it would build a future, discard it, and
/// leave the handle attached while looking like it had cleaned up. Recovering
/// on the way *in* needs no destructor, works regardless of how the handle
/// leaked, and turns permanent poisoning of the connection into one failed
/// statement nobody sees.
pub async
/// Reconstruct database state as believed at past instant `ts` using window-function log fold (§5.5, D-026).
///
/// When `ts` predates the hot log's horizon the cold database is ATTACHed for
/// exactly one fold and DETACHed unconditionally on the way out, error paths
/// included. ATTACH is not transactional and survives ROLLBACK, so a handle
/// leaked by an early return would make every later `reconstruct` *and* every
/// later `archive` fail with "database cold is already in use" — one corrupt
/// payload would permanently poison the connection. This is the same failure
/// mode `archive()` carries a note about, and the two now share a shape.
/// Snapshot composition (§5.5, D-049) applies when `snapshots_dir` holds a
/// snapshot at or before `ts` and no archive database exists — see
/// `snapshot_anchor` for why archiving disables it. Otherwise the fold runs
/// from genesis, which is correct and costs what the whole log costs.
pub async
/// [`reconstruct`] with the anchor chosen by the caller (0.15.19, review C-18).
///
/// The whole of `reconstruct` except the one line that picks a base, split out
/// because [`verify_last_link`] needs to compose onto a **named** snapshot
/// rather than onto whichever one is newest at `ts` — and picking it is the
/// only thing the two do differently. Written as a split rather than as a
/// second copy of the ATTACH bracket for the reason the module keeps
/// re-learning: a read spelled twice drifts, and the half nobody calls is the
/// half that drifts first ([D-227]).
///
/// `base` of `None` is a fold from genesis.
///
/// [D-227]: ../../docs/architecture/s13-decision-register.md#d-227
async
/// Fold from genesis and compare against the composed answer (§5.5, T5.3,
/// D-092).
///
/// # The problem this exists for
///
/// [`crate::temporal::save_snapshot`] is written by `write_final`, which calls
/// [`reconstruct`] — and `reconstruct` composes onto the *previous* snapshot
/// whenever one is usable. So snapshot *n* is derived from snapshot *n−1*, and
/// there is no periodic full fold anywhere in the chain. An error introduced at
/// any link is copied forward indefinitely, and every subsequent read agrees
/// with it, because they are all reading the same descendant.
///
/// The project's own open item names the difficulty honestly: a full fold is
/// exactly the cost snapshots exist to avoid, so this cannot run on every read.
/// It is a **scheduling** problem, and this function is the thing to schedule.
///
/// # It reports; it does not repair
///
/// Deliberate, and not merely conservative. Under [Doctrine VI] a snapshot is
/// derivative and disposable, so the repair is *delete the snapshots* — one
/// line, available to the caller, and correct without this function's help.
/// What the caller cannot get for themselves is the knowledge that the chain
/// diverged, and silently rewriting the file would destroy the only evidence of
/// a bug in composition. A divergence here is not a corrupt database; it is a
/// wrong **cache**, and it means composition has a defect worth finding.
///
/// # Cost
///
/// One fold from genesis over the whole log, plus one composed reconstruction.
/// That is the expensive path by construction — see [`crate::Database::
/// verify_snapshot_chain`] for the handle-level entry point and the note on
/// when to run it.
///
/// [Doctrine VI]: ../../../docs/architecture/s0-s3-foundations.md#doctrine-vi
pub async
/// Check the **newest link** of the snapshot chain (0.15.19, review C-18).
///
/// # What it is for
///
/// [`verify_snapshot_chain`] is right and unaffordable: two folds, one of them
/// from genesis over the whole log. Its own rustdoc calls scheduling it the
/// open problem, and nothing schedules it, so in practice a composition defect
/// is copied forward with nothing looking. This is the cheap half of the same
/// idea — re-derive snapshot *n* from snapshot *n−1* and compare — which costs
/// one anchored delta and can therefore run whenever a snapshot is written.
/// The snapshot cadence does exactly that and logs a divergence.
///
/// `Ok(None)` when there are not two snapshots to compare, which is a young
/// database and not a fault.
///
/// # What it catches, and what it does not
///
/// It catches a defect **as it is introduced**: a snapshot that does not
/// survive its own serialize/load round trip, an `apply_to` that composes
/// differently from how it was composed, or a delta that has stopped covering
/// the window between the two anchors. That last one is the practical case —
/// rows archived out of the hot log between the two writes, with no archive
/// path given here to fold them back in.
///
/// It does **not** catch a defect inherited from further back. If the chain
/// went wrong at link three and every link since has composed faithfully onto
/// it, this agrees at every one of them, because both sides descend from the
/// same wrong state. Only a genesis fold answers that, which is what
/// [`verify_snapshot_chain`] is and why it stays.
///
/// Pass `archive_path` whenever there is an archive. Without it the delta is
/// folded from the hot log alone, and a link spanning an archive session will
/// disagree for a reason that is not a defect.
///
/// # It reports; it does not repair
///
/// [`verify_snapshot_chain`]'s reasoning, unchanged: under [Doctrine VI] a
/// snapshot is derivative, so the repair is *delete the snapshots*, which is
/// one line and the caller's to run. Rewriting the file here would destroy the
/// only evidence that composition has a bug.
///
/// [Doctrine VI]: ../../../docs/architecture/s0-s3-foundations.md#doctrine-vi
pub async
/// The result of a [`verify_snapshot_chain`] cross-check.
///
/// Carries the disagreements rather than a bool, because "the chain diverged" is
/// not actionable and "these three concepts differ, and this edge is present in
/// one and not the other" is. Bounded — see [`ChainCheck::SAMPLE_LIMIT`] — since
/// a chain that went wrong early can disagree about every row, and a report that
/// is the size of the database is one nobody reads.
/// The newest usable snapshot at or before `ts`, or `None` to fold from genesis.
///
/// **Composition used to be disabled once an archive database existed, and as of
/// 0.5.5 it is not.** The reason for the refusal was real: `LOG_ARCHIVABLE`
/// (§5.7) removes superseded rows scattered through the sequence, so a row above
/// the anchor and at or before `ts` could be in cold while a newer row for the
/// same entity — recorded *after* `ts`, invisible to the fold — kept it out of
/// the hot log. The delta missed it and the snapshot answered with a stale
/// value. The fix is the one that note named: the cold log is now in the delta,
/// via [`ANCHORED_COLD_FOLD`], so the archived row is visible again and there is
/// nothing left to refuse.
///
/// Selection loads candidates newest-first and stops at the first whose
/// timestamp is at or before `ts`, so the common case — `reconstruct(now)` —
/// reads exactly one file. A snapshot this build cannot read
/// ([`DbError::SnapshotIncompatible`], D-043) is skipped, not raised: an
/// incompatible snapshot is an ordinary consequence of upgrading, and the whole
/// point of distinguishing it from corruption is that the answer is to carry on
/// without it.
///
/// # It runs on a blocking thread, and a lost one costs speed only (0.13.11, W8.1, D-184)
///
/// The scan is a directory listing plus one or more full
/// [`load_snapshot`](super::snapshot::load_snapshot) calls — decompression and
/// bincode over the whole state, on a worker that has other tasks waiting. The
/// *whole scan* is offloaded rather than each file, because the loop is
/// sequential by construction (it stops at the first usable file) and a hop per
/// candidate would add scheduling to a path whose common case reads exactly one.
///
/// A [`tokio::task::JoinError`] means the loader panicked, and the answer is the
/// same one this function already gives for every other kind of unusable file:
/// `None`, and fold from genesis. That is not leniency, it is what a snapshot
/// *is* — derivative and disposable under [Doctrine VI], so the cost of ignoring
/// one is a slower reconstruction and never a wrong one. It is also a real
/// improvement over the previous arrangement: inline, a panic in the loader
/// unwound through [`reconstruct`] and took the caller's task with it, which
/// meant a single corrupt file could stop a process that had a correct answer
/// available the whole time. W8.4 fuzzes for exactly those panics; this is what
/// happens to the ones it has not found yet.
///
/// [Doctrine VI]: ../../../docs/architecture/s0-s3-foundations.md#doctrine-vi
async
/// The blocking half of [`snapshot_anchor`]: read the directory, load
/// newest-first, stop at the first snapshot at or before `ts`.
/// The two newest snapshots on disk, oldest first (0.15.19, review C-18).
///
/// `None` when there are not two loadable ones with distinct anchors, which is
/// the ordinary state of a young database and not a failure. Unreadable and
/// incompatible files are skipped with a warning, exactly as
/// [`newest_usable_snapshot`] skips them: this is a check, and a check that
/// cannot run should not be the thing that raises.
///
/// Distinct `seq_anchor`s rather than distinct paths, because two files at one
/// anchor describe the same instant and comparing them would test the writer's
/// determinism, not the chain's composition.
async
/// Where the answer for `ts` lives.
///
/// Three cases, not two (0.8.0, B5, D-121). This used to be a `bool`, and the
/// missing third case is the whole of B5: *below the log's floor* was folded in
/// with *the delta is elsewhere*, so a question about a time before the ledger
/// started came back as [`DbError::ReplayCorrupt`] — the class meaning the
/// ledger is damaged — naming an archive file the caller had never created.
/// Whether the hot log alone can answer for `ts` — a *completeness* test.
///
/// **This replaces a reach test that was not one (0.5.5).** The previous version
/// asked `MIN(recorded_at) <= ts`: whether the hot log stretches back far enough
/// to contain `ts`. That is a different question from whether it still contains
/// everything needed to answer at `ts`, and `LOG_ARCHIVABLE` (§5.7) is exactly
/// what pulls the two apart — it removes *superseded* rows, scattered through
/// the sequence rather than forming a prefix. One entity archived and another
/// not is enough: the unarchived one keeps `MIN` pointing before the cutoff
/// while the archived one's winning row is gone, and the fold silently returns a
/// state missing an entity. Measured, not theorised — see
/// `reconstructing_before_the_archive_cutoff_keeps_every_entity`.
///
/// The sound test rests on the one guarantee the archive does make: **the newest
/// row per entity is never archivable**, because archivability requires a later
/// row to exist. So if `ts` is at or after the newest hot stamp, every entity's
/// winning row at `ts` is its newest row overall, and every such row is hot.
/// That covers `reconstruct(now)` — the common case, and the case §5.7 designed
/// `LOG_ARCHIVABLE` around — and nothing else.
///
/// Anything earlier goes to the cold file. That is more ATTACHes than the old
/// rule performed, and the trade is not close: the old rule was cheaper because
/// it was answering a question nobody asked.
///
/// With no archive database in play the reach test *is* the completeness test —
/// nothing has been removed, so the hot log is the whole log — and it is kept,
/// because it is also what distinguishes "before recorded history" from "the
/// cold file is missing" (D-026).
async
/// The verdict the **hot file alone** supports (0.15.4, W14.2, review C-2).
///
/// This is the whole of the reach question minus the one thing an archive path
/// adds, and it is a separate function because two callers need exactly it:
/// [`hot_log_reach`] when no archive file is present, and
/// [`hot_log_answers_for`] on behalf of readers that never had a path to offer.
/// Those two used to answer differently — the first on `MIN(recorded_at)`, the
/// second on intactness alone — and neither answer was the right one.
///
/// # Two cases, and the split is intactness rather than the timestamp
///
/// **Nothing was ever removed.** The hot log is the whole log, so it answers at
/// every instant. Above its floor the fold runs; below it, *nothing had been
/// recorded yet* is not a failure to find the answer, it is the answer
/// ([`HotLogReach::PredatesRecordedHistory`], D-121).
///
/// **Rows were removed.** Only [`reach_with_rows_removed`]'s rule holds, and it
/// is a bound on `ts` from above rather than below. This is the case the old
/// `MIN(recorded_at) <= ts` arm got wrong: it asked whether the hot log
/// *stretches back* far enough, which is the question 0.5.5 already established
/// is not the same as whether it is still *complete*. With no archive path to
/// fall through to, a `reconstruct` on an archived database whose cold file was
/// not passed folded whatever was left and returned it as history — the silent
/// short answer D-189 refused at the two connection-only readers, reachable at
/// the one reader that takes a path and was handed `None`. Pinned by
/// `reconstructing_without_the_archive_path_refuses_rather_than_folding_a_gap`.
async
/// The one rule that survives archiving, in the one place both callers read it.
///
/// `LOG_ARCHIVABLE` requires a later row at the same entity, so **the newest row
/// per entity is never archivable**. If `ts` is at or after the newest stamp
/// still in the hot log, then every entity's winning row at `ts` is its newest
/// row overall, and every such row is hot — the fold is complete without
/// knowing anything about what left. That covers `reconstruct(now)`, the common
/// case and the one §5.7 designed `LOG_ARCHIVABLE` around, and nothing earlier.
///
/// Which is also why the two halves of the question have opposite senses. On an
/// intact log the test is `MIN <= ts`: *does the log reach back to `ts`*. Once
/// rows have gone it is `MAX <= ts`: *is `ts` late enough that nothing missing
/// could matter*. Reading the second as a weaker form of the first is the
/// mistake 0.5.5 corrected once and W14.2 corrected again in the arm 0.5.5 did
/// not reach.
async
/// The rule itself, as a predicate, because two callers now read it and one of
/// them ([`hot_log_reach_within`]'s first arm) is not deciding between the same
/// two verdicts.
///
/// An empty log covers nothing, which is the arm that keeps a fully-archived
/// database from reporting its own emptiness as history.
async
/// The oldest `recorded_at` still in the hot log, or `None` if it is empty.
async
/// The newest `recorded_at` still in the hot log, or `None` if it is empty.
async
/// One aggregate over `transaction_log.recorded_at`, which `idx_txlog_time`
/// serves as an index scan of one row at either end.
async
/// What the caller needs to know when the cold delta cannot be reached —
/// **assembled from the hot file alone** (0.9.0, C4).
///
/// # This is the message the hot-side marker was wanted for
///
/// [D-121](../../docs/architecture/s13-decision-register.md) rejected a hot-side
/// marker recording *archived at* and *horizon*, then left the door open: 0.9.0
/// was to adopt it "only if it wants the richer message". C4 asked for the
/// message and found the marker cannot supply it, because the proposed message —
/// *"this database was archived on X; pass the archive path"* — is **weaker**
/// than what the hot log already carries:
///
/// * *how many rows went* is `MAX(seq_id) - COUNT(*)`, exact for the reason
/// [`hot_log_is_intact`] gives;
/// * *how far back the hot file still reaches* is `MIN(seq_id)` and its
/// `recorded_at` — which is the fact that actually tells a caller whether the
/// archive is worth fetching, and which a marker's archive **timestamp** does
/// not give them;
/// * *that archiving happened at all* is the one bit [`hot_log_is_intact`]
/// already answers.
///
/// The only datum a marker would add is the wall-clock instant of the last
/// archive run, and no branch and no caller needs it. So the marker is refused
/// outright rather than deferred again: under
/// [D-036](../../docs/architecture/s13-decision-register.md) a hot-table addition
/// lands pre-1.0 or not at all, and a table whose whole content is a timestamp
/// used in one error string is not worth a rung.
///
/// # There is no "nothing was archived" case, and that was settled by injection
///
/// This first carried a branch for `removed == 0`, on the reasoning that the
/// `NeedsArchive` arm is reachable without any archiving. That reasoning was
/// **wrong about where the cost lands and right about the branch**, and only a
/// probe told the two apart: replacing the branch body with a panic showed it
/// firing from `a_failed_cold_reconstruct_still_detaches`, a test that raises
/// nothing from here — because the hint was being computed *before* the two
/// arms that use it, on every cold fold. Made lazy, the probe went quiet across
/// all 27 targets.
///
/// So the branch was dead at the use sites: both arms require
/// [`hot_log_is_intact`] to have returned false, or an archive file to have
/// existed when `hot_log_reach` looked and to have gone by the time this did.
/// Rows really were removed in every case that gets here, and the message may
/// say so without qualification. Deleted rather than kept as a defensive
/// fallback, for the reason `delete_guarded` records about
/// `classify_archive_violation`: unreachable code that looks reasonable is
/// harder to remove later than now.
///
/// Best-effort by construction: this runs on the error path, where a second
/// failure must not replace the diagnosis with its own. A query that does not
/// answer yields a hint that says so, and the caller still gets the error it came
/// for.
async
/// Was any row ever removed from `transaction_log`? — answered exactly, from
/// the hot file alone (0.8.0, B5, D-121).
///
/// # Why this question needs answering at all
///
/// With `ts` below the hot log's floor and no archive file present, the state
/// on disk is consistent with two very different histories: **nothing was ever
/// archived**, in which case the hot log is the whole log and the answer to
/// *what was believed at `ts`* is "nothing yet"; or **rows were archived and
/// the cold file is gone**, in which case the answer is unknowable and saying
/// "nothing" would be inventing one. Before this, the two were conflated and
/// both raised — which made an ordinary question about a young database report
/// the ledger as damaged.
///
/// # It was a `COUNT(*)` until 0.15.7, and the count was the whole cost
///
/// The v15 form was `MIN(seq_id) = 1 AND COUNT(*) = MAX(seq_id)`, and the
/// argument for it was a proof rather than a heuristic. `transaction_log.seq_id`
/// is `INTEGER PRIMARY KEY AUTOINCREMENT`, so values are allocated 1, 2, 3, …
/// and **never reused**; a rolled-back transaction leaves no gap, which
/// [D-049](../../docs/architecture/s13-decision-register.md#d-049) established
/// by measurement after assuming the opposite; and `trg_txlog_guard_delete`
/// confines deletion to an archive session. So if nothing was removed the ids
/// are exactly `1..=MAX`, and conversely those two equalities force the `COUNT`
/// distinct ids inside `[1, MAX]` to be all of it. Exact in both directions.
///
/// It was also a scan. `MIN` and `MAX` on the rowid are index seeks and
/// `COUNT(*)` is not, so this cost the whole hot log — **0.134 ms at 2,000 rows
/// and 32.6 ms at 500,000** — on every recorded-time read below the newest
/// surviving stamp, in front of an id-bounded hydration that is flat at 0.14 ms
/// however long the log is (`examples/log_integrity_probe.rs`, review C-5,
/// [D-247], [D-249]). The one-row read is **0.033 ms and does not move with the
/// log**, which is the shape of the change rather than the factor: at 2,000
/// rows it is 4x, at half a million it is 930x, and the difference between
/// those two is the whole finding.
///
/// # So the storage writes it down at the moment it becomes true
///
/// `log_integrity.rows_removed`, maintained by `trg_txlog_mark_gap`, is the
/// same bit as a one-row read. A **trigger** rather than the archive code,
/// because there is no route to deleting a log row that avoids it — §4.2 admits
/// that raw SQL against the file can do what this API refuses, and a bit
/// maintained in Rust would be wrong after exactly that, in the direction that
/// folds a gap silently.
///
/// The proof above did not go away; it moved. It is what the v15 → v16 rung
/// runs, once, to seed a database that may already have been archived, and what
/// `the_bit_agrees_with_the_count_it_replaced` asserts it against.
///
/// # One state changed hands, and it was wrong before
///
/// An **empty** hot log used to answer *intact*, unconditionally: `count == 0`
/// returned `true`. That conflates a young database with a fully archived one,
/// and the second then reported its own emptiness as history — the caller was
/// told nothing had been recorded by `ts` when in truth everything had, and was
/// told it without an error. [`hot_log_reach`] catches that case when it has an
/// archive path to look at; [`hot_log_answers_for`] has none and could not.
/// The bit tells them apart on the log alone, which is what a young database
/// and an emptied one differ by.
///
/// # What it deliberately does not claim
///
/// Nothing about *when* the archiving happened or *what* went, which is what
/// the marker [D-132](../../docs/architecture/s13-decision-register.md#d-132)
/// refused would have carried, and [D-249] does not revisit that refusal — this
/// row answers the guard's own question and holds nothing a message would want.
///
/// [D-247]: ../../docs/architecture/s13-decision-register.md#d-247
/// [D-249]: ../../docs/architecture/s13-decision-register.md#d-249
async
/// Whether a connection alone can fold `transaction_log` at `ts` (W7.1, D-174).
///
/// The completeness question [`hot_log_reach`] answers, minus the archive file
/// it does not have. Both callers take a `Connection`, so when the hot log is
/// short they have nowhere to go and must refuse rather than fold what is left:
/// [`crate::graph::TraversalBuilder::as_of_recorded`] folds for topology, and
/// [`crate::temporal::hydrate_attributes`] folds for the text (0.13.16, W9.1,
/// [D-189](../../docs/architecture/s13-decision-register.md#d-189)). The second
/// was folding without asking, which is what §3.2 was.
///
/// # It ignored `ts` until 0.15.4 (W14.2, review C-2)
///
/// The body was `hot_log_is_intact(conn)` and the parameter was `_ts`: one bit,
/// *was anything ever removed*, with the instant discarded. So the first archive
/// session a deployment ever ran took `AttributeMode::AtTime` and every
/// `as_of_recorded` traversal away from it permanently, for its whole history
/// rather than for the archived part of it — including `as_of_recorded(now)`,
/// which is the instant the archive is *guaranteed* to answer.
///
/// The old comment here justified that as conservative-by-one-bit on the ground
/// that the archive cutoff is not recorded hot-side (D-132's refused marker),
/// and the ground was sound. The conclusion did not follow: the cutoff is not
/// needed. [`reach_with_rows_removed`] decides the same question from the newest
/// surviving stamp, which is hot by construction, and [`hot_log_reach`] had been
/// computing exactly that verdict per timestamp since 0.5.5 two functions away.
/// Both readers now take the three-way verdict and refuse on one arm of it.
///
/// [`HotLogReach::PredatesRecordedHistory`] is an answer, not a refusal: the
/// fold returns the empty state, which is what was believed at an instant before
/// anything was recorded. That is also what the old bit did there, so the arm is
/// unchanged rather than newly permitted.
pub async
/// Run one fold query from nothing — the unanchored path.
async
/// Run one fold query and collect the winning rows, deletions included.
async