Files
breakpilot-compliance/ai-compliance-sdk/migrations/030_iace_mitigation_unique.sql
Benjamin Admin 0a64da74bb
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Successful in 56s
CI / iace-gt-coverage (push) Successful in 27s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / detect-changes (push) Successful in 11s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 17s
CI / loc-budget (push) Failing after 17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
fix(iace/mitigations): idempotent CreateMitigation + UNIQUE(hazard_id, name)
[migration-approved]

The init-handler was non-idempotent. A second click on "Neu initialisieren
in Grenzen" inserted every engine-suggested mitigation a second time —
e.g. the Bremsscheibe project ended up with 5 (hazard_id, name) duplicate
pairs (HMI-Usability-Pruefung, Eindeutiges visuelles Feedback,
Betriebsarten-Anzeige, Sicher begrenzter Bewegungsbereich, …). 45 such
duplicates accumulated across all projects.

Migration 030_iace_mitigation_unique.sql:
  1. Picks one winning row per (hazard_id, name) using a stable rank:
       is_relevant DESC      (expert decision wins over engine default)
       status      DESC      (verified > implemented > planned)
       created_at  DESC      (newest beats older on otherwise-equal rows)
     and deletes the losers (Bremsscheibe: 5 rows; total: 45).
  2. Adds UNIQUE constraint iace_mitigations_hazard_name_uniq
     (hazard_id, name).

Store-Layer (CreateMitigation):
  INSERT … ON CONFLICT (hazard_id, name) DO NOTHING RETURNING id.
  pgx.ErrNoRows from RETURNING → look up the existing row and return that.
  Callers (engine init + manual add) always get a usable Mitigation; the
  second click is silently swallowed instead of failing.

Frontend dedupe in groupByTitle stays — it covers any pre-existing
duplicates that survived the migration in edge cases (multi-row write
in flight, etc.). With the UNIQUE constraint live, the in-memory
dedupe is a belt-and-suspenders safety net rather than the load-bearing
mechanism.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 19:55:13 +02:00

47 lines
2.0 KiB
SQL

-- Migration 030: De-duplicate iace_mitigations + add UNIQUE(hazard_id, name)
-- ==========================================================================
-- The mitigation init-handler used to be non-idempotent: a second click on
-- "Neu initialisieren in Grenzen" inserted every engine-suggested mitigation
-- a second time. The Bremsscheibe benchmark accumulated 5 such duplicate
-- (hazard_id, name) pairs (HMI-Usability, Eindeutiges-Feedback,
-- Betriebsarten-Anzeige, Sicher begrenzter Bewegungsbereich, …).
--
-- Frontend mitigates the symptom with a per-hazard dedupe in groupByTitle,
-- but the DB still carries the redundant rows: confusing for SQL audits,
-- and the cleanup work was deferred only because the expert had not yet
-- decided which copy to keep.
--
-- This migration:
-- 1. Picks the winning row per (hazard_id, name) using a stable rank:
-- is_relevant DESC (expert decision survives)
-- status DESC (verified > implemented > planned)
-- created_at DESC (newest wins when nothing else differs)
-- and deletes the rest.
-- 2. Adds a UNIQUE constraint so future engine init runs cannot
-- re-create the duplicates.
-- ==========================================================================
WITH ranked AS (
SELECT id,
ROW_NUMBER() OVER (
PARTITION BY hazard_id, name
ORDER BY is_relevant DESC,
CASE status
WHEN 'verified' THEN 2
WHEN 'implemented' THEN 1
ELSE 0
END DESC,
created_at DESC
) AS rn
FROM iace_mitigations
)
DELETE FROM iace_mitigations
WHERE id IN (SELECT id FROM ranked WHERE rn > 1);
ALTER TABLE iace_mitigations
DROP CONSTRAINT IF EXISTS iace_mitigations_hazard_name_uniq;
ALTER TABLE iace_mitigations
ADD CONSTRAINT iace_mitigations_hazard_name_uniq
UNIQUE (hazard_id, name);