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 }
59 }
60
61 #[must_use]
63 pub const fn get(&self, galaxy: Galaxy) -> u64 {
64 self.clocks[Self::galaxy_index(galaxy)]
65 }
66
67 pub const fn set(&mut self, galaxy: Galaxy, val: u64) {
69 self.clocks[Self::galaxy_index(galaxy)] = val;
70 }
71
72 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 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 #[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 #[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 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct WriteOp {
124 pub operation_id: Option<String>,
126 pub galaxy: Galaxy,
128 pub key: String,
130 pub content_hash: String,
132 pub vector_clock: VectorClock,
134 pub timestamp: u64,
136 pub source: String,
138 pub dharma_provenance: bool,
140}
141
142#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
201pub enum Resolution {
202 AcceptIncoming,
204 KeepActive,
206 MergeClocks,
208}
209
210pub trait CrossMemoryConsistency {
212 fn record_write(&mut self, op: &WriteOp) -> Result<CoherenceReceipt, ConsistencyError>;
214
215 fn verify_causal_read(
217 &self,
218 galaxy: Galaxy,
219 required_clock: &VectorClock,
220 ) -> Result<(), ConsistencyError>;
221
222 fn resolve_conflict(
224 &mut self,
225 conflict: &ConflictReport,
226 strategy: Resolution,
227 ) -> CoherenceReceipt;
228
229 fn snapshot(&self) -> CoherenceSnapshot;
231}
232
233#[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 #[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 #[must_use]
266 pub const fn with_dharma_enforcement(mut self, enforce: bool) -> Self {
267 self.enforce_dharma = enforce;
268 self
269 }
270
271 pub fn register_uncommitted_barrier(&mut self, op_id: &str) {
273 self.uncommitted_barriers.insert(op_id.to_string());
274 }
275
276 pub fn commit_barrier(&mut self, op_id: &str) {
278 self.uncommitted_barriers.remove(op_id);
279 }
280
281 #[must_use]
283 pub fn is_uncommitted(&self, op_id: &str) -> bool {
284 self.uncommitted_barriers.contains(op_id)
285 }
286
287 #[must_use]
289 pub const fn global_clock(&self) -> &VectorClock {
290 &self.global_clock
291 }
292
293 #[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 if self.enforce_dharma && !op.dharma_provenance {
309 return Err(ConsistencyError::MissingDharmaProvenance { galaxy: op.galaxy });
310 }
311
312 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 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 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 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 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 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}