1pub mod hnsw_index;
12pub mod structured_index;
13pub mod text_index;
14pub mod vector_index;
15
16use cerememory_core::error::CerememoryError;
17use cerememory_core::traits::AssociationGraph;
18use cerememory_core::types::*;
19use std::collections::HashMap;
20use tokio::sync::RwLock;
21use uuid::Uuid;
22
23#[derive(Debug, Clone)]
25pub struct RegistryEntry {
26 pub store_type: StoreType,
27 pub associations: Vec<Association>,
28}
29
30pub struct HippocampalCoordinator {
33 registry: RwLock<HashMap<Uuid, RegistryEntry>>,
34}
35
36impl HippocampalCoordinator {
37 pub fn new() -> Self {
38 Self {
39 registry: RwLock::new(HashMap::new()),
40 }
41 }
42
43 pub async fn register(
45 &self,
46 record_id: Uuid,
47 store_type: StoreType,
48 associations: Vec<Association>,
49 ) {
50 self.registry.write().await.insert(
51 record_id,
52 RegistryEntry {
53 store_type,
54 associations,
55 },
56 );
57 }
58
59 pub async fn unregister(&self, record_id: &Uuid) -> bool {
61 let mut reg = self.registry.write().await;
62 let existed = reg.remove(record_id).is_some();
63 if existed {
65 for entry in reg.values_mut() {
66 entry.associations.retain(|a| a.target_id != *record_id);
67 }
68 }
69 existed
70 }
71
72 pub async fn update_associations(
74 &self,
75 record_id: &Uuid,
76 associations: Vec<Association>,
77 ) -> Result<(), CerememoryError> {
78 let mut reg = self.registry.write().await;
79 match reg.get_mut(record_id) {
80 Some(entry) => {
81 entry.associations = associations;
82 Ok(())
83 }
84 None => Err(CerememoryError::RecordNotFound(record_id.to_string())),
85 }
86 }
87
88 pub async fn add_association(
90 &self,
91 record_id: &Uuid,
92 association: Association,
93 ) -> Result<(), CerememoryError> {
94 let mut reg = self.registry.write().await;
95 match reg.get_mut(record_id) {
96 Some(entry) => {
97 let exists = entry.associations.iter().any(|a| {
99 a.target_id == association.target_id
100 && a.association_type == association.association_type
101 });
102 if !exists {
103 entry.associations.push(association);
104 }
105 Ok(())
106 }
107 None => Err(CerememoryError::RecordNotFound(record_id.to_string())),
108 }
109 }
110
111 pub async fn records_in_store(&self, store_type: StoreType) -> Vec<Uuid> {
113 self.registry
114 .read()
115 .await
116 .iter()
117 .filter(|(_, e)| e.store_type == store_type)
118 .map(|(id, _)| *id)
119 .collect()
120 }
121
122 pub async fn total_records(&self) -> usize {
124 self.registry.read().await.len()
125 }
126
127 pub async fn records_by_store(&self) -> HashMap<StoreType, u32> {
129 let reg = self.registry.read().await;
130 let mut counts = HashMap::new();
131 for entry in reg.values() {
132 *counts.entry(entry.store_type).or_insert(0) += 1;
133 }
134 counts
135 }
136
137 pub async fn total_associations(&self) -> u32 {
139 self.registry
140 .read()
141 .await
142 .values()
143 .map(|e| e.associations.len() as u32)
144 .sum()
145 }
146
147 pub async fn rebuild(&self, records: Vec<(Uuid, StoreType, Vec<Association>)>) {
150 let mut reg = self.registry.write().await;
151 reg.clear();
152 for (id, store_type, associations) in records {
153 reg.insert(
154 id,
155 RegistryEntry {
156 store_type,
157 associations,
158 },
159 );
160 }
161 }
162}
163
164impl Default for HippocampalCoordinator {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170impl AssociationGraph for HippocampalCoordinator {
171 async fn get_associations(
172 &self,
173 record_id: &Uuid,
174 ) -> Result<Vec<Association>, CerememoryError> {
175 let reg = self.registry.read().await;
176 match reg.get(record_id) {
177 Some(entry) => Ok(entry.associations.clone()),
178 None => Ok(Vec::new()),
179 }
180 }
181
182 async fn get_record_store_type(
183 &self,
184 record_id: &Uuid,
185 ) -> Result<Option<StoreType>, CerememoryError> {
186 Ok(self
187 .registry
188 .read()
189 .await
190 .get(record_id)
191 .map(|e| e.store_type))
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use chrono::Utc;
199
200 fn make_association(target: Uuid) -> Association {
201 Association {
202 target_id: target,
203 association_type: AssociationType::Temporal,
204 weight: 0.8,
205 created_at: Utc::now(),
206 last_co_activation: Utc::now(),
207 }
208 }
209
210 #[tokio::test]
211 async fn register_and_lookup() {
212 let coord = HippocampalCoordinator::new();
213 let id = Uuid::now_v7();
214 coord.register(id, StoreType::Episodic, vec![]).await;
215
216 let store = coord.get_record_store_type(&id).await.unwrap();
217 assert_eq!(store, Some(StoreType::Episodic));
218 }
219
220 #[tokio::test]
221 async fn unregister_removes_record() {
222 let coord = HippocampalCoordinator::new();
223 let id = Uuid::now_v7();
224 coord.register(id, StoreType::Semantic, vec![]).await;
225 assert!(coord.unregister(&id).await);
226 assert_eq!(coord.get_record_store_type(&id).await.unwrap(), None);
227 }
228
229 #[tokio::test]
230 async fn get_associations_returns_registered() {
231 let coord = HippocampalCoordinator::new();
232 let id_a = Uuid::now_v7();
233 let id_b = Uuid::now_v7();
234 let assoc = make_association(id_b);
235 coord.register(id_a, StoreType::Episodic, vec![assoc]).await;
236
237 let associations = coord.get_associations(&id_a).await.unwrap();
238 assert_eq!(associations.len(), 1);
239 assert_eq!(associations[0].target_id, id_b);
240 }
241
242 #[tokio::test]
243 async fn cross_store_associations() {
244 let coord = HippocampalCoordinator::new();
245 let ep_id = Uuid::now_v7();
246 let sem_id = Uuid::now_v7();
247
248 let ep_assoc = make_association(sem_id);
249 let sem_assoc = make_association(ep_id);
250
251 coord
252 .register(ep_id, StoreType::Episodic, vec![ep_assoc])
253 .await;
254 coord
255 .register(sem_id, StoreType::Semantic, vec![sem_assoc])
256 .await;
257
258 let ep_assocs = coord.get_associations(&ep_id).await.unwrap();
260 assert_eq!(
261 coord
262 .get_record_store_type(&ep_assocs[0].target_id)
263 .await
264 .unwrap(),
265 Some(StoreType::Semantic)
266 );
267
268 let sem_assocs = coord.get_associations(&sem_id).await.unwrap();
270 assert_eq!(
271 coord
272 .get_record_store_type(&sem_assocs[0].target_id)
273 .await
274 .unwrap(),
275 Some(StoreType::Episodic)
276 );
277 }
278
279 #[tokio::test]
280 async fn rebuild_from_records() {
281 let coord = HippocampalCoordinator::new();
282 let id1 = Uuid::now_v7();
283 let id2 = Uuid::now_v7();
284
285 coord
286 .rebuild(vec![
287 (id1, StoreType::Episodic, vec![]),
288 (id2, StoreType::Semantic, vec![]),
289 ])
290 .await;
291
292 assert_eq!(coord.total_records().await, 2);
293 let by_store = coord.records_by_store().await;
294 assert_eq!(by_store[&StoreType::Episodic], 1);
295 assert_eq!(by_store[&StoreType::Semantic], 1);
296 }
297
298 #[tokio::test]
299 async fn records_in_store_filter() {
300 let coord = HippocampalCoordinator::new();
301 let e1 = Uuid::now_v7();
302 let e2 = Uuid::now_v7();
303 let s1 = Uuid::now_v7();
304
305 coord.register(e1, StoreType::Episodic, vec![]).await;
306 coord.register(e2, StoreType::Episodic, vec![]).await;
307 coord.register(s1, StoreType::Semantic, vec![]).await;
308
309 let episodic = coord.records_in_store(StoreType::Episodic).await;
310 assert_eq!(episodic.len(), 2);
311 }
312
313 #[tokio::test]
314 async fn add_association_to_existing() {
315 let coord = HippocampalCoordinator::new();
316 let id_a = Uuid::now_v7();
317 let id_b = Uuid::now_v7();
318 coord.register(id_a, StoreType::Episodic, vec![]).await;
319
320 coord
321 .add_association(&id_a, make_association(id_b))
322 .await
323 .unwrap();
324
325 let assocs = coord.get_associations(&id_a).await.unwrap();
326 assert_eq!(assocs.len(), 1);
327 }
328
329 #[tokio::test]
330 async fn total_associations_counts_all() {
331 let coord = HippocampalCoordinator::new();
332 let a = Uuid::now_v7();
333 let b = Uuid::now_v7();
334 let c = Uuid::now_v7();
335
336 coord
337 .register(a, StoreType::Episodic, vec![make_association(b)])
338 .await;
339 coord
340 .register(
341 b,
342 StoreType::Semantic,
343 vec![make_association(a), make_association(c)],
344 )
345 .await;
346 coord.register(c, StoreType::Semantic, vec![]).await;
347
348 assert_eq!(coord.total_associations().await, 3);
349 }
350}