feat(retention): warn before a recording is deleted for good (#402) #458

Merged
claude-bot merged 1 commit from fix/402-warn-before-deletion into main 2026-08-31 03:43:01 +00:00
Contributor

Closes #402 (HIGH). Cross-component (backend + bot), so it ships as one PR per CLAUDE.md.

The defect

A failed transcription's audio is the only copy of anything for that session — none was produced — and retention removes it when the grace expires. Nothing told the GM. The countdown existed only on the admin recordings page, so noticing required knowing to go and look somewhere a GM has no particular reason to visit. The issue's own scenario is a GM heading into a week off, which rules that out entirely.

Scope: one criterion was already met

The issue asks for the failed-case retention window to be "independently configurable… or otherwise deliberately widened". #427 already did the widening: every mode now reaches deletion through the trash, with a 7-day grace floored in code rather than merely defaulted. A failed session's audio survives retention_window + 7 days, not "vanishes on a Beat tick" as the issue describes.

Treating that as met rather than adding a separate knob — more retention settings is more ways to misconfigure the one artefact that cannot be regenerated. Confirmed with the issue's author before building.

What ships

A daily task finds trashed audio inside a 3-day lead and posts to the campaign's Discord channel: which recording, exactly when it goes, and how to keep it. It says explicitly when transcription failed, because that is the case where losing the audio loses the session rather than just its media.

Once per trip through the trash, via sessions.audio_deletion_warned_at (migration d2e3f4a5b6c8) — the task runs daily, and a warning that arrives every morning is one a GM learns to ignore, which is precisely the message that must not be ignored. The marker is cleared wherever audio leaves the trash, so a restored-then-retrashed recording is warned about again; a plain boolean would mark it warned forever and go silent on the trip that actually ends in deletion.

The countdown is on the session page, not just the admin console.

One deadline, computed once

permanent_deletion_at was inline in the admin router, and the sweeper derives the same boundary from the other direction. A third copy in the warning task is how the date a GM is shown drifts from when the audio actually goes — and a wrong countdown is worse than none, because it gets believed. It now lives in app/retention.py and all three read it.

Bot side

The handler is purely additive: the dispatch table already logs and ignores unknown event types, so a backend that has this and a bot that does not degrades gracefully. No BOT_CONTRACT_VERSION bump — the contract rule covers /api/bot/* changes, and this adds neither an endpoint nor a shape an older bot would mishandle.

The marker is written before the publish, not after. A publish that fails is retried by the stream; a publish that succeeds while the marker write fails would re-announce tomorrow, and rarity is the whole point.

Verification

Mutation-checked on both guards:

  • Ignoring the lead time → test_a_recording_with_time_left_is_not_warned_about_yet fails.
  • Dropping the sent-marker filter → test_the_warning_is_sent_once_not_every_day fails.

There is also a test that the lead time is shorter than the minimum grace it warns within — a lead longer than the grace would warn about everything the instant it was trashed, which is the same as not warning.

1,470 backend, 457 frontend, 220 bot tests pass. Migration verified applying and downgrading over the full chain. Lint clean at pinned ruff 0.4.4.

Note: the bot suite needs CI's install order (pip install --no-deps -r requirements-git.txt after the others) — installing all three together hits a discord-py[voice] / pynacl resolution conflict.

🤖 Generated with Claude Code

Closes #402 (HIGH). Cross-component (backend + bot), so it ships as one PR per `CLAUDE.md`. ## The defect A failed transcription's audio is the only copy of anything for that session — none was produced — and retention removes it when the grace expires. **Nothing told the GM.** The countdown existed only on the admin recordings page, so noticing required knowing to go and look somewhere a GM has no particular reason to visit. The issue's own scenario is a GM heading into a week off, which rules that out entirely. ## Scope: one criterion was already met The issue asks for the failed-case retention window to be *"independently configurable… or otherwise deliberately widened"*. **#427 already did the widening**: every mode now reaches deletion through the trash, with a 7-day grace floored in code rather than merely defaulted. A failed session's audio survives `retention_window + 7 days`, not "vanishes on a Beat tick" as the issue describes. Treating that as met rather than adding a separate knob — more retention settings is more ways to misconfigure the one artefact that cannot be regenerated. Confirmed with the issue's author before building. ## What ships **A daily task** finds trashed audio inside a 3-day lead and posts to the campaign's Discord channel: which recording, exactly when it goes, and how to keep it. It says explicitly when transcription **failed**, because that is the case where losing the audio loses the session rather than just its media. **Once per trip through the trash**, via `sessions.audio_deletion_warned_at` (migration `d2e3f4a5b6c8`) — the task runs daily, and a warning that arrives every morning is one a GM learns to ignore, which is precisely the message that must not be ignored. The marker is cleared **wherever audio leaves the trash**, so a restored-then-retrashed recording is warned about again; a plain boolean would mark it warned forever and go silent on the trip that actually ends in deletion. **The countdown is on the session page**, not just the admin console. ## One deadline, computed once `permanent_deletion_at` was inline in the admin router, and the sweeper derives the same boundary from the other direction. A third copy in the warning task is how the date a GM is shown drifts from when the audio actually goes — and **a wrong countdown is worse than none, because it gets believed.** It now lives in `app/retention.py` and all three read it. ## Bot side The handler is **purely additive**: the dispatch table already logs and ignores unknown event types, so a backend that has this and a bot that does not degrades gracefully. No `BOT_CONTRACT_VERSION` bump — the contract rule covers `/api/bot/*` changes, and this adds neither an endpoint nor a shape an older bot would mishandle. The marker is written **before** the publish, not after. A publish that fails is retried by the stream; a publish that succeeds while the marker write fails would re-announce tomorrow, and rarity is the whole point. ## Verification Mutation-checked on both guards: - Ignoring the lead time → `test_a_recording_with_time_left_is_not_warned_about_yet` fails. - Dropping the sent-marker filter → `test_the_warning_is_sent_once_not_every_day` fails. There is also a test that the lead time is shorter than the minimum grace it warns within — a lead longer than the grace would warn about everything the instant it was trashed, which is the same as not warning. **1,470 backend, 457 frontend, 220 bot tests pass.** Migration verified applying and downgrading over the full chain. Lint clean at pinned ruff 0.4.4. Note: the bot suite needs CI's install order (`pip install --no-deps -r requirements-git.txt` after the others) — installing all three together hits a `discord-py[voice]` / `pynacl` resolution conflict. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(retention): warn before a recording is deleted for good (#402)
All checks were successful
CI / Bot/backend version sync (pull_request) Successful in 21s
CI / Backend lint (ruff) (pull_request) Successful in 26s
CI / Docker image build (pull_request) Successful in 38s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m26s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m24s
CI / Bot tests and audit (pull_request) Successful in 2m0s
CI / Backend migration, tests, and audit (pull_request) Successful in 5m12s
da9b656daa
A failed transcription's audio is the only copy of anything for that
session — none was ever produced — and retention removes it when the grace
expires. Nothing told the GM. The countdown existed only on the admin
recordings page, so noticing required knowing to go and look there, which
the issue's own scenario (a GM heading into a week off) rules out.

Adds a daily task that posts to the campaign's Discord channel a few days
out, naming the recording, the deadline, and how to keep it. Once per trip
through the trash via sessions.audio_deletion_warned_at (d2e3f4a5b6c8), not
daily: a message that arrives every morning is one a GM stops reading. The
marker is cleared wherever audio leaves the trash, so a restored-then-
retrashed recording is warned about again — the failure a plain boolean
would have.

The countdown is now on the session page too, and the deadline itself moved
into app/retention.py. It was inline in the admin router and derived from
the other direction in the sweeper; a third copy in the warning task is how
the date a GM is shown drifts from when the audio actually goes, and a
wrong countdown is worse than none because it is believed.

Scope, agreed with the issue's author: the "independently configurable
window for the failed case" criterion is treated as already met by #427,
which routed every mode through the trash with a floored 7-day grace. A
failed session's audio now survives retention_window + 7 days. Another
retention knob would be more ways to misconfigure the one artefact that
cannot be regenerated.

The bot handler is purely additive — unknown event types are already logged
and ignored, so an older bot degrades gracefully and no contract bump is
needed.

Mutation-checked: ignoring the lead time fails the not-yet test; dropping
the sent-marker fails the once-only test. 1,470 backend, 457 frontend and
220 bot tests pass. Migration verified up and down over the full chain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
claude-bot deleted branch fix/402-warn-before-deletion 2026-08-31 03:43:02 +00:00
Sign in to join this conversation.
No description provided.