pub use crate::oplog::FoldTier;
use crate::oplog::{canonical_json, Hlc, OpRecord};
use car_state::crdt::{LwwMap, LwwRegister};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sha2::{Digest, Sha256};
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FoldedRecord {
pub op_id: String,
pub hlc: Hlc,
pub payload: Value,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct IntentAgent {
pub fencing_epoch: u64,
pub runs: BTreeMap<String, FoldedRecord>,
#[serde(default)]
pub committed_runs: BTreeMap<String, FoldedRecord>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct SyncState {
pub logs: BTreeMap<String, BTreeMap<String, FoldedRecord>>,
pub registries: BTreeMap<String, BTreeMap<String, FoldedRecord>>,
#[serde(default)]
pub intents: BTreeMap<String, IntentAgent>,
}
impl SyncState {
pub fn log_entries(&self, surface_tag: &str) -> Vec<&FoldedRecord> {
let mut entries: Vec<&FoldedRecord> = self
.logs
.get(surface_tag)
.map(|m| m.values().collect())
.unwrap_or_default();
entries.sort_by(|a, b| (&a.hlc, &a.op_id).cmp(&(&b.hlc, &b.op_id)));
entries
}
pub fn replay<T, F>(&self, surface_tag: &str, init: T, apply: F) -> T
where
F: FnMut(T, &FoldedRecord) -> T,
{
self.log_entries(surface_tag).into_iter().fold(init, apply)
}
pub fn intent(&self, agent_id: &str, run_id: &str) -> Option<&FoldedRecord> {
self.intents.get(agent_id)?.runs.get(&format!("id:{run_id}"))
}
pub fn committed_run(&self, agent_id: &str, run_id: &str) -> Option<&FoldedRecord> {
self.intents
.get(agent_id)?
.committed_runs
.get(&format!("id:{run_id}"))
}
pub fn committed_run_ids(&self, agent_id: &str) -> Vec<&str> {
self.intents
.get(agent_id)
.map(|a| {
a.committed_runs
.keys()
.filter_map(|k| k.strip_prefix("id:"))
.collect()
})
.unwrap_or_default()
}
pub fn fencing_epoch(&self, agent_id: &str) -> Option<u64> {
self.intents.get(agent_id).map(|a| a.fencing_epoch)
}
}
fn intent_is_terminal(rec: &FoldedRecord) -> bool {
crate::lease::intent_status_rank(&rec.payload) == 1
}
fn intent_priority(rec: &FoldedRecord) -> (u8, u64, &Hlc, &str) {
(
crate::lease::intent_status_rank(&rec.payload),
crate::lease::intent_epoch(&rec.payload),
&rec.hlc,
rec.op_id.as_str(),
)
}
pub fn fold(ops: &[OpRecord]) -> SyncState {
fold_onto(&SyncState::default(), ops)
}
pub fn fold_onto(base: &SyncState, ops: &[OpRecord]) -> SyncState {
let canonical_record = |op: &OpRecord| -> String {
canonical_json(&serde_json::to_value(op).expect("OpRecord serializes"))
};
let mut unique: BTreeMap<&str, &OpRecord> = BTreeMap::new();
for op in ops {
unique
.entry(&op.op_id)
.and_modify(|existing| {
if *existing != op && canonical_record(op) < canonical_record(existing) {
*existing = op;
}
})
.or_insert(op);
}
let mut state = base.clone();
{
let mut agent_max: BTreeMap<String, u64> = BTreeMap::new();
for op in unique.values() {
if op.surface.fold_tier() == FoldTier::Leased {
let slot = agent_max
.entry(crate::lease::intent_agent(&op.payload).to_string())
.or_insert(0);
*slot = (*slot).max(crate::lease::intent_epoch(&op.payload));
}
}
for (agent, max_epoch) in agent_max {
let entry = state.intents.entry(agent).or_default();
if max_epoch > entry.fencing_epoch {
entry.fencing_epoch = max_epoch;
entry.runs.retain(|_, r| intent_is_terminal(r)); }
}
}
for op in unique.values() {
let record = FoldedRecord {
op_id: op.op_id.clone(),
hlc: op.hlc.clone(),
payload: op.payload.clone(),
};
match op.surface.fold_tier() {
FoldTier::GrowOnly => {
let slot = state
.logs
.entry(op.surface.tag())
.or_default()
.entry(op.fold_key());
slot.and_modify(|existing| {
if (&record.hlc, &record.op_id) < (&existing.hlc, &existing.op_id) {
*existing = record.clone();
}
})
.or_insert(record);
}
FoldTier::Registry => {
let slot = state
.registries
.entry(op.surface.tag())
.or_default()
.entry(op.fold_key());
slot.and_modify(|existing| {
if (&record.hlc, &record.op_id) > (&existing.hlc, &existing.op_id) {
*existing = record.clone();
}
})
.or_insert(record);
}
FoldTier::Leased => {
let agent = crate::lease::intent_agent(&op.payload).to_string();
let epoch = crate::lease::intent_epoch(&op.payload);
let key = op.fold_key();
let is_terminal = crate::lease::intent_status_rank(&op.payload) == 1;
let is_committed = crate::lease::intent_is_committed(&op.payload);
let entry = state.intents.entry(agent).or_default();
if is_committed {
let better = entry
.committed_runs
.get(&key)
.is_none_or(|existing| intent_priority(&record) > intent_priority(existing));
if better {
entry.committed_runs.insert(key.clone(), record.clone());
}
}
let eligible = is_terminal || epoch == entry.fencing_epoch;
if eligible {
let better = entry
.runs
.get(&key)
.is_none_or(|existing| intent_priority(&record) > intent_priority(existing));
if better {
entry.runs.insert(key, record);
}
}
}
}
}
state
}
pub fn state_hash(state: &SyncState) -> String {
let value = serde_json::to_value(state).expect("SyncState serializes");
let mut hasher = Sha256::new();
hasher.update(canonical_json(&value).as_bytes());
let digest = hasher.finalize();
let hex: String = digest.iter().take(16).map(|b| format!("{b:02x}")).collect();
format!("state-{hex}")
}
pub fn hlc_version(hlc: &Hlc) -> u64 {
debug_assert!(hlc.counter < (1 << 20), "HLC counter exceeds encoding range");
debug_assert!(
hlc.wall_ms < (1 << 44),
"HLC wall_ms exceeds encoding range (the << 20 would drop high bits in release)"
);
(hlc.wall_ms << 20) | (u64::from(hlc.counter) & 0xF_FFFF)
}
pub fn registry_as_lww(state: &SyncState, surface_tag: &str) -> LwwMap {
state
.registries
.get(surface_tag)
.map(|records| {
records
.iter()
.map(|(key, rec)| {
(
key.clone(),
LwwRegister::new(
rec.payload.clone(),
hlc_version(&rec.hlc),
rec.hlc.device_id.clone(),
),
)
})
.collect()
})
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lease::{Intent, IntentStatus};
use crate::oplog::{DeviceLog, Scope, Surface};
use serde_json::json;
fn intent_op(
dev: &mut DeviceLog,
agent: &str,
run: &str,
epoch: u64,
status: IntentStatus,
) -> OpRecord {
dev.append(
Scope::Personal,
Surface::Intent,
Intent::new(agent, run, epoch, status).payload(),
)
}
#[test]
fn grow_only_unions_by_stable_key() {
let mut a = DeviceLog::new("a");
let mut b = DeviceLog::new("b");
let ops = vec![
a.append(Scope::Personal, Surface::Knowledge, json!({"id": "f1", "v": 1})),
b.append(Scope::Personal, Surface::Knowledge, json!({"id": "f2", "v": 2})),
];
let state = fold(&ops);
let knowledge = &state.logs[&Surface::Knowledge.tag()];
assert_eq!(knowledge.len(), 2);
assert_eq!(knowledge["id:f1"].payload["v"], json!(1));
assert_eq!(knowledge["id:f2"].payload["v"], json!(2));
}
#[test]
fn grow_only_key_collision_resolves_to_earliest_deterministically() {
let mut a = DeviceLog::new("a");
let mut b = DeviceLog::new("b");
let oa = a.append(Scope::Personal, Surface::Knowledge, json!({"id": "f", "v": "a"}));
b.observe(&oa.hlc); let ob = b.append(Scope::Personal, Surface::Knowledge, json!({"id": "f", "v": "b"}));
let fwd = fold(&[oa.clone(), ob.clone()]);
let rev = fold(&[ob, oa]);
assert_eq!(fwd, rev);
assert_eq!(fwd.logs[&Surface::Knowledge.tag()]["id:f"].payload["v"], json!("a"));
}
#[test]
fn registry_is_lww_per_record_not_per_file() {
let mut a = DeviceLog::new("a");
let mut b = DeviceLog::new("b");
let oa = a.append(Scope::Personal, Surface::Declagent, json!({"id": "x", "owner": "a"}));
let ob = b.append(Scope::Personal, Surface::Declagent, json!({"id": "y", "owner": "b"}));
b.observe(&oa.hlc);
let ob2 = b.append(Scope::Personal, Surface::Declagent, json!({"id": "x", "owner": "b"}));
let state = fold(&[oa, ob, ob2]);
let reg = &state.registries[&Surface::Declagent.tag()];
assert_eq!(reg.len(), 2, "both records survive");
assert_eq!(reg["id:x"].payload["owner"], json!("b"), "later HLC wins the shared record");
assert_eq!(reg["id:y"].payload["owner"], json!("b"));
}
#[test]
fn registry_concurrent_tie_breaks_on_device_deterministically() {
let mut a = DeviceLog::new("a");
let mut b = DeviceLog::new("b");
let oa = a.append(Scope::Personal, Surface::Declagent, json!({"id": "x", "owner": "a"}));
let ob = b.append(Scope::Personal, Surface::Declagent, json!({"id": "x", "owner": "b"}));
assert_eq!(oa.hlc.wall_ms, ob.hlc.wall_ms);
let fwd = fold(&[oa.clone(), ob.clone()]);
let rev = fold(&[ob, oa]);
assert_eq!(fwd, rev);
assert_eq!(
fwd.registries[&Surface::Declagent.tag()]["id:x"].payload["owner"],
json!("b")
);
}
#[test]
fn state_hash_detects_divergence_and_agrees_on_convergence() {
let mut a = DeviceLog::new("a");
let o1 = a.append(Scope::Personal, Surface::Knowledge, json!({"id": "f1"}));
let o2 = a.append(Scope::Personal, Surface::Knowledge, json!({"id": "f2"}));
let h_full = state_hash(&fold(&[o1.clone(), o2.clone()]));
let h_full_again = state_hash(&fold(&[o2.clone(), o1.clone()]));
assert_eq!(h_full, h_full_again, "same op-set → same hash");
let h_partial = state_hash(&fold(&[o1]));
assert_ne!(h_full, h_partial, "different op-set → different hash");
assert!(h_full.starts_with("state-"));
}
#[test]
fn hlc_version_preserves_order() {
let stamps = [
Hlc { wall_ms: 1, counter: 0, device_id: "a".into() },
Hlc { wall_ms: 1, counter: 1, device_id: "a".into() },
Hlc { wall_ms: 2, counter: 0, device_id: "a".into() },
];
for w in stamps.windows(2) {
assert!(hlc_version(&w[0]) < hlc_version(&w[1]));
}
}
#[test]
fn registry_as_lww_matches_crdt_merge_including_export_shape() {
let mut a = DeviceLog::new("dev-a");
let mut b = DeviceLog::new("dev-b");
let oa1 = a.append(Scope::Personal, Surface::Registry { kind: "agents".into() }, json!({"id": "r1", "v": "a"}));
let oa2 = a.append(Scope::Personal, Surface::Registry { kind: "agents".into() }, json!({"id": "r2", "v": "a"}));
b.observe(&oa1.hlc);
b.observe(&oa2.hlc);
let ob1 = b.append(Scope::Personal, Surface::Registry { kind: "agents".into() }, json!({"id": "r1", "v": "b"}));
let tag = Surface::Registry { kind: "agents".into() }.tag();
let union = registry_as_lww(&fold(&[oa1.clone(), oa2.clone(), ob1.clone()]), &tag);
let export_a = registry_as_lww(&fold(&[oa1, oa2]), &tag);
let export_b = registry_as_lww(&fold(&[ob1]), &tag);
assert_eq!(car_state::crdt::merge_maps(&export_a, &export_b), union);
assert_eq!(car_state::crdt::merge_many(&[export_b, export_a]), union);
let plain = car_state::crdt::materialize(&union);
assert_eq!(plain["id:r1"]["v"], json!("b"));
assert_eq!(plain["id:r2"]["v"], json!("a"));
}
#[test]
fn log_entries_are_hlc_ordered() {
let mut a = DeviceLog::new("a");
let mut b = DeviceLog::new("b");
let o1 = a.append(Scope::Personal, Surface::Conversation, json!({"t": "first"}));
b.observe(&o1.hlc);
let o2 = b.append(Scope::Personal, Surface::Conversation, json!({"t": "second"}));
a.observe(&o2.hlc); let o3 = a.append(Scope::Personal, Surface::Conversation, json!({"t": "third"}));
let state = fold(&[o3, o1, o2]);
let texts: Vec<&Value> = state
.log_entries(&Surface::Conversation.tag())
.iter()
.map(|r| &r.payload["t"])
.collect();
assert_eq!(texts, vec![&json!("first"), &json!("second"), &json!("third")]);
}
#[test]
fn routing_observations_fold_as_a_multiset() {
let mut dev = DeviceLog::new("dev-a");
let ops = vec![
dev.append(Scope::Personal, Surface::Routing, json!({"sample": 1.0})),
dev.append(Scope::Personal, Surface::Routing, json!({"sample": 1.0})),
];
let state = fold(&ops);
assert_eq!(
state.log_entries(&Surface::Routing.tag()).len(),
2,
"two byte-identical observations are two events"
);
let ema = |s: f64, rec: &FoldedRecord| 0.7 * s + 0.3 * rec.payload["sample"].as_f64().unwrap();
let value = state.replay(&Surface::Routing.tag(), 0.5_f64, ema);
assert!((value - 0.755).abs() < 1e-12, "EMA over both events: got {value}");
}
#[test]
fn logical_entity_surfaces_dedup_identical_content() {
let mut a = DeviceLog::new("a");
let mut b = DeviceLog::new("b");
let fact = json!({"kind": "note", "body": "the sky is blue"});
let oa = a.append(Scope::Personal, Surface::Knowledge, fact.clone());
let ob = b.append(Scope::Personal, Surface::Knowledge, fact);
let state = fold(&[oa, ob]);
assert_eq!(state.log_entries(&Surface::Knowledge.tag()).len(), 1);
}
#[test]
fn forged_colliding_op_id_dedups_order_independently() {
let mut dev = DeviceLog::new("d1");
let genuine = dev.append(Scope::Personal, Surface::Knowledge, json!({"id": "f", "v": 1}));
let mut forged = genuine.clone();
forged.payload = json!({"id": "f", "v": 2}); assert!(crate::oplog::verify_log(&[forged.clone()]).is_err());
let ab = fold(&[genuine.clone(), forged.clone()]);
let ba = fold(&[forged, genuine]);
assert_eq!(ab, ba, "colliding-id dedup must not depend on arrival order");
assert_eq!(state_hash(&ab), state_hash(&ba));
}
#[test]
fn fold_onto_prefix_fold_equals_full_fold() {
let mut a = DeviceLog::new("a");
let mut b = DeviceLog::new("b");
let prefix = vec![
a.append(Scope::Personal, Surface::Knowledge, json!({"id": "f", "v": "old"})),
a.append(Scope::Personal, Surface::Declagent, json!({"id": "x", "owner": "a"})),
a.append(Scope::Personal, Surface::Routing, json!({"sample": 1.0})),
];
for op in &prefix {
b.observe(&op.hlc);
}
let suffix = vec![
b.append(Scope::Personal, Surface::Knowledge, json!({"id": "f", "v": "new"})),
b.append(Scope::Personal, Surface::Declagent, json!({"id": "x", "owner": "b"})),
b.append(Scope::Personal, Surface::Routing, json!({"sample": 0.0})),
];
let mut full = prefix.clone();
full.extend(suffix.iter().cloned());
let via_base = fold_onto(&fold(&prefix), &suffix);
assert_eq!(via_base, fold(&full));
assert_eq!(state_hash(&via_base), state_hash(&fold(&full)));
assert_eq!(via_base.logs[&Surface::Knowledge.tag()]["id:f"].payload["v"], json!("old"));
assert_eq!(
via_base.registries[&Surface::Declagent.tag()]["id:x"].payload["owner"],
json!("b")
);
assert_eq!(via_base.log_entries(&Surface::Routing.tag()).len(), 2);
assert_eq!(fold_onto(&via_base, &prefix), via_base);
}
#[test]
fn empty_fold_is_empty_and_stable() {
let state = fold(&[]);
assert_eq!(state, SyncState::default());
assert_eq!(state_hash(&state), state_hash(&fold(&[])));
assert!(state.log_entries("conversation").is_empty());
assert!(registry_as_lww(&state, "declagent").is_empty());
assert!(state.intent("milo", "R").is_none());
assert!(state.fencing_epoch("milo").is_none());
}
#[test]
fn intent_fold_fences_stale_epoch_order_independently() {
let mut a = DeviceLog::new("dev-a");
let mut b = DeviceLog::new("dev-b");
let ops = vec![
intent_op(&mut a, "milo", "R", 1, IntentStatus::Pending),
intent_op(&mut a, "milo", "R", 1, IntentStatus::Committed),
intent_op(&mut b, "milo", "R", 2, IntentStatus::Pending),
intent_op(&mut b, "milo", "R", 2, IntentStatus::Committed),
];
let b_commit = ops[3].clone();
let baseline = fold(&ops);
assert_eq!(baseline.fencing_epoch("milo"), Some(2));
assert_eq!(baseline.intents["milo"].runs.len(), 1, "single record — no double-commit");
let winner = baseline.intent("milo", "R").expect("R survives");
let decoded = Intent::from_payload(&winner.payload).unwrap();
assert_eq!((decoded.epoch, decoded.status), (2, IntentStatus::Committed));
assert_eq!(winner.op_id, b_commit.op_id, "the current holder's commit wins");
for order in [
vec![ops[3].clone(), ops[2].clone(), ops[1].clone(), ops[0].clone()],
vec![ops[2].clone(), ops[0].clone(), ops[3].clone(), ops[1].clone()],
vec![ops[1].clone(), ops[3].clone(), ops[0].clone(), ops[2].clone()],
] {
assert_eq!(fold(&order), baseline);
assert_eq!(state_hash(&fold(&order)), state_hash(&baseline));
}
}
#[test]
fn intent_per_agent_pending_fencing_with_committed_immunity() {
let mut a = DeviceLog::new("dev-a");
let mut b = DeviceLog::new("dev-b");
let ops = vec![
intent_op(&mut a, "milo", "S", 1, IntentStatus::Pending), intent_op(&mut a, "milo", "K", 1, IntentStatus::Committed), intent_op(&mut b, "milo", "T", 2, IntentStatus::Committed), ];
let state = fold(&ops);
assert_eq!(state.fencing_epoch("milo"), Some(2));
assert!(state.intent("milo", "S").is_none(), "unshared zombie pending is fenced");
assert!(state.committed_run("milo", "S").is_none(), "S never committed");
assert!(
state.committed_run("milo", "K").is_some(),
"committed run survives an unrelated epoch bump (idempotency oracle)"
);
assert!(state.intent("milo", "K").is_some(), "committed is terminal-immune in runs too");
assert!(state.committed_run("milo", "T").is_some());
let mut c = DeviceLog::new("dev-c");
let mixed = {
let mut v = ops.clone();
v.push(intent_op(&mut c, "other", "U", 1, IntentStatus::Committed));
v
};
assert!(
fold(&mixed).committed_run("other", "U").is_some(),
"fencing does not cross agents"
);
}
#[test]
fn intent_fold_onto_equals_full_fold_across_an_epoch_bump() {
let mut a = DeviceLog::new("dev-a");
let mut b = DeviceLog::new("dev-b");
let prefix = vec![intent_op(&mut a, "milo", "R", 1, IntentStatus::Committed)];
for op in &prefix {
b.observe(&op.hlc);
}
let tail = vec![intent_op(&mut b, "milo", "S", 2, IntentStatus::Committed)];
let full = {
let mut v = prefix.clone();
v.extend(tail.iter().cloned());
v
};
let base = fold(&prefix); assert_eq!(base.fencing_epoch("milo"), Some(1));
assert!(base.committed_run("milo", "R").is_some());
let via_base = fold_onto(&base, &tail);
assert_eq!(via_base, fold(&full), "fold_onto == fold across the epoch bump");
assert_eq!(state_hash(&via_base), state_hash(&fold(&full)));
assert_eq!(via_base.fencing_epoch("milo"), Some(2));
assert!(
via_base.committed_run("milo", "R").is_some(),
"committed R survives the epoch bump in the idempotency oracle"
);
assert!(via_base.intent("milo", "R").is_some(), "committed R is terminal-immune in runs");
assert!(via_base.committed_run("milo", "S").is_some());
assert_eq!(fold_onto(&via_base, &tail), via_base);
}
#[test]
fn intent_fencing_beats_a_later_hlc() {
let mut cloud = DeviceLog::new("cloud");
let mut zombie = DeviceLog::new("laptop");
let c = intent_op(&mut cloud, "milo", "R", 2, IntentStatus::Committed);
zombie.observe(&c.hlc); let z = intent_op(&mut zombie, "milo", "R", 1, IntentStatus::Committed);
assert!(z.hlc > c.hlc, "the zombie op is later in HLC");
let state = fold(&[c.clone(), z]);
let winner = state.intent("milo", "R").unwrap();
assert_eq!(winner.op_id, c.op_id, "higher epoch wins despite lower HLC");
assert_eq!(state.fencing_epoch("milo"), Some(2));
}
#[test]
fn idempotent_run_under_failover_uses_the_same_deterministic_run_id() {
let run_id = car_proto::deterministic_run_id("milo", "3am digest", "2026-07-02T03:00");
assert_eq!(
run_id,
car_proto::deterministic_run_id("milo", "3am digest", "2026-07-02T03:00"),
"same occurrence → same run_id"
);
let mut zombie = DeviceLog::new("laptop");
let mut cloud = DeviceLog::new("cloud");
let z = intent_op(&mut zombie, "milo", &run_id, 1, IntentStatus::Committed);
let c = intent_op(&mut cloud, "milo", &run_id, 2, IntentStatus::Committed);
let state = fold(&[z, c.clone()]);
assert_eq!(state.intents["milo"].runs.len(), 1, "exactly one execution record");
assert_eq!(
state.intent("milo", &run_id).unwrap().op_id,
c.op_id,
"the epoch-2 holder's run wins; the zombie is a no-op"
);
}
#[test]
fn intent_fold_is_order_independent_over_every_permutation() {
fn permutations<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
fn heap<T: Clone>(k: usize, arr: &mut Vec<T>, out: &mut Vec<Vec<T>>) {
if k == 1 {
out.push(arr.clone());
return;
}
for i in 0..k {
heap(k - 1, arr, out);
if k.is_multiple_of(2) {
arr.swap(i, k - 1);
} else {
arr.swap(0, k - 1);
}
}
}
let mut arr = items.to_vec();
let mut out = Vec::new();
heap(arr.len(), &mut arr, &mut out);
out
}
let mut a = DeviceLog::new("a");
let mut b = DeviceLog::new("b");
let ops = vec![
intent_op(&mut a, "milo", "R", 1, IntentStatus::Committed), intent_op(&mut a, "milo", "S", 1, IntentStatus::Pending), intent_op(&mut a, "milo", "T", 1, IntentStatus::Committed), intent_op(&mut b, "milo", "R", 2, IntentStatus::Pending), intent_op(&mut b, "milo", "S", 2, IntentStatus::Committed), ];
let baseline = fold(&ops);
assert_eq!(baseline.fencing_epoch("milo"), Some(2));
let mut committed = baseline.committed_run_ids("milo");
committed.sort();
assert_eq!(committed, vec!["R", "S", "T"], "the oracle keeps every committed run");
assert!(baseline.intent("milo", "S").map(|r| r.op_id.clone()).is_some_and(|_| {
Intent::from_payload(&baseline.intent("milo", "S").unwrap().payload).unwrap().status
== IntentStatus::Committed
}));
assert_eq!(
Intent::from_payload(&baseline.intent("milo", "R").unwrap().payload).unwrap().status,
IntentStatus::Committed,
"R is not reverted to pending"
);
for perm in permutations(&ops) {
assert_eq!(fold(&perm), baseline, "leased fold must be order-independent");
assert_eq!(state_hash(&fold(&perm)), state_hash(&baseline));
}
}
#[test]
fn c1_committed_run_survives_an_unrelated_higher_epoch_run() {
let mut a = DeviceLog::new("dev-a");
let mut b = DeviceLog::new("dev-b");
let r_commit = intent_op(&mut a, "milo", "R", 1, IntentStatus::Committed);
b.observe(&r_commit.hlc);
let t_pending = intent_op(&mut b, "milo", "T", 2, IntentStatus::Pending);
let state = fold(&[r_commit.clone(), t_pending]);
assert_eq!(state.fencing_epoch("milo"), Some(2), "the unrelated run bumped the fence");
assert_eq!(
state.committed_run("milo", "R").unwrap().op_id,
r_commit.op_id,
"committed_run(R) survives the unrelated epoch bump (C1 fixed)"
);
assert_eq!(state.committed_run_ids("milo"), vec!["R"]);
}
#[test]
fn c3_committed_then_pending_across_a_bump_stays_committed() {
let mut orig = DeviceLog::new("orig");
let mut failover = DeviceLog::new("failover");
let committed = intent_op(&mut orig, "milo", "R", 1, IntentStatus::Committed);
failover.observe(&committed.hlc);
let late_pending = intent_op(&mut failover, "milo", "R", 2, IntentStatus::Pending);
assert!(late_pending.hlc > committed.hlc, "the pending is even later in HLC");
for order in [
vec![committed.clone(), late_pending.clone()],
vec![late_pending.clone(), committed.clone()],
] {
let state = fold(&order);
assert_eq!(
state.committed_run("milo", "R").unwrap().op_id,
committed.op_id,
"committed stays committed across the bump (oracle)"
);
let decoded = Intent::from_payload(&state.intent("milo", "R").unwrap().payload).unwrap();
assert_eq!(decoded.status, IntentStatus::Committed, "runs view is not reverted to pending");
}
}
}