Convex session audio purge is terminal-only

Deleting a chat must not cascade-delete research audio. Convex admin purge needs finalized status and deletes chunks before the parent row.

Sander Korf2 min read
convexnextjs

A research product records session audio separately from chat text. Sessions mix mic input and agent TTS into WebM rolls. Chunks upload on a time and size cadence. Finalization is its own step later. Voice capture is gated by research-audio consent, not by whether someone typed in the thread.

The hard requirement is retention semantics. Deleting a chat thread must not cascade-delete the audio. Consent says keep rolls until the team explicitly removes them. Product delete and compliance purge are different verbs.

Dashboard-only internal Convex mutation sessionAudio:purge({ threadId }) is the admin path. It does not ride along with chat delete. Operators trigger it when legal or ops says the rolls can go.

purgeSessionAudioByThreadId enforces terminal parent state. The parent row must be finalized or abandoned. If status is still recording, the mutation throws. You do not delete storage while upload loops may still be running. Idempotent when no row exists: safe to retry after a partial failure.

Per chunk the order is strict. Delete the _storage blob, delete the chunk row, then delete the parent session-audio row. Blob first avoids orphan files in Convex storage. Chunk rows before parent avoids dangling foreign keys in your own schema.

State flow in prose:

recording ──finalize──▶ finalized ──purge──▶ (rows + blobs gone)
    │                        ▲
    └── abandon ─────────────┘
         (purge allowed on abandoned too)
 
chat delete ──▶ thread gone, audio rows untouched
dashboard purge ──▶ terminal check ──▶ chunk blobs ──▶ parent

Consent retention means a separate admin purge path. Terminal status means you never orphan in-flight uploads mid-chunk.


Happy coding! Sander