[Privacy] Configurable retention policies for audio and transcripts #119

Closed
opened 2026-07-14 19:54:11 +00:00 by claude-bot · 1 comment
Contributor

Context / Motivation

Retention is currently implicit. Inspected behavior today:

  • Raw audio (per-speaker WAV dirs on the audio_temp volume) is NOT deleted when processing finishes. process_audio (webapp/backend/app/tasks/reminder_tasks.py:1461) explicitly keeps the session directory on both success and failure (docstring at :1483: the directory "stays until a GM approves it").
  • Deletion happens only via the daily Beat task cleanup_trashed_audio (reminder_tasks.py:1715): sessions whose audio_processing_status is trashed (i.e. GM approved) and whose audio_trashed_at is older than the retention window (get_audio_trash_retention_days, services/settings_service.py:243, default 7 days) get shutil.rmtree on the audio dir, then status → approved.
  • Consequence: audio for sessions the GM never approves is retained indefinitely, and there is no policy at all for transcripts — Session.transcript (models/session.py:114) lives forever.

Spec

Settings — instance-level defaults (Admin UI, stored via settings_service in app_settings like existing keys) + per-campaign overrides (nullable columns = inherit instance default):

  • audio_retention: delete_after_processing (recommended default) | retain_days_N | retain_indefinitely
    • delete_after_processing changes the pipeline contract: delete the WAV dir at the end of successful process_audio (transcript is the artifact of record; note this removes the ability to reprocess — surface that in the settings help text). Failed processing keeps audio for debugging until N days.
    • retain_days_N generalizes today's behavior but anchored on processing completion, not GM approval, so unapproved sessions can't hoard audio forever.
  • transcript_retention: retain_indefinitely (default) | retain_months_N — expiry clears Session.transcript (and per-issue-#118 artifacts like LoreExtractCache rows); summaries and wiki/lore entries are unaffected.

Enforcement — extend/replace cleanup_trashed_audio with a general retention Beat task: computes overdue artifacts from settings, hard-deletes (rmtree for audio; NULL-out transcript columns — the generated tsvector columns (models/session.py:116-131) empty automatically), and logs a summary line of what it removed (counts per campaign). Idempotent and exactly-once per artifact: deletion is driven by current state, so a rerun finds nothing to delete.

Audit: one audit entry per enforcement run that deleted anything (retention.enforced, counts in context) via audit_service.log_event.

Docs: README privacy stance section — "transcript-only by default": recommended config keeps transcripts, deletes raw audio after processing.

Out of scope

  • Summary/wiki/journal retention (explicitly unaffected).
  • Erasure of individual members (issue #118).
  • Backup rotation (separate existing backup config).

Acceptance criteria

  • With delete_after_processing, the WAV dir is gone immediately after a successful pipeline run; transcript intact.
  • With retain_days_N, audio expires N days after processing regardless of approval state (test the never-approved case).
  • Transcript expiry clears transcript + FTS (search returns nothing) and purges LoreExtractCache; summary untouched.
  • Enforcement task is idempotent (second run deletes nothing, no duplicate audit entries).
  • Per-campaign override beats instance default; unset inherits.
  • Admin UI + campaign settings surface both policies; README updated.

References

  • webapp/backend/app/tasks/reminder_tasks.py:1461 (process_audio; retention docstring :1483), :1715 (cleanup_trashed_audio)
  • webapp/backend/app/services/settings_service.py:243 (get_audio_trash_retention_days)
  • webapp/backend/app/models/session.py:114 (transcript), :116-131 (generated tsvector), :143-156 (audio status/trash columns)
  • webapp/backend/app/models/lore_extract_cache.py
  • webapp/backend/app/services/audit_service.py:12 (log_event)

Filed from the July 2026 full-project review.

## Context / Motivation Retention is currently implicit. Inspected behavior today: - **Raw audio (per-speaker WAV dirs on the audio_temp volume) is NOT deleted when processing finishes.** `process_audio` (`webapp/backend/app/tasks/reminder_tasks.py:1461`) explicitly keeps the session directory on both success and failure (docstring at `:1483`: the directory "stays until a GM approves it"). - Deletion happens only via the daily Beat task `cleanup_trashed_audio` (`reminder_tasks.py:1715`): sessions whose `audio_processing_status` is `trashed` (i.e. GM approved) and whose `audio_trashed_at` is older than the retention window (`get_audio_trash_retention_days`, `services/settings_service.py:243`, default 7 days) get `shutil.rmtree` on the audio dir, then status → `approved`. - Consequence: audio for sessions the GM never approves is retained **indefinitely**, and there is no policy at all for transcripts — `Session.transcript` (`models/session.py:114`) lives forever. ## Spec **Settings** — instance-level defaults (Admin UI, stored via `settings_service` in `app_settings` like existing keys) + per-campaign overrides (nullable columns = inherit instance default): - `audio_retention`: `delete_after_processing` (recommended default) | `retain_days_N` | `retain_indefinitely` - `delete_after_processing` changes the pipeline contract: delete the WAV dir at the end of successful `process_audio` (transcript is the artifact of record; note this removes the ability to reprocess — surface that in the settings help text). Failed processing keeps audio for debugging until N days. - `retain_days_N` generalizes today's behavior but anchored on processing completion, not GM approval, so unapproved sessions can't hoard audio forever. - `transcript_retention`: `retain_indefinitely` (default) | `retain_months_N` — expiry clears `Session.transcript` (and per-issue-#118 artifacts like `LoreExtractCache` rows); **summaries and wiki/lore entries are unaffected**. **Enforcement** — extend/replace `cleanup_trashed_audio` with a general retention Beat task: computes overdue artifacts from settings, hard-deletes (rmtree for audio; `NULL`-out transcript columns — the generated tsvector columns (`models/session.py:116-131`) empty automatically), and logs a summary line of what it removed (counts per campaign). Idempotent and exactly-once per artifact: deletion is driven by current state, so a rerun finds nothing to delete. **Audit**: one audit entry per enforcement run that deleted anything (`retention.enforced`, counts in context) via `audit_service.log_event`. **Docs**: README privacy stance section — "transcript-only by default": recommended config keeps transcripts, deletes raw audio after processing. ## Out of scope - Summary/wiki/journal retention (explicitly unaffected). - Erasure of individual members (issue #118). - Backup rotation (separate existing backup config). ## Acceptance criteria - With `delete_after_processing`, the WAV dir is gone immediately after a successful pipeline run; transcript intact. - With `retain_days_N`, audio expires N days after processing regardless of approval state (test the never-approved case). - Transcript expiry clears transcript + FTS (search returns nothing) and purges `LoreExtractCache`; summary untouched. - Enforcement task is idempotent (second run deletes nothing, no duplicate audit entries). - Per-campaign override beats instance default; unset inherits. - Admin UI + campaign settings surface both policies; README updated. ## References - `webapp/backend/app/tasks/reminder_tasks.py:1461` (`process_audio`; retention docstring `:1483`), `:1715` (`cleanup_trashed_audio`) - `webapp/backend/app/services/settings_service.py:243` (`get_audio_trash_retention_days`) - `webapp/backend/app/models/session.py:114` (`transcript`), `:116-131` (generated tsvector), `:143-156` (audio status/trash columns) - `webapp/backend/app/models/lore_extract_cache.py` - `webapp/backend/app/services/audit_service.py:12` (`log_event`) _Filed from the July 2026 full-project review._
Author
Contributor

Done — merged in PR #201 (backend f31d7f8 + frontend). CI green (first try).

Shipped: instance-default + per-campaign audio/transcript retention (get_effective_retention resolves per-field, override-wins/null-inherits); migration f1a2b3c4d5e6. delete_after_processing deletes the WAV dir inline at the end of a successful process_audio (defensive, → terminal approved); enforce_retention Beat task anchors audio deletion on transcript_updated_at regardless of approval (fixes the never-approved hoarding), and retain_months clears transcript+FTS + purges LoreExtractCache (summaries untouched); one idempotent retention.enforced audit entry. Admin "Data retention" tab + per-campaign overrides; README privacy section.

Tests: backend 550 (+9), frontend 286 (+10). All acceptance criteria met (delete_after_processing immediacy, never-approved retain_days, transcript+FTS+cache expiry with summary intact, idempotency, override resolution). Closing.

Done — merged in PR #201 (backend `f31d7f8` + frontend). CI green (first try). **Shipped:** instance-default + per-campaign audio/transcript retention (`get_effective_retention` resolves per-field, override-wins/null-inherits); migration `f1a2b3c4d5e6`. `delete_after_processing` deletes the WAV dir inline at the end of a successful `process_audio` (defensive, → terminal `approved`); `enforce_retention` Beat task anchors audio deletion on `transcript_updated_at` **regardless of approval** (fixes the never-approved hoarding), and `retain_months` clears transcript+FTS + purges `LoreExtractCache` (summaries untouched); one idempotent `retention.enforced` audit entry. Admin "Data retention" tab + per-campaign overrides; README privacy section. **Tests:** backend 550 (+9), frontend 286 (+10). All acceptance criteria met (delete_after_processing immediacy, never-approved retain_days, transcript+FTS+cache expiry with summary intact, idempotency, override resolution). Closing.
rbrooks referenced this issue from a commit 2026-07-18 09:02:57 +00:00
Sign in to join this conversation.
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#119
No description provided.