1use std::sync::Arc;
28
29use crate::storage::{Entity, EntityType, StorageBackend, StorageResult};
30
31#[derive(Debug, Clone)]
37pub struct ArchivalMemoryConfig {
38 pub default_recall_limit: usize,
40 pub auto_name_max_chars: usize,
42}
43
44impl Default for ArchivalMemoryConfig {
45 fn default() -> Self {
46 Self {
47 default_recall_limit: 10,
48 auto_name_max_chars: 50,
49 }
50 }
51}
52
53#[derive(Debug)]
64pub struct ArchivalMemory<B: StorageBackend> {
65 backend: Arc<B>,
66 config: ArchivalMemoryConfig,
67}
68
69impl<B: StorageBackend> ArchivalMemory<B> {
70 pub fn new(backend: B) -> Self {
72 Self {
73 backend: Arc::new(backend),
74 config: ArchivalMemoryConfig::default(),
75 }
76 }
77
78 pub fn with_config(backend: B, config: ArchivalMemoryConfig) -> Self {
80 Self {
81 backend: Arc::new(backend),
82 config,
83 }
84 }
85
86 pub async fn remember(
98 &self,
99 content: &str,
100 entity_type: EntityType,
101 name: Option<&str>,
102 ) -> StorageResult<Entity> {
103 let name = match name {
105 Some(n) => n.to_string(),
106 None => self.auto_name(content),
107 };
108
109 assert!(!content.is_empty(), "content must not be empty");
111 assert!(!name.is_empty(), "name must not be empty");
112
113 let entity = Entity::new(entity_type, name, content.to_string());
114
115 self.backend.store_entity(&entity).await?;
116
117 Ok(entity)
118 }
119
120 pub async fn recall(&self, query: &str, limit: Option<usize>) -> StorageResult<Vec<Entity>> {
129 let limit = limit.unwrap_or(self.config.default_recall_limit);
130 self.backend.search(query, limit).await
131 }
132
133 pub async fn forget(&self, id: &str) -> StorageResult<bool> {
138 self.backend.delete_entity(id).await
139 }
140
141 pub async fn get(&self, id: &str) -> StorageResult<Option<Entity>> {
143 self.backend.get_entity(id).await
144 }
145
146 pub async fn list(
148 &self,
149 entity_type: Option<EntityType>,
150 limit: usize,
151 offset: usize,
152 ) -> StorageResult<Vec<Entity>> {
153 self.backend.list_entities(entity_type, limit, offset).await
154 }
155
156 pub async fn count(&self, entity_type: Option<EntityType>) -> StorageResult<usize> {
158 self.backend.count_entities(entity_type).await
159 }
160
161 pub async fn update(&self, entity: &Entity) -> StorageResult<String> {
163 self.backend.store_entity(entity).await
164 }
165
166 pub async fn remember_entity(&self, entity: Entity) -> StorageResult<Entity> {
168 self.backend.store_entity(&entity).await?;
169 Ok(entity)
170 }
171
172 fn auto_name(&self, content: &str) -> String {
174 let first_line = content.lines().next().unwrap_or(content);
176 let trimmed = first_line.trim();
177
178 if trimmed.len() <= self.config.auto_name_max_chars {
179 trimmed.to_string()
180 } else {
181 let truncated = &trimmed[..self.config.auto_name_max_chars];
183 if let Some(last_space) = truncated.rfind(' ') {
184 format!("{}...", &truncated[..last_space])
185 } else {
186 format!("{}...", truncated)
187 }
188 }
189 }
190
191 #[cfg(test)]
193 pub fn backend(&self) -> &B {
194 &self.backend
195 }
196}
197
198#[cfg(test)]
203mod tests {
204 use super::*;
205 use crate::dst::SimConfig;
206 use crate::storage::SimStorageBackend;
207
208 fn create_memory() -> ArchivalMemory<SimStorageBackend> {
209 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
210 ArchivalMemory::new(backend)
211 }
212
213 #[tokio::test]
218 async fn test_remember_with_name() {
219 let memory = create_memory();
220
221 let entity = memory
222 .remember("Alice is my friend", EntityType::Person, Some("Alice"))
223 .await
224 .unwrap();
225
226 assert_eq!(entity.name, "Alice");
227 assert_eq!(entity.content, "Alice is my friend");
228 assert_eq!(entity.entity_type, EntityType::Person);
229 }
230
231 #[tokio::test]
232 async fn test_remember_auto_name() {
233 let memory = create_memory();
234
235 let entity = memory
236 .remember(
237 "This is a note about something important",
238 EntityType::Note,
239 None,
240 )
241 .await
242 .unwrap();
243
244 assert_eq!(entity.name, "This is a note about something important");
245 assert_eq!(entity.entity_type, EntityType::Note);
246 }
247
248 #[tokio::test]
249 async fn test_remember_auto_name_truncates() {
250 let memory = create_memory();
251
252 let long_content = "This is a very long piece of content that exceeds the maximum auto-name length and should be truncated at a word boundary";
253 let entity = memory
254 .remember(long_content, EntityType::Note, None)
255 .await
256 .unwrap();
257
258 assert!(entity.name.len() <= 55); assert!(entity.name.ends_with("..."));
260 }
261
262 #[tokio::test]
263 async fn test_remember_auto_name_first_line() {
264 let memory = create_memory();
265
266 let multiline = "First line title\nSecond line with more content\nThird line";
267 let entity = memory
268 .remember(multiline, EntityType::Note, None)
269 .await
270 .unwrap();
271
272 assert_eq!(entity.name, "First line title");
273 }
274
275 #[tokio::test]
280 async fn test_recall_finds_matching() {
281 let memory = create_memory();
282
283 memory
284 .remember(
285 "Alice is a software engineer",
286 EntityType::Person,
287 Some("Alice"),
288 )
289 .await
290 .unwrap();
291 memory
292 .remember("Bob is a designer", EntityType::Person, Some("Bob"))
293 .await
294 .unwrap();
295
296 let results = memory.recall("software", None).await.unwrap();
297 assert_eq!(results.len(), 1);
298 assert_eq!(results[0].name, "Alice");
299 }
300
301 #[tokio::test]
302 async fn test_recall_respects_limit() {
303 let memory = create_memory();
304
305 for i in 0..10 {
306 memory
307 .remember(&format!("Note {} about coding", i), EntityType::Note, None)
308 .await
309 .unwrap();
310 }
311
312 let results = memory.recall("coding", Some(3)).await.unwrap();
313 assert_eq!(results.len(), 3);
314 }
315
316 #[tokio::test]
317 async fn test_recall_empty_results() {
318 let memory = create_memory();
319
320 memory
321 .remember("Something about Rust", EntityType::Note, Some("Rust"))
322 .await
323 .unwrap();
324
325 let results = memory.recall("Python", None).await.unwrap();
326 assert!(results.is_empty());
327 }
328
329 #[tokio::test]
334 async fn test_forget() {
335 let memory = create_memory();
336
337 let entity = memory
338 .remember("Temporary note", EntityType::Note, Some("Temp"))
339 .await
340 .unwrap();
341
342 let forgotten = memory.forget(&entity.id).await.unwrap();
343 assert!(forgotten);
344
345 let retrieved = memory.get(&entity.id).await.unwrap();
346 assert!(retrieved.is_none());
347 }
348
349 #[tokio::test]
350 async fn test_forget_nonexistent() {
351 let memory = create_memory();
352
353 let forgotten = memory.forget("nonexistent-id").await.unwrap();
354 assert!(!forgotten);
355 }
356
357 #[tokio::test]
362 async fn test_get() {
363 let memory = create_memory();
364
365 let entity = memory
366 .remember("Test content", EntityType::Note, Some("Test"))
367 .await
368 .unwrap();
369
370 let retrieved = memory.get(&entity.id).await.unwrap();
371 assert!(retrieved.is_some());
372
373 let retrieved = retrieved.unwrap();
374 assert_eq!(retrieved.id, entity.id);
375 assert_eq!(retrieved.name, "Test");
376 }
377
378 #[tokio::test]
379 async fn test_get_nonexistent() {
380 let memory = create_memory();
381
382 let retrieved = memory.get("nonexistent").await.unwrap();
383 assert!(retrieved.is_none());
384 }
385
386 #[tokio::test]
391 async fn test_list_all() {
392 let memory = create_memory();
393
394 memory
395 .remember("Person 1", EntityType::Person, Some("Alice"))
396 .await
397 .unwrap();
398 memory
399 .remember("Project 1", EntityType::Project, Some("Umi"))
400 .await
401 .unwrap();
402 memory
403 .remember("Note 1", EntityType::Note, Some("Note"))
404 .await
405 .unwrap();
406
407 let all = memory.list(None, 100, 0).await.unwrap();
408 assert_eq!(all.len(), 3);
409 }
410
411 #[tokio::test]
412 async fn test_list_by_type() {
413 let memory = create_memory();
414
415 memory
416 .remember("Alice", EntityType::Person, Some("Alice"))
417 .await
418 .unwrap();
419 memory
420 .remember("Bob", EntityType::Person, Some("Bob"))
421 .await
422 .unwrap();
423 memory
424 .remember("Umi", EntityType::Project, Some("Umi"))
425 .await
426 .unwrap();
427
428 let people = memory.list(Some(EntityType::Person), 100, 0).await.unwrap();
429 assert_eq!(people.len(), 2);
430
431 let projects = memory
432 .list(Some(EntityType::Project), 100, 0)
433 .await
434 .unwrap();
435 assert_eq!(projects.len(), 1);
436 }
437
438 #[tokio::test]
439 async fn test_count() {
440 let memory = create_memory();
441
442 memory
443 .remember("A", EntityType::Note, Some("A"))
444 .await
445 .unwrap();
446 memory
447 .remember("B", EntityType::Note, Some("B"))
448 .await
449 .unwrap();
450 memory
451 .remember("C", EntityType::Person, Some("C"))
452 .await
453 .unwrap();
454
455 assert_eq!(memory.count(None).await.unwrap(), 3);
456 assert_eq!(memory.count(Some(EntityType::Note)).await.unwrap(), 2);
457 assert_eq!(memory.count(Some(EntityType::Person)).await.unwrap(), 1);
458 }
459
460 #[tokio::test]
465 async fn test_update() {
466 let memory = create_memory();
467
468 let mut entity = memory
469 .remember("Original content", EntityType::Note, Some("Note"))
470 .await
471 .unwrap();
472
473 entity.update_content("Updated content".to_string());
474 memory.update(&entity).await.unwrap();
475
476 let retrieved = memory.get(&entity.id).await.unwrap().unwrap();
477 assert_eq!(retrieved.content, "Updated content");
478 }
479}
480
481#[cfg(test)]
486mod dst_tests {
487 use super::*;
488 use crate::dst::{FaultConfig, FaultType, SimConfig};
489 use crate::storage::SimStorageBackend;
490
491 #[tokio::test]
492 async fn test_remember_with_fault_injection() {
493 let backend = SimStorageBackend::new(SimConfig::with_seed(42))
494 .with_faults(FaultConfig::new(FaultType::StorageWriteFail, 1.0).with_filter("store"));
495 let memory = ArchivalMemory::new(backend);
496
497 let result = memory
498 .remember("Test", EntityType::Note, Some("Test"))
499 .await;
500
501 assert!(result.is_err());
502 }
503
504 #[tokio::test]
505 async fn test_recall_with_fault_injection() {
506 let backend = SimStorageBackend::new(SimConfig::with_seed(42))
507 .with_faults(FaultConfig::new(FaultType::StorageReadFail, 1.0).with_filter("search"));
508 let memory = ArchivalMemory::new(backend);
509
510 let result = memory.recall("test", None).await;
511 assert!(result.is_err());
512 }
513}
514
515#[cfg(test)]
520mod property_tests {
521 use super::*;
522 use crate::dst::{
523 DeterministicRng, PropertyTest, PropertyTestable, SimClock, SimConfig, TimeAdvanceConfig,
524 };
525 use crate::storage::SimStorageBackend;
526
527 #[derive(Debug, Clone)]
529 enum MemoryOp {
530 Remember {
531 content: String,
532 entity_type: EntityType,
533 },
534 Recall {
535 query: String,
536 },
537 Forget {
538 id: String,
539 },
540 Get {
541 id: String,
542 },
543 Count,
544 }
545
546 struct MemoryWrapper {
547 memory: ArchivalMemory<SimStorageBackend>,
548 known_ids: Vec<String>,
549 }
550
551 impl PropertyTestable for MemoryWrapper {
552 type Operation = MemoryOp;
553
554 fn generate_operation(&self, rng: &mut DeterministicRng) -> Self::Operation {
555 let op_type = rng.next_usize(0, 4);
556
557 match op_type {
558 0 => {
559 let types = EntityType::all();
560 let type_idx = rng.next_usize(0, types.len() - 1);
561 MemoryOp::Remember {
562 content: format!("Content {}", rng.next_usize(0, 999)),
563 entity_type: types[type_idx],
564 }
565 }
566 1 => MemoryOp::Recall {
567 query: format!("Content {}", rng.next_usize(0, 9)),
568 },
569 2 => {
570 let id = if !self.known_ids.is_empty() && rng.next_bool(0.7) {
571 let idx = rng.next_usize(0, self.known_ids.len() - 1);
572 self.known_ids[idx].clone()
573 } else {
574 format!("unknown_{}", rng.next_usize(0, 99))
575 };
576 MemoryOp::Forget { id }
577 }
578 3 => {
579 let id = if !self.known_ids.is_empty() && rng.next_bool(0.7) {
580 let idx = rng.next_usize(0, self.known_ids.len() - 1);
581 self.known_ids[idx].clone()
582 } else {
583 format!("unknown_{}", rng.next_usize(0, 99))
584 };
585 MemoryOp::Get { id }
586 }
587 _ => MemoryOp::Count,
588 }
589 }
590
591 fn apply_operation(&mut self, op: &Self::Operation, _clock: &SimClock) {
592 let rt = tokio::runtime::Builder::new_current_thread()
593 .enable_all()
594 .build()
595 .unwrap();
596
597 rt.block_on(async {
598 match op {
599 MemoryOp::Remember {
600 content,
601 entity_type,
602 } => {
603 if let Ok(entity) = self.memory.remember(content, *entity_type, None).await
604 {
605 self.known_ids.push(entity.id);
606 }
607 }
608 MemoryOp::Recall { query } => {
609 let _ = self.memory.recall(query, None).await;
610 }
611 MemoryOp::Forget { id } => {
612 if self.memory.forget(id).await.unwrap_or(false) {
613 self.known_ids.retain(|i| i != id);
614 }
615 }
616 MemoryOp::Get { id } => {
617 let _ = self.memory.get(id).await;
618 }
619 MemoryOp::Count => {
620 let _ = self.memory.count(None).await;
621 }
622 }
623 });
624 }
625
626 fn check_invariants(&self) -> Result<(), String> {
627 let rt = tokio::runtime::Builder::new_current_thread()
628 .enable_all()
629 .build()
630 .unwrap();
631
632 rt.block_on(async {
633 let count = self.memory.count(None).await.map_err(|e| e.to_string())?;
634 if count != self.known_ids.len() {
635 return Err(format!(
636 "count {} != known_ids.len() {}",
637 count,
638 self.known_ids.len()
639 ));
640 }
641 Ok(())
642 })
643 }
644
645 fn describe_state(&self) -> String {
646 format!("ArchivalMemory {{ known_ids: {} }}", self.known_ids.len())
647 }
648 }
649
650 #[test]
651 fn test_property_invariants() {
652 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
653 let memory = ArchivalMemory::new(backend);
654
655 let wrapper = MemoryWrapper {
656 memory,
657 known_ids: Vec::new(),
658 };
659
660 PropertyTest::new(42)
661 .with_max_operations(200)
662 .with_time_advance(TimeAdvanceConfig::none())
663 .run_and_assert(wrapper);
664 }
665
666 #[test]
667 fn test_property_multi_seed() {
668 for seed in [0, 1, 42, 12345] {
669 let backend = SimStorageBackend::new(SimConfig::with_seed(seed));
670 let memory = ArchivalMemory::new(backend);
671
672 let wrapper = MemoryWrapper {
673 memory,
674 known_ids: Vec::new(),
675 };
676
677 PropertyTest::new(seed)
678 .with_max_operations(100)
679 .with_time_advance(TimeAdvanceConfig::none())
680 .run_and_assert(wrapper);
681 }
682 }
683}