CMP-07: dsgvo-loeschantrag-recht-auf-loeschung

- dpreport.SubjectRecord additiv um RetentionObjectID erweitert (CMP-02,
  bestehendes Verhalten unveraendert)
- migrations/0011_dsgvo_decision_log: vollstaendiges Protokoll jeder
  Einzelentscheidung
- archive/internal/dsgvorequest.ProcessDeletionRequest: ruft
  ausschliesslich CMP-02 (Suche), RET-03 (Sperrpruefung), CMP-06
  (Freigabe) auf - keine zweite Aufbewahrungs-/Freigabelogik (vermeidet
  den im Ticket dokumentierten archivmail-Fehler)
- 3 Tests real bestanden: gemischter Datenbestand (1 Loeschung + 1
  Ablehnung, Loeschung vollstaendig bis zur tatsaechlichen Vernichtung
  durchgefuehrt), Legal Hold blockiert trotz abgelaufener Frist,
  Mandantentrennung real ueber zwei physisch getrennte Tenant-DBs
- Migration real auf dms_tenant_test angewendet

Pruefungen siehe archive/docs/CMP-07-PRUEFPROTOKOLL.md
This commit is contained in:
sysops
2026-08-30 22:16:53 +02:00
parent 6626f8a1e3
commit af892e1536
6 changed files with 409 additions and 7 deletions
+13 -7
View File
@@ -21,11 +21,17 @@ import (
// SubjectRecord ist EIN gespeichertes Objekt einer betroffenen Person
// (Akzeptanzkriterium 1).
type SubjectRecord struct {
ObjectType string
ObjectReference string
RetentionClass string
Status string
RegisteredAt time.Time
// RetentionObjectID ist CMP-07s additive Ergänzung (RET-01-interne
// ID) — nötig, um für ein gefundenes Objekt den Löschworkflow
// (RET-03/CMP-06) anzustoßen. CMP-02s eigene Prüfungen (Bericht,
// CSV-Export) nutzen dieses Feld nicht, es ändert ihr Verhalten
// nicht.
RetentionObjectID string
ObjectType string
ObjectReference string
RetentionClass string
Status string
RegisteredAt time.Time
}
// SubjectReport liefert ALLE gespeicherten Objekte einer betroffenen
@@ -40,7 +46,7 @@ func SubjectReport(ctx context.Context, pool *pgxpool.Pool, dataSubjectRef strin
FROM retention_class_assignments
ORDER BY retention_object_id, assigned_at DESC
)
SELECT o.object_type, o.object_reference,
SELECT o.id, o.object_type, o.object_reference,
COALESCE(a.retention_class, ''), o.status, o.created_at
FROM retention_objects o
LEFT JOIN latest_assignment a ON a.retention_object_id = o.id
@@ -55,7 +61,7 @@ func SubjectReport(ctx context.Context, pool *pgxpool.Pool, dataSubjectRef strin
var out []SubjectRecord
for rows.Next() {
var r SubjectRecord
if err := rows.Scan(&r.ObjectType, &r.ObjectReference, &r.RetentionClass, &r.Status, &r.RegisteredAt); err != nil {
if err := rows.Scan(&r.RetentionObjectID, &r.ObjectType, &r.ObjectReference, &r.RetentionClass, &r.Status, &r.RegisteredAt); err != nil {
return nil, fmt.Errorf("dpreport: zeile lesen: %w", err)
}
out = append(out, r)