CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE submission ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), platform TEXT NOT NULL CHECK (platform IN ('instagram', 'tiktok', 'youtube', 'linkedin')), post_type TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'checked', 'published', 'archived')), caption TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE asset ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), submission_id UUID NOT NULL REFERENCES submission (id), kind TEXT NOT NULL CHECK (kind IN ('image', 'video', 'file')), path TEXT NOT NULL, sha256 TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- Stufe 1: striktes JSON aus der Claude-Extraktion. Append-only, siehe Trigger unten. CREATE TABLE extraction ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), submission_id UUID NOT NULL REFERENCES submission (id), payload JSONB NOT NULL, model_version TEXT NOT NULL, prompt_version TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- Stufe 2: Ergebnis pro Regel. Append-only, Korrektur = neue Zeile. -- supersedes zeigt auf die alte Zeile, die diese Zeile ersetzt (gesetzt -- beim INSERT der Korrektur, nie per UPDATE — der Trigger würde das -- verbieten). "Aktuell gültig" = Zeilen, auf die kein supersedes zeigt, -- siehe Index unten für den Anti-Join. CREATE TABLE finding ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), submission_id UUID NOT NULL REFERENCES submission (id), extraction_id UUID REFERENCES extraction (id), rule_id TEXT NOT NULL, rule_version INTEGER NOT NULL, severity TEXT NOT NULL CHECK (severity IN ('niedrig', 'mittel', 'hoch')), message TEXT NOT NULL, supersedes UUID REFERENCES finding (id), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX finding_supersedes_idx ON finding (supersedes) WHERE supersedes IS NOT NULL; -- Dossier + Beweiskette (Hash, RFC-3161-Token). Append-only. CREATE TABLE evidence_package ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), submission_id UUID NOT NULL REFERENCES submission (id), dossier_path TEXT NOT NULL, sha256 TEXT NOT NULL, timestamp_token BYTEA NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE participant ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), submission_id UUID NOT NULL REFERENCES submission (id), role TEXT NOT NULL CHECK (role IN ('creator', 'agentur', 'marke', 'kanzlei')), name TEXT NOT NULL, vorgegeben BOOLEAN NOT NULL DEFAULT false, freigegeben BOOLEAN NOT NULL DEFAULT false, approved_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- Ein Beweisarchiv, in dem man Zeilen ändern kann, ist kein Beweisarchiv. CREATE FUNCTION forbid_update_delete() RETURNS TRIGGER AS $$ BEGIN RAISE EXCEPTION 'append-only table: % on % is not allowed', TG_OP, TG_TABLE_NAME; END; $$ LANGUAGE plpgsql; CREATE TRIGGER extraction_append_only BEFORE UPDATE OR DELETE ON extraction FOR EACH ROW EXECUTE FUNCTION forbid_update_delete(); CREATE TRIGGER finding_append_only BEFORE UPDATE OR DELETE ON finding FOR EACH ROW EXECUTE FUNCTION forbid_update_delete(); CREATE TRIGGER evidence_package_append_only BEFORE UPDATE OR DELETE ON evidence_package FOR EACH ROW EXECUTE FUNCTION forbid_update_delete();