pub trait MemoryStore:
Send
+ Sync
+ Debug {
Show 18 methods
// Required methods
fn seals(&self) -> bool;
fn remember<'life0, 'life1, 'async_trait>(
&'life0 self,
item: &'life1 MemoryItem,
) -> Pin<Box<dyn Future<Output = Result<u64, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn recall<'life0, 'life1, 'async_trait>(
&'life0 self,
query: &'life1 Recall,
) -> Pin<Box<dyn Future<Output = Result<Vec<MemoryItem>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn subject_ids<'life0, 'life1, 'async_trait>(
&'life0 self,
subject: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<String>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn version<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
version: u64,
) -> Pin<Box<dyn Future<Output = Result<Option<MemoryItem>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn current<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
as_of: Option<Timestamp>,
) -> Pin<Box<dyn Future<Output = Result<Option<MemoryItem>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn forget<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn forget_subject<'life0, 'life1, 'async_trait>(
&'life0 self,
subject: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<usize, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn derivatives<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<MemoryItem>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn forget_cascading<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Cascade, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn set_legal_hold<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
held: bool,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn legal_hold<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn legal_holds<'life0, 'life1, 'async_trait>(
&'life0 self,
after: Option<&'life1 str>,
limit: usize,
) -> Pin<Box<dyn Future<Output = Result<Vec<String>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn sweep_expired<'life0, 'async_trait>(
&'life0 self,
at: Timestamp,
) -> Pin<Box<dyn Future<Output = Result<Vec<(String, u64)>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn touch<'life0, 'life1, 'async_trait>(
&'life0 self,
ids: &'life1 [String],
at: Timestamp,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
// Provided methods
fn tenant(&self) -> &str { ... }
fn erasure_is_distributed(&self) -> Option<bool> { ... }
fn erasure_index(&self) -> Option<Arc<dyn SemanticRetriever>> { ... }
}Expand description
Where memories live.
Required Methods§
Sourcefn seals(&self) -> bool
fn seals(&self) -> bool
Whether this store seals payloads, and so runs a subject erasure of its own beneath any wrapper the plane adds.
No default: a decorator that does not seal delegates, and one that
answered false over a sealed store would have the plane wrap an index
above the seal, where a person’s erasure never reaches it.
Sourcefn remember<'life0, 'life1, 'async_trait>(
&'life0 self,
item: &'life1 MemoryItem,
) -> Pin<Box<dyn Future<Output = Result<u64, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn remember<'life0, 'life1, 'async_trait>(
&'life0 self,
item: &'life1 MemoryItem,
) -> Pin<Box<dyn Future<Output = Result<u64, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Append a new version of a memory.
Appends rather than replaces: the previous version is marked superseded and kept, so lineage survives and one bad write can be undone without guessing what it replaced.
§Errors
If the store cannot be reached.
Sourcefn recall<'life0, 'life1, 'async_trait>(
&'life0 self,
query: &'life1 Recall,
) -> Pin<Box<dyn Future<Output = Result<Vec<MemoryItem>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn recall<'life0, 'life1, 'async_trait>(
&'life0 self,
query: &'life1 Recall,
) -> Pin<Box<dyn Future<Output = Result<Vec<MemoryItem>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Sourcefn subject_ids<'life0, 'life1, 'async_trait>(
&'life0 self,
subject: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<String>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn subject_ids<'life0, 'life1, 'async_trait>(
&'life0 self,
subject: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<String>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Every id currently belonging to a subject — the erasure path’s enumeration.
A separate operation rather than a recall with a huge limit, because
erasure must not ride a bounded, content-returning query: a backend is
free to cap or refuse extreme recall limits (the PostgreSQL store
refuses limits beyond BIGINT), and a cryptographic erasure that
enumerated through one silently missed whatever the cap cut off — while
reporting success. This returns ids only, unbounded, in stable order.
What it does not cover: forgotten and swept ids. They have no current version, no content, and their tombstones are not subject-keyed — an erasure that needs them already erased them.
§Errors
If the store cannot be reached.
Sourcefn version<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
version: u64,
) -> Pin<Box<dyn Future<Output = Result<Option<MemoryItem>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn version<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
version: u64,
) -> Pin<Box<dyn Future<Output = Result<Option<MemoryItem>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
One exact version, superseded or not.
This is what replay uses: it names the version the run actually read, not whatever is current now.
§Errors
If the store cannot be reached.
Sourcefn current<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
as_of: Option<Timestamp>,
) -> Pin<Box<dyn Future<Output = Result<Option<MemoryItem>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn current<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
as_of: Option<Timestamp>,
) -> Pin<Box<dyn Future<Output = Result<Option<MemoryItem>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
The one version of id a fresh recall at as_of would be allowed to
see — or None, which covers a memory that was never written, was
forgotten or swept, or whose effective expiry (min(expires_at, access window)) has passed the cutoff. None for as_of skips the expiry
check, exactly as it does on recall.
The by-id twin of recall’s lifecycle rule, and a
question version deliberately cannot answer:
version serves replay, which must keep reading superseded and
expired state, so nothing built on it can tell still current from
still stored. The semantic tier’s lifecycle screen is the consumer —
see SemanticRecall for
why a stale hit leaves the selection rather than being served or
failing the query.
§Errors
If the store cannot be reached.
Sourcefn forget<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn forget<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Forget a memory and every version of it.
Selective by construction: one id, not a purge. A repair that can only drop everything is one nobody performs. The id remains reserved after erasure: recycling it could make an old journal selection or derivation edge refer to unrelated new content.
§Errors
If the store cannot be reached.
Sourcefn forget_subject<'life0, 'life1, 'async_trait>(
&'life0 self,
subject: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<usize, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn forget_subject<'life0, 'life1, 'async_trait>(
&'life0 self,
subject: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<usize, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Forget everything about a subject.
The unit an erasure request names — a person, an account, a matter.
§Errors
If the store cannot be reached.
Sourcefn derivatives<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<MemoryItem>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn derivatives<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<MemoryItem>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Memories derived from this one, directly.
The repair path. A summary absorbs what it summarised, so a poisoned memory does not stop being a problem when it is forgotten — it stops being visible while its content continues to arrive in every summary that read it.
§Errors
If the store cannot be reached.
Sourcefn forget_cascading<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Cascade, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn forget_cascading<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Cascade, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Forget a memory and everything derived from it, transitively.
The form an erasure request needs. forget is the
form a correction needs: a stale memory whose summaries are still
legitimate should not take them with it. The two are separate calls
because they answer different questions, and defaulting either way would
be wrong half the time — silently.
This is a required store operation rather than a default assembled from
derivatives and forget. Those
calls are individually atomic but leave a gap in which another writer
can add a derivative that the erasure never sees. Implementations must
serialize derivative creation with the complete traversal and deletion.
The traversal passes through tombstones: a memory forgotten individually keeps its derivation edges in both directions, so a later cascade from further upstream still reaches everything transitively derived — A → B → C with B corrected away must not shelter C from A’s erasure.
It is also version-granular, because supersession does not
un-absorb anything. A derivative whose current version read a doomed
node is erased as a whole id. A derivative that has since been honestly
re-derived from clean sources keeps its current version — but the
superseded versions that named the doomed source are erased, along
with anything transitively derived from exactly those versions.
Otherwise a rolling summary’s v1, which absorbed the poisoned memory
and remained readable through version, would outlive
the erasure that claimed to reach everything derived.
Returns the ids whose state this call actually removed, split into ids erased whole and ids that lost only superseded versions; a tombstone passed through is routed, not named. Named, with their versions, rather than counted because a sealing wrapper seals every version under a key of its own and destroys exactly the keys of what this removed: every version of an id erased whole, and only the trimmed versions of one that stays.
§Errors
If the store cannot be reached, or
StoreError::UnderLegalHold naming a held id the traversal reached —
in which case nothing was removed.
Sourcefn set_legal_hold<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
held: bool,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn set_legal_hold<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
held: bool,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Place or release a legal hold. A held id cannot be forgotten, swept, or
removed as part of subject/cascading erasure: those refuse with
StoreError::UnderLegalHold, and the sweep passes over it.
Sourcefn legal_hold<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn legal_hold<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Whether an id is currently protected by legal hold.
Sourcefn legal_holds<'life0, 'life1, 'async_trait>(
&'life0 self,
after: Option<&'life1 str>,
limit: usize,
) -> Pin<Box<dyn Future<Output = Result<Vec<String>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn legal_holds<'life0, 'life1, 'async_trait>(
&'life0 self,
after: Option<&'life1 str>,
limit: usize,
) -> Pin<Box<dyn Future<Output = Result<Vec<String>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Every held id, in id order, starting after after.
The half that makes the hold a control rather than a flag:
legal_hold is keyed by the id you are asking
about, so it answers only for somebody who already knows which memory to
name — not the person certifying what is still preserved and why.
Paged by id, because an item hold is a bare flag: a case carries a
LegalHold with its own instant and reason,
since a case is the unit a preservation order names and this is the
finer instrument reached for inside one.
Sourcefn sweep_expired<'life0, 'async_trait>(
&'life0 self,
at: Timestamp,
) -> Pin<Box<dyn Future<Output = Result<Vec<(String, u64)>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn sweep_expired<'life0, 'async_trait>(
&'life0 self,
at: Timestamp,
) -> Pin<Box<dyn Future<Output = Result<Vec<(String, u64)>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Atomically erase current memories whose effective expiry has passed, unless held. Returns the ids erased, each with the highest version it held — what a sealing wrapper needs to destroy every version’s key.
The effective expiry is min(expires_at, access window) — the hard
ceiling wins, and a lapsed sliding window collects an item its ceiling
would still have kept. The cutoff is inclusive (<= at), and the sweep
keeps derivation edges in both directions exactly as forget does, so
a later cascading erasure still routes through the tombstone.
Sourcefn touch<'life0, 'life1, 'async_trait>(
&'life0 self,
ids: &'life1 [String],
at: Timestamp,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn touch<'life0, 'life1, 'async_trait>(
&'life0 self,
ids: &'life1 [String],
at: Timestamp,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Refresh sliding access retention for current ids at a journaled instant.
Slides each touched id’s window to at + window. It cannot extend a
life past an expires_at — that ceiling is immutable, and both recall
and the sweep take the earlier of the two.
A touch on an id whose window has lapsed but not yet been swept
deliberately resurrects it: expiry becomes fact at the sweep, not at
the instant the window closes, and stores take max(existing, at + window) so a late touch simply opens a new window. The alternative —
refusing the touch — would make the answer depend on how recently the
sweeper ran, which is an ambient race no journal records. What this
choice does not cover: the expires_at ceiling, which no touch moves,
and a swept id, which is a tombstone no touch revives.
Provided Methods§
Sourcefn tenant(&self) -> &str
fn tenant(&self) -> &str
Whose rows this handle can reach.
Defaults to TenantId::DEFAULT, the
tenant a store serves until told otherwise. Override it with the tenant
the handle is actually scoped to.
This exists so a mismatch with the plane’s tenant is a startup
refusal. A keyring::EncryptedMemoryStore
seals this state under the tenant it was constructed with while the
store writes rows under its own; the two disagreeing is not a leak — the
scopes simply differ — but it seals the state under a scope erasure
will never destroy. That is an erasure that
reports success and misses, which is the one failure a deletion
guarantee cannot have.
Sourcefn erasure_is_distributed(&self) -> Option<bool>
fn erasure_is_distributed(&self) -> Option<bool>
Whether this store’s erasure lifecycle lock spans instances.
None — the default, and the honest answer for most stores — means
there is no lifecycle lock because there is no cryptographic erasure to
serialise. Some(false) means there is one and it is process-local, so
a plane sharing its durable state with another instance would have a
window between an erasure’s hold check and its key destruction in which
the other instance can write. The runtime refuses that pairing at
build, which is why this is a question a store can be asked rather
than a fact an operator has to remember.
Sourcefn erasure_index(&self) -> Option<Arc<dyn SemanticRetriever>>
fn erasure_index(&self) -> Option<Arc<dyn SemanticRetriever>>
The semantic index every erasure through this handle reaches — a sealing layer’s own subject erasure included.
None, the default, is a store that tells no index anything. The
runtime asks it at build when a semantic index is wired, and refuses a
sealed store whose subject erasure would miss that index.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".