Skip to main content

forge_reasoning/hypothesis/
storage.rs

1//! Storage abstraction for hypotheses
2//!
3//! Provides trait-based storage with in-memory implementation for testing.
4//! Future implementations can use SQLiteGraph or other backends.
5
6use async_trait::async_trait;
7use std::collections::HashMap;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10use crate::hypothesis::types::{Hypothesis, HypothesisId, HypothesisStatus};
11use crate::hypothesis::confidence::Confidence;
12use crate::hypothesis::evidence::{Evidence, EvidenceId};
13use crate::errors::{ReasoningError, Result};
14
15/// Storage trait for hypotheses (allows test mocks)
16#[async_trait]
17pub trait HypothesisStorage: Send + Sync {
18    /// Create a new hypothesis
19    async fn create_hypothesis(&self, hypothesis: &Hypothesis) -> Result<HypothesisId>;
20
21    /// Get a hypothesis by ID
22    async fn get_hypothesis(&self, id: HypothesisId) -> Result<Option<Hypothesis>>;
23
24    /// Update hypothesis confidence
25    async fn update_confidence(
26        &self,
27        id: HypothesisId,
28        posterior: Confidence,
29    ) -> Result<()>;
30
31    /// Set hypothesis status
32    async fn set_status(&self, id: HypothesisId, status: HypothesisStatus) -> Result<()>;
33
34    /// List all hypotheses
35    async fn list_hypotheses(&self) -> Result<Vec<Hypothesis>>;
36
37    /// Delete a hypothesis
38    async fn delete_hypothesis(&self, id: HypothesisId) -> Result<bool>;
39
40    /// Attach evidence to a hypothesis
41    async fn attach_evidence(&self, evidence: &Evidence) -> Result<EvidenceId>;
42
43    /// Get evidence by ID
44    async fn get_evidence(&self, id: EvidenceId) -> Result<Option<Evidence>>;
45
46    /// List all evidence for a hypothesis
47    async fn list_evidence_for_hypothesis(&self, hypothesis_id: HypothesisId) -> Result<Vec<Evidence>>;
48
49    /// List all evidence
50    async fn list_all_evidence(&self) -> Result<Vec<Evidence>>;
51
52    /// Delete evidence
53    async fn delete_evidence(&self, id: EvidenceId) -> Result<bool>;
54}
55
56/// In-memory storage for testing
57pub struct InMemoryHypothesisStorage {
58    hypotheses: Arc<RwLock<HashMap<HypothesisId, Hypothesis>>>,
59    evidence: Arc<RwLock<HashMap<EvidenceId, Evidence>>>,
60    hypothesis_evidence_index: Arc<RwLock<HashMap<HypothesisId, Vec<EvidenceId>>>>,
61}
62
63impl InMemoryHypothesisStorage {
64    pub fn new() -> Self {
65        Self {
66            hypotheses: Arc::new(RwLock::new(HashMap::new())),
67            evidence: Arc::new(RwLock::new(HashMap::new())),
68            hypothesis_evidence_index: Arc::new(RwLock::new(HashMap::new())),
69        }
70    }
71}
72
73impl Default for InMemoryHypothesisStorage {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79#[async_trait]
80impl HypothesisStorage for InMemoryHypothesisStorage {
81    async fn create_hypothesis(&self, hypothesis: &Hypothesis) -> Result<HypothesisId> {
82        let mut store = self.hypotheses.write().await;
83        let id = hypothesis.id();
84        store.insert(id, hypothesis.clone());
85        Ok(id)
86    }
87
88    async fn get_hypothesis(&self, id: HypothesisId) -> Result<Option<Hypothesis>> {
89        let store = self.hypotheses.read().await;
90        Ok(store.get(&id).cloned())
91    }
92
93    async fn update_confidence(&self, id: HypothesisId, posterior: Confidence) -> Result<()> {
94        let mut store = self.hypotheses.write().await;
95        if let Some(h) = store.get_mut(&id) {
96            h.update_posterior(posterior).map_err(|e| ReasoningError::InvalidState(e))?;
97            Ok(())
98        } else {
99            Err(ReasoningError::NotFound(format!("Hypothesis {} not found", id)))
100        }
101    }
102
103    async fn set_status(&self, id: HypothesisId, status: HypothesisStatus) -> Result<()> {
104        let mut store = self.hypotheses.write().await;
105        if let Some(h) = store.get_mut(&id) {
106            h.set_status(status).map_err(|e| ReasoningError::InvalidState(e))?;
107            Ok(())
108        } else {
109            Err(ReasoningError::NotFound(format!("Hypothesis {} not found", id)))
110        }
111    }
112
113    async fn list_hypotheses(&self) -> Result<Vec<Hypothesis>> {
114        let store = self.hypotheses.read().await;
115        Ok(store.values().cloned().collect())
116    }
117
118    async fn delete_hypothesis(&self, id: HypothesisId) -> Result<bool> {
119        let mut store = self.hypotheses.write().await;
120
121        // Clean up associated evidence
122        let mut evidence_store = self.evidence.write().await;
123        let mut index = self.hypothesis_evidence_index.write().await;
124
125        if let Some(evidence_ids) = index.remove(&id) {
126            for evidence_id in evidence_ids {
127                evidence_store.remove(&evidence_id);
128            }
129        }
130
131        Ok(store.remove(&id).is_some())
132    }
133
134    async fn attach_evidence(&self, evidence: &Evidence) -> Result<EvidenceId> {
135        let id = evidence.id();
136        let hypothesis_id = evidence.hypothesis_id();
137
138        // Store evidence
139        let mut evidence_store = self.evidence.write().await;
140        evidence_store.insert(id, evidence.clone());
141
142        // Update index
143        let mut index = self.hypothesis_evidence_index.write().await;
144        index.entry(hypothesis_id).or_insert_with(Vec::new).push(id);
145
146        Ok(id)
147    }
148
149    async fn get_evidence(&self, id: EvidenceId) -> Result<Option<Evidence>> {
150        let store = self.evidence.read().await;
151        Ok(store.get(&id).cloned())
152    }
153
154    async fn list_evidence_for_hypothesis(&self, hypothesis_id: HypothesisId) -> Result<Vec<Evidence>> {
155        let index = self.hypothesis_evidence_index.read().await;
156        let evidence_store = self.evidence.read().await;
157
158        if let Some(evidence_ids) = index.get(&hypothesis_id) {
159            let mut result = Vec::new();
160            for evidence_id in evidence_ids {
161                if let Some(evidence) = evidence_store.get(evidence_id) {
162                    result.push(evidence.clone());
163                }
164            }
165            Ok(result)
166        } else {
167            Ok(Vec::new())
168        }
169    }
170
171    async fn list_all_evidence(&self) -> Result<Vec<Evidence>> {
172        let store = self.evidence.read().await;
173        Ok(store.values().cloned().collect())
174    }
175
176    async fn delete_evidence(&self, id: EvidenceId) -> Result<bool> {
177        // First get the evidence to update the index
178        let evidence_opt = {
179            let store = self.evidence.read().await;
180            store.get(&id).cloned()
181        };
182
183        if let Some(evidence) = evidence_opt {
184            let hypothesis_id = evidence.hypothesis_id();
185
186            // Remove from evidence store
187            let mut evidence_store = self.evidence.write().await;
188            evidence_store.remove(&id);
189
190            // Update index
191            let mut index = self.hypothesis_evidence_index.write().await;
192            if let Some(evidence_ids) = index.get_mut(&hypothesis_id) {
193                evidence_ids.retain(|e_id| *e_id != id);
194                if evidence_ids.is_empty() {
195                    index.remove(&hypothesis_id);
196                }
197            }
198
199            Ok(true)
200        } else {
201            Ok(false)
202        }
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::hypothesis::types::Hypothesis;
210    use crate::hypothesis::evidence::{EvidenceType, EvidenceMetadata};
211
212    #[tokio::test]
213    async fn test_in_memory_create_and_get() {
214        let storage = InMemoryHypothesisStorage::new();
215        let prior = Confidence::new(0.5).unwrap();
216        let h = Hypothesis::new("Test hypothesis", prior);
217        let id = h.id();
218
219        let created_id = storage.create_hypothesis(&h).await.unwrap();
220        assert_eq!(created_id, id);
221
222        let retrieved = storage.get_hypothesis(id).await.unwrap();
223        assert!(retrieved.is_some());
224        assert_eq!(retrieved.unwrap().statement(), "Test hypothesis");
225    }
226
227    #[tokio::test]
228    async fn test_in_memory_get_not_found() {
229        let storage = InMemoryHypothesisStorage::new();
230        let id = HypothesisId::new();
231
232        let result = storage.get_hypothesis(id).await.unwrap();
233        assert!(result.is_none());
234    }
235
236    #[tokio::test]
237    async fn test_in_memory_update_confidence() {
238        let storage = InMemoryHypothesisStorage::new();
239        let prior = Confidence::new(0.5).unwrap();
240        let h = Hypothesis::new("Test", prior);
241        let id = h.id();
242
243        storage.create_hypothesis(&h).await.unwrap();
244
245        let new_posterior = Confidence::new(0.8).unwrap();
246        storage.update_confidence(id, new_posterior).await.unwrap();
247
248        let retrieved = storage.get_hypothesis(id).await.unwrap().unwrap();
249        assert_eq!(retrieved.posterior(), new_posterior);
250    }
251
252    #[tokio::test]
253    async fn test_in_memory_update_confidence_not_found() {
254        let storage = InMemoryHypothesisStorage::new();
255        let id = HypothesisId::new();
256        let posterior = Confidence::new(0.8).unwrap();
257
258        let result = storage.update_confidence(id, posterior).await;
259        assert!(result.is_err());
260    }
261
262    #[tokio::test]
263    async fn test_in_memory_set_status() {
264        let storage = InMemoryHypothesisStorage::new();
265        let prior = Confidence::new(0.5).unwrap();
266        let h = Hypothesis::new("Test", prior);
267        let id = h.id();
268
269        storage.create_hypothesis(&h).await.unwrap();
270        storage.set_status(id, HypothesisStatus::UnderTest).await.unwrap();
271
272        let retrieved = storage.get_hypothesis(id).await.unwrap().unwrap();
273        assert_eq!(retrieved.status(), HypothesisStatus::UnderTest);
274    }
275
276    #[tokio::test]
277    async fn test_in_memory_set_status_not_found() {
278        let storage = InMemoryHypothesisStorage::new();
279        let id = HypothesisId::new();
280
281        let result = storage.set_status(id, HypothesisStatus::UnderTest).await;
282        assert!(result.is_err());
283    }
284
285    #[tokio::test]
286    async fn test_in_memory_list_empty() {
287        let storage = InMemoryHypothesisStorage::new();
288        let list = storage.list_hypotheses().await.unwrap();
289        assert!(list.is_empty());
290    }
291
292    #[tokio::test]
293    async fn test_in_memory_list_multiple() {
294        let storage = InMemoryHypothesisStorage::new();
295
296        let h1 = Hypothesis::new("Hypothesis 1", Confidence::new(0.5).unwrap());
297        let h2 = Hypothesis::new("Hypothesis 2", Confidence::new(0.7).unwrap());
298
299        storage.create_hypothesis(&h1).await.unwrap();
300        storage.create_hypothesis(&h2).await.unwrap();
301
302        let list = storage.list_hypotheses().await.unwrap();
303        assert_eq!(list.len(), 2);
304    }
305
306    #[tokio::test]
307    async fn test_in_memory_delete() {
308        let storage = InMemoryHypothesisStorage::new();
309        let prior = Confidence::new(0.5).unwrap();
310        let h = Hypothesis::new("Test", prior);
311        let id = h.id();
312
313        storage.create_hypothesis(&h).await.unwrap();
314
315        let deleted = storage.delete_hypothesis(id).await.unwrap();
316        assert!(deleted);
317
318        let deleted_again = storage.delete_hypothesis(id).await.unwrap();
319        assert!(!deleted_again);
320
321        let retrieved = storage.get_hypothesis(id).await.unwrap();
322        assert!(retrieved.is_none());
323    }
324
325    #[tokio::test]
326    async fn test_attach_evidence() {
327        let storage = InMemoryHypothesisStorage::new();
328        let prior = Confidence::new(0.5).unwrap();
329        let h = Hypothesis::new("Test", prior);
330        let id = h.id();
331
332        storage.create_hypothesis(&h).await.unwrap();
333
334        let metadata = EvidenceMetadata::Observation {
335            description: "Test observation".to_string(),
336            source_path: None,
337        };
338
339        let evidence = Evidence::new(
340            id,
341            EvidenceType::Observation,
342            0.3,
343            metadata,
344        );
345
346        let evidence_id: EvidenceId = storage.attach_evidence(&evidence).await.unwrap();
347        assert_eq!(evidence_id, evidence.id());
348
349        let retrieved: Option<Evidence> = storage.get_evidence(evidence_id).await.unwrap();
350        assert!(retrieved.is_some());
351        assert_eq!(retrieved.unwrap().hypothesis_id(), id);
352    }
353
354    #[tokio::test]
355    async fn test_list_evidence_for_hypothesis() {
356        let storage = InMemoryHypothesisStorage::new();
357        let prior = Confidence::new(0.5).unwrap();
358        let h = Hypothesis::new("Test", prior);
359        let id = h.id();
360
361        storage.create_hypothesis(&h).await.unwrap();
362
363        // Attach multiple evidence
364        for i in 0..3 {
365            let metadata = EvidenceMetadata::Observation {
366                description: format!("Observation {}", i),
367                source_path: None,
368            };
369            let evidence = Evidence::new(id, EvidenceType::Observation, 0.3, metadata);
370            storage.attach_evidence(&evidence).await.unwrap();
371        }
372
373        let evidence_list = storage.list_evidence_for_hypothesis(id).await.unwrap();
374        assert_eq!(evidence_list.len(), 3);
375    }
376
377    #[tokio::test]
378    async fn test_delete_evidence() {
379        let storage = InMemoryHypothesisStorage::new();
380        let prior = Confidence::new(0.5).unwrap();
381        let h = Hypothesis::new("Test", prior);
382        let id = h.id();
383
384        storage.create_hypothesis(&h).await.unwrap();
385
386        let metadata = EvidenceMetadata::Observation {
387            description: "Test".to_string(),
388            source_path: None,
389        };
390
391        let evidence = Evidence::new(id, EvidenceType::Observation, 0.3, metadata);
392        let evidence_id: EvidenceId = storage.attach_evidence(&evidence).await.unwrap();
393
394        let deleted = storage.delete_evidence(evidence_id).await.unwrap();
395        assert!(deleted);
396
397        let retrieved: Option<Evidence> = storage.get_evidence(evidence_id).await.unwrap();
398        assert!(retrieved.is_none());
399
400        // Index should be cleaned up
401        let evidence_list = storage.list_evidence_for_hypothesis(id).await.unwrap();
402        assert_eq!(evidence_list.len(), 0);
403    }
404
405    #[tokio::test]
406    async fn test_delete_hypothesis_cleans_evidence() {
407        let storage = InMemoryHypothesisStorage::new();
408        let prior = Confidence::new(0.5).unwrap();
409        let h = Hypothesis::new("Test", prior);
410        let id = h.id();
411
412        storage.create_hypothesis(&h).await.unwrap();
413
414        let metadata = EvidenceMetadata::Observation {
415            description: "Test".to_string(),
416            source_path: None,
417        };
418
419        let evidence = Evidence::new(id, EvidenceType::Observation, 0.3, metadata);
420        let evidence_id: EvidenceId = storage.attach_evidence(&evidence).await.unwrap();
421
422        // Delete hypothesis
423        storage.delete_hypothesis(id).await.unwrap();
424
425        // Evidence should be cleaned up
426        let retrieved: Option<Evidence> = storage.get_evidence(evidence_id).await.unwrap();
427        assert!(retrieved.is_none());
428    }
429
430    #[tokio::test]
431    async fn test_list_all_evidence() {
432        let storage = InMemoryHypothesisStorage::new();
433        let prior = Confidence::new(0.5).unwrap();
434
435        let h1 = Hypothesis::new("H1", prior);
436        let h2 = Hypothesis::new("H2", prior);
437
438        let id1 = storage.create_hypothesis(&h1).await.unwrap();
439        let id2 = storage.create_hypothesis(&h2).await.unwrap();
440
441        let evidence1 = Evidence::new(
442            id1,
443            EvidenceType::Observation,
444            0.3,
445            EvidenceMetadata::Observation {
446                description: "Test 1".to_string(),
447                source_path: None,
448            },
449        );
450        let evidence2 = Evidence::new(
451            id2,
452            EvidenceType::Observation,
453            0.3,
454            EvidenceMetadata::Observation {
455                description: "Test 2".to_string(),
456                source_path: None,
457            },
458        );
459
460        storage.attach_evidence(&evidence1).await.unwrap();
461        storage.attach_evidence(&evidence2).await.unwrap();
462
463        let all_evidence = storage.list_all_evidence().await.unwrap();
464        assert_eq!(all_evidence.len(), 2);
465    }
466}