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§
Sourcefn 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<'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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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,
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.
Sourcefn 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 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,
Sourcefn 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 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::CaseConflictif the case has moved pastexpected. The caller re-reads and decides again; retrying the same write is the lost update this exists to prevent.StoreError::NotFoundif 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::CaseErasedif the case carries an erasure marker: state written after the erasure began is sealed under a scope that is gone or going.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
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,
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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<'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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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,
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§
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. 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".