Repo: /home/dylon/Workspace/f1r3fly.io/libdictenstein · 2026-06-02 · implementation-ready. Prerequisite the
owner chose before the F0-F5 lock-free flip. Reversible, gated, ZERO new unsafe. The decisive deliverable is
the LOOM/proptest/TLA RE-PROOF that the composite (insert $\cup$ remove) stays linearizable once finality is no
longer monotone. Persisted from the Plan-agent design (full prose in the session transcript).
FEASIBLE with the single-root-CAS arbiter (atomic_ptr.rs:131, loser-safe per Arc::ptr_eq:143) — but ONLY
after a re-proof. The arbiter is operation-agnostic (serializes published root versions); a remove that
path-copies the spine with a clear-finality leaf then root-CAS-publishes is structurally identical to
insert_cas_durable. The catch: the content invariant the proofs rely on — finality monotone (0→1 only) — is
broken by delete (1→0). Where the proofs rely on it:
try_set_final=fetch_or(IS_FINAL) (overlay/node.rs:724), as_final ORs (:809); no
as_non_final exists.build_path_recursive depth==len returns the SHARED existing Arc so try_set_final's
fetch_or is the single arbiter (lockfree_cas.rs:345-365, comment "membership only ever goes 0→1"). A
concurrent remove clearing that node breaks the "0→1 only" reasoning.tests/persistent_lockfree_overlay_loom.rs): no clear-finality action; assertions are insert-only
theorems.tests/persistent_lockfree_overlay_proptest.rs): BTreeSet insert-only oracle; Op = Insert/Contains.contains_lockfree checks lockfree_cache.contains_key
FIRST and short-circuits true (lockfree_cas.rs:516-520); insert_cas_durable does
lockfree_cache.insert(term,true) (:259). It's insert-only-positive → a remove that clears the trie but
leaves the cache entry makes the term read present FOREVER. Remove MUST invalidate the cache (§3.4).OverlayEvictionCas: acked only grows; no removed-set. Needs a RemoveCas action + removed set.
Conclusion: R-B is feasible; the code is small (one node method + one durable method + router branch + cache
invalidation). The real work — and the GATE — is the re-proof (§4). Delete IS linearizable under the
single-root-CAS arbiter; the models simply don't yet witness it, and two (NoLostAck, the positive cache) would be
UNSOUND if delete were wired without §3.4/§4.as_non_finalAdd to impl<K: KeyEncoding, V: Clone> OverlayNode<K,V> (mirror of as_final, overlay/node.rs:809):
pub fn as_non_final(&self) -> Self {
Self {
version: AtomicU64::new(self.version.load(Acquire) + 1),
store: self.store.clone(), // SUBTREE RETAINED (remove "cat" keeps "cats")
flags: AtomicU8::new(self.flags.load(Acquire) & !(flags::IS_FINAL | flags::HAS_VALUE)),
value: None, // drop the value (mirror owned remove)
prefix: self.prefix.clone(), prefix_len: self.prefix_len,
}
}
Clears IS_FINAL+HAS_VALUE on a COPY (immutability preserved), retains children/prefix (compaction = future opt,
out of scope — matches owned remove which also leaves the node). No without_value (folded in). ZERO unsafe
(same shape as as_final; Send/Sync unaffected). Node unit tests: clear→not-final/no-value/children-preserved/
original-unchanged; as_non_final $\circ$ as_final round-trip; deep-child retention ("cat" cleared keeps "cats" final);
both ByteKey+CharKey.
remove_cas_durable — Order-A mirror of insert_cas_durableAdd to impl<V: DictionaryValue, S: BlockStorage> PersistentARTrieChar<V,S> (after insert_cas_durable:207):
pub fn remove_cas_durable(&self, term: &str) -> Result<bool> — Ok(true) iff it cleared a present term, Ok(false)
if absent. Steps:
find_leaf_faulting for OnDisk
prefixes); if not present → Ok(false) with NO WAL (matches owned preflight_remove_no_wal; a no-op remove
mustn't burn an LSN/punch a watermark hole). NOTE: the positive cache is NOT a sufficient presence oracle for
remove (cache-miss $\ne$ trie-absent after a recovery rebuild) → consult the TRIE, not just the cache.append_to_wal_returning_lsn(WalRecord::Remove{term}) append+sync DURABLE (same chokepoint
→ invalidate_eviction_registry); one append, never re-logged on retry.enter_read() pinned): try_remove_lockfree_path:
Removed → invalidate cache (lockfree_cache.remove(term)) → mark_committed(lsn) → Ok(true).AlreadyAbsent (raced/cleared) → invalidate cache → mark_committed(lsn) → Ok(false) (LSN durable, watermark
mustn't stall — same as insert's AlreadyExists arm).Conflict → cas_retries+=1, re-find on retry.IoError(e) (gated) → Remove already durable; return Err, do NOT advance watermark (contiguous prefix stalls;
recovery replays). Order-A "durable-but-visible-after-reopen" window, identical to insert.
Helpers: try_remove_lockfree_path + build_remove_path_recursive (dual of build_path_recursive): at
depth==len, if !is_final → Err(AlreadyAbsent) (don't publish a no-op spine); if final → node.as_non_final()
as a FRESH cleared leaf in a NEW spine (root CAS = sole arbiter; NOT shared-Arc like insert). Descend: InMem→
recurse+splice; OnDisk (gated)→fault-in first (load_overlay_node_from_disk) then recurse; None/null→AlreadyAbsent.
Reuse BuildPathError + add AlreadyAbsent.§3.4 CACHE INVALIDATION (DATA-CORRECTNESS — do not omit): remove_cas_durable MUST lockfree_cache.remove(term)
on every state-changing arm (Removed + AlreadyAbsent), BEFORE mark_committed, else removed terms read present
forever via the positive cache. Asserted by the proptest Contains check + a remove‖contains loom schedule.
§3.5 Why FRESH-COPY-via-root-CAS, NOT in-place fetch_and: insert's fetch_or is in-place-safe BECAUSE
finality is monotone (an early observer of 0→1 is benign). An in-place fetch_and(!IS_FINAL) could race an in-place
fetch_or on the same shared node with no serialization → resurrection/lost-update. By publishing a fresh cleared
node version ONLY via the root CAS, the clear is atomic with a specific published root and the CAS arbiter
linearizes it. The node's flags is only ever flipped in-place 0→1 by try_set_final (still monotone in-place);
the 1→0 happens only on a fresh copy via as_non_final, arbitrated by the root CAS. The §4.4 TLA negative control
proves this choice is required.
R-B is NOT done until loom + remove-aware proptest + TLA (with a firing negative control) are all green (inside
verify-formal-correspondence.sh + nextest $\ge$2489). Reviewer must NOT approve on the code diff alone.
Theorem (composite linearizability, no-lost-op): for any concurrent history over {insert,remove,contains},
published-root membership = a last-writer-wins linearization respecting the root-CAS real-time order; no op lost;
no resurrection (a removed term reappears only via a later insert); no double-clear UAF. Monotonicity is DROPPED,
replaced by last-writer-wins under the root-CAS total order.
tests/persistent_lockfree_overlay_loom.rs): add ModelNode::as_non_final_clone + remove_one_char
(fresh cleared copy + root-CAS, NOT in-place) + 5 schedules: (1) remove‖insert same key → one wins, consistent
last-writer, no lost op; (2) remove‖prefix-finalize → "ab" always preserved, prefix-insert fix STILL holds
(the decisive schedule); (3) remove‖remove → one true/one false, idempotent, no double-clear UAF; (4) remove
through faulted-in prefix (reuse OE9 machinery); (5) reader-snapshot survives concurrent remove (no-UAF).Op::Remove; keep BTreeSet oracle for V=() (set insert/remove = LWW) mutated by both;
Contains assertion catches the stale-cache bug; multi-thread insert/remove convergence via a deterministic
quiescent settling phase (remove-all-then-insert-known-subset, assert exact final membership); ADD a V=u64
BTreeMap oracle test (remove drops value → get_value==None not Some(0)).LockFreeOverlayRemoveCas.tla (+.cfg+_Unsafe.cfg) — vars root(MaxRoot-capped),present,
acked,removed; actions InsertCas/RemoveCas/RemoveAbsentNoop; invariant LastWriterWins == \A t: (t∈present) <=> (t∉removed) + NoResurrection + NoLostOp. Negative control _Unsafe.cfg:
USE_FRESH_COPY_CLEAR=FALSE models the rejected in-place clear (clear present WITHOUT bumping root) → TLC MUST
violate LastWriterWins (resurrection/lost-remove). Register in verify-formal-correspondence.sh SANY/RUN_TLC/
negative-control lists (:251/:269/:323). Bounded: 2 Terms, MaxRoot $\approx$ 6.fetch_or);
remove never flips an existing node's bit, it swaps in a fresh node version via root-CAS. The two never write the
same atomic; the root-CAS total order resolves them. Loom schedule #2 is the machine-checked witness.Owned WAL-replay already handles Remove (recovery.rs:187, mmap_ctor.rs:420) — same WalRecord::Remove, no
codec change. Under REC-A (rebuild overlay from iter() live set), a removed term is non-final in the recovered
owned tree → naturally absent from the rebuilt overlay → recovery correct with ZERO new recovery code (strengthens
the flip's §6 assertion). Overlay-as-source recovery (REC-B follow-on): tail Remove replay calls a
remove_cas_no_wal (CAS loop minus the WAL append) — specified, lands with REC-B. Order-A: a durably-acked remove
survives crash (replay clears it); the IoError window is durable-but-not-yet-visible → replay honors it (correct).
remove/SharedCharARTrie::remove (mutation_api.rs:66) gains if self.route_overlay() { return self.remove_cas_durable(term); } — replaces R-A's error/fallback. &self-compatible (Phase-F-ready). PS3 flips
from "remove errors" to "remove WORKS + absent after reopen"; PS1 production soak gains mixed insert/remove.
Negative-delta increment STAYS the separate gap (R-B solves remove only). Gate ordering dependency: RB6
(production remove routing) lands only AFTER the flip's "un-gate fault-in to production" (flip F0), because
remove-under-evicted-prefix needs fault-in — flag to owner.
\ge$2489 + verify-formal-correspondence exit 0 + unsafe-inventory exit 0; systemd 32G + real-disk)as_non_final + node unit tests (both keys). No callers. Rollback: delete.remove_cas_durable + path helpers + BuildPathError::AlreadyAbsent + cache invalidation + reject test
V=u64 BTreeMap value test). The
stale-cache Contains assertion is load-bearing.LockFreeOverlayRemoveCas.tla + negative control fires). R-B's re-proof complete only
when RB2+RB3+RB4 all green.concurrent_durable_writers_all_survive_reopen): reopen →
live set == acknowledged net membership.WalRecord::Remove).LastWriterWins check it. Re-proof IS the work.lockfree_cache.remove;
proptest Contains + remove‖contains loom witness it. The single most likely implementation slip.get_value==None not Some(0); BTreeMap proptest guards.src/persistent_artrie_core/overlay/node.rs (add as_non_final after :809; node tests :895+)src/persistent_artrie_char/lockfree_cas.rs (add remove_cas_durable after :207; build_remove_path_recursive
mirror of :331; reuse find_leaf_faulting:681 + load_overlay_node_from_disk; cache invalidation; reject test)tests/persistent_lockfree_overlay_loom.rs (5 remove schedules + as_non_final_clone; reuse prefix fixture
:200 + OE9 machinery :262)tests/persistent_lockfree_overlay_proptest.rs (remove-aware BTreeSet oracle + Op::Remove + multi-thread +
V=u64 BTreeMap)src/persistent_artrie_char/mutation_api.rs (remove:66 gains route_overlay()→remove_cas_durable)formal-verification/tla+/LockFreeOverlayRemoveCas.{tla,cfg} + _Unsafe.cfg; register in
scripts/verify-formal-correspondence.sh:251/269/323. Recovery (recovery.rs:187, mmap_ctor.rs:420) NO change
under REC-A.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |