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>
31 lines
1.6 KiB
SQL
31 lines
1.6 KiB
SQL
-- Migration 015: transcripts become SHARED public-content storage (ADR-021).
|
|
--
|
|
-- The per-user transcripts table from 001 (PK videos.id, user_id NOT NULL, RLS
|
|
-- FORCEd in 003) was dead: no application code ever read or wrote it — only the
|
|
-- transcript_status columns on `videos` (007) carried fetch outcomes. ADR-021
|
|
-- repurposes it as the single shared store of public caption content, keyed by
|
|
-- the cross-user dedup key (provider, provider_video_id) — the video's public
|
|
-- identity, not Tapir's per-user videos.id — so re-analysis never re-fetches
|
|
-- from YouTube (ADR-010/014).
|
|
--
|
|
-- It holds ONLY public caption content + the video's public id (nothing
|
|
-- user-identifying), so it is deliberately NOT RLS-scoped: no user_id, no
|
|
-- policy, no FORCE. This is the single, intentional exception to the ADR-012
|
|
-- isolation boundary; rls_test.go asserts the boundary is exactly here and
|
|
-- nowhere else. Dropping the old table drops its RLS policy with it; it held no
|
|
-- real data, so drop+recreate loses nothing.
|
|
DROP TABLE transcripts;
|
|
|
|
CREATE TABLE transcripts (
|
|
provider TEXT NOT NULL,
|
|
provider_video_id TEXT NOT NULL,
|
|
source TEXT NOT NULL, -- 'captions' (content set) | 'none' (no captions; content NULL)
|
|
language TEXT,
|
|
content TEXT,
|
|
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
PRIMARY KEY (provider, provider_video_id)
|
|
);
|
|
|
|
COMMENT ON TABLE transcripts IS
|
|
'Shared public caption content keyed by (provider, provider_video_id). NOT RLS-scoped — public content only, de-facto cross-user dedup (ADR-021).';
|