made_core/ports/memory_writer.rs
1use async_trait::async_trait;
2
3use crate::error::DomainError;
4use crate::value_objects::{MemoryCapabilities, MemoryScope, MemoryWrite};
5
6/// What became of a write.
7///
8/// Retrying is a normal thing for a caller to do — a network gave up,
9/// a process restarted — and the second attempt must not double the
10/// memory. Saying which of the two happened, rather than answering
11/// "fine" both times, is what lets a caller tell a retry that worked
12/// from a write it never sent.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum MemoryWriteOutcome {
15 /// The entries are now in memory, put there by this call.
16 Remembered,
17 /// This exact write had already been made. Nothing changed.
18 AlreadyRemembered,
19 /// The backend does not keep memory, and said so in its
20 /// capabilities. Not an error: a session with nowhere to record
21 /// what it decided still runs, it just forgets.
22 NotRemembered,
23}
24
25/// Writing what a working session decided into memory that outlives it.
26///
27/// The engine already keeps an audit journal, and this is not that. A
28/// journal proves what happened in one session; memory is what a later
29/// session can navigate. One is evidence, the other is experience.
30///
31/// A write carries entries **and the reasons between them**, because
32/// the reasons are not decoration on the entries — they are the part a
33/// later session follows. What was decided can be listed; how one
34/// thing led to another can only be walked.
35#[async_trait]
36pub trait MemoryWriterPort: Send + Sync {
37 /// Record a write about `scope`.
38 ///
39 /// `idempotency_key` names the write, not the moment: the same key
40 /// twice is the same write twice, whatever the clock says.
41 ///
42 /// A backend that does not keep reasons still accepts a write that
43 /// carries them, and keeps what it can. Refusing would make a
44 /// caller choose between explaining itself and being stored, and
45 /// the honest place to learn what survives is the capabilities.
46 async fn remember(
47 &self,
48 scope: &MemoryScope,
49 write: MemoryWrite,
50 idempotency_key: &str,
51 ) -> Result<MemoryWriteOutcome, DomainError>;
52
53 /// What this backend can do. A caller may ask before it acts, and
54 /// the conformance suite checks the answer against behaviour: a
55 /// backend that claims to remember and then does not is worse than
56 /// one that claims nothing.
57 fn capabilities(&self) -> MemoryCapabilities;
58}