Skip to main content

CaseStore

Trait CaseStore 

Source
pub trait CaseStore:
    Send
    + Sync
    + Debug {
Show 33 methods // Required methods fn correlate<'life0, 'life1, 'async_trait>( &'life0 self, keys: &'life1 [CorrelationKey], ) -> Pin<Box<dyn Future<Output = Result<Option<CaseId>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn correlate_or_open<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, kind: &'life1 str, keys: &'life2 [CorrelationKey], at: Timestamp, ) -> Pin<Box<dyn Future<Output = Result<Correlation, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait; fn case<'life0, 'async_trait>( &'life0 self, id: CaseId, ) -> Pin<Box<dyn Future<Output = Result<Option<Case>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn cases<'life0, 'async_trait>( &'life0 self, after: Option<CaseId>, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Case>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn import_case<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, case: &'life1 Case, deadlines: &'life2 [Deadline], blobs: &'life3 [Digest], ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait; fn attach_run<'life0, 'async_trait>( &'life0 self, case: CaseId, run: RunId, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn link_blob<'life0, 'async_trait>( &'life0 self, case: CaseId, digest: Digest, at: Timestamp, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn blobs_of<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<Vec<Digest>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn put_state<'life0, 'async_trait>( &'life0 self, case: CaseId, expected: CaseVersion, state: Value, ) -> Pin<Box<dyn Future<Output = Result<CaseVersion, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn set_status<'life0, 'async_trait>( &'life0 self, case: CaseId, status: CaseStatus, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn close<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn register_deadline<'life0, 'life1, 'async_trait>( &'life0 self, deadline: &'life1 Deadline, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn deadlines<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<Vec<Deadline>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn set_deadline_state<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, name: &'life1 str, state: DeadlineState, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn breach_deadline<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, name: &'life1 str, now: Timestamp, ) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn breaches_to_note<'life0, 'async_trait>( &'life0 self, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Deadline>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn mark_breach_noted<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, name: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn due<'life0, 'async_trait>( &'life0 self, now: Timestamp, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Deadline>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn breached<'life0, 'async_trait>( &'life0 self, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Deadline>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn acknowledge_breach<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, case: CaseId, name: &'life1 str, note: &'life2 BreachNote, ) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait; fn place_hold<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, hold: &'life1 LegalHold, ) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn release_hold<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn release_hold_if<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, standing: &'life1 LegalHold, ) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn hold<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<Option<LegalHold>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn begin_erasure<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, at: Timestamp, reason: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<ErasureStart, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn complete_erasure<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn erasure<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<Option<Erasure>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn holds<'life0, 'async_trait>( &'life0 self, after: Option<CaseId>, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<(CaseId, LegalHold)>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn by_status<'life0, 'async_trait>( &'life0 self, status: CaseStatus, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Case>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn census<'life0, 'async_trait>( &'life0 self, now: Timestamp, ) -> Pin<Box<dyn Future<Output = Result<CaseCensus, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn record_drill<'life0, 'life1, 'async_trait>( &'life0 self, record: &'life1 DrillRecord, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait; fn last_drill<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Option<DrillRecord>, StoreError>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; // Provided method fn tenant(&self) -> &str { ... }
}
Expand description

Long-lived case state, correlated by business key.

Correlation is a deterministic lookup — never a model call. It runs at admission, before planning, because which case a message belongs to is a question of fact, not of judgement.

Required Methods§

Source

fn correlate<'life0, 'life1, 'async_trait>( &'life0 self, keys: &'life1 [CorrelationKey], ) -> Pin<Box<dyn Future<Output = Result<Option<CaseId>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Find an open case matching any of these keys.

Closed cases are not matched: a new message about a settled matter opens a new case rather than reanimating one that was concluded and audited.

Source

fn correlate_or_open<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, kind: &'life1 str, keys: &'life2 [CorrelationKey], at: Timestamp, ) -> Pin<Box<dyn Future<Output = Result<Correlation, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Correlate, or open a new case if nothing matched.

Implementations must make this atomic. Two messages for the same new case arriving concurrently must produce one case, not two — otherwise a process fragments across cases and its obligations are tracked in neither.

Source

