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:
sysops
2026-08-30 14:27:21 +02:00
parent 8ff4e82d38
commit e23f514850
5 changed files with 565 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
# 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, nur
`released_at`/`released_by` gesetzt), 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`),
`ErrAlreadyOnHold` bei 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
direkt `deleted`. Objekte mit aktiver Sperre werden übersprungen,
unabhängig vom Fristablauf.
- `Destroy` — verlangt vorherigen Status `expired`
(`ErrNotReleased` sonst), 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 wird `expired` (NICHT `deleted`), gesperrtes fälliges Objekt
bleibt `active`, nicht fälliges Objekt bleibt `active`.
- `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.