prolly-map 0.4.0

Content-addressed versioned map storage primitives.
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
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
# Versioned Map Guide

`VersionedMap` is the application-facing lifecycle layer built on top of a
prolly tree. It combines immutable trees, content-addressed nodes, durable named
roots, and strict transactions into one handle that applications can use as a
normal ordered key/value map with history.

This guide explains the complete `VersionedMap` API, its consistency model,
how it relates to the lower-level `Prolly` engine, and how to use its advanced
features safely in production.

## Contents

- [Introduction]#introduction
- [The correct mental model]#the-correct-mental-model
- [What `VersionedMap` manages]#what-versionedmap-manages
- [Store capabilities]#store-capabilities
- [Opening and initializing a map]#opening-and-initializing-a-map
- [Versions and version identity]#versions-and-version-identity
- [Basic reads]#basic-reads
- [Atomic writes]#atomic-writes
- [Optimistic concurrency and conditional writes]#optimistic-concurrency-and-conditional-writes
- [Pinned snapshots]#pinned-snapshots
- [Historical reads and comparison]#historical-reads-and-comparison
- [Proofs and verification]#proofs-and-verification
- [Three-way merge]#three-way-merge
- [Rollback]#rollback
- [Portability and synchronization]#portability-and-synchronization
- [Large values and blob offload]#large-values-and-blob-offload
- [Retention and version pruning]#retention-and-version-pruning
- [Integrity verification and garbage collection]#integrity-verification-and-garbage-collection
- [Cache pinning and hot-prefix hints]#cache-pinning-and-hot-prefix-hints
- [Ingestion and rebuild workflows]#ingestion-and-rebuild-workflows
- [Atomic multi-map transactions]#atomic-multi-map-transactions
- [Typed maps and schema validation]#typed-maps-and-schema-validation
- [Schema migration]#schema-migration
- [Change subscriptions]#change-subscriptions
- [Async versioned maps]#async-versioned-maps
- [Error handling]#error-handling
- [Consistency and durability guarantees]#consistency-and-durability-guarantees
- [Performance guidance]#performance-guidance
- [Production checklist]#production-checklist
- [Choosing a lower-level or higher-level API]#when-to-use-a-lower-level-or-higher-level-api
- [Related documentation]#related-documentation

## Introduction

The core `Prolly` API is intentionally explicit: an update receives a `Tree`
and returns another immutable `Tree`. That is ideal when an application wants
to manage branches, roots, or repository semantics itself. Most applications,
however, want a simpler abstraction:

- open a named map;
- read its current contents;
- atomically write one or more keys;
- retain immutable historical versions;
- compare, prove, export, merge, or roll back those versions;
- avoid manually coordinating nodes, tree handles, manifests, and transactions.

`VersionedMap` provides exactly that layer.

Add the package to the application. The package is named `prolly-map`, while
the Rust library import is named `prolly`:

```toml
[dependencies]
prolly-map = "0.4"
```

Here is a complete introductory example:

```rust
use prolly::{Config, MemStore, Prolly};

fn main() -> Result<(), prolly::Error> {
    let engine = Prolly::new(MemStore::new(), Config::default());
    let users = engine.versioned_map(b"users");

    // One atomic edit creates the first immutable version and advances head.
    let first = users.edit(|edit| {
        edit.put(b"user/1/name", b"Ada");
        edit.put(b"user/1/status", b"active");
        edit.put(b"user/2/name", b"Grace");
    })?;

    // Convenience updates automatically use a strict transaction and retry
    // optimistic transaction conflicts.
    let second = users.put(b"user/1/name", b"Ada Lovelace")?;

    assert_eq!(
        users.get(b"user/1/name")?,
        Some(b"Ada Lovelace".to_vec())
    );

    // Historical versions remain readable until retention removes them.
    assert_eq!(
        users.get_at(&first.id, b"user/1/name")?,
        Some(b"Ada".to_vec())
    );

    // Versions are content-derived tree states, so they can be compared.
    let changes = users.diff(&first.id, &second.id)?;
    assert_eq!(changes.len(), 1);

    // Rollback moves head to an existing immutable version. It does not erase
    // the newer version or manufacture a duplicate history entry.
    users.rollback_to(&first.id)?;
    assert_eq!(users.get(b"user/1/name")?, Some(b"Ada".to_vec()));

    Ok(())
}
```

The important point is that `users` is not a mutable tree. It is a durable
handle that resolves a mutable **head** to an immutable tree version. Every
successful content change publishes a new immutable version and atomically
moves the head.

## The Correct Mental Model

There are four related concepts:

| Concept | Meaning |
|---|---|
| `Prolly<S>` | The low-level engine operating on immutable `Tree` handles in store `S`. |
| `Tree` | One immutable ordered byte-key/byte-value snapshot. |
| `VersionedMap<'a, S>` | A named lifecycle facade with a current head and immutable version catalog. |
| `MapSnapshot<'a, S>` | A request-scoped view pinned to exactly one immutable map version. |
| `IndexedMap<'a, S>` | A strict coordinator for one source `VersionedMap` and its derived secondary indexes. |

A `VersionedMap` is not automatically a database secondary index. It can hold
an authoritative collection, a secondary index, a materialized view, or any
other ordered key/value state. “Versioned map” describes lifecycle and history;
“database index” describes an application's data model.

When the engine must maintain derived indexes synchronously and prevent writes
from bypassing maintenance, use `IndexedMap` rather than manually coordinating
separate `VersionedMap` heads. See [IndexedMap Secondary Indexes](secondary-index-design.md).

For example:

- `users`: authoritative map from `user_id` to user records;
- `users_by_email`: derived database index from normalized email to `user_id`;
- `active_user_count`: materialized view containing an aggregate.

All three may be `VersionedMap`s. Use a multi-map transaction to keep them
consistent, as described later in this guide.

## What `VersionedMap` Manages

For each application map ID, the facade manages:

1. A mutable named root for the current head.
2. An immutable named root for every cataloged version.
3. Strict transactional publication of new nodes, version root, and head.
4. Content-derived `MapVersionId` values.
5. Retention, verification, backup, synchronization, and maintenance helpers.

Application IDs are encoded before being placed below the reserved
`maps/versioned/` namespace. Arbitrary application bytes therefore cannot
escape the namespace or collide through path-like separators.

You can inspect the generated names when integrating operational tooling:

```rust
use prolly::{Config, MemStore, Prolly};

let engine = Prolly::new(MemStore::new(), Config::default());
let map = engine.versioned_map(b"orders/us-west");

assert_eq!(map.id(), b"orders/us-west");
println!("head root: {:?}", map.head_name());
println!("version prefix: {:?}", map.versions_prefix());
```

Applications should treat these root names as diagnostic information. Normal
reads and writes should use the `VersionedMap` methods.

## Store Capabilities

The API uses Rust trait bounds to expose only operations supported by the
backing store:

| Capability | Required trait |
|---|---|
| Tree node reads | `Store` |
| Head/version root reads | `ManifestStore` |
| Atomic writes | `TransactionalStore` |
| Listing versions and retention | `ManifestStoreScan` |
| Store node GC | `NodeStoreScan` |
| Large values | `BlobStore` |
| Blob GC | `BlobStoreScan` |

`MemStore` and `FileNodeStore` support the common managed-map path. Other
backends must implement the relevant traits for the operations they expose.

## Opening and Initializing a Map

Opening a handle does not create durable data:

```rust
use prolly::{Config, MemStore, Prolly};

let engine = Prolly::new(MemStore::new(), Config::default());
let settings = engine.versioned_map(b"settings");

assert!(!settings.is_initialized()?);
assert!(settings.head()?.is_none());

let empty_version = settings.initialize()?;
assert!(settings.is_initialized()?);
assert_eq!(settings.head_id()?, Some(empty_version.id));

```

`initialize()` publishes an empty tree if the map is absent. If the map already
has a head, it returns that head without creating a new version.

### Durable local example

`MemStore` is useful for tests and transient state. A durable store allows the
same map ID and version catalog to be reopened after process restart:

```rust
use prolly::{Config, FileNodeStore, Prolly};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let path = "./application.prolly";

    {
        let engine = Prolly::new(FileNodeStore::open(path)?, Config::default());
        let settings = engine.versioned_map(b"settings");
        settings.put(b"theme", b"dark")?;
    }

    {
        let engine = Prolly::new(FileNodeStore::open(path)?, Config::default());
        let settings = engine.versioned_map(b"settings");
        assert_eq!(settings.get(b"theme")?, Some(b"dark".to_vec()));
        assert_eq!(settings.versions()?.len(), 1);
    }

    Ok(())
}
```

Use an application-controlled directory and lifecycle policy in production.
Do not have independent processes open a backend concurrently unless that
backend explicitly documents safe multi-process transactions.

## Versions and Version Identity

`MapVersion` contains:

- `id`: a stable `MapVersionId` derived from the complete tree manifest;
- `tree`: the immutable `Tree` handle;
- `created_at_millis`: catalog publication time when available;
- `is_head`: whether this catalog entry was head when loaded.

Version IDs identify **content states**, not write events. Repeating an
idempotent write does not create a distinct logical version. Rolling back to a
previous state reuses that state's ID.

Consequences:

- version IDs are suitable for optimistic concurrency tokens;
- equal map contents under the same tree configuration produce equal IDs;
- IDs do not encode author, message, parents, branch, or commit ancestry;
- a full Git-like repository belongs in a higher-level VCS layer.

List cataloged versions newest first:

```rust
let versions = settings.versions()?;
for version in versions {
    println!(
        "{} created={:?} head={}",
        version.id, version.created_at_millis, version.is_head
    );
}

```

## Basic Reads

Current-head convenience reads resolve the head once for each operation:

```rust
let value = settings.get(b"theme")?;
let exists = settings.contains_key(b"theme")?;

let values = settings.get_many(&[
    b"theme".as_slice(),
    b"locale".as_slice(),
    b"missing".as_slice(),
])?;

assert_eq!(exists, value.is_some());
assert_eq!(values.len(), 3);

```

`get_many` preserves input order, duplicate positions, and missing values. It
also allows a backend to use efficient batched node reads.

Range and prefix methods return lazy iterators:

```rust
let rows = settings
    .range(b"feature/", Some(b"feature0"))?
    .collect::<Result<Vec<_>, _>>()?;

let flags = settings
    .prefix(b"feature/")?
    .collect::<Result<Vec<_>, _>>()?;

assert_eq!(rows, flags);

```

Ranges are half-open: `[start, end)`. Prefix scans compute the correct byte
upper bound automatically.

## Atomic Writes

### Single-key writes

`put` and `delete` are convenience methods:

```rust
let written = settings.put(b"theme", b"dark")?;
assert_eq!(settings.get(b"theme")?, Some(b"dark".to_vec()));

let deleted = settings.delete(b"theme")?;
assert_ne!(written.id, deleted.id);
assert_eq!(settings.get(b"theme")?, None);

```

They automatically retry optimistic transaction conflicts up to the managed
map retry limit. Independent concurrent updates are therefore normally merged
by retrying the logical mutation batch against the latest head.

### Multi-key edits

Use `edit` to publish related mutations as one version:

```rust
let version = settings.edit(|edit| {
    edit.put(b"theme", b"dark");
    edit.put(b"locale", b"en-CA");
    edit.delete(b"deprecated-option");
})?;

println!("published {}", version.id);

```

The closure only collects mutations. Publication occurs after it returns. If
the transaction fails, none of the new head, immutable version root, or node
writes become visible.

