pub trait DaemonStorage: Send + Sync {
// Required methods
fn persist_daemon<'life0, 'life1, 'async_trait, T>(
&'life0 self,
record: &'life1 DaemonRecord<T>,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where AnyDaemonRecord: From<DaemonRecord<T>>,
T: 'async_trait + DaemonState + Clone,
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn get_daemon<'life0, 'async_trait>(
&'life0 self,
daemon_id: DaemonId,
) -> Pin<Box<dyn Future<Output = Result<AnyDaemonRecord>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn list_daemons<'life0, 'async_trait>(
&'life0 self,
status_filter: Option<DaemonStatus>,
) -> Pin<Box<dyn Future<Output = Result<Vec<AnyDaemonRecord>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn purge_orphaned_rows<'life0, 'async_trait>(
&'life0 self,
batch_size: i64,
) -> Pin<Box<dyn Future<Output = Result<u64>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn archive_batch<'life0, 'async_trait>(
&'life0 self,
batch_id: BatchId,
) -> Pin<Box<dyn Future<Output = Result<ArchiveOutcome>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn list_archivable_batches<'life0, 'async_trait>(
&'life0 self,
limit: i64,
oldest_first: bool,
cancel_grace_secs: f64,
min_frozen_age_secs: f64,
) -> Pin<Box<dyn Future<Output = Result<Vec<BatchId>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn count_archivable_batches<'life0, 'async_trait>(
&'life0 self,
cancel_grace_secs: f64,
) -> Pin<Box<dyn Future<Output = Result<i64>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn count_unfrozen_terminal_batches<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<i64>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn ensure_archive_partitions<'life0, 'async_trait>(
&'life0 self,
weeks_ahead: i32,
) -> Pin<Box<dyn Future<Output = Result<(i64, i64)>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn purge_model_filter_events<'life0, 'async_trait>(
&'life0 self,
batch_size: i64,
keep_per_model: i64,
retention_secs: f64,
) -> Pin<Box<dyn Future<Output = Result<u64>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
}Expand description
Daemon lifecycle persistence.
This trait provides storage operations for tracking daemon state, including registration, heartbeat updates, and graceful shutdown.
Required Methods§
Sourcefn persist_daemon<'life0, 'life1, 'async_trait, T>(
&'life0 self,
record: &'life1 DaemonRecord<T>,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
AnyDaemonRecord: From<DaemonRecord<T>>,
T: 'async_trait + DaemonState + Clone,
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn persist_daemon<'life0, 'life1, 'async_trait, T>(
&'life0 self,
record: &'life1 DaemonRecord<T>,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
AnyDaemonRecord: From<DaemonRecord<T>>,
T: 'async_trait + DaemonState + Clone,
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Persist daemon state update.
This is a low-level method used by state transition methods.
The type parameter T ensures type-safe state transitions.
Sourcefn get_daemon<'life0, 'async_trait>(
&'life0 self,
daemon_id: DaemonId,
) -> Pin<Box<dyn Future<Output = Result<AnyDaemonRecord>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_daemon<'life0, 'async_trait>(
&'life0 self,
daemon_id: DaemonId,
) -> Pin<Box<dyn Future<Output = Result<AnyDaemonRecord>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Get daemon by ID.
Returns an AnyDaemonRecord which can hold the daemon in any state.
Sourcefn list_daemons<'life0, 'async_trait>(
&'life0 self,
status_filter: Option<DaemonStatus>,
) -> Pin<Box<dyn Future<Output = Result<Vec<AnyDaemonRecord>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn list_daemons<'life0, 'async_trait>(
&'life0 self,
status_filter: Option<DaemonStatus>,
) -> Pin<Box<dyn Future<Output = Result<Vec<AnyDaemonRecord>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
List all daemons with optional status filter.
If status_filter is None, returns all daemons regardless of status.
Otherwise, returns only daemons matching the specified status.
Sourcefn purge_orphaned_rows<'life0, 'async_trait>(
&'life0 self,
batch_size: i64,
) -> Pin<Box<dyn Future<Output = Result<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn purge_orphaned_rows<'life0, 'async_trait>(
&'life0 self,
batch_size: i64,
) -> Pin<Box<dyn Future<Output = Result<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Purge orphaned request_templates and requests whose parent (file or batch) has been soft-deleted or whose FK is NULL.
Deletes at most batch_size rows from each table per call.
Returns total rows deleted across both tables. Called periodically by
the daemon purge task for right-to-erasure compliance.
Sourcefn archive_batch<'life0, 'async_trait>(
&'life0 self,
batch_id: BatchId,
) -> Pin<Box<dyn Future<Output = Result<ArchiveOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn archive_batch<'life0, 'async_trait>(
&'life0 self,
batch_id: BatchId,
) -> Pin<Box<dyn Future<Output = Result<ArchiveOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Move one terminal batch’s request rows from requests (live) into
batch_requests_archive in a single bounded transaction (batches are
capped at 50k rows), stamping batches.location = 'archive' and
batches.archive_bucket.
Preconditions are checked inside the transaction; violations return a
Skipped* outcome rather than an error — the sweeper treats skips as
normal flow:
- batch exists, not soft-deleted,
location = 'live', counts frozen (counts_frozen_atset). Only frozen batches move: freezing guarantees rows are settled and the counters are the durable record, and it carries Phase 2’sretry_versionprotection — any retry un-freezes and bumps the version first. - the weekly archive partition for the batch’s bucket exists; otherwise the batch simply stays live (fully served, exactly as today) and the caller alerts — graceful degradation, no failure.
- no row is referenced by
response_steps(those stay live until the batchless store gives them a home).
Transaction invariants (fusillade-requests-phase3-plan.md §1):
- forward move is
INSERT ... SELECT r.*, $bucketwithON CONFLICT DO NOTHING— idempotent under crash-resume replay. - the DELETE removes only rows verifiably present in the archive and the transaction aborts if any row would be left behind: a row lives in exactly one table, always.
- the location stamp re-checks
retry_version(CAS) even though the batch-row lock makes a race impossible on this path — belt and braces against future callers taking weaker locks.
Sourcefn list_archivable_batches<'life0, 'async_trait>(
&'life0 self,
limit: i64,
oldest_first: bool,
cancel_grace_secs: f64,
min_frozen_age_secs: f64,
) -> Pin<Box<dyn Future<Output = Result<Vec<BatchId>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn list_archivable_batches<'life0, 'async_trait>(
&'life0 self,
limit: i64,
oldest_first: bool,
cancel_grace_secs: f64,
min_frozen_age_secs: f64,
) -> Pin<Box<dyn Future<Output = Result<Vec<BatchId>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
List batches eligible for archiving (location = 'live', counts
frozen, not soft-deleted). Both production movers — the steady-state
sweeper AND the historical backfill — pass oldest_first = true: in
steady state the sweeper drains its whole candidate set every few
ticks so order is cosmetic, and under any backlog the
least-recently-created batches are the least likely to ever be read
again, so early issues have minimal blast radius. false
(newest-first) exists as an ordering choice for other callers.
cancel_grace_secs is the cancellation grace window: a batch is NOT
a candidate while it has canceled rows that were IN FLIGHT at cancel
(the cascade leaves claimed_at set on them; pending-canceled rows
have it NULL) with canceled_at younger than the grace. Cancellation
is async and best-effort, and billed in-flight results SUPERSEDE the
cancel (see the persist() transition matrix, fusillade 21.2.1) — the
supersede lands on the LIVE row, so the rows must not move until all
in-flight work has had time to declare itself. Default the grace to
the processing timeout (~10 min): only cancelled batches archive
later, fully served from live meanwhile; normal batches have no such
rows and are unaffected. A frozen batch can never GAIN such a row
(the cascade only touches non-terminal rows and freezing requires
all-terminal), so this selection-time check cannot be raced by the
move itself.
min_frozen_age_secs is the post-freeze dwell: 0 means frozen
batches are candidates immediately (the default — reads are mid-move
safe by construction and the sweep cadence provides organic dwell).
Sourcefn count_archivable_batches<'life0, 'async_trait>(
&'life0 self,
cancel_grace_secs: f64,
) -> Pin<Box<dyn Future<Output = Result<i64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn count_archivable_batches<'life0, 'async_trait>(
&'life0 self,
cancel_grace_secs: f64,
) -> Pin<Box<dyn Future<Output = Result<i64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Count of batches currently eligible for archiving (same predicate as
Self::list_archivable_batches minus the ordering/limit) — the
sweep-backlog gauge. Index-only on the partial sweep index.
Sourcefn count_unfrozen_terminal_batches<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<i64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn count_unfrozen_terminal_batches<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<i64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Count of terminal batches (a terminal timestamp set) whose counters have not been frozen — the finalization-lag gauge. Sustained nonzero means batches are stuck on the recount path and cannot archive.
Sourcefn ensure_archive_partitions<'life0, 'async_trait>(
&'life0 self,
weeks_ahead: i32,
) -> Pin<Box<dyn Future<Output = Result<(i64, i64)>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn ensure_archive_partitions<'life0, 'async_trait>(
&'life0 self,
weeks_ahead: i32,
) -> Pin<Box<dyn Future<Output = Result<(i64, i64)>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Ensure weekly archive partitions exist through now + weeks_ahead
(create -> bounds CHECK -> attach; advisory-locked; idempotent).
Returns (created, ahead): partitions created this call, and how
many future weeks (including the current one) now have partitions —
the fusillade_archive_partitions_ahead gauge, alert-worthy when it
shrinks below 2.
Sourcefn purge_model_filter_events<'life0, 'async_trait>(
&'life0 self,
batch_size: i64,
keep_per_model: i64,
retention_secs: f64,
) -> Pin<Box<dyn Future<Output = Result<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn purge_model_filter_events<'life0, 'async_trait>(
&'life0 self,
batch_size: i64,
keep_per_model: i64,
retention_secs: f64,
) -> Pin<Box<dyn Future<Output = Result<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Purge old model_filters events, ALWAYS retaining, per model, the most
recent keep_per_model events (so the current-state lookup and a short
history window survive) AND every event newer than retention_secs
regardless of count.
Deletes at most batch_size rows per call. Returns rows deleted.
Called periodically by the daemon purge task to bound the append-only
log. keep_per_model >= 1 guarantees the latest event per model is
never purged, so the claim gate never loses a model’s current state.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".