[Privacy] Per-player erasure from transcripts and recordings #118
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context / Motivation
A player leaving a group can reasonably ask to be scrubbed from recorded material. This is tractable because content is speaker-attributed end to end:
[HH:MM:SS] Speaker: text(Session.transcript,webapp/backend/app/models/session.py:114; built bymerge_attributed_transcript,services/audio_service.py:401, from per-speaker segments{start, end, text, speaker}). Speaker labels are character names resolved fromspeakers.json→PlatformLink→CampaignMember(tasks/reminder_tasks.py:1541-1584).cleanup_trashed_audio,reminder_tasks.py:1715— deletes whole dirs afteraudio_trashed_at+ retention days).transcript_search_vector/summary_search_vectorare generated TSVECTOR columns with GIN indexes (models/session.py:116-131, migrationalembic/versions/d4e5f6g7h8i9_session_summary_transcript_search.py) — rewritingtranscriptautomatically reindexes them.LoreExtractCacherows hold per-chunk extracted content (models/lore_extract_cache.py—session_id,chunk_index,extractsJSONB).Spec
Trigger: GM (per their campaign) or instance admin action: "Erase member from recordings" — endpoint like
POST /campaigns/{campaign_id}/members/{member_id}/erase-recordings, with type-to-confirm UI on the campaign member management page.Erasure task (Celery, per campaign member):
Session.transcript, replacing the text of each line attributed to that member's speaker label(s) with[removed]— preserve the[HH:MM:SS] Speaker:skeleton so the timeline stays readable. Resolve all labels the member may appear under: currentcharacter_name, and their Discord display name (the pre-character_mapfallback). Generated tsvector columns reindex automatically — verify with a search test.PlatformLink) from any session audio directory still on the audio_temp volume; leave other speakers' files.LoreExtractCacherows for affected sessions (simplest: delete rows for those sessions — they are cache).member.recordings_erasedentry viaaudit_service.log_event(services/audit_service.py:12) with session counts in context.Out of scope
Acceptance criteria
[removed]placeholders with an intact timeline.LoreExtractCacherows for affected sessions are purged.References
webapp/backend/app/models/session.py:114(transcript),:116-131(generated tsvector columns)webapp/backend/alembic/versions/d4e5f6g7h8i9_session_summary_transcript_search.pywebapp/backend/app/services/audio_service.py:401(merge_attributed_transcript)webapp/backend/app/tasks/reminder_tasks.py:1541-1584(speaker→member resolution),:1715(cleanup_trashed_audio)webapp/backend/app/models/lore_extract_cache.pywebapp/backend/app/services/audit_service.py:12(log_event)Filed from the July 2026 full-project review.
Done — merged in PR #202 (backend
653dacc+ frontend). CI green.Shipped:
run_member_erasurescrubs a member's transcript lines to[HH:MM:SS] <label>: [removed](skeleton preserved; FTS reindexes → erased words unsearchable), deletes their{discord_id}.wav(verified PlatformLink), purgesLoreExtractCache, deletes their attributed quote highlights (#116), appends a sessionerasure_notesentry (migrationa2b3c4d5e6f7), and writes onemember.recordings_erasedaudit entry attributed to the acting GM/admin. Idempotent. GM/admin endpoint; frontend type-to-confirm (type the member's name) + erasure notice on affected sessions.Two review fixes: threaded the actor into the audit entry (was system-actor — a destructive action needs accountability); fixed a test whose FTS token sat in the preserved speaker label rather than the redacted spoken text (scrub code was correct).
Tests: backend 561 (+11, incl. FTS-unsearchable + idempotency + authz); frontend 296 (+10). All acceptance criteria met. Closing.