Use `apply(Vec<Mutation>)` when mutations are already represented in the engine
type:

```rust
use prolly::Mutation;

let version = settings.apply(vec![
    Mutation::Upsert {
        key: b"theme".to_vec(),
        val: b"light".to_vec(),
    },
    Mutation::Delete {
        key: b"old-key".to_vec(),
    },
])?;

println!("published {}", version.id);

```

## Optimistic Concurrency and Conditional Writes

Automatic retry is useful when independent logical mutations can be replayed
on a newer head. Some application operations must instead reject a stale
caller. Use `put_if`, `delete_if`, `edit_if`, or `apply_if` for compare-and-swap
semantics.

```rust
use prolly::VersionedMapUpdate;

let expected = settings.head_id()?;

// Another request could update the map here.
let update = settings.put_if(expected.as_ref(), b"theme", b"solarized")?;

match update {
    VersionedMapUpdate::Applied { previous, current } => {
        println!("advanced {:?} -> {}", previous, current.id);
    }
    VersionedMapUpdate::Unchanged { current } => {
        println!("the requested content was already present: {:?}", current);
    }
    VersionedMapUpdate::Conflict { current } => {
        println!("stale caller; current head is {:?}", current);
    }
}

```

Passing `None` as the expected version means “the map must be uninitialized.”
This is useful for create-if-absent workflows.

`VersionedMapUpdate` also provides:

- `current()` to borrow the resulting or observed current version;
- `is_applied()` to test successful advancement;
- `is_conflict()` to test stale-head rejection.

## Pinned Snapshots

Calling several convenience reads independently permits head to advance between
those calls. For request-level consistency, pin a `MapSnapshot` once:

```rust
let snapshot = settings
    .snapshot()?
    .expect("settings must be initialized");

let version_id = snapshot.id().clone();
let theme = snapshot.get(b"theme")?;
let locale = snapshot.get(b"locale")?;

// Even if another writer advances settings here, both values above and every
// later read through snapshot use version_id.
println!("read {:?} and {:?} at {}", theme, locale, version_id);

```

Pin a historical version with `snapshot_at(&version_id)`.

### Snapshot point and boundary queries

Snapshots expose:

- `get`, `contains_key`, and `get_many`;
- `first_entry` and `last_entry`;
- `lower_bound`, returning the first key greater than or equal to a key;
- `upper_bound`, returning the first key strictly greater than a key;
- `cursor_window`, returning seek position plus a bounded forward page.

```rust
let first = snapshot.first_entry()?;
let last = snapshot.last_entry()?;
let lower = snapshot.lower_bound(b"feature/b")?;
let upper = snapshot.upper_bound(b"feature/b")?;
let window = snapshot.cursor_window(b"feature/b", None, 25)?;

println!("first={first:?} last={last:?}");
println!("lower={lower:?} upper={upper:?}");
println!("seek found={} entries={}", window.found, window.entries.len());

```

### Forward pagination

`RangeCursor` resumes strictly after its stored key:

```rust
use prolly::RangeCursor;

let mut cursor = RangeCursor::start();
loop {
    let page = snapshot.prefix_page(b"feature/", &cursor, 100)?;
    for (key, value) in &page.entries {
        println!("{:?} = {:?}", key, value);
    }

    match page.next_cursor {
        Some(next) => cursor = next,
        None => break,
    }
}

```

Because the snapshot is immutable, a cursor sequence cannot skip or duplicate
entries due to concurrent writes. Persist both the cursor and version ID if a
scan must resume across process or request boundaries.

### Reverse pagination and lazy reverse scans

```rust
use prolly::ReverseCursor;

let page = snapshot.prefix_reverse_page(
    b"feature/",
    &ReverseCursor::end(),
    100,
)?;

for (key, value) in page.entries {
    println!("descending: {:?} = {:?}", key, value);
}

// The lazy iterator fetches bounded reverse pages internally.
for entry in snapshot.prefix_reverse_scan(b"feature/", 128) {
    let (key, value) = entry?;
    println!("descending: {:?} = {:?}", key, value);
}

```

`reverse_scan(start, page_size)` scans `[start, +inf)` in descending order.
`prefix_reverse_scan(prefix, page_size)` limits the scan to one prefix.

### Streaming large ranges

`range`, `prefix`, `stream_range`, and `stream_prefix` are lazy. They do not
materialize the full result unless the caller collects it.

```rust
let mut processed = 0usize;
for entry in snapshot.stream_prefix(b"event/")? {
    let (key, value) = entry?;
    process_record(&key, &value);
    processed += 1;
}
println!("processed {processed} records");

```

### Statistics and debug views

```rust
let stats = snapshot.stats()?;
println!("entries: {}", stats.total_key_value_pairs);
println!("serialized bytes: {}", stats.total_tree_size_bytes);

let debug = snapshot.debug_view()?;
println!("tree levels: {}", debug.levels.len());

```

Statistics are suitable for monitoring growth and tuning. Debug views expose
tree structure and should primarily be used for diagnostics, inspection tools,
and tests rather than application behavior.

## Historical Reads and Comparison

For a single historical operation, use `get_at`, `get_many_at`, `range_at`,
`prefix_at`, `range_page_at`, or `prefix_page_at`.

For several repeatable operations, pin snapshots or create a `MapComparison`:

```rust
let old = settings.versions()?.last().unwrap().id.clone();
let current = settings.head_id()?.unwrap();
let comparison = settings.compare(&old, &current)?;

let diffs = comparison.diff()?;
let growth = comparison.stats()?;
let structure = comparison.debug_view()?;

println!("logical changes: {}", diffs.len());
println!("stats comparison: {growth:?}");
println!("structural comparison: {structure:?}");

```

`compare_to_head(&base)` pins a historical base and the current head in one
comparison handle.

### Streaming diff

```rust
for diff in comparison.stream_diff()? {
    match diff? {
        prolly::Diff::Added { key, val } => {
            println!("added {:?} = {:?}", key, val);
        }
        prolly::Diff::Removed { key, val } => {
            println!("removed {:?}, old {:?}", key, val);
        }
        prolly::Diff::Changed { key, old, new } => {
            println!("changed {:?}: {:?} -> {:?}", key, old, new);
        }
    }
}

```

The engine skips structurally equal subtrees. Streaming avoids collecting every
logical change before processing begins.

### Resumable and structural diff pages

Use `diff_page` when a durable key cursor is sufficient:

```rust
use prolly::RangeCursor;

let page = comparison.diff_page(&RangeCursor::start(), None, 500)?;
println!("changes in page: {}", page.diffs.len());

```

Use `structural_diff_page` when work should resume from the exact CID traversal
frontier. Its cursor preserves structural traversal state and is tied to the
specific base and target roots.

### Changed-span hints

Changed-span hints are correctness-optional acceleration metadata. A producer
can publish normalized key spans for an indexer or synchronization job, and a
consumer can load them later:

```rust
use prolly::ChangedSpan;

comparison.publish_changed_spans([
    ChangedSpan::new(b"user/100".to_vec(), Some(b"user/200".to_vec())),
])?;

if let Some(hint) = comparison.changed_spans()? {
    println!("hinted spans: {}", hint.spans.len());
}

```

Hints must never replace proof, diff, or tree traversal for correctness. Missing
or malformed hints are ignored by the engine.

## Proofs and Verification

Prolly tree roots authenticate their reachable node graph. A snapshot can
produce self-contained proofs that a verifier checks without opening the
original store.

### Key proofs

```rust
let proof = snapshot.prove_key(b"theme")?;
let verification = proof.verify();

assert!(verification.valid);
println!("verified value: {:?}", verification.value);

let multi = snapshot.prove_keys(&[
    b"theme".as_slice(),
    b"missing".as_slice(),
])?;
assert!(multi.verify().all_valid());

```

Absence proofs are supported. A valid proof can show that a requested key does
not exist at the authenticated root.

### Complete range and prefix proofs

```rust
let range_proof = snapshot.prove_range(b"feature/", Some(b"feature0"))?;
let range_verification = range_proof.verify();
assert!(range_verification.valid);

let prefix_proof = snapshot.prove_prefix(b"feature/")?;
assert!(prefix_proof.verify().valid);

```

These proofs authenticate completeness, not only the returned entries. The
verifier checks that no matching entry was omitted from the requested range.

### Cursor-page and diff-page proofs

```rust
use prolly::RangeCursor;

let proved_page = snapshot.prove_range_page(
    &RangeCursor::start(),
    None,
    100,
)?;
assert!(proved_page.proof.verify().valid);

let proved_diff = comparison.prove_diff_page(
    &RangeCursor::start(),
    None,
    100,
)?;
assert!(proved_diff.proof.verify().valid);

```

### Authenticated proof envelopes

A Merkle proof authenticates data against a root, but a recipient may also need
to authenticate who issued that root and bind it to a domain, validity window,
or nonce. `ProofAuthentication` builds an HMAC-SHA256 envelope:

```rust
use prolly::{
    verify_authenticated_proof_bundle,
    ProofAuthentication,
};

let proof = snapshot.prove_key(b"theme")?;
let envelope = snapshot.authenticate_proof_bundle(
    proof.to_bundle_bytes()?,
    b"shared-secret",
    ProofAuthentication::new(b"key-2026-07")
        .with_context(b"settings-api/v1")
        .with_validity(Some(1_700_000_000_000), Some(1_700_000_060_000))
        .with_nonce(b"request-42"),
)?;

let verified = verify_authenticated_proof_bundle(
    &envelope.to_bytes()?,
    b"shared-secret",
    Some(1_700_000_030_000),
)?;
assert!(verified.valid);

```

Use separate secrets and contexts for separate trust domains. A nonce only
prevents replay when the application records or otherwise validates nonce use.

## Three-Way Merge

`prepare_merge(base, candidate)` pins three immutable versions:

- `base`: the common application-selected ancestor;
- `head`: the current map head at preparation time;
- `candidate`: the version whose changes should be incorporated.

The map catalog is linear and does not calculate ancestry or a merge base. The
application must select `base` from its own workflow metadata.

```rust
let merge = settings.prepare_merge(&base_id, &candidate_id)?;

println!("base: {}", merge.base().id);
println!("pinned head: {}", merge.head().id);
println!("candidate: {}", merge.candidate().id);

let conflicts = merge
    .stream_conflicts()?
    .collect::<Result<Vec<_>, _>>()?;
println!("conflicts: {}", conflicts.len());

let update = merge.publish(None)?;
if update.is_conflict() {
    // Head changed after prepare_merge. Re-read state and prepare again.
}

```

`publish` performs a three-way merge and moves head only if the pinned head is
still current. The compare-and-swap prevents a merge prepared against stale
state from overwriting a concurrent writer.

### Merge policy registry

Use a `MergePolicyRegistry` when different keyspaces need different conflict
rules:

```rust
use prolly::{MergePolicyRegistry, Resolution};

let policies = MergePolicyRegistry::with_default(|conflict| {
    conflict
        .right
        .clone()
        .map(Resolution::value)
        .unwrap_or_else(Resolution::delete)
});

let update = merge.publish_with_policy(&policies)?;
assert!(!update.is_conflict());

```

The registry also supports exact-key, prefix, and pattern rules. Conflict
callbacks should be deterministic and side-effect free.

### CRDT merge

`crdt_merge` and `publish_crdt` use `CrdtConfig` for conflict-free strategies
such as last-writer-wins, multi-value preservation, delete policies, and custom
resolution:

```rust
use prolly::CrdtConfig;

let config = CrdtConfig::default();
let merged_tree = merge.crdt_merge(&config)?;
println!("candidate merged root: {:?}", merged_tree.root);

let update = merge.publish_crdt(&config)?;
assert!(!update.is_conflict());

```

CRDT strategy avoids unresolved logical conflicts; CAS can still report a
publication conflict if head moved after the merge was prepared.

## Rollback

Rollback moves head to an existing cataloged version:

```rust
let target = settings.versions()?.last().unwrap().id.clone();
let restored = settings.rollback_to(&target)?;
assert_eq!(restored.id, target);

```

Rollback does not:

- delete versions newer than the target;
- create a duplicate version for the same tree state;
- record parentage, a revert commit, author, or reason.

If audit semantics require a recorded revert event, store that metadata in an
application log or use a repository layer.

## Portability and Synchronization

### Export and import one version

Every pinned snapshot can be exported as a self-contained `SnapshotBundle`:

```rust
let bundle = snapshot.export()?;
let verification = bundle.verify()?;
assert!(verification.valid);

let destination_engine = Prolly::new(MemStore::new(), Config::default());
let destination = destination_engine.versioned_map(b"settings-copy");
let imported = destination.import_as_head(&bundle)?;

assert_eq!(imported.id, *snapshot.id());

```

Import verifies that the bundle is complete and uses the same tree
configuration as the destination engine. It imports nodes, catalogs the
version, and atomically makes it head.

### Missing-node planning and copying

For content-addressed synchronization, avoid sending nodes already present at
the destination:

```rust
let destination_store = MemStore::new();

let plan = snapshot.plan_missing_nodes(&destination_store)?;
println!("missing nodes: {}", plan.missing_nodes);

let copied = snapshot.copy_missing_nodes(&destination_store)?;
println!("copied nodes: {}", copied.copied_nodes);

```

Node copying alone does not publish a destination head. Use `push_to` when the
destination is another managed map:

```rust
let destination_engine = Prolly::new(MemStore::new(), Config::default());
let destination = destination_engine.versioned_map(b"settings-replica");

let pushed = snapshot.push_to(&destination)?;
assert_eq!(pushed.id, *snapshot.id());

```

### Full catalog backup and restore

`backup()` captures every cataloged version, original timestamps, and the head
selection:

```rust
let backup = settings.backup()?;
backup.verify()?;

let bytes = backup.to_bytes()?;
let decoded = prolly::VersionedMapBackup::from_bytes(&bytes)?;

let restore_engine = Prolly::new(MemStore::new(), Config::default());
let restore_map = restore_engine.versioned_map(b"settings");
let restored_head = restore_map.restore_backup(&decoded)?;

assert_eq!(restored_head.id, decoded.head);

```

Restore requires:

- a fully verified backup;
- the same application map ID;
- a compatible tree configuration;
- an uninitialized destination map.

The encoded backup format is versioned deterministic CBOR. Verification checks
bundle completeness, unique version IDs, tree/ID agreement, and head presence.

## Large Values and Blob Offload

Prolly tree leaf values are byte vectors. Large payloads can instead be stored
in a content-addressed blob store while the tree stores a compact `ValueRef`.

```rust
use prolly::{LargeValueConfig, MemBlobStore};

let blobs = MemBlobStore::new();
let policy = LargeValueConfig::new(4 * 1024);

let version = settings.put_large_value(
    &blobs,
    b"document/1/body",
    vec![b'x'; 64 * 1024],
    policy,
)?;

let body = settings.get_large_value(&blobs, b"document/1/body")?;
assert_eq!(body.unwrap().len(), 64 * 1024);
println!("published {}", version.id);

```

Values at or below the inline threshold remain inline. Larger values are
deduplicated in the blob store by content and represented by a blob reference.

On a snapshot, `get_value_ref` inspects whether a value is inline or blob-backed
without resolving the blob. `get_large_value` resolves either representation.

Conditional blob-aware publication is available through
`put_large_value_if`.

## Retention and Version Pruning

Catalog roots keep historical tree nodes reachable. Bound catalog growth with
one of the retention helpers:

```rust
use std::time::Duration;

// Keep the newest ten versions, plus head even if head is an older rollback.
let result = settings.keep_last(10)?;
println!("removed {} versions", result.removed_count());

// Keep versions from the last seven days, plus head.
let result = settings.keep_for(Duration::from_secs(7 * 24 * 60 * 60))?;

// Keep an explicit set, plus head. Unknown IDs are rejected.
if let Some(head) = settings.head_id()? {
    let result = settings.keep_versions([&head])?;
    assert!(result.retained.contains(&head));
}

```

`prune_versions(n)` is an alias for keeping the newest `n` versions plus head.
Pruning removes immutable **root names**, not node bytes. This separation makes
pruning fast and transactional. Run GC afterward to reclaim content no longer
reachable from any retained named root.

The current head is always protected, including after rollback to an older
version.

## Integrity Verification and Garbage Collection

### Catalog verification

```rust
let report = settings.verify_catalog()?;
println!("head: {}", report.head);
println!("versions: {}", report.version_count);
println!("reachable nodes: {}", report.reachable_nodes);
println!("reachable bytes: {}", report.reachable_bytes);

```

`verify_catalog` checks head membership, version IDs, manifests, and every
reachable node. Missing or corrupt content produces an error rather than a
partial success report.

### Node GC

```rust
let plan = settings.plan_gc()?;
println!("reclaimable nodes: {}", plan.reclaimable_nodes);

// Run only after inspecting or accepting the plan.
let sweep = settings.sweep_gc()?;
println!("deleted nodes: {}", sweep.deleted_nodes);

```

