//! Background expiry/retention sweeper. Mirrors the durable-run worker: a single //! periodic loop that deletes overdue/stale rows so security-relevant tables //! (auth sessions, oauth states) don't leak credentials and journal/audit tables //! don't grow unbounded. Best-effort — a failed sweep is logged and retried next //! tick. The first tick fires immediately on boot. use std::time::Duration; use sqlx::PgPool; use cm_db::repo::cleanup; // Retention windows (days). Security-relevant rows expire with no grace. const OUTBOX_SENT_DAYS: i32 = 7; const RUN_EVENTS_DAYS: i32 = 14; // beyond the live SSE-replay window const ROUTINE_RUNS_DAYS: i32 = 30; const TOPOLOGY_RUNS_DAYS: i32 = 90; // user-visible run history const CONSUMED_GRANTS_DAYS: i32 = 7; /// Spawn the cleanup sweeper, running every `interval` (e.g. hourly). pub fn spawn(pool: PgPool, interval: Duration) { tokio::spawn(async move { let mut tick = tokio::time::interval(interval); loop { tick.tick().await; sweep_once(&pool).await; } }); } async fn sweep_once(pool: &PgPool) { let steps: [(&str, Result); 7] = [ ("auth_sessions", cleanup::expire_auth_sessions(pool).await), ("oauth_states", cleanup::expire_oauth_states(pool).await), ( "outbox", cleanup::prune_sent_outbox(pool, OUTBOX_SENT_DAYS).await, ), ( "run_events", cleanup::prune_run_events(pool, RUN_EVENTS_DAYS).await, ), ( "routine_runs", cleanup::prune_routine_runs(pool, ROUTINE_RUNS_DAYS).await, ), ( "topology_runs", cleanup::prune_topology_runs(pool, TOPOLOGY_RUNS_DAYS).await, ), ( "execution_grants", cleanup::prune_consumed_grants(pool, CONSUMED_GRANTS_DAYS).await, ), ]; let mut total = 0u64; for (table, result) in steps { match result { Ok(n) => total += n, Err(e) => eprintln!("cleanup_sweeper: {table} sweep failed: {e}"), } } if total > 0 { eprintln!("cleanup_sweeper: removed {total} stale row(s)"); } }