-- ============================================================================ -- Migration 022 - Metriche Incidente (TTD/TTC/TTR) + Post-Incident Review -- ---------------------------------------------------------------------------- -- 1) Timestamp di fase su incidents per calcolare le metriche: -- triaged_at, contained_at, eradicated_at, recovered_at. -- (la tabella aveva solo detected_at e closed_at) -- TTD = triaged_at - detected_at (Time to Detect/triage) -- TTC = contained_at - detected_at (Time to Contain) -- TTR = recovered_at - detected_at (Time to Recover) -- 2) Tabella incident_pir: Post-Incident Review strutturato (RC.CO-03 / NIST CSF), -- con Root Cause Analysis 5-Whys, metriche, costo stimato, lesson learned. -- -- Idempotente. mysql -h localhost nis2_agile_db -e "source docs/sql/022_incident_metrics_pir.sql" -- ============================================================================ DELIMITER // DROP PROCEDURE IF EXISTS _mig022_add_col // CREATE PROCEDURE _mig022_add_col(IN col VARCHAR(64), IN ddl TEXT) BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'incidents' AND COLUMN_NAME = col ) THEN SET @sql = CONCAT('ALTER TABLE incidents ADD COLUMN ', ddl); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; END IF; END // DELIMITER ; CALL _mig022_add_col('triaged_at', "triaged_at DATETIME NULL COMMENT 'Inizio triage'"); CALL _mig022_add_col('contained_at', "contained_at DATETIME NULL COMMENT 'Incidente contenuto'"); CALL _mig022_add_col('eradicated_at', "eradicated_at DATETIME NULL COMMENT 'Minaccia eradicata'"); CALL _mig022_add_col('recovered_at', "recovered_at DATETIME NULL COMMENT 'Servizi ripristinati'"); DROP PROCEDURE IF EXISTS _mig022_add_col; -- Post-Incident Review (1:1 con incident) CREATE TABLE IF NOT EXISTS incident_pir ( id INT AUTO_INCREMENT PRIMARY KEY, incident_id INT NOT NULL, organization_id INT NOT NULL, -- Root Cause Analysis - 5 Whys problem_statement TEXT, why_1 TEXT, why_2 TEXT, why_3 TEXT, why_4 TEXT, why_5 TEXT, root_cause TEXT, -- Metriche (snapshot al momento della review, in minuti) ttd_minutes INT NULL, ttc_minutes INT NULL, ttr_minutes INT NULL, downtime_minutes INT NULL, affected_users INT NULL, estimated_cost_eur DECIMAL(12,2) NULL, notification_compliance TINYINT(1) NULL COMMENT '1 se notifiche entro le tempistiche NIS2', -- Lesson learned & azioni di miglioramento what_went_well TEXT, what_to_improve TEXT, improvement_actions JSON NULL COMMENT 'lista azioni {desc, owner, due_date, status}', participants TEXT, reviewed_by INT NULL, reviewed_at DATETIME NULL, status ENUM('draft','completed') DEFAULT 'draft', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uniq_incident (incident_id), INDEX idx_org (organization_id), CONSTRAINT fk_pir_incident FOREIGN KEY (incident_id) REFERENCES incidents(id) ON DELETE CASCADE, CONSTRAINT fk_pir_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ROLLBACK: -- DROP TABLE IF EXISTS incident_pir; -- ALTER TABLE incidents DROP COLUMN triaged_at, DROP COLUMN contained_at, -- DROP COLUMN eradicated_at, DROP COLUMN recovered_at;