feat(storage key rot): add admin panel

This commit is contained in:
Edouard Vanbelle
2026-08-02 00:06:08 +02:00
parent a9d5aae781
commit a58351b7ad
12 changed files with 282 additions and 43 deletions
+16
View File
@@ -76,4 +76,20 @@ pub trait JobHandler: Send + Sync {
///
/// See trait-level docs for guidance on when to return Ok vs Err.
async fn run(&self, args: &JobRunArgs) -> JobOutcome;
/// `true` iff this handler persists per-run rows to
/// `jobs.recoverable_runs` (cursor + findings + resume). Surfaced
/// on [`crate::infrastructure::scheduler::registry::JobSummary`]
/// so the admin UI can decide whether the row is expandable to
/// show a run history + findings drawer, without hardcoding a
/// name-based allowlist.
///
/// Default is `false` — Part 1 periodic handlers (`TrashCleanup`,
/// `StorageReconcile`, `GrantCleanup`, `DedupGc`) don't have runs
/// or findings. `RecoverableAdapter` overrides to `true` so every
/// tenant registered via `register_recoverable_job` flips the flag
/// automatically at registration time.
fn is_recoverable(&self) -> bool {
false
}
}
@@ -802,6 +802,14 @@ impl JobHandler for RecoverableAdapter {
async fn run(&self, args: &JobRunArgs) -> JobOutcome {
run_or_resume(self.inner.clone(), self.provider.clone(), args).await
}
fn is_recoverable(&self) -> bool {
// Every tenant registered through `register_recoverable_job` is
// wrapped by this adapter, so this flag flips true for exactly
// the set of jobs whose runs + findings the admin UI should
// let operators drill into. No name-based allowlists needed
// downstream.
true
}
}
// ─── Ergonomics: JobRegistry extension for recoverable jobs ─────────────────
+6
View File
@@ -225,6 +225,7 @@ impl JobRegistry {
last_run_at,
last_outcome,
running: state.current_run_start.is_some(),
recoverable: entry.handler.is_recoverable(),
}
})
.collect()
@@ -288,6 +289,10 @@ pub enum RegisterError {
/// - `running` — true iff the in-flight permit is currently held
/// (either the supervisor tick is in progress or an admin trigger
/// raced in).
/// - `recoverable` — true iff the job persists runs + findings to
/// `jobs.recoverable_runs`. Consumed by the admin UI to decide
/// whether the row is expandable (drawer with run history +
/// findings) and to gate the retention/purge action.
#[derive(Debug, Clone, Serialize)]
pub struct JobSummary {
pub name: String,
@@ -300,6 +305,7 @@ pub struct JobSummary {
#[serde(skip_serializing_if = "Option::is_none")]
pub last_outcome: Option<JobOutcome>,
pub running: bool,
pub recoverable: bool,
}
#[cfg(test)]