Nothing can preview a retention sweep, so IS-313 has to hedge #778

Closed
opened 2026-09-23 10:19:12 +00:00 by claude-bot · 1 comment
Collaborator

Found building CAMP-13 (#776, lane 3).

IS-313 asks the page to tell a GM what shortening retention would actually take — "Three sessions have audio older than that" — before they save it. Its own note says the count is the whole of it.

Nothing on the wire can answer that. SessionListItem (webapp/backend/app/schemas/session.py:109) carries confirmed_time, title and has_summary, and nothing about whether a session still holds audio or a transcript.

So #776 counts what it can know exactly — sessions old enough to be caught — and hedges what it cannot: "Any audio they still have goes to the bin for a week…".

Two things that were rejected as substitutes:

  • Guessing from has_summary over-counts, because a summary deliberately outlives its transcript. A GM told "three sessions" who then loses one would have been misled by the confirmation that was supposed to protect them.
  • Saying nothing would drop the row, and the confirmation before an irreversible delete is the point of it.

The change

GET /api/campaigns/{id}/retention-preview?audio_mode=…&audio_days=…&transcript_mode=…&transcript_months=…

returning the sessions each side would actually take, given a proposed pair rather than the stored one — because the question is asked about a value the GM has typed and not yet saved.

It should reuse enforce_retention's own selection rather than reimplement it; a preview that disagrees with the sweep is worse than no preview. Note enforce_retention's per-campaign cache deliberately includes trashed campaigns (#763) — the preview does not want that.

Then the hedge comes out, and "nothing old enough" becomes exact rather than "no session is old enough to be caught".

Why it matters more than it looks

This is the confirmation in front of deleting people's recorded voices. The retention sweep is the one job in this product that destroys the most sensitive data it holds, and D24 has just given instances a cap that makes shortening a more common operation. The sentence in front of it should be exact.

Not a blocker: #776 ships with the honest hedge.

🤖 Generated with Claude Code

Found building CAMP-13 (#776, lane 3). `IS-313` asks the page to tell a GM what shortening retention would actually take — *"**Three sessions have audio** older than that"* — before they save it. Its own note says the count is the whole of it. **Nothing on the wire can answer that.** `SessionListItem` (`webapp/backend/app/schemas/session.py:109`) carries `confirmed_time`, `title` and `has_summary`, and nothing about whether a session still holds **audio** or a **transcript**. So #776 counts what it can know exactly — **sessions old enough to be caught** — and hedges what it cannot: *"Any audio they still have goes to the bin for a week…"*. Two things that were rejected as substitutes: - **Guessing from `has_summary`** over-counts, because a summary deliberately outlives its transcript. A GM told "three sessions" who then loses one would have been misled by the confirmation that was supposed to protect them. - **Saying nothing** would drop the row, and the confirmation before an irreversible delete is the point of it. ## The change ``` GET /api/campaigns/{id}/retention-preview?audio_mode=…&audio_days=…&transcript_mode=…&transcript_months=… ``` returning the sessions each side would actually take, given a *proposed* pair rather than the stored one — because the question is asked about a value the GM has typed and not yet saved. It should reuse `enforce_retention`'s own selection rather than reimplement it; a preview that disagrees with the sweep is worse than no preview. Note `enforce_retention`'s per-campaign cache deliberately includes trashed campaigns (#763) — the preview does not want that. Then the hedge comes out, and "nothing old enough" becomes exact rather than "no session is old enough to be caught". ## Why it matters more than it looks This is the confirmation in front of **deleting people's recorded voices**. The retention sweep is the one job in this product that destroys the most sensitive data it holds, and D24 has just given instances a cap that makes shortening a more common operation. The sentence in front of it should be exact. Not a blocker: #776 ships with the honest hedge. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

Done in #792, merged 2026-09-24.

  • GET /api/campaigns/{id}/retention-preview takes a proposed pair and returns the sessions each side would actually take. The preview and the nightly sweep call one selection function, select_retention_catch in app/retention.py. A test runs the real sweep for 12 pair/cap combinations and checks it deleted exactly what the preview listed. An over-cap proposal gets the PATCH's own 400, checked byte for byte.
  • IS-313 is exact. "Three sessions have audio older than that — …", previewed as the GM types, with the over-cap refusal shown before Save.
  • The member erase has an exact count from the erase's own plan: "it takes Ade's audio from 11 sessions, their lines from 9 transcripts and 2 quotes". The count and the erase share one plan.
  • Also fixed: a campaign kept above a later cap couldn't save anything on the Recording page. A field is now checked against the cap only if it actually changes.
  • Owner's Option A: "Bin it once transcribed" also clears all already-transcribed audio at the next sweep. Failed and unfinished runs are kept for reprocessing.

A root cause found along the way is #791: retention anchors on transcript_updated_at, which a member erase and a reprocess both reset.

🤖 Generated with Claude Code

Done in #792, merged 2026-09-24. - **`GET /api/campaigns/{id}/retention-preview`** takes a proposed pair and returns the sessions each side would actually take. The preview and the nightly sweep call **one** selection function, `select_retention_catch` in `app/retention.py`. A test runs the real sweep for 12 pair/cap combinations and checks it deleted exactly what the preview listed. An over-cap proposal gets the PATCH's own 400, checked byte for byte. - **`IS-313` is exact.** "Three sessions have audio older than that — …", previewed as the GM types, with the over-cap refusal shown before Save. - **The member erase has an exact count** from the erase's own plan: "it takes Ade's audio from 11 sessions, their lines from 9 transcripts and 2 quotes". The count and the erase share one plan. - **Also fixed:** a campaign kept above a later cap couldn't save anything on the Recording page. A field is now checked against the cap only if it actually changes. - **Owner's Option A:** "Bin it once transcribed" also clears all already-transcribed audio at the next sweep. Failed and unfinished runs are kept for reprocessing. A root cause found along the way is #791: retention anchors on `transcript_updated_at`, which a member erase and a reprocess both reset. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#778
No description provided.