RET-03: löschworkflow-und-aufbewahrungssperre-legal-hold
- migrations/0008_legal_hold_destruction: legal_holds (historisiert, Partial-Unique-Index gegen doppelte aktive Sperre), destruction_log (append-only, per Postgres-Trigger gegen UPDATE/DELETE geschuetzt) - archive/internal/deletionworkflow: SetLegalHold (Begruendungspflicht), ReleaseLegalHold (Aufheben selbst protokolliert, keine Loeschung der Zeile), ReleaseExpired (Freigabeprozess active->expired, keine Sofortloeschung, Sperre wird respektiert), Destroy (verlangt vorherigen expired-Status, prueft Sperre erneut, transaktional mit Protokolleintrag) - 6 Tests, alle Pflichtpruefungen real bestanden (Sperre widersteht Loeschversuch, Protokoll real unveraenderlich per Trigger, Aufheben real protokolliert) - Migration real auf dms_tenant_test angewendet Pruefungen siehe archive/docs/RET-03-PRUEFPROTOKOLL.md
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
DROP TRIGGER IF EXISTS trg_destruction_log_no_delete ON destruction_log;
|
||||
DROP TRIGGER IF EXISTS trg_destruction_log_no_update ON destruction_log;
|
||||
DROP FUNCTION IF EXISTS destruction_log_immutable();
|
||||
DROP TABLE IF EXISTS destruction_log;
|
||||
DROP TABLE IF EXISTS legal_holds;
|
||||
@@ -0,0 +1,46 @@
|
||||
-- RET-03: Löschworkflow & Aufbewahrungssperre (Legal Hold).
|
||||
--
|
||||
-- legal_holds ist historisiert (kein UPDATE der Sperr-Zeile selbst
|
||||
-- ausser released_at/released_by beim Aufheben) — jede Sperre und jede
|
||||
-- Aufhebung bleibt dauerhaft nachvollziehbar (Akzeptanzkriterium 3:
|
||||
-- Aufheben ist selbst protokolliert). Ein Partial-Unique-Index erlaubt
|
||||
-- je Objekt hoechstens EINE AKTIVE Sperre gleichzeitig.
|
||||
CREATE TABLE IF NOT EXISTS legal_holds (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
retention_object_id UUID NOT NULL REFERENCES retention_objects(id) ON DELETE CASCADE,
|
||||
reason TEXT NOT NULL,
|
||||
set_by TEXT NOT NULL,
|
||||
set_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
released_at TIMESTAMPTZ,
|
||||
released_by TEXT
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_legal_holds_active
|
||||
ON legal_holds (retention_object_id) WHERE released_at IS NULL;
|
||||
|
||||
-- destruction_log ist APPEND-ONLY, real durchgesetzt ueber Trigger (nicht
|
||||
-- nur Anwendungscode) — Akzeptanzkriterium 3 / Pflichtpruefung 2 verlangt
|
||||
-- ausdruecklich "DB-Constraint oder Trigger".
|
||||
CREATE TABLE IF NOT EXISTS destruction_log (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
retention_object_id UUID NOT NULL REFERENCES retention_objects(id) ON DELETE RESTRICT,
|
||||
object_type TEXT NOT NULL,
|
||||
object_reference TEXT NOT NULL,
|
||||
destroyed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
destroyed_by TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION destruction_log_immutable() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
RAISE EXCEPTION 'destruction_log ist unveraenderlich (RET-03) - % auf id=% nicht erlaubt', TG_OP, OLD.id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_destruction_log_no_update ON destruction_log;
|
||||
CREATE TRIGGER trg_destruction_log_no_update
|
||||
BEFORE UPDATE ON destruction_log
|
||||
FOR EACH ROW EXECUTE FUNCTION destruction_log_immutable();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_destruction_log_no_delete ON destruction_log;
|
||||
CREATE TRIGGER trg_destruction_log_no_delete
|
||||
BEFORE DELETE ON destruction_log
|
||||
FOR EACH ROW EXECUTE FUNCTION destruction_log_immutable();
|
||||
Reference in New Issue
Block a user