-- ============================================================================ -- Migration 010: Incident/Breach Management Schema -- DSGVO Art. 33 (Authority Notification) & Art. 34 (Data Subject Notification) -- -- Art. 33 requires notification of the supervisory authority within 72 hours -- of becoming aware of a personal data breach, unless the breach is unlikely -- to result in a risk to the rights and freedoms of natural persons. -- -- Art. 34 requires notification of affected data subjects without undue delay -- when the breach is likely to result in a high risk to their rights and freedoms. -- ============================================================================ -- Incident incidents table CREATE TABLE IF NOT EXISTS incident_incidents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, -- Incident info title VARCHAR(255) NOT NULL, description TEXT, category VARCHAR(50) NOT NULL, -- data_breach, unauthorized_access, data_loss, system_compromise, phishing, ransomware, insider_threat, physical_breach, other status VARCHAR(50) DEFAULT 'detected', -- detected, assessment, containment, notification_required, notification_sent, remediation, closed severity VARCHAR(50) NOT NULL, -- critical, high, medium, low -- Detection & reporting detected_at TIMESTAMPTZ NOT NULL, reported_by UUID NOT NULL, -- Affected scope affected_data_categories JSONB DEFAULT '[]', -- e.g. ["personal_data", "health_data", "financial_data"] affected_data_subject_count INT DEFAULT 0, affected_systems JSONB DEFAULT '[]', -- e.g. ["crm", "email_server", "database"] -- Assessments & notifications (JSONB embedded objects) risk_assessment JSONB, -- {likelihood, impact, risk_level, assessed_at, assessed_by, notes} authority_notification JSONB, -- {status, deadline, submitted_at, authority_name, reference_number, contact_person, notes} data_subject_notification JSONB, -- {required, status, sent_at, affected_count, notification_text, channel} -- Resolution root_cause TEXT, lessons_learned TEXT, -- Timeline (JSONB array of events) timeline JSONB DEFAULT '[]', -- [{timestamp, action, user_id, details}, ...] -- Audit created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), closed_at TIMESTAMPTZ ); -- Incident measures table (corrective and preventive measures) CREATE TABLE IF NOT EXISTS incident_measures ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), incident_id UUID NOT NULL REFERENCES incident_incidents(id) ON DELETE CASCADE, -- Measure info title VARCHAR(255) NOT NULL, description TEXT, measure_type VARCHAR(50) NOT NULL, -- immediate, long_term status VARCHAR(50) DEFAULT 'planned', -- planned, in_progress, completed responsible VARCHAR(255), due_date TIMESTAMPTZ, completed_at TIMESTAMPTZ, -- Audit created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ============================================================================ -- Indexes -- ============================================================================ -- Incident indexes CREATE INDEX IF NOT EXISTS idx_incident_incidents_tenant ON incident_incidents(tenant_id); CREATE INDEX IF NOT EXISTS idx_incident_incidents_status ON incident_incidents(tenant_id, status); CREATE INDEX IF NOT EXISTS idx_incident_incidents_severity ON incident_incidents(tenant_id, severity); CREATE INDEX IF NOT EXISTS idx_incident_incidents_detected_at ON incident_incidents(detected_at DESC); CREATE INDEX IF NOT EXISTS idx_incident_incidents_category ON incident_incidents(tenant_id, category); -- Measure indexes CREATE INDEX IF NOT EXISTS idx_incident_measures_incident ON incident_measures(incident_id); CREATE INDEX IF NOT EXISTS idx_incident_measures_status ON incident_measures(incident_id, status); -- ============================================================================ -- Triggers -- ============================================================================ -- Reuse existing update_updated_at_column function -- Incidents trigger DROP TRIGGER IF EXISTS update_incident_incidents_updated_at ON incident_incidents; CREATE TRIGGER update_incident_incidents_updated_at BEFORE UPDATE ON incident_incidents FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- ============================================================================ -- Comments -- ============================================================================ COMMENT ON TABLE incident_incidents IS 'Security and data breach incidents per DSGVO Art. 33/34'; COMMENT ON TABLE incident_measures IS 'Corrective and preventive measures for incidents'; COMMENT ON COLUMN incident_incidents.detected_at IS 'When the incident was first detected - starts the 72h Art. 33 notification clock'; COMMENT ON COLUMN incident_incidents.authority_notification IS 'JSONB: Supervisory authority notification tracking per DSGVO Art. 33 (72h deadline)'; COMMENT ON COLUMN incident_incidents.data_subject_notification IS 'JSONB: Data subject notification tracking per DSGVO Art. 34'; COMMENT ON COLUMN incident_incidents.risk_assessment IS 'JSONB: Risk assessment with likelihood, impact, and auto-calculated risk level'; COMMENT ON COLUMN incident_incidents.timeline IS 'JSONB array: Chronological record of all actions taken during incident response'; COMMENT ON COLUMN incident_incidents.affected_data_categories IS 'JSONB array: Categories of personal data affected (e.g. health, financial)'; COMMENT ON COLUMN incident_incidents.affected_systems IS 'JSONB array: Systems affected by the incident'; COMMENT ON COLUMN incident_measures.measure_type IS 'immediate = short-term containment, long_term = preventive/structural fix';