-- Migration 011: Derived Controls Library (Clean-Room MCs from external sources) -- Schema: compliance -- -- Holds Master Controls + atomic controls + mitigations + metrics that were -- derived Clean-Room from external regulatory sources (BSI QUAIDAL today, -- Grundschutz++/CRA/NIST AI RMF next). Kept separate from the gpre2 -- master_controls table because: -- 1) The shape is different (no object_group/phase concepts). -- 2) Source-Layer-Trennung: derivations from external IP must be cleanly -- separable from internally-generated artifacts. -- 3) Each row carries the licence + provenance for due diligence. -- -- Run: ssh macmini "docker exec -i bp-core-postgres psql -U breakpilot -d breakpilot_db" \ -- < control-pipeline/migrations/011_derived_controls.sql SET search_path TO compliance, public; CREATE TABLE IF NOT EXISTS derived_controls ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), derived_id VARCHAR(200) UNIQUE NOT NULL, -- e.g. MC-AI-DATA-QKB-01-repraesentativitaet kind VARCHAR(30) NOT NULL, -- criterion | building_block | measure | metric canonical_name VARCHAR(300) NOT NULL, description TEXT NOT NULL, -- our own wording, never the original regulation_anchor TEXT, -- e.g. "EU AI Act Art. 10" related_quaidal_ids JSONB NOT NULL DEFAULT '[]', -- ["QB-03", "QB-04", ...] external_refs JSONB NOT NULL DEFAULT '[]', -- [{framework, citation}, ...] source_framework VARCHAR(80) NOT NULL, -- "BSI QUAIDAL" source_section VARCHAR(80) NOT NULL, -- "QKB-01" source_url TEXT, source_commit_sha VARCHAR(80), source_title_original TEXT, -- original title (label, not protected) source_license_note TEXT, plagiarism_score_at_generation NUMERIC(5,4), -- 0..1; gate was 0.20 generated_by_model VARCHAR(80), yaml_path TEXT, -- pointer back to source YAML created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_derived_controls_kind ON derived_controls(kind); CREATE INDEX IF NOT EXISTS idx_derived_controls_source_framework ON derived_controls(source_framework); CREATE INDEX IF NOT EXISTS idx_derived_controls_source_section ON derived_controls(source_section); CREATE INDEX IF NOT EXISTS idx_derived_controls_related_quaidal_gin ON derived_controls USING GIN(related_quaidal_ids); -- Trigger to keep updated_at fresh CREATE OR REPLACE FUNCTION trg_derived_controls_set_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS derived_controls_updated_at ON derived_controls; CREATE TRIGGER derived_controls_updated_at BEFORE UPDATE ON derived_controls FOR EACH ROW EXECUTE FUNCTION trg_derived_controls_set_updated_at();