Although invoked through one map, node GC must use a store-wide safety
boundary. Content-addressed nodes can be shared between versions and maps.
`plan_gc` and `sweep_gc` therefore retain **every remaining named root** in the
store. They will not treat nodes used by another managed map, branch, tag, or
custom named root as garbage.

Do not call a store-wide sweep using only one map's `retention_policy()` when
other roots share that node store. The prefix policy is useful for selecting or
inspecting the map catalog; it is not by itself a safe global GC root set.

### Blob GC

```rust
let plan = settings.plan_blob_gc(&blobs)?;
println!("reclaimable blobs: {}", plan.reclaimable_blob_count);

let sweep = settings.sweep_blob_gc(&blobs)?;
println!("deleted blobs: {}", sweep.deleted_blobs);

```

Blob GC likewise retains blob references reachable from every remaining named
root because multiple maps may share one blob store.

## Cache Pinning and Hot-Prefix Hints

Long-running requests or latency-sensitive keyspaces can warm and pin relevant
paths:

```rust
let pinned_nodes = snapshot.pin_root()?;
println!("pinned root nodes: {pinned_nodes}");

let pinned_path = snapshot.pin_path(b"user/42")?;
println!("pinned path nodes: {pinned_path}");

snapshot.publish_prefix_hint(b"user/")?;
snapshot.hydrate_prefix_hint(b"user/")?;

```

Cache hints affect performance, not correctness. Pinned nodes may temporarily
exceed configured cache limits. Use bounded, measured pinning rather than
pinning every historical tree.

## Ingestion and Rebuild Workflows

### Sorted bulk initialization

When creating a large map from sorted input, avoid repeated point updates:

```rust
let engine = Prolly::new(MemStore::new(), Config::default());
let catalog = engine.versioned_map(b"catalog");

let update = catalog.initialize_sorted([
    (b"item/0001".to_vec(), b"keyboard".to_vec()),
    (b"item/0002".to_vec(), b"monitor".to_vec()),
    (b"item/0003".to_vec(), b"mouse".to_vec()),
])?;
assert!(update.is_applied());

```

Input must be in byte-lexicographic order. An out-of-order key returns
`Error::UnsortedInput` rather than creating an invalid tree.

### Append-optimized edits

For monotonically increasing keys such as event IDs or timestamps:

```rust
use prolly::Mutation;

let version = catalog.append(vec![
    Mutation::Upsert {
        key: b"item/0004".to_vec(),
        val: b"speaker".to_vec(),
    },
    Mutation::Upsert {
        key: b"item/0005".to_vec(),
        val: b"webcam".to_vec(),
    },
])?;
println!("append version: {}", version.id);

```

The engine uses its right-edge fast path when the mutation shape permits it and
falls back safely when it does not.

### Parallel batch updates

```rust
use prolly::{Mutation, ParallelConfig};

let result = catalog.parallel_apply(
    vec![
        Mutation::Upsert {
            key: b"item/0100".to_vec(),
            val: b"desk".to_vec(),
        },
        Mutation::Delete {
            key: b"item/0001".to_vec(),
        },
    ],
    &ParallelConfig::default(),
)?;

println!("version: {}", result.version.id);
println!("batch stats: {:?}", result.stats);

```

### Build first, then atomically replace

Rebuild helpers construct the complete candidate tree before moving head:

```rust
let expected = catalog.head_id()?;

let update = catalog.rebuild_sorted_if(
    expected.as_ref(),
    [
        (b"item/a".to_vec(), b"new-a".to_vec()),
        (b"item/b".to_vec(), b"new-b".to_vec()),
    ],
)?;

if update.is_conflict() {
    // The old map remains current. Decide whether to rebuild against the new
    // head or discard the completed candidate.
}

```

`rebuild_from_iter_if` accepts unsorted iterator input and uses the general
batch builder. Both methods CAS-publish only after the candidate tree is fully
built, so readers never observe a half-built replacement.

## Atomic Multi-Map Transactions

Real database indexes must change atomically with their source data. Use
`Prolly::versioned_maps_transaction` to update any number of managed maps in one
strict backend transaction:

```rust
let engine = Prolly::new(MemStore::new(), Config::default());

engine.versioned_maps_transaction(|maps| {
    // Authoritative record.
    maps.put(b"users", b"user/1/name", b"Ada")?;
    maps.put(b"users", b"user/1/email", b"ada@example.com")?;

    // Derived database index: normalized email -> user ID.
    maps.put(
        b"users_by_email",
        b"ada@example.com",
        b"user/1",
    )?;

    // Materialized view.
    maps.put(
        b"user_counts",
        b"total",
        1_u64.to_be_bytes(),
    )?;

    Ok(())
})?;

let users = engine.versioned_map(b"users");
let by_email = engine.versioned_map(b"users_by_email");

assert_eq!(users.get(b"user/1/name")?, Some(b"Ada".to_vec()));
assert_eq!(
    by_email.get(b"ada@example.com")?,
    Some(b"user/1".to_vec())
);

```

The transaction validates and commits all original heads, new tree nodes,
immutable version roots, and head movements together. If the closure returns an
error or the transaction conflicts, none of the participating maps advance.

Within the closure, `VersionedMapsTransaction` supports `head`, `get`, `apply`,
`apply_if`, `put`, `delete`, and `edit`. Reads observe writes already staged by
the same transaction.

This is the recommended path for strict secondary indexes. Diff-driven
asynchronous indexers remain useful when eventual consistency is acceptable or
the derived engine cannot participate in the same transaction.

## Typed Maps and Schema Validation

The core map stores byte keys and values. `TypedVersionedMap<K, V, KC, VC>`
keeps application encoding policy near the map.

`StringKeyCodec` maps Rust strings to UTF-8 bytes. `BytesKeyCodec` is the
identity codec for `Vec<u8>`. Applications can implement `KeyCodec<K>` for
order-preserving numeric, tuple, or domain-specific keys.

