Skip to main content

made_core/ports/
memory_reader.rs

1use async_trait::async_trait;
2
3use crate::error::DomainError;
4use crate::value_objects::{
5    MemoryCapabilities, MemoryEntry, MemoryEntryId, MemoryMoment, MemoryQuestion, MemoryRelation,
6    MemoryScope,
7};
8
9/// What memory gave back.
10///
11/// `Unsupported` is a first-class answer rather than an error because
12/// a backend that cannot travel in time is not misbehaving — it is a
13/// smaller backend, and a caller told so plainly can offer the person
14/// something else instead of showing them a failure.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum MemoryRecollection {
17    /// What was remembered, and what connects it.
18    ///
19    /// Both, always. Handing back entries alone would answer "what
20    /// happened here" and silently refuse "how did this come about",
21    /// and a caller has no way to tell a memory with no reasons from a
22    /// reader that dropped them.
23    Recalled {
24        entries: Vec<MemoryEntry>,
25        relations: Vec<MemoryRelation>,
26    },
27    /// The backend does not do this, and said so in its capabilities.
28    Unsupported,
29}
30
31impl MemoryRecollection {
32    /// Entries with nothing connecting them yet.
33    #[must_use]
34    pub fn of(entries: Vec<MemoryEntry>) -> Self {
35        Self::Recalled {
36            entries,
37            relations: Vec::new(),
38        }
39    }
40
41    /// Nothing is remembered here.
42    #[must_use]
43    pub fn nothing() -> Self {
44        Self::of(Vec::new())
45    }
46
47    #[must_use]
48    pub fn entries(&self) -> &[MemoryEntry] {
49        match self {
50            Self::Recalled { entries, .. } => entries,
51            Self::Unsupported => &[],
52        }
53    }
54
55    /// The reasons between what came back — the part that can be
56    /// followed rather than only read.
57    #[must_use]
58    pub fn relations(&self) -> &[MemoryRelation] {
59        match self {
60            Self::Recalled { relations, .. } => relations,
61            Self::Unsupported => &[],
62        }
63    }
64
65    #[must_use]
66    pub const fn is_supported(&self) -> bool {
67        matches!(self, Self::Recalled { .. })
68    }
69}
70
71/// Reading what earlier sessions learned, and why.
72///
73/// Three ways of asking, because three different questions get asked:
74/// what is known about this at all, what does memory say about one
75/// thing in particular, and what was known at a moment. The third is
76/// not the first two filtered by date — it excludes what was learned
77/// later about earlier events, which is the whole point of asking it.
78#[async_trait]
79pub trait MemoryReaderPort: Send + Sync {
80    /// Everything memory holds about `scope`.
81    async fn recall(&self, scope: &MemoryScope) -> Result<MemoryRecollection, DomainError>;
82
83    /// What memory says in answer to a question put in words.
84    async fn ask(
85        &self,
86        scope: &MemoryScope,
87        question: &MemoryQuestion,
88    ) -> Result<MemoryRecollection, DomainError>;
89
90    /// What was known about `scope` at `moment`.
91    async fn as_known_at(
92        &self,
93        scope: &MemoryScope,
94        moment: MemoryMoment,
95    ) -> Result<MemoryRecollection, DomainError>;
96
97    /// The chain of reasons leading from `from` back to `to`.
98    ///
99    /// The question the whole contract exists to answer, and the only
100    /// one whose failure means the memory has stopped being worth
101    /// keeping: everything else can be reconstructed by reading, and
102    /// this cannot.
103    ///
104    /// It answers with the reasons and not with the prose — the edges
105    /// on the path, in the order they connect. What each end says is
106    /// what `recall` is for, and a backend that padded the chain with
107    /// text would make two contracts out of one.
108    ///
109    /// An empty chain is a real answer: the two are not connected by
110    /// anything anyone wrote down.
111    async fn follow(
112        &self,
113        scope: &MemoryScope,
114        from: &MemoryEntryId,
115        to: &MemoryEntryId,
116    ) -> Result<MemoryRecollection, DomainError>;
117
118    fn capabilities(&self) -> MemoryCapabilities;
119}