-- Migration 017: uploaded_transcripts — PRIVATE transcripts of audio a user -- uploaded themselves (ADR-033, tapir#33). -- -- This is deliberately NOT the shared `transcripts` table (migration 015, -- ADR-021). That table is non-RLS on the explicit grounds that it holds only -- public caption content and nothing user-identifying. A recorded meeting is the -- opposite: private third-party content owned by exactly one user. Reusing -- `transcripts` — or adding a user_id to it with a partial policy — would put a -- private row in the one table the isolation test asserts is shared, and would -- pass CI green. A separate table keeps ADR-021's exception singular. -- -- Row-level security follows migration 003 exactly: ENABLE + FORCE (the app -- connects as the table owner, which bypasses RLS unless forced), and a policy -- keyed off the per-request GUC tapir.current_user_id. With no GUC set the -- predicate is NULL and the table is deny-all. -- -- The original audio is never stored (ADR-033): this row is all that is kept. -- segments holds the timestamped cues; re-transcribing 90 minutes costs ~7 -- minutes, so discarding the timestamps would be irreversible. CREATE TABLE uploaded_transcripts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, title TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'queued' CHECK (status IN ('queued', 'transcribing', 'done', 'failed')), content TEXT, segments JSONB, endpoint TEXT, -- which backend served it (iguana | berget); a quality fact audio_seconds DOUBLE PRECISION, error_kind TEXT, -- typed, never a provider's error string (ADR-033) created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), finished_at TIMESTAMPTZ, CONSTRAINT uploaded_transcripts_done_has_content CHECK (status <> 'done' OR content IS NOT NULL), CONSTRAINT uploaded_transcripts_failed_has_error_kind CHECK (status <> 'failed' OR error_kind IS NOT NULL) ); CREATE INDEX idx_uploaded_transcripts_user ON uploaded_transcripts (user_id, created_at DESC); ALTER TABLE uploaded_transcripts ENABLE ROW LEVEL SECURITY; ALTER TABLE uploaded_transcripts FORCE ROW LEVEL SECURITY; CREATE POLICY uploaded_transcripts_isolation ON uploaded_transcripts FOR ALL USING (user_id = current_setting('tapir.current_user_id', true)::uuid); COMMENT ON TABLE uploaded_transcripts IS 'PRIVATE per-user transcripts of user-supplied audio. RLS-scoped (ADR-012). Never merge into the shared transcripts table (ADR-021, ADR-033).';