Skip to main content

mnemo_cma/
tree.rs

1//! CMA filesystem layout types (v0.4.1 P0-2).
2
3use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub enum SyncMode {
9    /// Mnemo answers reads from CMA tree but does not persist into
10    /// its own DuckDB — useful for migration discovery without
11    /// committing to the bridge.
12    ReadThrough,
13    /// Mnemo writes to its own DuckDB AND to the CMA tree on every
14    /// `remember`. The bridged audit row chains both.
15    WriteThrough,
16    /// Mnemo and CMA tree are kept in lock-step via a background
17    /// reconciler. Conflict resolution is `engine wins`.
18    Mirror,
19}
20
21impl SyncMode {
22    pub fn as_str(&self) -> &'static str {
23        match self {
24            SyncMode::ReadThrough => "read_through",
25            SyncMode::WriteThrough => "write_through",
26            SyncMode::Mirror => "mirror",
27        }
28    }
29}
30
31/// A pointer at one CMA `.memory/` tree on disk plus the namespace
32/// it should map into in mnemo.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct CmaTreeRoot {
35    pub root: PathBuf,
36    pub namespace: String,
37    pub sync: SyncMode,
38}
39
40impl CmaTreeRoot {
41    pub fn new(root: PathBuf, namespace: impl Into<String>, sync: SyncMode) -> Self {
42        Self {
43            root,
44            namespace: namespace.into(),
45            sync,
46        }
47    }
48
49    pub fn memory_dir(&self) -> PathBuf {
50        self.root.join(".memory")
51    }
52
53    pub fn audit_log(&self) -> PathBuf {
54        self.root.join("audit.jsonl")
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn paths_are_relative_to_root() {
64        let r = CmaTreeRoot::new(
65            PathBuf::from("/tmp/agent"),
66            "primary",
67            SyncMode::WriteThrough,
68        );
69        assert_eq!(r.memory_dir(), PathBuf::from("/tmp/agent/.memory"));
70        assert_eq!(r.audit_log(), PathBuf::from("/tmp/agent/audit.jsonl"));
71    }
72
73    #[test]
74    fn sync_mode_strings_are_stable() {
75        for m in [
76            SyncMode::ReadThrough,
77            SyncMode::WriteThrough,
78            SyncMode::Mirror,
79        ] {
80            assert!(!m.as_str().is_empty());
81        }
82    }
83}