1#![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
15const DEFAULT_RECEIPT_CAPACITY: usize = 512;
17
18#[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 #[must_use]
33 pub const fn new() -> Self {
34 Self {
35 clocks: [0; Galaxy::COUNT],
36 }
37 }
38
39 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 #[must_use]
64 pub const fn get(&self, galaxy: Galaxy) -> u64 {
65 self.clocks[Self::galaxy_index(galaxy)]
66 }
67
68 pub const fn set(&mut self, galaxy: Galaxy, val: u64) {
70 self.clocks[Self::galaxy_index(galaxy)] = val;
71 }
72
73 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 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 #[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 #[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 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct WriteOp {
125 pub operation_id: Option<String>,
127 pub galaxy: Galaxy,
129 pub key: String,
131 pub content_hash: String,
133 pub vector_clock: VectorClock,
135 pub timestamp: u64,
137 pub source: String,
139 pub dharma_provenance: bool,
141}
142
143#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
202pub enum Resolution {
203 AcceptIncoming,
205 KeepActive,
207 MergeClocks,
209}
210
211pub trait CrossMemoryConsistency {
213 fn record_write(&mut self, op: &WriteOp) -> Result<CoherenceReceipt, ConsistencyError>;
215
216 fn verify_causal_read(
218 &self,
219 galaxy: Galaxy,
220 required_clock: &VectorClock,
221 ) -> Result<(), ConsistencyError>;
222
223 fn resolve_conflict(
225 &mut self,
226 conflict: &ConflictReport,
227 strategy: Resolution,
228 ) -> CoherenceReceipt;
229
230 fn snapshot(&self) -> CoherenceSnapshot;
232}
233
234#[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 #[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 #[must_use]
267 pub const fn with_dharma_enforcement(mut self, enforce: bool) -> Self {
268 self.enforce_dharma = enforce;
269 self
270 }
271
272 pub fn register_uncommitted_barrier(&mut self, op_id: &str) {
274 self.uncommitted_barriers.insert(op_id.to_string());
275 }
276
277 pub fn commit_barrier(&mut self, op_id: &str) {
279 self.uncommitted_barriers.remove(op_id);
280 }
281
282 #[must_use]
284 pub fn is_uncommitted(&self, op_id: &str) -> bool {
285 self.uncommitted_barriers.contains(op_id)
286 }
287
288 #[must_use]
290 pub const fn global_clock(&self) -> &VectorClock {
291 &self.global_clock
292 }
293
294 #[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 if self.enforce_dharma && !op.dharma_provenance {
310 return Err(ConsistencyError::MissingDharmaProvenance { galaxy: op.galaxy });
311 }
312
313 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 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 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 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 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 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}