Skip to main content

memory_crystal/
tile.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sha2::{Digest, Sha256};
4use std::collections::HashMap;
5use uuid::Uuid;
6
7/// Unique identifier for a tile.
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
9pub struct TileId(pub String);
10
11impl TileId {
12    pub fn new() -> Self {
13        Self(Uuid::new_v4().to_string())
14    }
15}
16
17impl std::fmt::Display for TileId {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        write!(f, "{}", self.0)
20    }
21}
22
23/// A crystallized memory tile — lossy, compressed representation of content.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct Tile {
26    pub id: TileId,
27    pub source_hash: String,
28    pub constraints: HashMap<String, String>,
29    pub summary: String,
30    pub context_required: Vec<String>,
31    pub valence: f64,
32    pub created_at: DateTime<Utc>,
33    pub accessed_at: DateTime<Utc>,
34    pub access_count: u64,
35    pub generation: u32,
36    pub parent_id: Option<TileId>,
37}
38
39impl Tile {
40    /// Compute SHA-256 hash of content.
41    pub fn hash_content(content: &str) -> String {
42        let mut hasher = Sha256::new();
43        hasher.update(content.as_bytes());
44        format!("{:x}", hasher.finalize())
45    }
46
47    /// Record an access, updating timestamp and count.
48    pub fn touch(&mut self) {
49        self.accessed_at = Utc::now();
50        self.access_count += 1;
51    }
52
53    /// Age of the tile in seconds.
54    pub fn age_seconds(&self) -> i64 {
55        Utc::now()
56            .signed_duration_since(self.created_at)
57            .num_seconds()
58    }
59}
60
61/// Map of content salience — which parts matter most.
62#[derive(Debug, Clone, Default)]
63pub struct SalienceMap {
64    pub constraints: HashMap<String, String>,
65    pub context_required: Vec<String>,
66    pub valence: Option<f64>,
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn tile_id_is_unique() {
75        let a = TileId::new();
76        let b = TileId::new();
77        assert_ne!(a, b);
78    }
79
80    #[test]
81    fn hash_content_deterministic() {
82        let h1 = Tile::hash_content("hello world");
83        let h2 = Tile::hash_content("hello world");
84        assert_eq!(h1, h2);
85        assert_eq!(h1.len(), 64); // SHA-256 hex
86    }
87
88    #[test]
89    fn hash_content_different() {
90        let h1 = Tile::hash_content("hello");
91        let h2 = Tile::hash_content("world");
92        assert_ne!(h1, h2);
93    }
94
95    #[test]
96    fn tile_touch_updates_access() {
97        let mut tile = Tile {
98            id: TileId::new(),
99            source_hash: String::new(),
100            constraints: HashMap::new(),
101            summary: "test".into(),
102            context_required: vec![],
103            valence: 0.5,
104            created_at: Utc::now(),
105            accessed_at: Utc::now(),
106            access_count: 0,
107            generation: 0,
108            parent_id: None,
109        };
110        let before = tile.accessed_at;
111        tile.touch();
112        assert_eq!(tile.access_count, 1);
113        assert!(tile.accessed_at >= before);
114    }
115
116    #[test]
117    fn tile_roundtrip_json() {
118        let tile = Tile {
119            id: TileId::new(),
120            source_hash: "abc".into(),
121            constraints: HashMap::from([("key".into(), "val".into())]),
122            summary: "a summary".into(),
123            context_required: vec!["ctx1".into()],
124            valence: 0.8,
125            created_at: Utc::now(),
126            accessed_at: Utc::now(),
127            access_count: 3,
128            generation: 1,
129            parent_id: Some(TileId::new()),
130        };
131        let json = serde_json::to_string(&tile).unwrap();
132        let back: Tile = serde_json::from_str(&json).unwrap();
133        assert_eq!(tile.id, back.id);
134        assert_eq!(tile.summary, back.summary);
135        assert_eq!(tile.generation, back.generation);
136        assert_eq!(tile.parent_id, back.parent_id);
137    }
138}