Problem
The retention policy is configurable but never enforced. GET/PUT /v1/admin/retention (app/routes/v1/admin/retention.py:36-40) reads/writes keep_count and keep_days to <workspace_root>/retention.json, but no code consumes that file:
Snapshot.expired (app/core/models.py:133) is never set to True anywhere in the codebase — it is only read to refuse rollback against an already-expired snapshot.
app/main.py:61-66 registers only the orphan-reconcile apscheduler job; there is no retention/expiry/purge job.
Impact
- Snapshots accumulate indefinitely → unbounded Proxmox storage growth.
- The retention UI control is a no-op, which is misleading to operators.
Proposed work
- Add a periodic apscheduler job (alongside orphan reconcile in
app/main.py lifespan) that reads the policy and marks/deletes stale snapshots.
- Compute staleness from
keep_count (N most-recent) and keep_days (younger than D days); set Snapshot.expired = True and/or enqueue delete attempts.
Evidence
app/routes/v1/admin/retention.py — policy persisted, no enforcement path
app/core/models.py:133 — Snapshot.expired never written
app/main.py:61-66 — only orphan-reconcile job scheduled
Related (minor, can fold in here)
app/core/locks.py docstring states stale-lock cleanup() runs "from apscheduler every interval", but cleanup_stale_locks() is not registered as a scheduled job (stale locks are still evicted on acquire(), so this is low-risk). If a retention job is added, consider registering lock cleanup on the same scheduler to match the docstring.
Surfaced by a code audit; confirmed in the dev branch.
Problem
The retention policy is configurable but never enforced.
GET/PUT /v1/admin/retention(app/routes/v1/admin/retention.py:36-40) reads/writeskeep_countandkeep_daysto<workspace_root>/retention.json, but no code consumes that file:Snapshot.expired(app/core/models.py:133) is never set toTrueanywhere in the codebase — it is only read to refuse rollback against an already-expired snapshot.app/main.py:61-66registers only the orphan-reconcile apscheduler job; there is no retention/expiry/purge job.Impact
Proposed work
app/main.pylifespan) that reads the policy and marks/deletes stale snapshots.keep_count(N most-recent) andkeep_days(younger than D days); setSnapshot.expired = Trueand/or enqueue delete attempts.Evidence
app/routes/v1/admin/retention.py— policy persisted, no enforcement pathapp/core/models.py:133—Snapshot.expirednever writtenapp/main.py:61-66— only orphan-reconcile job scheduledRelated (minor, can fold in here)
app/core/locks.pydocstring states stale-lockcleanup()runs "from apscheduler every interval", butcleanup_stale_locks()is not registered as a scheduled job (stale locks are still evicted onacquire(), so this is low-risk). If a retention job is added, consider registering lock cleanup on the same scheduler to match the docstring.Surfaced by a code audit; confirmed in the
devbranch.