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 }
58 }
59
60 #[must_use]
62 pub const fn get(&self, galaxy: Galaxy) -> u64 {
63 self.clocks[Self::galaxy_index(galaxy)]
64 }
65
66 pub const fn set(&mut self, galaxy: Galaxy, val: u64) {
68 self.clocks[Self::galaxy_index(galaxy)] = val;
69 }
70
71 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 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 #[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 #[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 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct WriteOp {
123 pub operation_id: Option<String>,
125 pub galaxy: Galaxy,
127 pub key: String,
129 pub content_hash: String,
131 pub vector_clock: VectorClock,
133 pub timestamp: u64,
135 pub source: String,
137 pub dharma_provenance: bool,
139}
140
141#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
200pub enum Resolution {
201 AcceptIncoming,
203 KeepActive,
205 MergeClocks,
207}
208
209pub trait CrossMemoryConsistency {
211 fn record_write(&mut self, op: &WriteOp) -> Result<CoherenceReceipt, ConsistencyError>;
213
214 fn verify_causal_read(
216 &self,
217 galaxy: Galaxy,
218 required_clock: &VectorClock,
219 ) -> Result<(), ConsistencyError>;
220
221 fn resolve_conflict(
223 &mut self,
224 conflict: &ConflictReport,
225 strategy: Resolution,
226 ) -> CoherenceReceipt;
227
228 fn snapshot(&self) -> CoherenceSnapshot;
230}
231
232#[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 #[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 #[must_use]
265 pub const fn with_dharma_enforcement(mut self, enforce: bool) -> Self {
266 self.enforce_dharma = enforce;
267 self
268 }
269
270 pub fn register_uncommitted_barrier(&mut self, op_id: &str) {
272 self.uncommitted_barriers.insert(op_id.to_string());
273 }
274
275 pub fn commit_barrier(&mut self, op_id: &str) {
277 self.uncommitted_barriers.remove(op_id);
278 }
279
280 #[must_use]
282 pub fn is_uncommitted(&self, op_id: &str) -> bool {
283 self.uncommitted_barriers.contains(op_id)
284 }
285
286 #[must_use]
288 pub const fn global_clock(&self) -> &VectorClock {
289 &self.global_clock
290 }
291
292 #[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 if self.enforce_dharma && !op.dharma_provenance {
308 return Err(ConsistencyError::MissingDharmaProvenance { galaxy: op.galaxy });
309 }
310
311 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 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 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 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 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 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}