Skip to main content

wm_memory/
consistency.rs

1//! Cross-Memory Consistency Layer (CMCL) — Proposal 1
2//!
3//! Provides real-time cross-galaxy coherence, vector clocks, bounded-latency
4//! causal consensus, and crash-barrier write provenance across WhiteMagic's
5//! multi-galaxy LMDB memory shards (Codex, Karma, Citta, Aria, Dreams, etc.).
6
7#![forbid(unsafe_code)]
8#![allow(clippy::result_large_err)]
9
10use serde::{Deserialize, Serialize};
11use std::collections::{HashSet, VecDeque};
12use thiserror::Error;
13use wm_core::Galaxy;
14
15/// Maximum number of receipts preserved in the bounded history buffer.
16const DEFAULT_RECEIPT_CAPACITY: usize = 512;
17
18/// Vector clock tracking causal mutation progress across all 14 memory galaxies.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub struct VectorClock {
21    clocks: [u64; Galaxy::COUNT],
22}
23
24impl Default for VectorClock {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl VectorClock {
31    /// Create a zero-initialized vector clock.
32    #[must_use]
33    pub const fn new() -> Self {
34        Self {
35            clocks: [0; Galaxy::COUNT],
36        }
37    }
38
39    /// Helper to convert a galaxy into its zero-based index in `Galaxy::all()`.
40    const fn galaxy_index(galaxy: Galaxy) -> usize {
41        match galaxy {
42            Galaxy::Aria => 0,
43            Galaxy::Citta => 1,
44            Galaxy::Codex => 2,
45            Galaxy::Journals => 3,
46            Galaxy::Dreams => 4,
47            Galaxy::Research => 5,
48            Galaxy::Sessions => 6,
49            Galaxy::Substrate => 7,
50            Galaxy::Tutorial => 8,
51            Galaxy::Universal => 9,
52            Galaxy::Karma => 10,
53            Galaxy::Dharma => 11,
54            Galaxy::Associations => 12,
55            Galaxy::Embeddings => 13,
56            Galaxy::Valkyrie => 14,
57        }
58    }
59
60    /// Get clock value for a specific galaxy.
61    #[must_use]
62    pub const fn get(&self, galaxy: Galaxy) -> u64 {
63        self.clocks[Self::galaxy_index(galaxy)]
64    }
65
66    /// Set clock value for a specific galaxy.
67    pub const fn set(&mut self, galaxy: Galaxy, val: u64) {
68        self.clocks[Self::galaxy_index(galaxy)] = val;
69    }
70
71    /// Increment and return the new clock value for a specific galaxy.
72    pub const fn tick(&mut self, galaxy: Galaxy) -> u64 {
73        let idx = Self::galaxy_index(galaxy);
74        self.clocks[idx] = self.clocks[idx].saturating_add(1);
75        self.clocks[idx]
76    }
77
78    /// Merge with another vector clock by taking the component-wise maximum.
79    pub fn merge(&mut self, other: &Self) {
80        for i in 0..Galaxy::COUNT {
81            if other.clocks[i] > self.clocks[i] {
82                self.clocks[i] = other.clocks[i];
83            }
84        }
85    }
86
87    /// Returns `true` if `self` causally precedes `other` (`self < other`).
88    #[must_use]
89    pub fn happened_before(&self, other: &Self) -> bool {
90        let mut strictly_smaller = false;
91        for i in 0..Galaxy::COUNT {
92            if self.clocks[i] > other.clocks[i] {
93                return false;
94            }
95            if self.clocks[i] < other.clocks[i] {
96                strictly_smaller = true;
97            }
98        }
99        strictly_smaller
100    }
101
102    /// Returns `true` if `self` and `other` are causally concurrent (neither precedes the other).
103    #[must_use]
104    #[allow(clippy::suspicious_operation_groupings)]
105    pub fn is_concurrent(&self, other: &Self) -> bool {
106        !self.happened_before(other) && !other.happened_before(self) && self != other
107    }
108
109    /// Calculate Manhattan distance (total skew) between two clocks across all galaxies.
110    #[must_use]
111    pub fn distance(&self, other: &Self) -> u64 {
112        let mut total = 0u64;
113        for i in 0..Galaxy::COUNT {
114            total = total.saturating_add(self.clocks[i].abs_diff(other.clocks[i]));
115        }
116        total
117    }
118}
119
120/// A cross-memory write mutation operation submitted for consistency verification.
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct WriteOp {
123    /// Multi-step crash barrier correlation ID (e.g. from U6 WriteAuditJournal).
124    pub operation_id: Option<String>,
125    /// Target galaxy for the write.
126    pub galaxy: Galaxy,
127    /// Unique memory key or ID.
128    pub key: String,
129    /// SHA-256 or blake3 content hash for idempotency and integrity.
130    pub content_hash: String,
131    /// Causal vector clock associated with the write.
132    pub vector_clock: VectorClock,
133    /// Epoch timestamp in seconds.
134    pub timestamp: u64,
135    /// Mutation source (e.g. "agent:copilot", "mcp:client", "daemon:citta").
136    pub source: String,
137    /// Whether this write was signed and authorized by Dharma governance.
138    pub dharma_provenance: bool,
139}
140
141/// Errors raised by the Cross-Memory Consistency Layer.
142#[derive(Debug, Error, Clone, PartialEq, Eq)]
143#[allow(clippy::large_enum_variant, clippy::result_large_err)]
144pub enum ConsistencyError {
145    #[error("Causal violation on {galaxy:?}: required clock {required} > active clock {actual}")]
146    CausalViolation {
147        galaxy: Galaxy,
148        required: u64,
149        actual: u64,
150    },
151
152    #[error("Concurrent conflict on {galaxy:?}: active {active:?}, incoming {incoming:?}")]
153    ConcurrentConflict {
154        galaxy: Galaxy,
155        active: VectorClock,
156        incoming: VectorClock,
157    },
158
159    #[error("Uncommitted crash barrier detected for operation '{operation_id}'")]
160    UncommittedBarrierDetected { operation_id: String },
161
162    #[error("Missing Dharma provenance signature on write to {galaxy:?}")]
163    MissingDharmaProvenance { galaxy: Galaxy },
164}
165
166/// Provenance and coherence receipt issued upon successfully applying a cross-galaxy write.
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct CoherenceReceipt {
169    pub operation_id: Option<String>,
170    pub galaxy: Galaxy,
171    pub key: String,
172    pub vector_clock: VectorClock,
173    pub applied_at: u64,
174    pub coherence_score: f32,
175}
176
177/// Global system-wide snapshot of cross-memory consistency state.
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct CoherenceSnapshot {
180    pub global_clock: VectorClock,
181    pub total_writes_tracked: u64,
182    pub active_uncommitted_barriers: usize,
183    pub coherence_ratio: f32,
184    pub last_reconciled_epoch: u64,
185}
186
187/// Detail report of a detected conflict between concurrent cross-galaxy operations.
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct ConflictReport {
190    pub operation_id: Option<String>,
191    pub galaxy: Galaxy,
192    pub key: String,
193    pub active_clock: VectorClock,
194    pub incoming_clock: VectorClock,
195    pub reason: String,
196}
197
198/// Conflict resolution strategy for concurrent writes.
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
200pub enum Resolution {
201    /// Accept incoming write and advance vector clock.
202    AcceptIncoming,
203    /// Retain current active memory state, discarding incoming write.
204    KeepActive,
205    /// Merge vector clocks component-wise and mark reconciled.
206    MergeClocks,
207}
208
209/// Core trait defining the Cross-Memory Consistency contract.
210pub trait CrossMemoryConsistency {
211    /// Verify and record a cross-galaxy write, updating causal clocks.
212    fn record_write(&mut self, op: &WriteOp) -> Result<CoherenceReceipt, ConsistencyError>;
213
214    /// Check if reading from `galaxy` satisfies the caller's required vector clock.
215    fn verify_causal_read(
216        &self,
217        galaxy: Galaxy,
218        required_clock: &VectorClock,
219    ) -> Result<(), ConsistencyError>;
220
221    /// Resolve a concurrent mutation conflict.
222    fn resolve_conflict(
223        &mut self,
224        conflict: &ConflictReport,
225        strategy: Resolution,
226    ) -> CoherenceReceipt;
227
228    /// Retrieve the current point-in-time coherence snapshot.
229    fn snapshot(&self) -> CoherenceSnapshot;
230}
231
232/// Thread-safe manager implementing the Cross-Memory Consistency Layer (CMCL).
233#[derive(Debug)]
234pub struct CrossMemoryConsistencyManager {
235    global_clock: VectorClock,
236    uncommitted_barriers: HashSet<String>,
237    receipt_history: VecDeque<CoherenceReceipt>,
238    total_writes: u64,
239    last_epoch: u64,
240    enforce_dharma: bool,
241}
242
243impl Default for CrossMemoryConsistencyManager {
244    fn default() -> Self {
245        Self::new()
246    }
247}
248
249impl CrossMemoryConsistencyManager {
250    /// Create a new consistency manager with default buffer limits.
251    #[must_use]
252    pub fn new() -> Self {
253        Self {
254            global_clock: VectorClock::new(),
255            uncommitted_barriers: HashSet::new(),
256            receipt_history: VecDeque::with_capacity(DEFAULT_RECEIPT_CAPACITY),
257            total_writes: 0,
258            last_epoch: 0,
259            enforce_dharma: false,
260        }
261    }
262
263    /// Configure whether Dharma governance authorization is strictly enforced.
264    #[must_use]
265    pub const fn with_dharma_enforcement(mut self, enforce: bool) -> Self {
266        self.enforce_dharma = enforce;
267        self
268    }
269
270    /// Mark an operation ID as an active uncommitted crash barrier.
271    pub fn register_uncommitted_barrier(&mut self, op_id: &str) {
272        self.uncommitted_barriers.insert(op_id.to_string());
273    }
274
275    /// Commit or clear an operation ID's crash barrier.
276    pub fn commit_barrier(&mut self, op_id: &str) {
277        self.uncommitted_barriers.remove(op_id);
278    }
279
280    /// Check if an operation ID is currently uncommitted.
281    #[must_use]
282    pub fn is_uncommitted(&self, op_id: &str) -> bool {
283        self.uncommitted_barriers.contains(op_id)
284    }
285
286    /// Retrieve the current active vector clock.
287    #[must_use]
288    pub const fn global_clock(&self) -> &VectorClock {
289        &self.global_clock
290    }
291
292    /// Compute current coherence ratio (0.0 to 1.0) based on uncommitted barrier load.
293    #[must_use]
294    pub fn coherence_ratio(&self) -> f32 {
295        if self.uncommitted_barriers.is_empty() {
296            1.0
297        } else {
298            let penalty = (self.uncommitted_barriers.len() as f32 * 0.05).min(0.8);
299            1.0 - penalty
300        }
301    }
302}
303
304impl CrossMemoryConsistency for CrossMemoryConsistencyManager {
305    fn record_write(&mut self, op: &WriteOp) -> Result<CoherenceReceipt, ConsistencyError> {
306        // 1. Dharma governance check
307        if self.enforce_dharma && !op.dharma_provenance {
308            return Err(ConsistencyError::MissingDharmaProvenance { galaxy: op.galaxy });
309        }
310
311        // 2. Uncommitted barrier check: if the operation itself is an uncommitted crash barrier,
312        // we flag it if dependent cross-galaxy operations attempt to build on it before commit.
313        if let Some(ref op_id) = op.operation_id {
314            if self.uncommitted_barriers.contains(op_id) {
315                return Err(ConsistencyError::UncommittedBarrierDetected {
316                    operation_id: op_id.clone(),
317                });
318            }
319        }
320
321        // 3. Monotonic causal advance
322        self.global_clock.tick(op.galaxy);
323        self.global_clock.merge(&op.vector_clock);
324
325        self.total_writes = self.total_writes.saturating_add(1);
326        self.last_epoch = op.timestamp;
327
328        let receipt = CoherenceReceipt {
329            operation_id: op.operation_id.clone(),
330            galaxy: op.galaxy,
331            key: op.key.clone(),
332            vector_clock: self.global_clock,
333            applied_at: op.timestamp,
334            coherence_score: self.coherence_ratio(),
335        };
336
337        if self.receipt_history.len() >= DEFAULT_RECEIPT_CAPACITY {
338            self.receipt_history.pop_front();
339        }
340        self.receipt_history.push_back(receipt.clone());
341
342        Ok(receipt)
343    }
344
345    fn verify_causal_read(
346        &self,
347        galaxy: Galaxy,
348        required_clock: &VectorClock,
349    ) -> Result<(), ConsistencyError> {
350        let actual = self.global_clock.get(galaxy);
351        let required = required_clock.get(galaxy);
352
353        if actual < required {
354            return Err(ConsistencyError::CausalViolation {
355                galaxy,
356                required,
357                actual,
358            });
359        }
360        Ok(())
361    }
362
363    fn resolve_conflict(
364        &mut self,
365        conflict: &ConflictReport,
366        strategy: Resolution,
367    ) -> CoherenceReceipt {
368        let now = std::time::SystemTime::now()
369            .duration_since(std::time::UNIX_EPOCH)
370            .map_or(0, |d| d.as_secs());
371
372        match strategy {
373            Resolution::AcceptIncoming => {
374                self.global_clock.merge(&conflict.incoming_clock);
375                self.global_clock.tick(conflict.galaxy);
376            }
377            Resolution::KeepActive => {
378                self.global_clock.tick(conflict.galaxy);
379            }
380            Resolution::MergeClocks => {
381                self.global_clock.merge(&conflict.active_clock);
382                self.global_clock.merge(&conflict.incoming_clock);
383                self.global_clock.tick(conflict.galaxy);
384            }
385        }
386
387        CoherenceReceipt {
388            operation_id: conflict.operation_id.clone(),
389            galaxy: conflict.galaxy,
390            key: conflict.key.clone(),
391            vector_clock: self.global_clock,
392            applied_at: now,
393            coherence_score: self.coherence_ratio(),
394        }
395    }
396
397    fn snapshot(&self) -> CoherenceSnapshot {
398        CoherenceSnapshot {
399            global_clock: self.global_clock,
400            total_writes_tracked: self.total_writes,
401            active_uncommitted_barriers: self.uncommitted_barriers.len(),
402            coherence_ratio: self.coherence_ratio(),
403            last_reconciled_epoch: self.last_epoch,
404        }
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use super::*;
411
412    #[test]
413    fn test_vector_clock_ordering_and_merge() {
414        let mut vc1 = VectorClock::new();
415        let mut vc2 = VectorClock::new();
416
417        assert_eq!(vc1, vc2);
418        assert!(!vc1.happened_before(&vc2));
419
420        vc1.tick(Galaxy::Codex);
421        assert!(vc2.happened_before(&vc1));
422        assert!(!vc1.happened_before(&vc2));
423
424        vc2.tick(Galaxy::Karma);
425        // Now vc1 has Codex=1, Karma=0; vc2 has Codex=0, Karma=1 -> concurrent!
426        assert!(vc1.is_concurrent(&vc2));
427
428        vc1.merge(&vc2);
429        assert_eq!(vc1.get(Galaxy::Codex), 1);
430        assert_eq!(vc1.get(Galaxy::Karma), 1);
431    }
432
433    #[test]
434    fn test_consistency_manager_record_and_verify_read() {
435        let mut cm = CrossMemoryConsistencyManager::new();
436
437        let op = WriteOp {
438            operation_id: Some("op-100".into()),
439            galaxy: Galaxy::Codex,
440            key: "memory:123".into(),
441            content_hash: "blake3:abc".into(),
442            vector_clock: VectorClock::new(),
443            timestamp: 1_700_000_000,
444            source: "test".into(),
445            dharma_provenance: true,
446        };
447
448        let receipt = cm.record_write(&op).expect("write should apply cleanly");
449        assert_eq!(receipt.galaxy, Galaxy::Codex);
450        assert_eq!(receipt.vector_clock.get(Galaxy::Codex), 1);
451
452        // Verify causal read: requires Codex <= 1 -> OK
453        let mut req_clock = VectorClock::new();
454        req_clock.set(Galaxy::Codex, 1);
455        assert!(cm.verify_causal_read(Galaxy::Codex, &req_clock).is_ok());
456
457        // Verify causal read: requires Codex >= 5 -> CausalViolation
458        req_clock.set(Galaxy::Codex, 5);
459        let err = cm
460            .verify_causal_read(Galaxy::Codex, &req_clock)
461            .unwrap_err();
462        assert_eq!(
463            err,
464            ConsistencyError::CausalViolation {
465                galaxy: Galaxy::Codex,
466                required: 5,
467                actual: 1
468            }
469        );
470    }
471
472    #[test]
473    fn test_uncommitted_crash_barrier_detection() {
474        let mut cm = CrossMemoryConsistencyManager::new();
475        cm.register_uncommitted_barrier("uncommitted-op-999");
476        assert!(cm.is_uncommitted("uncommitted-op-999"));
477
478        let op = WriteOp {
479            operation_id: Some("uncommitted-op-999".into()),
480            galaxy: Galaxy::Karma,
481            key: "audit:001".into(),
482            content_hash: "hash".into(),
483            vector_clock: VectorClock::new(),
484            timestamp: 100,
485            source: "agent".into(),
486            dharma_provenance: true,
487        };
488
489        let err = cm.record_write(&op).unwrap_err();
490        assert_eq!(
491            err,
492            ConsistencyError::UncommittedBarrierDetected {
493                operation_id: "uncommitted-op-999".into()
494            }
495        );
496
497        // Once barrier committed, write succeeds
498        cm.commit_barrier("uncommitted-op-999");
499        assert!(!cm.is_uncommitted("uncommitted-op-999"));
500        assert!(cm.record_write(&op).is_ok());
501    }
502
503    #[test]
504    fn test_conflict_resolution_strategies() {
505        let mut cm = CrossMemoryConsistencyManager::new();
506        let mut active = VectorClock::new();
507        active.tick(Galaxy::Citta);
508
509        let mut incoming = VectorClock::new();
510        incoming.tick(Galaxy::Karma);
511
512        let conflict = ConflictReport {
513            operation_id: Some("op-conflict".into()),
514            galaxy: Galaxy::Citta,
515            key: "state:citta".into(),
516            active_clock: active,
517            incoming_clock: incoming,
518            reason: "concurrent divergence".into(),
519        };
520
521        let receipt = cm.resolve_conflict(&conflict, Resolution::MergeClocks);
522        assert!(receipt.vector_clock.get(Galaxy::Citta) >= 1);
523        assert!(receipt.vector_clock.get(Galaxy::Karma) >= 1);
524
525        let snap = cm.snapshot();
526        assert_eq!(snap.active_uncommitted_barriers, 0);
527        assert_eq!(snap.coherence_ratio, 1.0);
528    }
529}