fn case<'life0, 'async_trait>( &'life0 self, id: CaseId, ) -> Pin<Box<dyn Future<Output = Result<Option<Case>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Fetch one case.

Named for what it returns rather than get: a single store commonly implements several of these traits, and same-named methods make every call site ambiguous.

Source

fn cases<'life0, 'async_trait>( &'life0 self, after: Option<CaseId>, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Case>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Every case, one bounded page at a time, in stable id order.

This is the export’s read. by_status cannot serve it: a bounded list with no cursor enumerates a prefix and calls it everything — the silent truncation this project refuses elsewhere, at the one boundary whose whole job is completeness. after is the last id a caller saw, and paging resumes strictly beyond it.

Ordered by id rather than by anything business-shaped, because the order’s only job is that two pages never overlap and never gap.

Returns state as stored. A sealing decorator does not open it here, unlike case: this read exists for the export, and an export of plaintext would quietly undo erasure — the key destroyed tomorrow would no longer reach the copy taken today.

Source

fn import_case<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, case: &'life1 Case, deadlines: &'life2 [Deadline], blobs: &'life3 [Digest], ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait,

Write one complete case, exactly as given — an import authority, not a runtime path.

The ordinary write paths refuse to say some of what a restore must: put_state allocates versions one at a time, correlate_or_open mints a fresh id, and neither can reproduce a case at version 4 000 with the id every exported record already names. This is the same seam governed memory keeps for the same reason — direct complete-item writes are a deployment/import authority at the store boundary, never something a skill reaches.

Implementations must leave the imported case reachable by every read path — case, correlate, by_status, due, blobs_of — which is what the conformance battery holds them to: the read paths are the check on this method’s index maintenance, because an import that rebuilds five indexes out of six reads perfectly until somebody queries the sixth.

§Errors

A store that already holds this case id refuses: a restore rebuilds a case layer, it does not merge one.

Source

fn attach_run<'life0, 'async_trait>( &'life0 self, case: CaseId, run: RunId, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Record that a run touched this case.

Record that a case produced a blob.

The case is what an erasure request actually names — nobody asks to forget a digest — so something has to know which bytes belong to which matter. That association cannot live in the blob store, which is content-addressed on purpose and has no idea what a case is, and it cannot be recomputed later: a digest is deliberately not reversible.

Recording the same blob twice is the same record. Two runs on one case storing identical bytes land on one digest by construction, and that is one artifact, not two.

§Errors

If the store rejects the write.

Source

fn blobs_of<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<Vec<Digest>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Every blob this case produced, oldest first.

§Errors

If the store cannot be read.

Source

fn put_state<'life0, 'async_trait>( &'life0 self, case: CaseId, expected: CaseVersion, state: Value, ) -> Pin<Box<dyn Future<Output = Result<CaseVersion, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Replace a case’s state, if it is still at expected.

§Why this takes a version

A case is shared by every run correlated to it, and the window between reading its state and writing it back contains a model call — which is unbounded. Two runs on one case will overlap, and a blind write in that window silently discards whichever one lost. See CaseVersion.

Implementations must make the check part of the write itself — a single UPDATE ... WHERE version = ? — and not a read followed by a write. A check performed in application code is a check the next caller can race.

§Errors
  • StoreError::CaseConflict if the case has moved past expected. The caller re-reads and decides again; retrying the same write is the lost update this exists to prevent.
  • StoreError::NotFound if there is no such case. Implementations must tell these apart — reporting a missing case as a conflict sends the caller into a re-read loop against something that will never exist.
  • StoreError::CaseErased if the case carries an erasure marker: state written after the erasure began is sealed under a scope that is gone or going.
Source

fn set_status<'life0, 'async_trait>( &'life0 self, case: CaseId, status: CaseStatus, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Move a case to a status.

Closed routes through close, because closure releases the correlation keys as well as writing the column, and the two spellings of closed must not drift.

Leaving Closed re-claims them, which is the same rule read backwards. Without it a matter reopened by any route — a run calling set_case_status, or the sweep escalating over an expired task — comes back as a case no inbound message can ever correlate to again, which looks exactly like a live matter and is not one. A key another case has since claimed stays with that case: the identifier belongs to whichever matter is open for it now, and reopening must not take one back.

An erased case is refused, whatever the status: reopening one would re-claim its keys and route new work into a matter whose data is gone.

Source

fn close<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Close a case.

Fails while an obligation is open. A case with an unmet deadline cannot be closed silently: that is precisely how a missed regulatory window becomes invisible.

Source

fn register_deadline<'life0, 'life1, 'async_trait>( &'life0 self, deadline: &'life1 Deadline, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Register an obligation. The resolved instant is stored as given and never recomputed.

A name is registered once. Registering it again with the same instant, calendar and warning is the same registration and does nothing; with anything different it is refused, because the journal records each registration and a store that kept the first silently would enforce terms the journal does not show as current.

§Errors

StoreError::DeadlineExists if the name is registered with other terms. StoreError::CaseClosed if the matter is closed. close refuses while an obligation is outstanding, and that check is worth nothing on its own: it holds at one instant, and this is the write that walks past it afterwards. Both halves are needed for a closed case owes nothing to be a property of the store rather than of the order two callers happened to run in.

Implementations must make the two decide one at a time. The redb backend gets that from its single write transaction; a SQL backend has to take the case row’s lock, because two snapshots each reading the other’s pre-state is how both writes commit and the matter ends up closed and owing.

Source

fn deadlines<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<Vec<Deadline>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source

fn set_deadline_state<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, name: &'life1 str, state: DeadlineState, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source

fn breach_deadline<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, name: &'life1 str, now: Timestamp, ) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Breach an obligation and escalate its case, in one transaction, if it is still outstanding and due at now. Returns whether it applied.

One verb because the sweep decides from a read that is already stale when it acts: a run can meet the obligation and close the case in between. Spelled as separate writes, the sweep then escalates a closed case — reopening it and re-claiming its correlation keys — and breaches an obligation that was met. Here the check and both writes are one decision, and false means somebody got there first.

The case is escalated only while it is not Closed.

An applied breach is also marked as owing its account, in the same transaction: the sweep acts first and writes its notes after, so a crash between the two leaves a breach due no longer lists. breaches_to_note is where the next tick finds it, and mark_breach_noted is what the notes landing retires.

§Errors

StoreError::NotFound if the case or the obligation does not exist.

Source

fn breaches_to_note<'life0, 'async_trait>( &'life0 self, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Deadline>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Breaches breach_deadline applied whose account the sweep has not yet written, longest-overdue first.

Source

fn mark_breach_noted<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, name: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Record that a breach’s account is on the journal. Idempotent.

§Errors

StoreError::NotFound if the obligation does not exist.

Source

fn due<'life0, 'async_trait>( &'life0 self, now: Timestamp, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Deadline>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Obligations that are due or approaching, oldest first.

The sweep that turns a passing instant into an escalation. Without it a deadline is a stored number that nobody reads.

Source

fn breached<'life0, 'async_trait>( &'life0 self, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Deadline>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Missed obligations nobody has accounted for, longest-overdue first.

Reads the obligation’s own row, which outlives every status its case passes through — the escalation a breach causes is retired by close, and closure is when people stop looking.

Not derivable from due, which answers what still needs attention. A breach is past attention; it needs an account.

Acknowledged breaches are excluded, which is what makes this a backlog rather than a level that only rises: taken oldest-first and bounded by a page, a listing nothing ever leaves shows the same head forever, and the entries an operator could still act on are the ones they never see. acknowledge_breach is the verb that empties it; the obligation stays Breached either way, because what ends is the question, not the fact.

Source

fn acknowledge_breach<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, case: CaseId, name: &'life1 str, note: &'life2 BreachNote, ) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Record that somebody has accounted for a breach.

Takes it off breached and leaves the obligation Breached, with the account readable through deadlines — the fact and the answer to it are different things and both are kept.

Idempotent, first account wins. Returns whether this call was the one that recorded it, so a retry cannot rewrite who looked or when — the same rule KeyRing::destroy follows for the same reason.

§Errors

StoreError::NotFound if the case or the obligation does not exist, and StoreError::NotBreached if it exists and has not been breached — accepting an account before the breach would take an obligation off the listing while it was still going to be missed.

Source

fn place_hold<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, hold: &'life1 LegalHold, ) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Preserve this matter against every erasure verb until somebody lifts it.

A held case refuses erasure. The hold exists because a retention pass is automatic: it runs on a window nobody re-reads, and a matter under a preservation order looks exactly like every other closed case old enough to sweep.

Idempotent, first placement wins, returning whether this call placed it — acknowledge_breach’s rule, for its reason: a retry must not rewrite when the hold began or why.

§Errors

StoreError::NotFound if the case does not exist — a hold on a matter that is not there preserves nothing and reads as effective in the listing. StoreError::CaseErased if the case carries an erasure marker (begin_erasure): a hold accepted on a matter whose erasure has begun preserves nothing and reads as effective.

Source

fn release_hold<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Lift a hold, returning whether one was there to lift.

The verb that empties holds; without it that listing is a level that only rises, which breached refuses for the same reason.

The register answers what is preserved now, not what ever was. The register keeps no row for a released hold, deliberately: a kept row would make its free-text reason outlive the matter it preserved. The history is the journal’s — Runtime::release_hold records who released the hold, when, and who had placed it, stamped with the case and without the reason, before removing the hold through release_hold_if; both of the plane’s doors release through that. This is the raw register verb: a direct call is the embedder’s own act, and nothing records it.

§Errors

StoreError::NotFound if the case does not exist.

Source

fn release_hold_if<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, standing: &'life1 LegalHold, ) -> Pin<Box<dyn Future<Output = Result<bool, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Lift the hold on case only if it is still standing — same instant, same reason, same operator.

Answers whether that hold was removed. false when the matter is not held or holds a different hold; a different hold is left standing.

The compare and the removal MUST be one write. A release followed by a re-place puts a new hold where the old one was, so an unconditional removal by a releaser that read the old one would delete the new hold under a record that names the old. Runtime::release_hold removes through this, with the hold it journaled.

§Errors

StoreError::NotFound if the case does not exist; otherwise if the store cannot be reached, or holds a row it cannot read.

Source

fn hold<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<Option<LegalHold>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

The hold on one matter, if it is held.

The question every erasure verb asks before it destroys anything.

Source

fn begin_erasure<'life0, 'life1, 'async_trait>( &'life0 self, case: CaseId, at: Timestamp, reason: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<ErasureStart, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Record that case is being erased, deciding against a hold in the same write — the step erase_case takes before it destroys anything.

A hold read and then acted on is a check a concurrent place_hold walks past: the hold is accepted and the key is destroyed anyway. Here the hold check, the status check and the marker are one decision, and place_hold refuses a case carrying the marker, so of a hold and an erasure racing exactly one wins and the loser is told.

Answers ErasureStart::Held or ErasureStart::NotClosed — writing nothing — or ErasureStart::Marked with the marker that stands. First marker wins: a retry gets the original instant and reason back, so tombstones and key destructions written by the retry say what the first attempt said.

§Errors

StoreError::NotFound if the case does not exist.

Source

fn complete_erasure<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Record that the erasure begin_erasure marked has destroyed everything it reaches. Idempotent.

Separate from the marker because the marker has to land before the destruction and this after it: a crash in between leaves an erasure that is begun and not complete, which a retention pass runs again rather than skipping.

§Errors

StoreError::NotFound if the case does not exist, and an error if no erasure was begun on it.

Source

fn erasure<'life0, 'async_trait>( &'life0 self, case: CaseId, ) -> Pin<Box<dyn Future<Output = Result<Option<Erasure>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

The erasure record on one matter, if an erasure was begun on it.

The plane’s own account of erased by us: a destroyed key with no record here is a key that went missing, which is a finding rather than a completed erasure.

Source

fn holds<'life0, 'async_trait>( &'life0 self, after: Option<CaseId>, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<(CaseId, LegalHold)>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Every matter under hold, oldest hold first.

The half that makes this a control: a store answering only hold can be queried solely by somebody who already knows which case to ask about, which delivers nothing to the person whose job is to find out what is still preserved and why.

Ascending legitimately — release_hold removes entries, so the head is not permanent, and the longest-standing unlifted hold is the one that needs a question asked about it.

Source

fn by_status<'life0, 'async_trait>( &'life0 self, status: CaseStatus, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Case>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Cases matching a status, newest first.

Source

fn census<'life0, 'async_trait>( &'life0 self, now: Timestamp, ) -> Pin<Box<dyn Future<Output = Result<CaseCensus, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

How much is open right now, for the gauges in runtime::metrics.

Deliberately not expressible as by_status(...).len(): that is bounded by a limit, and a gauge computed from a truncated list is a number that stops rising exactly when the backlog becomes worth knowing about. It is also the only consumer of a case’s opened_at — a count alone cannot distinguish ten cases open for an hour from ten open for a month.

now is passed in so the reading is testable against arbitrary ageing and needs no escape from the determinism gate.

Source

fn record_drill<'life0, 'life1, 'async_trait>( &'life0 self, record: &'life1 DrillRecord, ) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Record what the last recovery rehearsal found.

One row, replaced. A history of drills is a different artifact and a larger promise; what an audit asks is when did you last rehearse and did it pass, and a row that the latest write replaces answers exactly that without becoming a table nobody prunes.

§Errors

StoreError if the store is unreachable. A rehearsal whose verdict could not be written is reported to its caller rather than swallowed: the next audit would otherwise read the previous drill’s date as the most recent one.

Source

fn last_drill<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Option<DrillRecord>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

The last recovery rehearsal, or None if this plane has never run one.

None is the honest answer and a meaningful one: nobody has rehearsed is what an auditor most wants to know and is exactly what a missing CI log cannot distinguish from the log rotated.

§Errors

StoreError if the store is unreachable.

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. When a key ring is wired, build() seals case state under the plane’s tenant while the store writes rows under its own; the two disagreeing is not a leak — the scopes simply differ — but it puts case state under a scope erase_case will never destroy. That is an erasure that reports success and misses, which is the one failure a deletion guarantee cannot have.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§