Reshape the dead per-user transcripts table (PK videos.id, user_id, RLS-FORCEd — never read or written by app code) into the shared public caption store ADR-021 specifies: keyed by (provider, provider_video_id), no user_id, NOT RLS-scoped. Migration 015 (reversible). Add ports.TranscriptStore + Store.GetTranscript/SaveTranscript via the raw pool (no withUser): public content, shared across users by construction. SaveTranscript persists only terminal outcomes (captions/none) and refuses SourceRateLimited so a transient 429 can never be stored as a false permanent absence (ADR-014). Flip the isolation proof: transcripts leaves the RLS-scoped set; TestTranscriptsTableIsSharedNotRLS asserts it is the SINGLE non-RLS surface (writable/readable with no user scope, no user_id column, RLS off on it alone, still on every user-owned table) — the proof the public-content classification was applied exactly here and leaked nowhere. appPool made idempotent so two tests can build it. Adjust the 010/011/014 up-down migration tests for the new HEAD. account.go: user deletion no longer strips shared transcripts. Reconcile data-model.md + CLAUDE.md. Wiring the engine to read-stored-first is the next commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
20 lines
705 B
SQL
20 lines
705 B
SQL
-- Down 015: restore the per-user RLS-scoped transcripts shape (001 + 003).
|
|
DROP TABLE transcripts;
|
|
|
|
CREATE TABLE transcripts (
|
|
video_id UUID PRIMARY KEY REFERENCES videos(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
source TEXT NOT NULL,
|
|
language TEXT,
|
|
content TEXT,
|
|
resolved_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_transcripts_user_id ON transcripts(user_id);
|
|
|
|
ALTER TABLE transcripts ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE transcripts FORCE ROW LEVEL SECURITY;
|
|
CREATE POLICY transcripts_isolation ON transcripts
|
|
FOR ALL
|
|
USING (user_id = current_setting('tapir.current_user_id', true)::uuid);
|