- 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
3.5 KiB
3.5 KiB
RET-03 – Prüfprotokoll: Löschworkflow & Aufbewahrungssperre (Legal Hold)
Voraussetzung RET-01, RET-02 – beide bereits Fertig.
Umsetzung
archive/migrations/0008_legal_hold_destruction.up/down.sql:legal_holds– historisiert (Sperre wird NIE gelöscht, nurreleased_at/released_bygesetzt), Partial-Unique-Index erlaubt höchstens eine AKTIVE Sperre je Objekt.destruction_log– append-only, real per Postgres-TRIGGER gegen UPDATE/DELETE geschützt (destruction_log_immutable(),RAISE EXCEPTION) — DB-Ebene, nicht nur Anwendungscode.
archive/internal/deletionworkflow:SetLegalHold— Begründung PFLICHT (ErrReasonRequired),ErrAlreadyOnHoldbei bereits aktiver Sperre (DB-Unique-Index als zusätzliche Absicherung gegen Wettlaufsituationen).ReleaseLegalHold— hebt auf, OHNE die Zeile zu löschen (das Aufheben selbst bleibt dauerhaft nachvollziehbar).ReleaseExpired— Freigabeprozess:active→expired, NIEMALS direktdeleted. Objekte mit aktiver Sperre werden übersprungen, unabhängig vom Fristablauf.Destroy— verlangt vorherigen Statusexpired(ErrNotReleasedsonst), prüft die Sperre ZUSÄTZLICH direkt vor der Vernichtung erneut (Verteidigung in der Tiefe gegen eine zwischenzeitlich gesetzte Sperre), schreibt Statuswechsel und Protokolleintrag in EINER Transaktion.
Prüfungen
| # | Prüfung | Ergebnis |
|---|---|---|
| 1 | Objekt mit aktiver Sperre widersteht einem direkten Löschversuch | bestanden – TestDestroy_ObjectWithActiveHoldResistsDeletion: Objekt bereits im Status expired (simuliert vorherige Freigabe), Sperre danach gesetzt, Destroy liefert ErrOnLegalHold, Status bleibt unverändert |
| 2 | Protokolleintrag nach Vernichtung ist nachträglich nicht änderbar (DB-Constraint oder Trigger) | bestanden – TestDestructionLog_IsImmutable: nach echter Destroy-Ausführung direkter UPDATE/DELETE per rohem SQL (umgeht die Go-API vollständig) — beide scheitern real am Postgres-Trigger, nicht nur weil das Paket keine Änderungsfunktion anbietet |
| 3 | Aufheben einer Sperre ist selbst protokolliert | bestanden – TestReleaseLegalHold_IsItselfLogged: nach ReleaseLegalHold sind released_at/released_by real gesetzt, die ursprüngliche Sperr-Zeile bleibt bestehen (kein DELETE) |
Zusätzliche Tests (Akzeptanzkriterium 1, nicht in den drei Pflichtprüfungen, aber je AC mindestens ein Test):
TestReleaseExpired_NoImmediateDeletionAndHoldIsRespected— fälliges Objekt wirdexpired(NICHTdeleted), gesperrtes fälliges Objekt bleibtactive, nicht fälliges Objekt bleibtactive.TestSetLegalHold_RequiresReason,TestDestroy_RequiresPriorRelease.
Build/Test-Ergebnis (192.168.1.131)
go build ./... -> clean
go vet ./... -> clean
golangci-lint run ./... -> 0 issues
go test ./... -p 1 -> alle Archive-Pakete bestanden (inkl. deletionworkflow, 6 Tests)
Migration 0008_legal_hold_destruction real auf dms_tenant_test
angewendet (Test-DB, in der auch RET-06-API läuft) — sauber, ohne
Fehler.
Gesamtergebnis
Bestanden. Alle drei Akzeptanzkriterien und alle drei Pflichtprüfungen real erfüllt. Damit ist das RET-Epic (RET-01 bis RET-10) vollständig abgeschlossen: Objektmodell, Fristenengine, Modul-Adapter (Interface + Dienst), Konfigurationsoberfläche (Backend + Frontend + CORS), Benachrichtigungen, WORM-Speicher und jetzt Löschworkflow mit Legal Hold.