```rust
use prolly::{
    Config, MemStore, Prolly, StringKeyCodec, VersionedJsonCodec,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct User {
    display_name: String,
    active: bool,
}

let engine = Prolly::new(MemStore::new(), Config::default());
let raw = engine.versioned_map(b"typed-users");
let users = raw.typed::<String, User, _, _>(
    StringKeyCodec,
    VersionedJsonCodec::new("app.User", 2),
);

users.put(
    &"user/1".to_string(),
    &User {
        display_name: "Ada".to_string(),
        active: true,
    },
)?;

let user = users.get(&"user/1".to_string())?.unwrap();
assert_eq!(user.display_name, "Ada");

let all = users.entries()?;
assert_eq!(all.len(), 1);

```

`VersionedJsonCodec` and `VersionedCborCodec` wrap each value with schema name,
schema version, encoding, and payload. Decode rejects a different schema or
version before deserializing the application type. Plain `JsonCodec` and
`CborCodec` are available when an envelope is unnecessary.

The typed facade deliberately exposes `raw()` for advanced snapshot, proof,
merge, backup, and maintenance operations.

### Custom key codecs

A `KeyCodec` must preserve the ordering expected by the application:

```rust
use prolly::{Error, KeyCodec};

#[derive(Clone, Copy)]
struct U64KeyCodec;

impl KeyCodec<u64> for U64KeyCodec {
    fn encode_key(&self, key: &u64) -> Result<Vec<u8>, Error> {
        Ok(key.to_be_bytes().to_vec())
    }

    fn decode_key(&self, bytes: &[u8]) -> Result<u64, Error> {
        let array: [u8; 8] = bytes.try_into().map_err(|_| {
            Error::Deserialize("u64 key must contain eight bytes".to_string())
        })?;
        Ok(u64::from_be_bytes(array))
    }
}
```

Big-endian unsigned integers preserve numeric order under byte-lexicographic
comparison. Signed integers and compound keys require an order-preserving
encoding rather than a naive platform representation.

## Schema Migration

`migrate_from` reads one explicitly pinned source version with an old codec,
transforms every value, encodes it with the new codec, and CAS-publishes the
result only if the source version is still head.

```rust
use prolly::{StringKeyCodec, VersionedJsonCodec};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct UserV1 {
    display_name: String,
}

#[derive(Serialize, Deserialize)]
struct UserV2 {
    display_name: String,
    active: bool,
}

let source_codec = VersionedJsonCodec::new("app.User", 1);
let source = raw.typed::<String, UserV1, _, _>(
    StringKeyCodec,
    source_codec.clone(),
);
let source_head = source.raw().head_id()?.unwrap();

let target = raw.typed::<String, UserV2, _, _>(
    StringKeyCodec,
    VersionedJsonCodec::new("app.User", 2),
);

let result = target.migrate_from::<UserV1, _>(
    &source_head,
    &source_codec,
    |old| {
        Ok(UserV2 {
            display_name: old.display_name,
            active: true,
        })
    },
)?;

println!("scanned: {}", result.scanned_values);
println!("rewritten: {}", result.rewritten_values);

if result.update.is_conflict() {
    // A writer changed head while migration was running. No partial migrated
    // head was published; restart or reconcile explicitly.
}

```

The migration is whole-map and atomic at publication. For very large datasets,
build a new map or external checkpointed pipeline, validate it, and switch
consumers with an application-level pointer or coordinated transaction.

## Change Subscriptions

Subscriptions are resumable, explicit polling handles. They do not create a
background thread and do not require a runtime.

### Observe future changes

```rust
let mut subscription = settings.subscribe()?;

// No change since subscription began.
assert!(subscription.poll()?.is_none());

settings.put(b"theme", b"dark")?;

if let Some(event) = subscription.poll()? {
    println!("previous: {:?}", event.previous);
    println!("current: {}", event.current.id);
    println!("diff count: {}", event.diffs.len());
}

```

`subscribe()` begins at the current head and emits only later transitions.
`subscribe_from(Some(version_id))` resumes from a persisted checkpoint. Passing
`None` compares the first observed head with an empty tree.

Polling compares the last observed version directly with current head. It may
coalesce several intermediate head movements into one logical diff. Use the
version catalog or an application event log when every transition must be
processed individually.

If the resume version was pruned, polling returns an error instead of silently
calculating a different change set.

## Async Versioned Maps

Enable the `async-store` feature for remote, browser, and async-native stores:

```toml
[dependencies]
prolly-map = { version = "0.4", features = ["async-store"] }
```

The Rust import name remains `prolly`.

The following example adapts a synchronous memory store for demonstration:

```rust
use std::sync::Arc;
use prolly::{AsyncProlly, Config, MemStore, RangeCursor, SyncStoreAsAsync};

let store = Arc::new(MemStore::new());
let engine = AsyncProlly::new(
    SyncStoreAsAsync::new(store),
    Config::default(),
);
let users = engine.versioned_map(b"users");

let first = users.put(b"user/1", b"Ada").await?;
users.edit(|edit| {
    edit.put(b"user/2", b"Grace");
    edit.put(b"user/3", b"Margaret");
}).await?;

let historical = users.snapshot_at(&first.id).await?.unwrap();
assert_eq!(historical.get(b"user/2").await?, None);

let snapshot = users.snapshot().await?.unwrap();
let page = snapshot
    .prefix_page(b"user/", &RangeCursor::start(), 100)
    .await?;
assert_eq!(page.entries.len(), 3);

let proof = snapshot.prove_key(b"user/1").await?;
assert!(proof.verify().valid);
```

`AsyncVersionedMap` supports current and historical heads, pinned snapshots,
point reads, range/prefix streams, cursor pages, statistics, key/multi-key/range
proofs, atomic mutation batches, convenience put/delete/edit, and change
subscriptions.

Async subscription polling mirrors the synchronous API:

```rust
let mut subscription = users.subscribe().await?;
if let Some(event) = subscription.poll().await? {
    println!("new async head: {}", event.current.id);
}
```

## Error Handling

All managed-map methods return the crate's `Error` type. Important failure
categories include:

- store or manifest I/O errors;
- transaction conflicts after retry exhaustion;
- explicit `VersionedMapUpdate::Conflict` from conditional operations;
- unknown, pruned, or mismatched version IDs;
- invalid or incomplete snapshot/backup data;
- tree configuration mismatch during import or restore;
- schema/version mismatch during typed decode;
- missing blob content;
- corrupt or missing reachable nodes;
- unsorted input supplied to a sorted builder.

Treat conditional conflicts differently from infrastructure failures:

```rust
let expected = settings.head_id()?;
let update = settings.put_if(expected.as_ref(), b"theme", b"dark")?;

if update.is_conflict() {
    // Normal application concurrency outcome: return HTTP 409, show a merge
    // UI, reload, or retry only if the business operation is replay-safe.
} else {
    // Applied or already unchanged.
}

```

Do not blindly retry a stale conditional operation. The caller selected an
expected version precisely so it could decide how to reconcile newer state.

## Consistency and Durability Guarantees

For a store implementing the required strict transaction traits, a successful
content-changing managed-map publication makes the following visible together:

1. All new content-addressed tree nodes.
2. The immutable version root.
3. The new mutable head root.

Readers therefore observe either the previous head or the complete new head,
never a partially published tree.

Additional guarantees:

- old tree versions are immutable;
- head reads are atomic at the manifest level;
- pinned snapshots remain on one version while head moves;
- conditional publication validates expected head;
- merge and rebuild publication use CAS;
- backup/import verification validates content completeness and identity.

Durability after process or machine failure depends on the selected store's
own persistence guarantees. `MemStore` is process-local. Use a durable backend
for durable application state.

## Performance Guidance

Choose the narrowest API that matches the workload:

| Workload | Recommended API |
|---|---|
| One independent write | `put` or `delete` |
| Related writes in one map | `edit` or `apply` |
| Caller-supplied concurrency token | `put_if`, `edit_if`, or `apply_if` |
| Consistent request reads | `snapshot` |
| Long scan | `stream_range` or `stream_prefix` |
| User-facing pages | pinned `range_page` or `prefix_page` |
| New sorted dataset | `initialize_sorted` |
| Monotonic event ingestion | `append` |
| Large mutation set | `parallel_apply` |
| Offline full replacement | `rebuild_sorted_if` or `rebuild_from_iter_if` |
| Source plus strict derived indexes | `versioned_maps_transaction` |
| Eventual derived index | subscription/diff worker with a persisted version ID |
| Large payloads | blob-aware put/get |

Avoid repeatedly calling current-head range helpers during one logical page
sequence. Pin a snapshot so concurrency cannot change the dataset between
pages.

Avoid retaining unlimited versions by accident. Monitor catalog count and
reachable bytes, establish a retention policy, verify, then run store-safe GC.

## Production Checklist

Before shipping a `VersionedMap` integration:

1. Define byte-order-preserving key formats and prefix boundaries.
2. Decide whether values are raw, JSON/CBOR, schema-versioned, or blob-backed.
3. Choose which maps are authoritative and which are derived database indexes.
4. Use multi-map transactions for derived state requiring strict consistency.
5. Use conditional writes for business operations that must detect stale data.
6. Pin snapshots for multi-read requests, scans, proofs, and pagination.
7. Persist both version ID and cursor for resumable work.
8. Set version retention intentionally and always protect required audit roots.
9. Run `verify_catalog` before destructive maintenance or after restore.
10. Use map-triggered GC helpers so all named roots in a shared store survive.
11. Back up and verify the complete catalog, not only the current head, when
    historical recovery matters.
12. Test store reopen, transaction conflicts, missing blobs, pruned resume
    versions, migration conflicts, and corrupted import data.
13. Record application audit metadata separately if authorship, messages,
    parentage, or every transition matters.

## When to Use a Lower-Level or Higher-Level API

Use raw `Prolly` and `Tree` handles when:

- the application deliberately manages many temporary branches;
- roots are ephemeral or managed in another transactional system;
- custom publication semantics are required;
- the application is implementing another storage abstraction.

Use `VersionedMap` when:

- one named map has a current authoritative head;
- linear version history and rollback are sufficient;
- application code should not manually coordinate manifests and nodes;
- proofs, synchronization, retention, typed values, or managed maintenance are
  desired from one handle.

Use a repository/VCS layer when:

- commits need parents and ancestry;
- branches, tags, reflogs, author, message, and signatures are domain concepts;
- merge-base calculation or fast-forward policy is required;
- history represents events rather than unique content states.

`VersionedMap` intentionally remains a focused bridge between the immutable
tree engine and common application state management. That boundary keeps the
simple path small while still exposing the engine's structural sharing, diff,
merge, proof, sync, ingestion, and maintenance capabilities when applications
need them.

## Related Documentation

- [Getting Started]getting-started.md introduces the immutable tree API and
  package setup.
- [Guides]guides.md covers key construction, values, stores, roots, sync, and
  general operational recipes.
- [Versioned Secondary Indexes]secondary-index-design.md goes deeper on strict
  source/index coordination, historical index reads, and index lifecycle.
- [Async Store]async-store.md explains async-native stores, adapters, and
  remote/browser integration.
- [Architecture]architecture.md shows how trees, nodes, stores, manifests,
  diff, merge, and synchronization fit together.
- [Wire Format]wire-format.md documents deterministic node and manifest
  encoding contracts.
- [prolly-vcs Design]prolly-vcs-design.md describes the proposed higher-level
  repository model for commits, ancestry, branches, tags, and reflogs.