car_sync/fold.rs
1//! The deterministic fold: `fold(ops) → materialized state`.
2//!
3//! The proposal's convergence contract, verbatim: "each daemon **folds** the
4//! full op-set into local state deterministically. Because the fold is
5//! commutative, associative, and idempotent over the op-set (CRDT
6//! properties), two laptops writing simultaneously converge the moment they
7//! exchange ops."
8//!
9//! Fold rules per surface tier (the proposal's table):
10//! - **Grow-only** (conversations, knowledge, skills, trajectories, runs,
11//! routing observations): union by [`crate::oplog::OpRecord::fold_key`] —
12//! the stable entity key for logical-entity surfaces, the `op_id` for
13//! event-stream surfaces (routing), which fold as a MULTISET: the proposal
14//! replays "the merged **multiset** of observations", so two
15//! byte-identical observations are two events and both survive.
16//! Entities are immutable in this tier (a change is a new op — e.g. a
17//! `Supersedes` fact), so on a key collision with *different* content the
18//! earliest `(hlc, op_id)` writer wins, deterministically.
19//! - **Registry** (declagents, the file registries): LWW-register per
20//! record keyed by id, ordered by HLC — *not per file*. Latest
21//! `(hlc, op_id)` wins; concurrent edits to different records both
22//! survive.
23//! - **Routing**: the fold materializes the hlc-ordered observation stream;
24//! the EMA replay is the caller-injected [`SyncState::replay`] ("sync the
25//! observations, not the result" — same observations + same canonical
26//! order ⇒ bit-identical result on every device).
27//! - **Leased** (`Intent`, B5): LWW-per-run_id (monotone status) with
28//! **per-agent epoch fencing** — a stale-epoch intent from a failed-over
29//! lease holder loses at the fold, order-independently. See [`crate::lease`]
30//! and the [`fold_onto`] `FoldTier::Leased` arm.
31//!
32//! Determinism discipline: all state is `BTreeMap`-backed and nothing here
33//! reads a clock — the proposal calls out "a non-determinism leak
34//! (wall-clock or HashMap iteration order sneaking into a fold)" as the bug
35//! class [`state_hash`] exists to catch.
36
37pub use crate::oplog::FoldTier;
38use crate::oplog::{canonical_json, Hlc, OpRecord};
39use car_state::crdt::{LwwMap, LwwRegister};
40use serde::{Deserialize, Serialize};
41use serde_json::Value;
42use sha2::{Digest, Sha256};
43use std::collections::BTreeMap;
44
45/// One folded entity: the winning op's payload plus the stamp/id it won with.
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47pub struct FoldedRecord {
48 pub op_id: String,
49 pub hlc: Hlc,
50 pub payload: Value,
51}
52
53/// One agent's leased execution-intent ledger — the [`FoldTier::Leased`]
54/// tier's per-agent folded state (B5).
55///
56/// This slice delivers **deterministic ledger convergence plus a durable
57/// idempotency oracle** — it does NOT by itself make execution exactly-once.
58/// The exactly-once *execution* gate is B6's dispatch fence (a linearizable
59/// "am I still epoch N?" check plus the durable non-fenced idempotency read
60/// **before** the external side effect); the fold decides who wins the
61/// *ledger*, not whether the effect happens.
62///
63/// Two distinct views live here, and confusing them causes double-execution:
64///
65/// - **`committed_runs` is the idempotency oracle** — a **fence-INDEPENDENT,
66/// keep-all** map `run_id → committed record`. Once a run commits, it stays
67/// here forever (a commit is a fact; no epoch bump erases it), so
68/// [`SyncState::committed_run`] is the correct "did this run already
69/// execute?" lookup. It is carried in the checkpoint and never trimmed by
70/// retention/compaction (see [`crate::compact`]).
71/// - **`runs` is the "who holds now" view** — per-agent epoch **fencing**
72/// applies to *pending* intents (a stale zombie holder's pending is fenced),
73/// while committed/failed records are **terminal-immune** (never reverted to
74/// pending, never cleared by a fence raise). Read via [`SyncState::intent`].
75/// Do **NOT** use `runs`/`intent()` as the idempotency oracle — a pending
76/// fenced by a later epoch is absent here yet the run may have committed; ask
77/// `committed_runs` / [`SyncState::committed_run`].
78///
79/// **Fencing is per AGENT, not per run** (the proposal's spec, deliberately):
80/// a zombie's *unique* post-failover **pending** — one the new holder never
81/// re-ran — is fenced too (per-run fencing would let it through). `fencing_epoch`
82/// is the agent's max-seen lease epoch. `committed_runs` is what makes that
83/// safe for idempotency: even after prior-epoch pendings drop from `runs`, the
84/// committed fact survives keep-all.
85#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
86pub struct IntentAgent {
87 /// The agent's fencing epoch — the max lease epoch any of its intents
88 /// carried. **Pending** intents below it are fenced (terminals are immune).
89 pub fencing_epoch: u64,
90 /// run_id key (`id:<run_id>`) → the "who holds now" winner (terminal-immune;
91 /// pendings fenced to `fencing_epoch`). NOT the idempotency oracle.
92 pub runs: BTreeMap<String, FoldedRecord>,
93 /// run_id key → the committed record. **Fence-independent, keep-all** — the
94 /// durable idempotency oracle that survives epoch bumps AND compaction.
95 /// Grow-only (highest `(epoch, hlc, op_id)` committed record wins on a
96 /// collision); never cleared by fencing. `#[serde(default)]` so a state
97 /// serialized before this field parses.
98 #[serde(default)]
99 pub committed_runs: BTreeMap<String, FoldedRecord>,
100}
101
102/// The materialized read model a full op-set folds to. On-disk files
103/// (`conversations/*.jsonl`, `declagents.json`, …) are projections of this
104/// (the proposal's "files are projections" reframe); B4's checkpoint is a
105/// serialized `SyncState` at a frontier.
106#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
107pub struct SyncState {
108 /// Grow-only tier: surface tag → stable key → record (union;
109 /// first-writer-wins on a key collision).
110 pub logs: BTreeMap<String, BTreeMap<String, FoldedRecord>>,
111 /// Registry tier: surface tag → record id → LWW winner.
112 pub registries: BTreeMap<String, BTreeMap<String, FoldedRecord>>,
113 /// Leased execution-intent tier (B5): agent_id → its fenced intent
114 /// ledger. Folded from [`crate::oplog::Surface::Intent`] ops with **epoch
115 /// fencing** applied deterministically. `#[serde(default)]` so a pre-B5
116 /// serialized state still parses.
117 #[serde(default)]
118 pub intents: BTreeMap<String, IntentAgent>,
119}
120
121impl SyncState {
122 /// A grow-only surface's entries in canonical `(hlc, op_id)` order — the
123 /// deterministic total order every device agrees on (used by the routing
124 /// replay, and the order B2's transcript materialization will consume).
125 pub fn log_entries(&self, surface_tag: &str) -> Vec<&FoldedRecord> {
126 let mut entries: Vec<&FoldedRecord> = self
127 .logs
128 .get(surface_tag)
129 .map(|m| m.values().collect())
130 .unwrap_or_default();
131 entries.sort_by(|a, b| (&a.hlc, &a.op_id).cmp(&(&b.hlc, &b.op_id)));
132 entries
133 }
134
135 /// Replay an order-sensitive fold (e.g. the routing EMA) over a surface's
136 /// canonically-ordered entries: `fold(routing) =
137 /// observations.sorted_by(hlc).fold(empty_store, apply_ema)`. The apply
138 /// function is injected — execution (and the EMA itself) stays out of
139 /// this crate, like the other pure cores.
140 pub fn replay<T, F>(&self, surface_tag: &str, init: T, apply: F) -> T
141 where
142 F: FnMut(T, &FoldedRecord) -> T,
143 {
144 self.log_entries(surface_tag).into_iter().fold(init, apply)
145 }
146
147 /// The **"who holds now"** leased intent for a run (terminal-immune,
148 /// pending-fenced) — NOT the idempotency oracle. `None` when the agent has
149 /// no such run, or the run is a *pending* fenced by a later, higher-epoch
150 /// holder. A committed run is terminal-immune and stays visible here.
151 ///
152 /// **For "did this run already execute?" use [`SyncState::committed_run`]**
153 /// — `intent()` can return `None`/pending for a run that actually committed
154 /// under a prior epoch, which would cause a double-execution if trusted as
155 /// the idempotency check.
156 pub fn intent(&self, agent_id: &str, run_id: &str) -> Option<&FoldedRecord> {
157 self.intents
158 .get(agent_id)?
159 .runs
160 .get(&format!("id:{run_id}"))
161 }
162
163 /// The idempotency oracle (B5): the committed record for a run, if it has
164 /// **ever** committed for this agent. **Fence-independent and keep-all** —
165 /// unaffected by epoch bumps and by compaction — so this is the correct
166 /// "did `run_id` already run?" lookup before dispatching a side effect.
167 /// `None` iff no committed intent for `(agent_id, run_id)` exists.
168 pub fn committed_run(&self, agent_id: &str, run_id: &str) -> Option<&FoldedRecord> {
169 self.intents
170 .get(agent_id)?
171 .committed_runs
172 .get(&format!("id:{run_id}"))
173 }
174
175 /// Every run_id this agent has committed (the keep-all oracle's keys, with
176 /// the `id:` prefix stripped) — for a failover executor scanning "what has
177 /// already run".
178 pub fn committed_run_ids(&self, agent_id: &str) -> Vec<&str> {
179 self.intents
180 .get(agent_id)
181 .map(|a| {
182 a.committed_runs
183 .keys()
184 .filter_map(|k| k.strip_prefix("id:"))
185 .collect()
186 })
187 .unwrap_or_default()
188 }
189
190 /// The agent's current fencing epoch — the max lease epoch its intents
191 /// carry — or `None` if it has none. Pending intents below this are fenced.
192 pub fn fencing_epoch(&self, agent_id: &str) -> Option<u64> {
193 self.intents.get(agent_id).map(|a| a.fencing_epoch)
194 }
195}
196
197/// Is this folded intent record terminal (committed/failed)? Terminals are
198/// immune to fencing; only pending intents are fenced.
199fn intent_is_terminal(rec: &FoldedRecord) -> bool {
200 crate::lease::intent_status_rank(&rec.payload) == 1
201}
202
203/// Total priority for the leased tier's winner selection: terminal-flag (a
204/// terminal always outranks a pending — terminal-immunity), then `epoch`
205/// (fencing / latest-holder), then `(hlc, op_id)`. `max` under this key is
206/// order-independent.
207fn intent_priority(rec: &FoldedRecord) -> (u8, u64, &Hlc, &str) {
208 (
209 crate::lease::intent_status_rank(&rec.payload),
210 crate::lease::intent_epoch(&rec.payload),
211 &rec.hlc,
212 rec.op_id.as_str(),
213 )
214}
215
216/// Fold an op-set into its materialized state. Order-independent (per-key
217/// winner selection under a total order), idempotent (ops dedup on `op_id`
218/// first), and pure.
219///
220/// # Input contract: verify before folding untrusted input
221///
222/// `fold` does NOT verify ids or chains — that is the explicit, separate
223/// [`crate::oplog::verify_log`] pass, and any caller feeding ops from a
224/// remote/untrusted source (the B3 relay pull path) MUST run it first.
225/// `fold` stays order-independent even on invalid input (two records forging
226/// the *same* claimed `op_id` with *different* content dedup by a
227/// content-deterministic tiebreak, not arrival order), but which forged
228/// record wins is meaningless — verification is what makes the answer mean
229/// something.
230pub fn fold(ops: &[OpRecord]) -> SyncState {
231 fold_onto(&SyncState::default(), ops)
232}
233
234/// Fold the op-set **as it stood at a past per-device seq frontier** — the
235/// state a branch would have forked from, without forking anything.
236///
237/// An op is included when its device appears in `frontier` and its `seq` is at
238/// or below that device's bound. A device absent from the frontier contributes
239/// nothing: the frontier describes what a reader had *seen*, and a device it
240/// had never heard from is correctly invisible rather than silently whole.
241///
242/// # What this is for
243///
244/// Counterfactual replay of the *state* suffix — "what did memory look like at
245/// F?", and the ability to assemble a context two ways and diff them. That is
246/// the evaluation primitive CAR is missing, and the reason it matters is
247/// recorded in CLAUDE.md: StateBench's per-track numbers carry a **±15pp noise
248/// floor**, so a per-track delta is uninterpretable on its own, and the only
249/// trustworthy check is to dump the assembled context both ways and compare. A
250/// re-run reintroduces stochastic and environmental variation unrelated to the
251/// edit; folding at a fixed frontier holds everything constant except the edit.
252/// See `docs/proposals/shepherd-substrate-adoption.md` (item 2) and
253/// `docs/proposals/oplog-branch-semantics.md` (Finding 2).
254///
255/// # What this is NOT
256///
257/// Not a branch. Nothing is written, nothing forks, and two callers folding at
258/// the same frontier get the same answer without coordinating. Writing a
259/// sibling chain that can later merge or discard needs a wire-format change, a
260/// digest change, branch-aware fold/checkpoint/compaction/relay/session, a
261/// five-surface FFI change, and a fleet-wide version floor — and the branch
262/// spec argues that side should stay gated, partly because a branch cannot hold
263/// an execution lease and so can speculate about state but not about tool
264/// calls. This function is the whole of the read side, and it needs none of it.
265///
266/// # Cost and equivalence
267///
268/// A filter plus the existing fold: no wire change, no digest change, no
269/// migration, and no new trust assumption. `fold_at(ops, &frontier_of(ops))`
270/// equals `fold(ops)`, and folding at a frontier is exactly folding the
271/// corresponding prefix — both pinned by tests.
272///
273/// Same input contract as [`fold`]: verify untrusted input with
274/// [`crate::oplog::verify_log`] first. Filtering by seq does not make an
275/// unverified log meaningful; it only bounds which of its records are read.
276pub fn fold_at(ops: &[OpRecord], frontier: &crate::relay::Frontier) -> SyncState {
277 let visible: Vec<OpRecord> = ops
278 .iter()
279 .filter(|op| {
280 frontier
281 .get(&op.device_id)
282 .is_some_and(|&bound| op.seq <= bound)
283 })
284 .cloned()
285 .collect();
286 fold(&visible)
287}
288
289/// Fold additional ops **onto an already-folded base state** — the B4
290/// checkpoint-consumption primitive: a truncated device reconstructs
291/// `fold(full log)` as `fold_onto(checkpoint.state, retained tail)`.
292///
293/// Uses the same per-key winner selection as [`fold`] (grow-only earliest
294/// `(hlc, op_id)` wins; registry latest wins; event streams keyed by
295/// `op_id`), so `fold_onto(fold(prefix), suffix) == fold(prefix ∪ suffix)`
296/// exactly — the equivalence that makes compaction safe, and the invariant
297/// the lib-level B4 tests pin per surface. Re-delivering an op already in
298/// the base is idempotent (equal `(hlc, op_id)` never displaces the slot).
299///
300/// Same input contract as [`fold`]: verify (via
301/// [`crate::oplog::verify_log`] / [`crate::checkpoint::verify_anchored`])
302/// before folding untrusted input. One caveat unique to invalid input: the
303/// base keeps only `FoldedRecord`s, so a forged op colliding with a
304/// *base* record's `op_id` cannot use the full-record content tiebreak
305/// [`fold`] applies within one op-set — verification is what makes the
306/// answer mean something.
307pub fn fold_onto(base: &SyncState, ops: &[OpRecord]) -> SyncState {
308 // Idempotence: a retransmitted op (same op_id) folds once. On an id
309 // collision with DIFFERENT content (invalid input — verify_log rejects
310 // it) the tiebreak must not depend on arrival order, so the
311 // lexicographically-smaller canonical serialization wins.
312 let canonical_record = |op: &OpRecord| -> String {
313 canonical_json(&serde_json::to_value(op).expect("OpRecord serializes"))
314 };
315 let mut unique: BTreeMap<&str, &OpRecord> = BTreeMap::new();
316 for op in ops {
317 unique
318 .entry(&op.op_id)
319 .and_modify(|existing| {
320 if *existing != op && canonical_record(op) < canonical_record(existing) {
321 *existing = op;
322 }
323 })
324 .or_insert(op);
325 }
326
327 let mut state = base.clone();
328
329 // Leased-tier pre-pass (B5): establish the FINAL per-agent `fencing_epoch`
330 // (max over the base and every new intent op) BEFORE the main loop, and
331 // evict base *pending* records that the raised fence makes stale — terminals
332 // (committed/failed) are immune and kept. Deciding pending-fencing against
333 // the final fence (not an intermediate one built up mid-loop) is what keeps
334 // the leased fold order-independent and base-composable
335 // (`fold_onto(checkpoint, tail) == fold(full)`).
336 {
337 let mut agent_max: BTreeMap<String, u64> = BTreeMap::new();
338 for op in unique.values() {
339 if op.surface.fold_tier() == FoldTier::Leased {
340 let slot = agent_max
341 .entry(crate::lease::intent_agent(&op.payload).to_string())
342 .or_insert(0);
343 *slot = (*slot).max(crate::lease::intent_epoch(&op.payload));
344 }
345 }
346 for (agent, max_epoch) in agent_max {
347 let entry = state.intents.entry(agent).or_default();
348 if max_epoch > entry.fencing_epoch {
349 entry.fencing_epoch = max_epoch;
350 entry.runs.retain(|_, r| intent_is_terminal(r)); // keep terminals, fence pendings
351 }
352 }
353 }
354
355 for op in unique.values() {
356 let record = FoldedRecord {
357 op_id: op.op_id.clone(),
358 hlc: op.hlc.clone(),
359 payload: op.payload.clone(),
360 };
361 match op.surface.fold_tier() {
362 FoldTier::GrowOnly => {
363 let slot = state
364 .logs
365 .entry(op.surface.tag())
366 .or_default()
367 .entry(op.fold_key());
368 slot.and_modify(|existing| {
369 // Immutable-entity union: earliest (hlc, op_id) wins.
370 // (Unreachable for event-stream surfaces — their fold_key
371 // IS the op_id, so a collision is the same op.)
372 if (&record.hlc, &record.op_id) < (&existing.hlc, &existing.op_id) {
373 *existing = record.clone();
374 }
375 })
376 .or_insert(record);
377 }
378 FoldTier::Registry => {
379 let slot = state
380 .registries
381 .entry(op.surface.tag())
382 .or_default()
383 .entry(op.fold_key());
384 slot.and_modify(|existing| {
385 // LWW: latest (hlc, op_id) wins.
386 if (&record.hlc, &record.op_id) > (&existing.hlc, &existing.op_id) {
387 *existing = record.clone();
388 }
389 })
390 .or_insert(record);
391 }
392 FoldTier::Leased => {
393 // `fencing_epoch` is already final for this agent (pre-pass).
394 let agent = crate::lease::intent_agent(&op.payload).to_string();
395 let epoch = crate::lease::intent_epoch(&op.payload);
396 let key = op.fold_key();
397 let is_terminal = crate::lease::intent_status_rank(&op.payload) == 1;
398 let is_committed = crate::lease::intent_is_committed(&op.payload);
399 let entry = state.intents.entry(agent).or_default();
400
401 // (A) The idempotency ORACLE: grow-only, fence-INDEPENDENT,
402 // keep-all. A commit is a permanent fact — recorded whatever
403 // its epoch, never cleared by the fence. Highest
404 // `(epoch, hlc, op_id)` committed record wins a collision.
405 if is_committed {
406 let better = entry.committed_runs.get(&key).is_none_or(|existing| {
407 intent_priority(&record) > intent_priority(existing)
408 });
409 if better {
410 entry.committed_runs.insert(key.clone(), record.clone());
411 }
412 }
413
414 // (B) The "who holds now" view: terminals are immune, pendings
415 // are fenced to `fencing_epoch`. Eligible = terminal (always)
416 // OR a live pending at the fence; below-fence pendings drop.
417 // The winner is `max` under `intent_priority`, so a terminal
418 // never reverts to a pending (terminal-immunity) and a
419 // stale-epoch pending never wins.
420 let eligible = is_terminal || epoch == entry.fencing_epoch;
421 if eligible {
422 let better = entry.runs.get(&key).is_none_or(|existing| {
423 intent_priority(&record) > intent_priority(existing)
424 });
425 if better {
426 entry.runs.insert(key, record);
427 }
428 }
429 }
430 }
431 }
432 state
433}
434
435/// Deterministic content hash of a folded state — the proposal's built-in
436/// divergence invariant: "Same frontier ⇒ same snapshot hash,
437/// deterministically. A mismatch is a fold bug or a non-determinism leak."
438/// B4's checkpoint hash is this value at a frontier.
439pub fn state_hash(state: &SyncState) -> String {
440 let value = serde_json::to_value(state).expect("SyncState serializes");
441 let mut hasher = Sha256::new();
442 hasher.update(canonical_json(&value).as_bytes());
443 let digest = hasher.finalize();
444 let hex: String = digest.iter().take(16).map(|b| format!("{b:02x}")).collect();
445 format!("state-{hex}")
446}
447
448/// Encode an [`Hlc`] as a single `u64` version that preserves the
449/// `(wall_ms, counter)` order — the bridge onto `car_state::crdt`'s
450/// `(version, replica)` total order. 44 bits of wall-clock milliseconds
451/// (good past year 2500) and 20 bits of counter; a counter ≥ 2^20 within one
452/// millisecond is outside the HLC's operating range (B3's clock guarantees
453/// far less) and would break the order-preservation, so it is debug-asserted.
454pub fn hlc_version(hlc: &Hlc) -> u64 {
455 debug_assert!(
456 hlc.counter < (1 << 20),
457 "HLC counter exceeds encoding range"
458 );
459 debug_assert!(
460 hlc.wall_ms < (1 << 44),
461 "HLC wall_ms exceeds encoding range (the << 20 would drop high bits in release)"
462 );
463 (hlc.wall_ms << 20) | (u64::from(hlc.counter) & 0xF_FFFF)
464}
465
466/// Project a folded registry surface onto the shipped
467/// [`car_state::crdt::LwwMap`], so the oplog fold composes with (and is
468/// testably equivalent to) `crdt_merge`/`crdt_export` where the domains
469/// overlap: `fold(union of ops)` ≡ `merge_maps(per-device exports)`.
470pub fn registry_as_lww(state: &SyncState, surface_tag: &str) -> LwwMap {
471 state
472 .registries
473 .get(surface_tag)
474 .map(|records| {
475 records
476 .iter()
477 .map(|(key, rec)| {
478 (
479 key.clone(),
480 LwwRegister::new(
481 rec.payload.clone(),
482 hlc_version(&rec.hlc),
483 rec.hlc.device_id.clone(),
484 ),
485 )
486 })
487 .collect()
488 })
489 .unwrap_or_default()
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495 use crate::lease::{Intent, IntentStatus};
496 use crate::oplog::{DeviceLog, Scope, Surface};
497 use serde_json::json;
498
499 /// Append a leased execution-intent op through a device log.
500 fn intent_op(
501 dev: &mut DeviceLog,
502 agent: &str,
503 run: &str,
504 epoch: u64,
505 status: IntentStatus,
506 ) -> OpRecord {
507 dev.append(
508 Scope::Personal,
509 Surface::Intent,
510 Intent::new(agent, run, epoch, status).payload(),
511 )
512 }
513
514 #[test]
515 fn fold_at_the_full_frontier_equals_fold() {
516 // The claim the doc makes about cost: fold_at is fold plus a filter, so
517 // at the log's own frontier it must be indistinguishable — including
518 // the state_hash, which is what a caller diffs.
519 let mut a = DeviceLog::new("a");
520 let mut b = DeviceLog::new("b");
521 let ops = vec![
522 a.append(
523 Scope::Personal,
524 Surface::Knowledge,
525 json!({"id": "f1", "v": 1}),
526 ),
527 b.append(
528 Scope::Personal,
529 Surface::Knowledge,
530 json!({"id": "f2", "v": 2}),
531 ),
532 a.append(
533 Scope::Personal,
534 Surface::Knowledge,
535 json!({"id": "f3", "v": 3}),
536 ),
537 ];
538 let full = crate::relay::frontier_of(&ops);
539 assert_eq!(state_hash(&fold_at(&ops, &full)), state_hash(&fold(&ops)));
540 }
541
542 #[test]
543 fn fold_at_a_past_frontier_equals_folding_the_prefix() {
544 // The equivalence that makes this a REPLAY rather than an approximation:
545 // folding at a frontier gives exactly the state that existed then. Two
546 // devices interleave, so a naive "first N ops" would differ from a
547 // per-device seq bound and this would catch it.
548 let mut a = DeviceLog::new("a");
549 let mut b = DeviceLog::new("b");
550 let a1 = a.append(
551 Scope::Personal,
552 Surface::Knowledge,
553 json!({"id": "f1", "v": 1}),
554 );
555 let b1 = b.append(
556 Scope::Personal,
557 Surface::Knowledge,
558 json!({"id": "f2", "v": 2}),
559 );
560 let a2 = a.append(
561 Scope::Personal,
562 Surface::Knowledge,
563 json!({"id": "f3", "v": 3}),
564 );
565 let b2 = b.append(
566 Scope::Personal,
567 Surface::Knowledge,
568 json!({"id": "f4", "v": 4}),
569 );
570 let all = vec![a1.clone(), b1.clone(), a2, b2];
571
572 // As of "a at seq 0, b at seq 0" — one op from each device.
573 let past = crate::relay::frontier_of(&[a1.clone(), b1.clone()]);
574 let prefix = vec![a1, b1];
575 assert_eq!(
576 state_hash(&fold_at(&all, &past)),
577 state_hash(&fold(&prefix)),
578 "folding at a frontier must equal folding that prefix"
579 );
580 let knowledge = &fold_at(&all, &past).logs[&Surface::Knowledge.tag()];
581 assert_eq!(
582 knowledge.len(),
583 2,
584 "later ops must be invisible: {knowledge:?}"
585 );
586 }
587
588 #[test]
589 fn fold_at_is_order_independent_like_fold() {
590 // fold's central property is order independence; the filter must not
591 // quietly reintroduce an arrival-order dependence.
592 let mut a = DeviceLog::new("a");
593 let mut b = DeviceLog::new("b");
594 let ops = vec![
595 a.append(
596 Scope::Personal,
597 Surface::Knowledge,
598 json!({"id": "f1", "v": 1}),
599 ),
600 b.append(
601 Scope::Personal,
602 Surface::Knowledge,
603 json!({"id": "f2", "v": 2}),
604 ),
605 a.append(
606 Scope::Personal,
607 Surface::Knowledge,
608 json!({"id": "f3", "v": 3}),
609 ),
610 ];
611 let f = crate::relay::frontier_of(&ops);
612 let mut shuffled = ops.clone();
613 shuffled.reverse();
614 assert_eq!(
615 state_hash(&fold_at(&ops, &f)),
616 state_hash(&fold_at(&shuffled, &f))
617 );
618 }
619
620 #[test]
621 fn fold_at_omits_devices_absent_from_the_frontier() {
622 // A frontier describes what a reader had SEEN. A device it never heard
623 // from must contribute nothing — treating "absent" as "unbounded" would
624 // silently fold in a whole device's history and make the answer larger
625 // than the moment being replayed.
626 let mut a = DeviceLog::new("a");
627 let mut b = DeviceLog::new("b");
628 let a1 = a.append(
629 Scope::Personal,
630 Surface::Knowledge,
631 json!({"id": "f1", "v": 1}),
632 );
633 let b1 = b.append(
634 Scope::Personal,
635 Surface::Knowledge,
636 json!({"id": "f2", "v": 2}),
637 );
638
639 let only_a = crate::relay::frontier_of(std::slice::from_ref(&a1));
640 let state = fold_at(&[a1, b1], &only_a);
641 let knowledge = &state.logs[&Surface::Knowledge.tag()];
642 assert_eq!(knowledge.len(), 1, "device b was never seen: {knowledge:?}");
643 assert!(knowledge.contains_key("id:f1"));
644 }
645
646 #[test]
647 fn fold_at_an_empty_frontier_is_the_empty_state() {
648 let mut a = DeviceLog::new("a");
649 let ops = vec![a.append(
650 Scope::Personal,
651 Surface::Knowledge,
652 json!({"id": "f1", "v": 1}),
653 )];
654 let empty = crate::relay::Frontier::new();
655 assert_eq!(
656 state_hash(&fold_at(&ops, &empty)),
657 state_hash(&SyncState::default())
658 );
659 }
660
661 #[test]
662 fn grow_only_unions_by_stable_key() {
663 let mut a = DeviceLog::new("a");
664 let mut b = DeviceLog::new("b");
665 let ops = vec![
666 a.append(
667 Scope::Personal,
668 Surface::Knowledge,
669 json!({"id": "f1", "v": 1}),
670 ),
671 b.append(
672 Scope::Personal,
673 Surface::Knowledge,
674 json!({"id": "f2", "v": 2}),
675 ),
676 ];
677 let state = fold(&ops);
678 let knowledge = &state.logs[&Surface::Knowledge.tag()];
679 assert_eq!(knowledge.len(), 2);
680 assert_eq!(knowledge["id:f1"].payload["v"], json!(1));
681 assert_eq!(knowledge["id:f2"].payload["v"], json!(2));
682 }
683
684 #[test]
685 fn grow_only_key_collision_resolves_to_earliest_deterministically() {
686 // Two devices emit different content under one stable id — an
687 // anomaly for the immutable tier, resolved first-writer-wins.
688 let mut a = DeviceLog::new("a");
689 let mut b = DeviceLog::new("b");
690 let oa = a.append(
691 Scope::Personal,
692 Surface::Knowledge,
693 json!({"id": "f", "v": "a"}),
694 );
695 b.observe(&oa.hlc); // b writes causally later
696 let ob = b.append(
697 Scope::Personal,
698 Surface::Knowledge,
699 json!({"id": "f", "v": "b"}),
700 );
701 let fwd = fold(&[oa.clone(), ob.clone()]);
702 let rev = fold(&[ob, oa]);
703 assert_eq!(fwd, rev);
704 assert_eq!(
705 fwd.logs[&Surface::Knowledge.tag()]["id:f"].payload["v"],
706 json!("a")
707 );
708 }
709
710 #[test]
711 fn registry_is_lww_per_record_not_per_file() {
712 let mut a = DeviceLog::new("a");
713 let mut b = DeviceLog::new("b");
714 // Concurrent edits to DIFFERENT agents both survive.
715 let oa = a.append(
716 Scope::Personal,
717 Surface::Declagent,
718 json!({"id": "x", "owner": "a"}),
719 );
720 let ob = b.append(
721 Scope::Personal,
722 Surface::Declagent,
723 json!({"id": "y", "owner": "b"}),
724 );
725 // Concurrent edits to the SAME agent resolve by HLC.
726 b.observe(&oa.hlc);
727 let ob2 = b.append(
728 Scope::Personal,
729 Surface::Declagent,
730 json!({"id": "x", "owner": "b"}),
731 );
732 let state = fold(&[oa, ob, ob2]);
733 let reg = &state.registries[&Surface::Declagent.tag()];
734 assert_eq!(reg.len(), 2, "both records survive");
735 assert_eq!(
736 reg["id:x"].payload["owner"],
737 json!("b"),
738 "later HLC wins the shared record"
739 );
740 assert_eq!(reg["id:y"].payload["owner"], json!("b"));
741 }
742
743 #[test]
744 fn registry_concurrent_tie_breaks_on_device_deterministically() {
745 // Same lamport stamp on two devices (true concurrency): the HLC's
746 // device_id component breaks the tie, both fold orders agree.
747 let mut a = DeviceLog::new("a");
748 let mut b = DeviceLog::new("b");
749 let oa = a.append(
750 Scope::Personal,
751 Surface::Declagent,
752 json!({"id": "x", "owner": "a"}),
753 );
754 let ob = b.append(
755 Scope::Personal,
756 Surface::Declagent,
757 json!({"id": "x", "owner": "b"}),
758 );
759 assert_eq!(oa.hlc.wall_ms, ob.hlc.wall_ms);
760 let fwd = fold(&[oa.clone(), ob.clone()]);
761 let rev = fold(&[ob, oa]);
762 assert_eq!(fwd, rev);
763 // "b" > "a" in the device tiebreak — matches crdt's replica tiebreak.
764 assert_eq!(
765 fwd.registries[&Surface::Declagent.tag()]["id:x"].payload["owner"],
766 json!("b")
767 );
768 }
769
770 #[test]
771 fn state_hash_detects_divergence_and_agrees_on_convergence() {
772 let mut a = DeviceLog::new("a");
773 let o1 = a.append(Scope::Personal, Surface::Knowledge, json!({"id": "f1"}));
774 let o2 = a.append(Scope::Personal, Surface::Knowledge, json!({"id": "f2"}));
775 let h_full = state_hash(&fold(&[o1.clone(), o2.clone()]));
776 let h_full_again = state_hash(&fold(&[o2.clone(), o1.clone()]));
777 assert_eq!(h_full, h_full_again, "same op-set → same hash");
778 let h_partial = state_hash(&fold(&[o1]));
779 assert_ne!(h_full, h_partial, "different op-set → different hash");
780 assert!(h_full.starts_with("state-"));
781 }
782
783 #[test]
784 fn hlc_version_preserves_order() {
785 let stamps = [
786 Hlc {
787 wall_ms: 1,
788 counter: 0,
789 device_id: "a".into(),
790 },
791 Hlc {
792 wall_ms: 1,
793 counter: 1,
794 device_id: "a".into(),
795 },
796 Hlc {
797 wall_ms: 2,
798 counter: 0,
799 device_id: "a".into(),
800 },
801 ];
802 for w in stamps.windows(2) {
803 assert!(hlc_version(&w[0]) < hlc_version(&w[1]));
804 }
805 }
806
807 #[test]
808 fn registry_as_lww_matches_crdt_merge_including_export_shape() {
809 // The equivalence the proposal leans on: per-device exports merged
810 // with the shipped crdt primitives == the fold of the op union.
811 let mut a = DeviceLog::new("dev-a");
812 let mut b = DeviceLog::new("dev-b");
813 let oa1 = a.append(
814 Scope::Personal,
815 Surface::Registry {
816 kind: "agents".into(),
817 },
818 json!({"id": "r1", "v": "a"}),
819 );
820 let oa2 = a.append(
821 Scope::Personal,
822 Surface::Registry {
823 kind: "agents".into(),
824 },
825 json!({"id": "r2", "v": "a"}),
826 );
827 b.observe(&oa1.hlc);
828 b.observe(&oa2.hlc);
829 let ob1 = b.append(
830 Scope::Personal,
831 Surface::Registry {
832 kind: "agents".into(),
833 },
834 json!({"id": "r1", "v": "b"}),
835 );
836
837 let tag = Surface::Registry {
838 kind: "agents".into(),
839 }
840 .tag();
841 let union = registry_as_lww(&fold(&[oa1.clone(), oa2.clone(), ob1.clone()]), &tag);
842 let export_a = registry_as_lww(&fold(&[oa1, oa2]), &tag);
843 let export_b = registry_as_lww(&fold(&[ob1]), &tag);
844
845 assert_eq!(car_state::crdt::merge_maps(&export_a, &export_b), union);
846 assert_eq!(car_state::crdt::merge_many(&[export_b, export_a]), union);
847 let plain = car_state::crdt::materialize(&union);
848 assert_eq!(plain["id:r1"]["v"], json!("b"));
849 assert_eq!(plain["id:r2"]["v"], json!("a"));
850 }
851
852 #[test]
853 fn log_entries_are_hlc_ordered() {
854 let mut a = DeviceLog::new("a");
855 let mut b = DeviceLog::new("b");
856 let o1 = a.append(
857 Scope::Personal,
858 Surface::Conversation,
859 json!({"t": "first"}),
860 );
861 b.observe(&o1.hlc);
862 let o2 = b.append(
863 Scope::Personal,
864 Surface::Conversation,
865 json!({"t": "second"}),
866 );
867 a.observe(&o2.hlc); // a's next write causally follows b's
868 let o3 = a.append(
869 Scope::Personal,
870 Surface::Conversation,
871 json!({"t": "third"}),
872 );
873 // Deliver out of order; the view is canonical.
874 let state = fold(&[o3, o1, o2]);
875 let texts: Vec<&Value> = state
876 .log_entries(&Surface::Conversation.tag())
877 .iter()
878 .map(|r| &r.payload["t"])
879 .collect();
880 assert_eq!(
881 texts,
882 vec![&json!("first"), &json!("second"), &json!("third")]
883 );
884 }
885
886 #[test]
887 fn routing_observations_fold_as_a_multiset() {
888 // The demonstrated kernel-review defect: "agent x succeeded" twice is
889 // TWO observations. Under content-keyed dedup the second collapsed
890 // into the first (1 entry, EMA 0.65); the proposal requires the
891 // merged MULTISET (2 entries, EMA 0.755).
892 let mut dev = DeviceLog::new("dev-a");
893 let ops = vec![
894 dev.append(Scope::Personal, Surface::Routing, json!({"sample": 1.0})),
895 dev.append(Scope::Personal, Surface::Routing, json!({"sample": 1.0})),
896 ];
897 let state = fold(&ops);
898 assert_eq!(
899 state.log_entries(&Surface::Routing.tag()).len(),
900 2,
901 "two byte-identical observations are two events"
902 );
903 let ema =
904 |s: f64, rec: &FoldedRecord| 0.7 * s + 0.3 * rec.payload["sample"].as_f64().unwrap();
905 let value = state.replay(&Surface::Routing.tag(), 0.5_f64, ema);
906 assert!(
907 (value - 0.755).abs() < 1e-12,
908 "EMA over both events: got {value}"
909 );
910 }
911
912 #[test]
913 fn logical_entity_surfaces_dedup_identical_content() {
914 // Content-keyed dedup is scoped to logical-ENTITY surfaces (knowledge,
915 // skills, …): the same fact emitted identically by two devices is ONE
916 // entity. (Conversation is NOT one of these — it's an event stream
917 // keyed by op_id — see the conversation module's CRIT-2 tests.)
918 let mut a = DeviceLog::new("a");
919 let mut b = DeviceLog::new("b");
920 let fact = json!({"kind": "note", "body": "the sky is blue"});
921 let oa = a.append(Scope::Personal, Surface::Knowledge, fact.clone());
922 let ob = b.append(Scope::Personal, Surface::Knowledge, fact);
923 let state = fold(&[oa, ob]);
924 assert_eq!(state.log_entries(&Surface::Knowledge.tag()).len(), 1);
925 }
926
927 #[test]
928 fn forged_colliding_op_id_dedups_order_independently() {
929 // Invalid input (verify_log rejects it), but the fold must stay
930 // order-independent: two records claiming the SAME op_id with
931 // DIFFERENT content tiebreak on content, not arrival order.
932 let mut dev = DeviceLog::new("d1");
933 let genuine = dev.append(
934 Scope::Personal,
935 Surface::Knowledge,
936 json!({"id": "f", "v": 1}),
937 );
938 let mut forged = genuine.clone();
939 forged.payload = json!({"id": "f", "v": 2}); // op_id NOT recomputed
940 assert!(crate::oplog::verify_log(&[forged.clone()]).is_err());
941
942 let ab = fold(&[genuine.clone(), forged.clone()]);
943 let ba = fold(&[forged, genuine]);
944 assert_eq!(
945 ab, ba,
946 "colliding-id dedup must not depend on arrival order"
947 );
948 assert_eq!(state_hash(&ab), state_hash(&ba));
949 }
950
951 #[test]
952 fn fold_onto_prefix_fold_equals_full_fold() {
953 // The B4 primitive: fold(prefix) then fold_onto(., suffix) must be
954 // byte-identical to fold(prefix ∪ suffix) — for every fold rule at
955 // once, including a grow-only collision and an LWW overwrite that
956 // CROSS the split point.
957 let mut a = DeviceLog::new("a");
958 let mut b = DeviceLog::new("b");
959 let prefix = vec![
960 a.append(
961 Scope::Personal,
962 Surface::Knowledge,
963 json!({"id": "f", "v": "old"}),
964 ),
965 a.append(
966 Scope::Personal,
967 Surface::Declagent,
968 json!({"id": "x", "owner": "a"}),
969 ),
970 a.append(Scope::Personal, Surface::Routing, json!({"sample": 1.0})),
971 ];
972 for op in &prefix {
973 b.observe(&op.hlc);
974 }
975 let suffix = vec![
976 // Grow-only collision across the split: earliest wins → "old".
977 b.append(
978 Scope::Personal,
979 Surface::Knowledge,
980 json!({"id": "f", "v": "new"}),
981 ),
982 // LWW across the split: latest wins → owner "b".
983 b.append(
984 Scope::Personal,
985 Surface::Declagent,
986 json!({"id": "x", "owner": "b"}),
987 ),
988 // Event stream across the split: both observations survive.
989 b.append(Scope::Personal, Surface::Routing, json!({"sample": 0.0})),
990 ];
991 let mut full = prefix.clone();
992 full.extend(suffix.iter().cloned());
993
994 let via_base = fold_onto(&fold(&prefix), &suffix);
995 assert_eq!(via_base, fold(&full));
996 assert_eq!(state_hash(&via_base), state_hash(&fold(&full)));
997 assert_eq!(
998 via_base.logs[&Surface::Knowledge.tag()]["id:f"].payload["v"],
999 json!("old")
1000 );
1001 assert_eq!(
1002 via_base.registries[&Surface::Declagent.tag()]["id:x"].payload["owner"],
1003 json!("b")
1004 );
1005 assert_eq!(via_base.log_entries(&Surface::Routing.tag()).len(), 2);
1006
1007 // Idempotent re-delivery: folding an op already in the base changes
1008 // nothing.
1009 assert_eq!(fold_onto(&via_base, &prefix), via_base);
1010 }
1011
1012 #[test]
1013 fn empty_fold_is_empty_and_stable() {
1014 let state = fold(&[]);
1015 assert_eq!(state, SyncState::default());
1016 assert_eq!(state_hash(&state), state_hash(&fold(&[])));
1017 assert!(state.log_entries("conversation").is_empty());
1018 assert!(registry_as_lww(&state, "declagent").is_empty());
1019 assert!(state.intent("milo", "R").is_none());
1020 assert!(state.fencing_epoch("milo").is_none());
1021 }
1022
1023 // ------------------------------------------------------------------
1024 // B5: leased execution-intent fencing as a deterministic fold property.
1025 // ------------------------------------------------------------------
1026
1027 #[test]
1028 fn intent_fold_fences_stale_epoch_order_independently() {
1029 // Failover: dev-a held epoch 1, dev-b stole epoch 2. Both fire the
1030 // SAME run R — a=zombie, b=legit holder. The fold must pick epoch 2
1031 // (b) in ANY delivery order and fence a's epoch-1 writes, leaving one
1032 // ledger record — no double-commit.
1033 let mut a = DeviceLog::new("dev-a");
1034 let mut b = DeviceLog::new("dev-b");
1035 let ops = vec![
1036 intent_op(&mut a, "milo", "R", 1, IntentStatus::Pending),
1037 intent_op(&mut a, "milo", "R", 1, IntentStatus::Committed),
1038 intent_op(&mut b, "milo", "R", 2, IntentStatus::Pending),
1039 intent_op(&mut b, "milo", "R", 2, IntentStatus::Committed),
1040 ];
1041 let b_commit = ops[3].clone();
1042
1043 let baseline = fold(&ops);
1044 assert_eq!(baseline.fencing_epoch("milo"), Some(2));
1045 assert_eq!(
1046 baseline.intents["milo"].runs.len(),
1047 1,
1048 "single record — no double-commit"
1049 );
1050 let winner = baseline.intent("milo", "R").expect("R survives");
1051 let decoded = Intent::from_payload(&winner.payload).unwrap();
1052 assert_eq!(
1053 (decoded.epoch, decoded.status),
1054 (2, IntentStatus::Committed)
1055 );
1056 assert_eq!(
1057 winner.op_id, b_commit.op_id,
1058 "the current holder's commit wins"
1059 );
1060
1061 // Order-independence: several explicit permutations agree exactly.
1062 for order in [
1063 vec![
1064 ops[3].clone(),
1065 ops[2].clone(),
1066 ops[1].clone(),
1067 ops[0].clone(),
1068 ],
1069 vec![
1070 ops[2].clone(),
1071 ops[0].clone(),
1072 ops[3].clone(),
1073 ops[1].clone(),
1074 ],
1075 vec![
1076 ops[1].clone(),
1077 ops[3].clone(),
1078 ops[0].clone(),
1079 ops[2].clone(),
1080 ],
1081 ] {
1082 assert_eq!(fold(&order), baseline);
1083 assert_eq!(state_hash(&fold(&order)), state_hash(&baseline));
1084 }
1085 }
1086
1087 #[test]
1088 fn intent_per_agent_pending_fencing_with_committed_immunity() {
1089 // Per-AGENT fencing applies to PENDINGS: dev-a (epoch 1) has an
1090 // unshared PENDING run S that dev-b (epoch 2) never touched → S's
1091 // pending is fenced (a stale holder's intent-to-do is silenced). But a
1092 // COMMITTED run is terminal-immune — a commit is a fact, not fenced —
1093 // so the zombie's unshared committed run K survives (the C1 fix: an
1094 // unrelated higher-epoch run must not evict it).
1095 let mut a = DeviceLog::new("dev-a");
1096 let mut b = DeviceLog::new("dev-b");
1097 let ops = vec![
1098 intent_op(&mut a, "milo", "S", 1, IntentStatus::Pending), // unshared zombie pending
1099 intent_op(&mut a, "milo", "K", 1, IntentStatus::Committed), // unshared zombie commit
1100 intent_op(&mut b, "milo", "T", 2, IntentStatus::Committed), // new holder, unrelated run
1101 ];
1102 let state = fold(&ops);
1103 assert_eq!(state.fencing_epoch("milo"), Some(2));
1104 // The unshared PENDING is fenced; it never committed.
1105 assert!(
1106 state.intent("milo", "S").is_none(),
1107 "unshared zombie pending is fenced"
1108 );
1109 assert!(
1110 state.committed_run("milo", "S").is_none(),
1111 "S never committed"
1112 );
1113 // The unshared COMMITTED run survives the unrelated epoch bump (C1).
1114 assert!(
1115 state.committed_run("milo", "K").is_some(),
1116 "committed run survives an unrelated epoch bump (idempotency oracle)"
1117 );
1118 assert!(
1119 state.intent("milo", "K").is_some(),
1120 "committed is terminal-immune in runs too"
1121 );
1122 assert!(state.committed_run("milo", "T").is_some());
1123 // A different agent is a different fencing group.
1124 let mut c = DeviceLog::new("dev-c");
1125 let mixed = {
1126 let mut v = ops.clone();
1127 v.push(intent_op(&mut c, "other", "U", 1, IntentStatus::Committed));
1128 v
1129 };
1130 assert!(
1131 fold(&mixed).committed_run("other", "U").is_some(),
1132 "fencing does not cross agents"
1133 );
1134 }
1135
1136 #[test]
1137 fn intent_fold_onto_equals_full_fold_across_an_epoch_bump() {
1138 // The compaction-safety equivalence for the leased tier across an epoch
1139 // bump: a checkpoint captured the agent at epoch 1 (COMMITTED run R). A
1140 // later, higher-epoch tail op (run S @ 2) raises the fence — and R,
1141 // being committed, is terminal-immune and SURVIVES (the C1/C3 fix; a
1142 // commit is a durable fact, not evicted by an unrelated bump). The
1143 // fold_onto == fold equivalence still holds exactly.
1144 let mut a = DeviceLog::new("dev-a");
1145 let mut b = DeviceLog::new("dev-b");
1146 let prefix = vec![intent_op(&mut a, "milo", "R", 1, IntentStatus::Committed)];
1147 for op in &prefix {
1148 b.observe(&op.hlc);
1149 }
1150 let tail = vec![intent_op(&mut b, "milo", "S", 2, IntentStatus::Committed)];
1151 let full = {
1152 let mut v = prefix.clone();
1153 v.extend(tail.iter().cloned());
1154 v
1155 };
1156
1157 let base = fold(&prefix); // the "checkpoint" state: epoch 1, R committed
1158 assert_eq!(base.fencing_epoch("milo"), Some(1));
1159 assert!(base.committed_run("milo", "R").is_some());
1160
1161 let via_base = fold_onto(&base, &tail);
1162 assert_eq!(
1163 via_base,
1164 fold(&full),
1165 "fold_onto == fold across the epoch bump"
1166 );
1167 assert_eq!(state_hash(&via_base), state_hash(&fold(&full)));
1168 assert_eq!(via_base.fencing_epoch("milo"), Some(2));
1169 // R committed@1 SURVIVES the bump in both views (terminal-immune / oracle).
1170 assert!(
1171 via_base.committed_run("milo", "R").is_some(),
1172 "committed R survives the epoch bump in the idempotency oracle"
1173 );
1174 assert!(
1175 via_base.intent("milo", "R").is_some(),
1176 "committed R is terminal-immune in runs"
1177 );
1178 assert!(via_base.committed_run("milo", "S").is_some());
1179 // Idempotent re-delivery of the tail changes nothing.
1180 assert_eq!(fold_onto(&via_base, &tail), via_base);
1181 }
1182
1183 #[test]
1184 fn intent_fencing_beats_a_later_hlc() {
1185 // Safety is by EPOCH, not wall clock: a zombie op with a LATER hlc but
1186 // a LOWER epoch still loses to the higher-epoch op — no wall-clock race.
1187 let mut cloud = DeviceLog::new("cloud");
1188 let mut zombie = DeviceLog::new("laptop");
1189 let c = intent_op(&mut cloud, "milo", "R", 2, IntentStatus::Committed);
1190 zombie.observe(&c.hlc); // the zombie's later write stamps a HIGHER hlc
1191 let z = intent_op(&mut zombie, "milo", "R", 1, IntentStatus::Committed);
1192 assert!(z.hlc > c.hlc, "the zombie op is later in HLC");
1193
1194 let state = fold(&[c.clone(), z]);
1195 let winner = state.intent("milo", "R").unwrap();
1196 assert_eq!(winner.op_id, c.op_id, "higher epoch wins despite lower HLC");
1197 assert_eq!(state.fencing_epoch("milo"), Some(2));
1198 }
1199
1200 #[test]
1201 fn idempotent_run_under_failover_uses_the_same_deterministic_run_id() {
1202 // B7 tie-in: two sites computing the same scheduled occurrence derive
1203 // the SAME run_id, so a failed-over holder and a zombie collapse to ONE
1204 // ledger record; epoch fencing then picks the legit (epoch-2) winner.
1205 let run_id = car_proto::deterministic_run_id("milo", "3am digest", "2026-07-02T03:00");
1206 assert_eq!(
1207 run_id,
1208 car_proto::deterministic_run_id("milo", "3am digest", "2026-07-02T03:00"),
1209 "same occurrence → same run_id"
1210 );
1211 let mut zombie = DeviceLog::new("laptop");
1212 let mut cloud = DeviceLog::new("cloud");
1213 let z = intent_op(&mut zombie, "milo", &run_id, 1, IntentStatus::Committed);
1214 let c = intent_op(&mut cloud, "milo", &run_id, 2, IntentStatus::Committed);
1215
1216 let state = fold(&[z, c.clone()]);
1217 assert_eq!(
1218 state.intents["milo"].runs.len(),
1219 1,
1220 "exactly one execution record"
1221 );
1222 assert_eq!(
1223 state.intent("milo", &run_id).unwrap().op_id,
1224 c.op_id,
1225 "the epoch-2 holder's run wins; the zombie is a no-op"
1226 );
1227 }
1228
1229 #[test]
1230 fn intent_fold_is_order_independent_over_every_permutation() {
1231 // Brute-force the redesigned leased fold (pre-pass + terminal-immunity
1232 // + committed oracle): a 5-op set mixing committed/pending across two
1233 // epochs and three runs must fold IDENTICALLY in all 120 orders.
1234 fn permutations<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
1235 fn heap<T: Clone>(k: usize, arr: &mut Vec<T>, out: &mut Vec<Vec<T>>) {
1236 if k == 1 {
1237 out.push(arr.clone());
1238 return;
1239 }
1240 for i in 0..k {
1241 heap(k - 1, arr, out);
1242 if k.is_multiple_of(2) {
1243 arr.swap(i, k - 1);
1244 } else {
1245 arr.swap(0, k - 1);
1246 }
1247 }
1248 }
1249 let mut arr = items.to_vec();
1250 let mut out = Vec::new();
1251 heap(arr.len(), &mut arr, &mut out);
1252 out
1253 }
1254
1255 let mut a = DeviceLog::new("a");
1256 let mut b = DeviceLog::new("b");
1257 let ops = vec![
1258 intent_op(&mut a, "milo", "R", 1, IntentStatus::Committed), // terminal-immune across bump
1259 intent_op(&mut a, "milo", "S", 1, IntentStatus::Pending), // unshared pending → fenced
1260 intent_op(&mut a, "milo", "T", 1, IntentStatus::Committed), // unshared committed → survives
1261 intent_op(&mut b, "milo", "R", 2, IntentStatus::Pending), // C3: must not revert R
1262 intent_op(&mut b, "milo", "S", 2, IntentStatus::Committed), // S commits at the higher epoch
1263 ];
1264 let baseline = fold(&ops);
1265 // Expected steady state.
1266 assert_eq!(baseline.fencing_epoch("milo"), Some(2));
1267 let mut committed = baseline.committed_run_ids("milo");
1268 committed.sort();
1269 assert_eq!(
1270 committed,
1271 vec!["R", "S", "T"],
1272 "the oracle keeps every committed run"
1273 );
1274 assert!(baseline
1275 .intent("milo", "S")
1276 .map(|r| r.op_id.clone())
1277 .is_some_and(|_| {
1278 Intent::from_payload(&baseline.intent("milo", "S").unwrap().payload)
1279 .unwrap()
1280 .status
1281 == IntentStatus::Committed
1282 }));
1283 assert_eq!(
1284 Intent::from_payload(&baseline.intent("milo", "R").unwrap().payload)
1285 .unwrap()
1286 .status,
1287 IntentStatus::Committed,
1288 "R is not reverted to pending"
1289 );
1290
1291 for perm in permutations(&ops) {
1292 assert_eq!(
1293 fold(&perm),
1294 baseline,
1295 "leased fold must be order-independent"
1296 );
1297 assert_eq!(state_hash(&fold(&perm)), state_hash(&baseline));
1298 }
1299 }
1300
1301 #[test]
1302 fn c1_committed_run_survives_an_unrelated_higher_epoch_run() {
1303 // C1 REPRO: run R commits at epoch 1; later an UNRELATED run T lands at
1304 // epoch 2 for the same agent (no concurrency). The old fold cleared
1305 // runs on the bump, so intent(R) → None → the idempotency check said
1306 // "not run" → double-execution. FIX: the fence-INDEPENDENT
1307 // committed_run oracle answers correctly regardless of the bump.
1308 let mut a = DeviceLog::new("dev-a");
1309 let mut b = DeviceLog::new("dev-b");
1310 let r_commit = intent_op(&mut a, "milo", "R", 1, IntentStatus::Committed);
1311 b.observe(&r_commit.hlc);
1312 let t_pending = intent_op(&mut b, "milo", "T", 2, IntentStatus::Pending);
1313
1314 let state = fold(&[r_commit.clone(), t_pending]);
1315 assert_eq!(
1316 state.fencing_epoch("milo"),
1317 Some(2),
1318 "the unrelated run bumped the fence"
1319 );
1320 // The oracle still says R committed — the correct idempotency answer.
1321 assert_eq!(
1322 state.committed_run("milo", "R").unwrap().op_id,
1323 r_commit.op_id,
1324 "committed_run(R) survives the unrelated epoch bump (C1 fixed)"
1325 );
1326 assert_eq!(state.committed_run_ids("milo"), vec!["R"]);
1327 }
1328
1329 #[test]
1330 fn c3_committed_then_pending_across_a_bump_stays_committed() {
1331 // C3 REPRO: R commits at epoch 1; a failed-over holder writes R PENDING
1332 // at epoch 2 (before checking). The old Greater arm unconditionally
1333 // cleared, reverting R to pending → looked un-run → double-execute.
1334 // FIX: terminal-immunity — the fold keeps committed for R in BOTH views,
1335 // in any delivery order.
1336 let mut orig = DeviceLog::new("orig");
1337 let mut failover = DeviceLog::new("failover");
1338 let committed = intent_op(&mut orig, "milo", "R", 1, IntentStatus::Committed);
1339 failover.observe(&committed.hlc);
1340 let late_pending = intent_op(&mut failover, "milo", "R", 2, IntentStatus::Pending);
1341 assert!(
1342 late_pending.hlc > committed.hlc,
1343 "the pending is even later in HLC"
1344 );
1345
1346 for order in [
1347 vec![committed.clone(), late_pending.clone()],
1348 vec![late_pending.clone(), committed.clone()],
1349 ] {
1350 let state = fold(&order);
1351 // Oracle: committed, unconditionally.
1352 assert_eq!(
1353 state.committed_run("milo", "R").unwrap().op_id,
1354 committed.op_id,
1355 "committed stays committed across the bump (oracle)"
1356 );
1357 // who-holds view: terminal-immune, still committed (not reverted).
1358 let decoded =
1359 Intent::from_payload(&state.intent("milo", "R").unwrap().payload).unwrap();
1360 assert_eq!(
1361 decoded.status,
1362 IntentStatus::Committed,
1363 "runs view is not reverted to pending"
1364 );
1365 }
1366 }
1367}