Skip to main content

DaemonStorage

Trait DaemonStorage 

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

Source

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.

Source

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.

Source

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.

Source

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.

Source

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_at set). Only frozen batches move: freezing guarantees rows are settled and the counters are the durable record, and it carries Phase 2’s retry_version protection — 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.*, $bucket with ON 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.
Source

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

Source

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.

Source

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.

Source

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.

Source

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

Implementors§