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

@@ -19,11 +19,12 @@ backup/
backup.controller.ts # Request handlers
backup.validation.ts # Zod schemas
domain/ # Business logic
backup.types.ts # TypeScript types
backup.types.ts # TypeScript types and constants
backup.service.ts # Core backup operations
backup-archive.service.ts # Archive creation
backup-restore.service.ts # Restore operations
backup-retention.service.ts # Retention enforcement
backup-archive.service.ts # Archive creation
backup-restore.service.ts # Restore operations
backup-retention.service.ts # Tiered retention enforcement
backup-classification.service.ts # Backup category classification
data/ # Data access
backup.repository.ts # Database queries
jobs/ # Scheduled jobs
@@ -31,6 +32,10 @@ backup/
backup-cleanup.job.ts # Retention cleanup
migrations/ # Database schema
001_create_backup_tables.sql
002_add_retention_categories.sql # Tiered retention columns
tests/ # Test files
unit/
backup-classification.service.test.ts # Classification tests
```
## API Endpoints
@@ -122,11 +127,45 @@ Scheduled backups use Redis distributed locking to prevent duplicate backups whe
- Lock TTL: 5 minutes (auto-release if container crashes)
- Only one container creates the backup; others skip
**Retention cleanup:**
**Retention cleanup (tiered):**
- Runs immediately after each successful scheduled backup
- Deletes backups exceeding the schedule's retention count
- Uses tiered classification: each backup can belong to multiple categories
- A backup is only deleted when it exceeds ALL applicable category quotas
- Also runs globally at 4 AM daily as a safety net
## Tiered Retention System
Backups are classified by their creation timestamp into categories:
| Category | Qualification | Retention Count |
|----------|--------------|-----------------|
| hourly | All backups | 8 |
| daily | First backup at midnight UTC | 7 |
| weekly | First backup on Sunday at midnight UTC | 4 |
| monthly | First backup on 1st of month at midnight UTC | 12 |
**Multi-category classification:**
- A backup can belong to multiple categories simultaneously
- Example: Backup at midnight on Sunday, January 1st qualifies as: hourly + daily + weekly + monthly
**Retention logic:**
```
For each category (hourly, daily, weekly, monthly):
1. Get all backups with this category
2. Keep top N (sorted by started_at DESC)
3. Add to protected set
A backup is deleted ONLY if it's NOT in the protected set
(i.e., exceeds quota for ALL its categories)
```
**Expiration calculation:**
- Each backup's `expires_at` is calculated based on its longest retention period
- Monthly backup: 12 months from creation
- Weekly-only backup: 4 weeks from creation
- Daily-only backup: 7 days from creation
- Hourly-only backup: 8 hours from creation
See `backend/src/core/scheduler/README.md` for the distributed locking pattern.
### Admin Routes