Skip to main content

MemoryStore

Trait MemoryStore 

Source
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§

Source

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.

Source

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.

Source

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,

The current versions matching a query — most trusted first, then newest, then id, globally across purposes (see Recall for why the ordering is contractual).

Superseded and forgotten versions are not returned.

§Errors

If the store cannot be reached.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

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.

Source

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.

Source

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.

Source

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.

Source

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§

Source

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.

Source

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.

Source

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".

Implementors§