Owner GO'd "Level 3: full no-residual". This is the multi-week, data-loss-critical, IRREVERSIBLE campaign
to eliminate the OWNED in-memory trie representation. Authoritative reference; supersedes
slice3-f5-f7-{execution-plan,revalidated-2026-06-08}.md for ordering. Baseline a46d9c1 / e44a877 (2713 green).
Process (owner directive): plan → red-team → refine until convergence → +1 confirming red-team → implement BY HAND,
each step green + committable. The 8 data-loss-critical steps (flagged ⚠️RT) each get a dedicated adversarial red-team
BEFORE implementation. Implementation is by hand (plan/explore/red-team agents allowed; no work delegation).
The +1 confirming red-team on the converged L0 found a BLOCKER the prior passes missed: the owned &self WRITE
mutators were never enumerated. L0.3 (collapse OR RwLock → bare field) CANNOT happen at Level 0 — RETRACTED:
- byte insert_impl_core/remove_impl_core + dirty_tracking clear_dirty_flags_recursive/propagate_dirty_to_root, and
char try_insert_impl_no_wal/_with_value/try_remove_impl_no_wal/preflight_existing_terminal_is_final all take
self.root.WRITE() through
&self (the OR lock's raison d'être). They are LOAD-BEARING (compaction staging via
insert_impl_no_wal until L2.1; reestablish/recovery/staging until L3.x); the plan deletes them only at L2.2/L3.3. - A bare field cannot express
&self writes (won't compile); removing the lock is a data race — commit_document is
now &self (e44a877), so an Arc embedder can kill_switch_to_owned + concurrently commit_document[owned write] +
contains[owned read] + enable_eviction[owned evict], serialized today ONLY by OR. persistent_lockfree_f4_lock_
hierarchy_loom.rs EMPIRICALLY proves OR serializes a concurrent insert_impl_core.
Resolution: the OR RwLock + owned root field persist UNTIL L3.3 deletes the field OUTRIGHT (after every &self
owned writer/reader is gone via L2.2 + L3.3). NO intermediate bare-field state — "collapse" == "delete at L3.3". L3.3
additionally deletes clear_dirty_flags_recursive/propagate_dirty_to_root and must RETIRE/REWRITE the loom suite (its
OR-lock premise evaporates). Level 0 is now ONLY L0.1 (L0.2 MOVED to L3.2 — see below), with these corrections:
- L0.1 commit must ALSO, in the same commit, migrate the owned-eviction tests: src/persistent_artrie_char/
eviction_registry_tests.rs; the persist.rs ~2725 region; the owned-arm assertions in tests/persistent_char_eviction_
{correspondence,proptest,registry_correspondence}.rs (re-assert vs the overlay registry / drop the kill-switched-
owned-evict scenario); confirm tests/persistent_artrie_loom_correspondence.rs:1748 is comment-only. DONE+COMMITTED (5aa6fc3).
- L0.2 ROLLED BACK + MOVED to L3.2 (2026-06-08). The original L0.2 (delete owned READ tails) was UNSOUND: it assumed
the owned read arm was reachable only via
kill_switch_to_owned, but in-memory ::new() tries (a PUBLIC API) are
permanently route_overlay()==false and use the owned read path — collapsing the reads broke 76 tests (44 basic
::new() char_integration tests, ZERO kill_switch). The read-tail deletion is causally DOWNSTREAM of the L3.2
::new()→overlay flip + struct-zipper re-point, so it is now FUSED into L3.2. Full retrospective + the rejected
"flip ::new() early" Strategy-B viability analysis: docs/design/slice3-l02-rollback-2026-06-08.md. - L0.1 is RT-CLEARED (mechanical). The OR-lock collapse + the former L0.2 read-tail deletion both live at L3 now
(L3.3 / L3.2 respectively) and inherit L3's ⚠️RT.
- Normal reopen is ALREADY overlay-drained. open_inner production arms use convert_owned_to_overlay_on_reopen
(Owned regime) / reconcile_and_drain_overlay (Overlay regime) — neither touches self.root. The owned
replay_records_lww + reestablish_overlay_from_owned survive in open_inner ONLY on the open_with_legacy_loader
(force_f5==false) oracle branch. So L1 redirects the CORRUPTION/ARCHIVE-rebuild ctors, not "reopen".
- An overlay→dense serializer EXISTS but is NOT path-compressing. serialize_overlay_node_to_disk (byte
overlay_checkpoint.rs / char persist.rs serialize_char_node_to_disk) is the shipping production overlay-arm
checkpoint — one owned Node/CharNode record per overlay node, UN-compressed. owned serialize_root emits
path-COMPRESSED images. C-opt-1 (L2) is a NEW path-compressing serializer; density is compact()'s whole purpose.
- 4th load-bearing owned consumer: the struct zipper. PersistentARTrieZipper / …CharZipper
(zipper.rs has_path/is_final_at_path/get_children_at_path walk inner.root.read(), log::warn! stub). Passes the
formal gate today only because zipper_language_correspondence exercises it on a ::new() in-memory trie
(route_overlay()==false). L3 must re-point it onto the overlay before deleting the owned root.
- kill_switch_to_owned has exactly ONE production caller (compaction_impl.rs:209) + ~80 TEST callers
(owned-white-box). So OverlayWriteMode deletion (L2) is entangled with retiring that owned-white-box test corpus.
- Owned NODE record types are NOT deletable, even at L3 — Node/Node4/16/48/256/StringBucket (nodes/mod.rs:576,
bucket.rs:287) + CharNode/CharNode4/16/48 (nodes/mod.rs:237) ARE the on-disk record format
(serialize_overlay_node_to_disk + load_overlay_node_from_disk consume them). Only the in-memory root HOLDERS —
TrieRoot (dict_impl.rs:484), CharTrieRoot (types.rs:753), CharTrieNodeInner (types.rs:468) — and the
owned methods over them are deletable. L3's codec drives the Node-record format DIRECTLY from overlay nodes,
never materializing a TrieRoot/CharTrieNodeInner.
Reorder vs naive 0→1→2→3: build the codec primitives as a DORMANT sub-phase (CX) AFTER L1 and BEFORE the L2/L3
flips, because (a) C-opt-1/C-opt-2 are the real new format work + prerequisites for BOTH L2 (owned-staging removal)
and L3 (owned-root deletion); landing them dormant lets the byte-identity proof land in isolation before load-bearing;
(b) L1 is codec-independent (reuses the shipping apply_recovered_operation_overlay/drain). Throughout: #41
checkpoint_lsn=committed-watermark capture ordering (core/overlay/checkpoint.rs overlay arm) is UNTOUCHED.
Per-step gate (every commit): full suite (feature-on default) + --no-default-features (feature-off) + doctests +
scripts/verify-formal-correspondence.sh exit 0 + scripts/verify-unsafe-boundary-inventory.sh set-equality + fmt +
cross-repo READ-ONLY cargo check (liblevenshtein-rust; and at L3.3 libgrammstein/lling-llang/pgmcp). Keep verification
LOGS to summaries (don't commit 25K-line test logs).
route_overlay() STAYS a computed fn (red-team #2: const true breaks compaction staging). A-decision: KEEP
OverlayWriteMode at L0 (deleted at L2). Hand-delete every route_overlay-false owned arm (the compiler will NOT flag
them — if route_overlay(){return X} <tail> borrow-checks the tail), then collapse the OR lock.
- L0.1 — delete eviction owned else-arms (mechanical, RT:no — already dead under production route_overlay==true).
Byte shared_trait_impl.rs start_eviction async cb (~291-317) + force_eviction (~392-401) → keep only the overlay
arm; char mod.rs start_char (~2141-2145) + force_eviction (~2231-2235) → drop the evict_char_nodes else + unused
quiescence locals. Now-dead → delete: byte evict_node_at_path (+find_parent_in_root), char evict_char_nodes +
evict_node_at_path (+inline relink). KEEP vocab evict_node_at_path (persistent_vocab_artrie/mod.rs:767/869 — distinct type).
- L0.2 — ❌ ROLLED BACK + MOVED to L3.2 (2026-06-08; was "delete route_overlay-false owned READ tails", RT:no).
UNSOUND at L0: in-memory
::new() tries are permanently route_overlay()==false (public API) and use the owned read
path, so deleting the read tails before the L3.2 ::new()→overlay flip broke 76 tests. The read-tail deletion (char
try_contains/get_value/try_get + iter_prefix*; byte iter_prefix_from_cursor + collect_terms_from_cursor) is now FUSED
into L3.2. KEPT-as-was (D1 seams): byte contains_impl/get_value_impl + unrouted_; char owned_try_/owned_root_guard/
navigate_to_prefix_from. Retrospective: docs/design/slice3-l02-rollback-2026-06-08.md. - L0.3 — collapse OR RwLock→bare field ⚠️RT (data-loss-critical: lock-collapse soundness). root: RwLock<TrieRoot>
→ TrieRoot (dict_impl.rs:280); char RwLock<CharTrieRoot> → CharTrieRoot (mod.rs:429). Surviving self.root
accesses after L0.1/L0.2: (i) ctor/reopen scratch (f5_loader get_mut, &mut); (ii) D1 converter seam readers;
(iii) byte compaction staging (insert_impl_no_wal/serialize_root/capture_owned_snapshot); (iv) recovery appliers;
(v) struct zipper; (vi) reestablish_overlay_from_owned. Rewrite each: &mut self → &mut self.root; &self read (byte
contains_impl/get_value_impl, char owned_root_guard) → direct &self.root borrow (owned_root_guard return type
MappedRwLockReadGuard → Option<&CharTrieNodeInner>). Soundness: every surviving access is &mut self (single-writer)
OR a &self read on a rep only reached by single-threaded reopen/recovery/compaction (production never writes owned).
Lock order after OR removed: CK > merge_lock > EC (no cycle); Send/Sync OK (SwizzledPtr = AtomicU64+AtomicPtr).
Gate adds the loom suites. Red-team focus: prove NO surviving &self self.root read can race a &mut self owned
writer in ANY config (Shared wrappers, kill-switched owned, in-memory zipper).*
Point the corruption/archive-rebuild ctors at the overlay drain, eliminating the apply_*_recovered_operation_no_wal +
reestablish_overlay_from_owned dependency. Reuse the SHIPPING apply_recovered_operation_overlay (flip.rs:1031) —
red-team #1 proved it subsumes counter semantics incl. u64>i64::MAX (counter_leaf_to_i128) + delta ACCUMULATE.
- L1.1 byte open_with_recovery_config (mmap_ctor.rs:935-1027): apply closures 938/977 → apply_recovered_operation_overlay;
DELETE reestablish_overlay_from_owned (1021-1027). Verify watermark base-seed covers drained LSNs.
- L1.2 char open_with_recovery_config (mmap_ctor.rs:1082-1343, apply :1166 + reestablish :1337), recover_from_archives
(:1531-1601, :1573 + :1592), open_with_full_recovery (:1403). Same transform + delete reestablish calls.
- L1.3 retire the legacy-loader oracle's owned dependency (migrate open_with_legacy_loader to overlay-built
correspondence or delete + re-point the both-loaders/owned-to-overlay suites to production reopen + BTreeMap oracle),
THEN delete owned replay_records_lww + apply_*_recovered_operation_no_wal + recompute_recovered_increment +
value_from_recovered_i64 (prove unreachable first). KEEP *_impl_no_wal (staging/reestablish; die L2/L3). UNSAFE rows
23-24 live in KEPT *_impl_no_wal → NO prune at L1 (re-run set-equality to confirm 0 delta).
Red-team focus: recover-via-drain $
\equiv$ recover-via-owned-then-convert across V $\times$ archive-layout $\times$ crash-point; the
recover-family uses rebuild_from_wal_segments_regime_aware (its own tx-resolution) — verify SAME tx-filter the owned
path applied (else aborted-tx records resurrect). Perf: bulk overlay path-copy rebuild vs owned dense. New TLA
RecoveryRebuildOverlay (archive-rebuild-into-overlay sink + tx-filter parity).
- CX.1 byte serializer serialize_overlay_snapshot_compressed: walk the immutable overlay root, emit the SAME dense
Node-record format serialize_root produces (collapse single-child chains → compressed prefixes; leaf runs →
StringBucket; ROOT_TYPE_BUCKET vs ART_NODE per the owned heuristics) via serialize_node_to_disk_with_value_len. Do
NOT touch serialize_overlay_node_to_disk (the un-compressed production checkpoint — STAYS). Proof: byte-identity (or
reopen-equivalence incl. compacted_bytes density bound) vs owned serialize_root over V $
\times${valued,term-only,""}$\times$deep key. - CX.2 byte loader load_overlay_root_compressed: read the dense path-compressed format, EXPAND multi-unit prefixes +
buckets directly into Arc<OverlayNode<ByteKey,V>>, never materializing TrieRoot. Reuse load_overlay_node_from_disk
(single-node). Proof: load(serialize(overlay))$
\equiv$overlay AND load_compressed(legacy_owned_image) $\equiv$
build_overlay_root_from_owned(load_root_from_disk(legacy_owned_image)) — back-compat vs the owned loader (the
"B2 brick-risk" mitigation: prove BEFORE it's the only path). - CX.3 char twins (serialize_char_*_compressed / load_overlay_char_root_compressed). Same proofs.
- Prefer ZERO new unsafe (build via Arc/OverlayNode::with_child); any new unsafe → new inventory row + contract.
Optional TLA OverlayDenseCodecRoundTrip; correspondence + back-compat tests are the empirical gate. Fully reversible.
Red-team focus: back-compat (every legacy on-disk format — bucket/ArtNode roots, all 4 node sizes, compressed
prefixes, value blobs incl. "" — read byte-equivalently to the owned loader); deep-term iterative expand; byte-identity/density.
- L2.1 flip compact() (byte-only; char has no compact()) ⚠️RT. compaction_impl.rs:110-370: replace create-staging
- kill_switch_to_owned (209) + insert_impl_no_wal loop (231) + checkpoint (264) with serialize_overlay_snapshot_compressed
(CX.1) of the source overlay snapshot into the temp file. compaction_snapshot enumeration (already overlay) stays the
verify oracle. No owned staging trie / kill_switch / owned insert; density preserved. Red-team: new staging image
byte-equivalent + complete; atomic-rename + WAL-sidecar dance unchanged; &mut self exclusivity → no past-snapshot WAL loss.
- L2.2 delete OverlayWriteMode + kill_switch_to_owned + field + owned checkpoint arm ⚠️RT. route_overlay() body →
self.lockfree_root().is_some() (overlay installed iff routing — true for all eligible V; ineligible V never installs
overlay). Delete OverlayWriteMode enum/field/seams, kill_switch_to_owned, wal_stamp_owned_regime, the owned checkpoint
else-arm in checkpoint_route_split (incl. RES-4 assert), capture_owned_snapshot/publish_owned_and_reclaim + trait
decls + byte serialize_root (superseded by CX.1). KEEP overlay arm + capture_overlay_snapshot (#41 untouched). Delete
byte staging mutators (insert_impl_no_wal/insert_impl_core/...); char twins survive to L3. Retire the ~80 owned-white-box
kill_switch_to_owned test callers as a GROUP (this commit). Prune any byte owned-walk UNSAFE rows + UNSAFE_CONTRACTS
entries (set-equality). Red-team: with the owned arm gone, prove by construction lockfree_root().is_some() at every
reachable checkpoint (the only counterexample was the kill-switched staging trie, now deleted).
- ⚠️ FINDING (2026-06-08, Plan + red-team — L2.2 IS NOT INDEPENDENTLY DOABLE; its substantive deletions are FORCED to
L3.3). L2.1 is DONE (73fc8eb). For L2.2: eligibility IS universal (
overlay_eligible_v()==true ∀V, byte+char), so a
created trie is always overlay-routed and the L2.1 owned-staging compaction fallback is dead-for-created-tries. BUT
OverlayWriteMode + kill_switch_to_owned + the route_overlay() body + the shared checkpoint_route_split else-arm
are SHARED byte/char surfaces: kill_switch_to_owned is the shared LockFreeOverlay default (flip.rs:577) that
char's ~20 #[cfg(test)] callers + char's owned-checkpoint coverage NEED until char flips. A "byte-local SHAPE-1"
(delete byte owned sinks — compaction owned arm, owned checkpoint arm — while keeping the kill-switch) is BROKEN:
kill_switch_to_owned() only flips the mode (NOT lockfree_root), so a kill-switched byte trie writes to self.root
then checkpoints/compacts via the owned arm (green guards m4b_old_owned_file_stays_owned_on_reopen,
empty_string_valued_owned_regime_reopen). Deleting the sinks while the kill-switch lives = compaction reads the
EMPTY lockfree_root (data loss) + checkpoint() Err regresses durability. So byte owned-staging removal MUST land
WITH the global kill_switch_to_owned removal — which requires char to flip first. ⇒ L2.2 is ABSORBED into L3.3
(mirrors L0.2→L3.2, L0.3→L3.3). Corrections to the above L2.2 line for when it executes at L3.3: do NOT delete
insert_impl_core/remove_impl_core/upsert_impl_no_wal (LIVE byte writers — die at L3.3 anyway, but as owned-root
deletion not "staging"); NO UNSAFE delta at L2.2-scope (rows 23-24 are CHAR types.rs, die at L3.3); serialize_root
sole caller is capture_owned_snapshot (NOT persist_to_disk); KEEP serialize_root_value_bytes (used by the live
iterative overlay serializer). L2 effectively = L2.1 only; NEXT real step = L3.1.
- L3.1 flip F5 reopen scratch onto CX.2/CX.3 ⚠️RT. load_root_immutable_seam (flip.rs:1557) + the F5 Overlay arm →
load_overlay_root_compressed (no TrieRoot scratch). Keep corrupt-image→empty+WAL fallback. Red-team: every normal
reopen now reads via the new codec with NO owned fallback (the brick-risk; CX back-compat proof is the precondition).
- L3.2 re-point the struct zipper onto the overlay +
::new()→empty overlay + ABSORB the former L0.2 read-tail
collapse ⚠️RT (4th consumer). zipper.rs has_path/is_final_at_path/get_children_at_path (+ char twin): replace
inner.root.read() navigation with the overlay-backed root()/DictionaryNode (NodeInner::Overlay; fault OnDisk via
fault_overlay_slot). Delete the log::warn! stub. ::new() MUST install an empty overlay so route_overlay() is
universally true (this is the PRECONDITION the rolled-back L0.2 lacked — see slice3-l02-rollback-2026-06-08.md +
the durability spike below). In the SAME fused commit, collapse the public read-method owned arms (the former L0.2:
char try_contains/get_value/try_get + iter_prefix*; byte iter_prefix_from_cursor) — once ::new() is overlay-backed
these arms are dead. Update zipper_language_correspondence to not rely on owned. Durability spike (do FIRST,
reversible): ::new()→overlay must resolve the WAL-less-write question (flip_to_overlay hard-requires a WAL;
insert_cas_durable forbids acknowledging a 0-LSN as durable) — prove a WAL-less in-memory overlay is
correctness-equivalent to the owned tree for insert/contains/get/iter/zipper across arbitrary V before this lands.
Red-team: the formal gate's PublicDictionaryNodeTraversal + zipper correspondence (wrong overlay-zipper = silent
wrong query results); + the ::new()-overlay equivalence across V $\in${(),i32,String,u64}; + no green-gate window where
reads are overlay-only while any ctor still yields route_overlay()==false. - L3.3 delete owned root field + holder TYPES + owned readers/converters ⚠️RT (biggest risk). Delete: root field
(dict_impl.rs:280, mod.rs:429); TrieRoot/CharTrieRoot/CharTrieNodeInner; owned loaders load_root_from_disk(with_arena);
D1 seams (owned_first_units/owned_units_under/owned_units_with_values_under/owned_has_empty_term_value/clear_owned);
build_overlay_root_from_owned; reestablish_overlay_from_owned; unrouted* + byte contains_impl/get_value_impl + char
owned_try_/owned_root_guard/navigate_to_prefix_from/collect_terms_; char owned mutators (insert_impl_no_wal*/
insert_impl_core/try_increment_impl_no_wal) + byte twins; inner_to_overlay/overlay_to_inner. KEEP Node/CharNode RECORD
types + single-node serde + load_overlay_node_from_disk + CX codec. compaction_snapshot owned fallback → overlay
unconditional. PRUNE UNSAFE rows 23-24 (+ any other owned-walk rows) + UNSAFE_CONTRACTS same-commit (inventory 100→fewer).
Red-team: (a) every deleted owned reader/converter has ZERO surviving caller in any feature combo; (b) the codec +
L3.1 reopen + L3.2 zipper fully subsume every owned capability across V types; (c) high-concurrency real-disk soak
(owned GONE; #41 witness — every committed key survives reopen); (d) the feature-off build.
L2/L3 re-RUN (not modify) the existing ConcurrentCheckpointSerialization/LockFreeDurableCheckpoint(+Eviction)/
OverlayEvictionCas/OverlayEvictionStale/LockFreeOverlayValueCas (all _Unsafe negative controls must still fire);
PublicDictionaryNodeTraversal covers L3.2. Prune UNSAFE rows 23-24 + owned-walk rows at L3.3 (no inventory change before).
L0.3, L1, CX, L2.1, L2.2, L3.1, L3.2, L3.3. Mechanical (no RT): L0.1, L0.2.
- core/overlay/flip.rs (route_overlay:348; apply_recovered_operation_overlay:1031; drain_segments_into_overlay:1281;
build_overlay_root_from_owned:873; reestablish_overlay_from_owned:946; convert_owned_to_overlay_on_reopen:1482;
load_root_immutable_seam:1557; owned_* D1 seams; kill_switch_to_owned:581)
- persistent_artrie/overlay_checkpoint.rs (capture_owned_snapshot:113; serialize_root:515; serialize_overlay_node_to_disk:961)
- core/overlay/checkpoint.rs (checkpoint_route_split:134 RES-4 owned arm; #41 ordering)
- persistent_artrie/compaction_impl.rs (kill_switch_to_owned:209; insert_impl_no_wal:231; checkpoint:264; snapshot fallback:396)
- persistent_artrie{,_char}/mmap_ctor.rs (recover-family ctors — L1)
- persistent_artrie/dict_impl.rs (root:280; TrieRoot:484) + persistent_artrie_char/types.rs (CharTrieRoot:753;
CharTrieNodeInner:468) + persistent_artrie_char/mod.rs (root:429; eviction owned arms:2141/2231; root():1330)
- persistent_artrie/zipper.rs (has_path:190/…; L3.2) + persistent_artrie/overlay_fault.rs (load_overlay_node_from_disk:49)
- persistent_artrie_char/disk_io.rs (load_overlay_node_from_disk:404)