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