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}