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