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