Skip to content

feat: enforce per-subscription retention policy#78

Merged
windoze95 merged 1 commit into
mainfrom
feature/backend-retention
Jun 28, 2026
Merged

feat: enforce per-subscription retention policy#78
windoze95 merged 1 commit into
mainfrom
feature/backend-retention

Conversation

@windoze95

Copy link
Copy Markdown
Owner

Make retention_policy/retention_count real (roadmap LATER tier, #12) — they were accepted + persisted but never enforced. Additive — no schema change.

Where / semantics

The fields live on UserSubscription (per-user, per-channel). The real wire values come from the clients: KEEP_ALL, KEEP_LAST_N, KEEP_WATCHED (the audit's "KEEP_LATEST_N" was a guess — matched the clients).

Sweep (app/services/retention.pyenforce_retention)

  • KEEP_LAST_N: for each subscription, keep the newest retention_count downloaded (COMPLETE) videos (by coalesce(uploaded_at, created_at) desc) and soft-remove the user's UserVideoRef for the rest. Missing/non-positive count → no-op (never interpreted as "delete all").
  • Reclamation reuses the orphan path (check_and_delete_orphan_sync, a new sync twin of the async one sharing one _reclaim_orphan_artifacts helper) — files are deleted + the Video row reset to CATALOGED only when no active ref remains, so shared downloads stay ref-counted and safe. Only touches videos the user actually downloaded.
  • KEEP_ALL / unknown → no-op. KEEP_WATCHED is intentionally NOT enforced (its literal reading would delete unwatched videos — destructive; dispatch is structured to add a defined behavior later).
  • Wired into the beat as enforce-retention every RETENTION_INTERVAL_HOURS (default 6h).

Verification (local)

  • pytest -q195 passed (+5: KEEP_LAST_N drops beyond N + reclaims; KEEP_ALL no-op; shared video with another active ref not reclaimed; only downloaded videos count; no-count no-op).
  • ruff + mypy clean · no migration.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RXMKM1rDWn8wNh93MMUtxY

retention_policy/retention_count on UserSubscription were persisted but never
applied — a self-hoster who set a policy got nothing. Add a periodic sweep that
makes them real.

A new Celery beat task (enforce_retention_task, every retention_interval_hours,
default 6h) applies each subscription's policy:

- KEEP_LAST_N: keep the newest N downloaded (COMPLETE) videos per
  (user, channel) and soft-remove the user's UserVideoRefs for the rest, ordered
  by upload date (falling back to catalog time) with a stable id tiebreak.
- KEEP_ALL (default): no-op.
- KEEP_WATCHED: recognized but intentionally left unenforced — its only sensible
  reading is destructive — pending product sign-off.
- A missing/non-positive count, or an unknown policy, is treated as a no-op.

Reclamation reuses the existing orphan cleanup rather than deleting files here:
the sweep soft-removes refs, then runs check_and_delete_orphan, which only
deletes the file and resets the Video row once no active ref remains. So shared
downloads stay ref-counted — soft-removing one user's ref never removes a video
another active ref still wants — and only videos the user actually downloaded
are ever touched.

check_and_delete_orphan gains a synchronous twin (check_and_delete_orphan_sync)
for the Celery worker's sync Session; both share one reclaim helper so the
file-deletion + row-reset logic lives in a single place. No schema change (the
columns already existed).

Tests: KEEP_LAST_N drops refs beyond N (newest kept) and orphan cleanup
reclaims; KEEP_ALL is a no-op; a video another active ref still wants is not
reclaimed; only downloaded videos count; a policy with no count is a no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RXMKM1rDWn8wNh93MMUtxY
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@windoze95 windoze95 merged commit cefa0b4 into main Jun 28, 2026
5 checks passed
@windoze95 windoze95 deleted the feature/backend-retention branch June 28, 2026 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant