fix: Implement tiered backup retention classification (refs #6)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 6m15s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 28s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped

Replace per-schedule count-based retention with unified tiered classification.
Backups are now classified by timestamp into categories (hourly/daily/weekly/monthly)
and are only deleted when they exceed ALL applicable category quotas.

Changes:
- Add backup-classification.service.ts for timestamp-based classification
- Rewrite backup-retention.service.ts with tiered logic
- Add categories and expires_at columns to backup_history
- Add Expires column to desktop and mobile backup UI
- Add unit tests for classification logic (22 tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-01-10 21:53:43 -06:00
parent 82a543b250
commit 19ece562ed
12 changed files with 681 additions and 114 deletions

View File

@@ -773,7 +773,7 @@ export const AdminBackupMobileScreen: React.FC = () => {
</span>
</div>
<div className="flex items-center gap-4 mb-3 text-xs text-slate-600">
<div className="flex flex-wrap items-center gap-2 mb-3 text-xs text-slate-600">
<span
className={`px-2 py-1 rounded ${
backup.backupType === 'scheduled'
@@ -784,6 +784,11 @@ export const AdminBackupMobileScreen: React.FC = () => {
{backup.backupType}
</span>
<span>{formatFileSize(backup.fileSizeBytes)}</span>
{backup.expiresAt && (
<span className="text-slate-500">
Expires: {formatDate(backup.expiresAt)}
</span>
)}
</div>
<div className="flex gap-2">

View File

@@ -288,6 +288,7 @@ export interface PromoteToAdminRequest {
export type BackupFrequency = 'hourly' | 'daily' | 'weekly' | 'monthly';
export type BackupType = 'scheduled' | 'manual';
export type BackupStatus = 'in_progress' | 'completed' | 'failed';
export type BackupCategory = 'hourly' | 'daily' | 'weekly' | 'monthly';
export interface BackupHistory {
id: string;
@@ -304,6 +305,8 @@ export interface BackupHistory {
completedAt: string | null;
createdBy: string | null;
metadata: Record<string, unknown>;
categories: BackupCategory[];
expiresAt: string | null;
}
export interface BackupSchedule {

View File

@@ -386,6 +386,7 @@ export const AdminBackupPage: React.FC = () => {
<TableCell>Size</TableCell>
<TableCell>Status</TableCell>
<TableCell>Created</TableCell>
<TableCell>Expires</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
@@ -415,6 +416,9 @@ export const AdminBackupPage: React.FC = () => {
/>
</TableCell>
<TableCell>{formatDate(backup.startedAt)}</TableCell>
<TableCell>
{backup.expiresAt ? formatDate(backup.expiresAt) : '-'}
</TableCell>
<TableCell align="right">
<IconButton
size="small"