diff --git a/frontend/src/lib/api/types.ts b/frontend/src/lib/api/types.ts index c23efc17..cfe95bc6 100644 --- a/frontend/src/lib/api/types.ts +++ b/frontend/src/lib/api/types.ts @@ -550,6 +550,16 @@ export interface JobSummary { last_run_at?: string; last_outcome?: JobOutcome; running: boolean; + /** + * `true` iff the job persists runs + findings to + * `jobs.recoverable_runs`. Consumed by the admin panel to decide + * whether the row is expandable (drawer with run history + + * findings) and to gate the retention/purge action — replaces + * the pre-K3 name-based allowlist that missed newly-added + * recoverable tenants (`storage_rotate` shipped first without a + * row-expand until this flag was added). + */ + recoverable: boolean; } /** diff --git a/frontend/src/lib/components/AdminJobsPanel.svelte b/frontend/src/lib/components/AdminJobsPanel.svelte index f7140657..8de34fe5 100644 --- a/frontend/src/lib/components/AdminJobsPanel.svelte +++ b/frontend/src/lib/components/AdminJobsPanel.svelte @@ -461,22 +461,13 @@ } function isRecoverable(job: JobSummary): boolean { - // Heuristic: recoverable jobs are the ones that publish runs via - // `jobs.recoverable_runs`. There's no direct flag on JobSummary - // (Part 1 handlers and Part 2 adapters share the same summary - // shape by design). Name-based recognition is fine for now — the - // admin panel is the only consumer; broader use would call for - // a `recoverable: bool` field in JobSummary. - return name_is_recoverable(job.name); - } - function name_is_recoverable(name: string): boolean { - // K3 storage-key-rotation adds `storage_rotate` to the recoverable - // tenant set. Same shape as `storage_migration` — walks blobs, - // records findings, supports resume from cursor — so it needs the - // same expand/runs/findings surface. - return ( - name.endsWith('_consistency') || name === 'storage_migration' || name === 'storage_rotate' - ); + // Backend authoritative source: the `recoverable` flag on + // `JobSummary` is set at registration time by + // `RecoverableAdapter::is_recoverable() -> true`. Every tenant + // registered via `register_recoverable_job` flips it + // automatically. No name-based allowlists — a new recoverable + // tenant is expandable in the UI as soon as it's registered. + return job.recoverable; } // Consistency batch shortcut — top button. Only shown when the diff --git a/frontend/static/locales/en.json b/frontend/static/locales/en.json index a2310759..275eba00 100644 --- a/frontend/static/locales/en.json +++ b/frontend/static/locales/en.json @@ -1205,7 +1205,7 @@ "jobs": { "tab": "Jobs", "title": "Jobs", - "hint": "Fires periodic + on-demand jobs. Consistency checks are safe to run at any time — they are read-only.", + "hint": "This section concerns internal jobs: periodic + on-demand jobs. Consistency checks are safe to run at any time — they are read-only.", "run_all_consistency": "Run all consistency checks", "run_deep": "Run deep", "run_deep_hint": "Also runs slow variants (blob re-hash, bitrot detection).",