internal/retentionengine: Frist je Aufbewahrungsklasse als natives Postgres-INTERVAL, Stichtagsberechnung an Postgres delegiert statt eigener Kalenderrechnung (Schaltjahr/Monatsende-Referenzwerte real verifiziert: 2024-02-29+1y=2025-02-28, 2026-01-31+1mo=2026-02-28). Periodischer Job (ListExpiringObjects) beschraenkt sich per DISTINCT ON auf die juengste Klassenzuordnung je Objekt - sonst wuerden Objekte mit mehrfach geaenderter Klasse (RET-01-Historisierung) doppelt auftauchen, real mit einem Zwei-Zuordnungen-Testobjekt bewiesen. Scope bewusst eng gehalten: keine RET-05-Anbindung, keine Vernichtungslogik - das ist Ticket-Scope, dependsOn ist nur RET-01.
3.5 KiB
RET-02 – Prüfprotokoll: Aufbewahrungsfristen-Engine
Voraussetzung RET-01 – erledigt, siehe eigenes Protokoll.
Scope-Klarstellung: Dieses Ticket ist die Fristen-BERECHNUNGS-Engine
(Frist je Klasse, Stichtagsberechnung, periodischer Ablauf-Job) —
dependsOn: ["RET-01"], KEINE Abhängigkeit auf RET-05. Die DMS-/Mail-
seitige Registrierung als RET-05-Adapter-Konsument sowie
Vernichtungs-Job-Fehlerbehandlung (2xx/Requeue) sind NICHT Teil dieser
Kachel — das ist ein späteres, eigenes Ticket (vermutlich im
ARC-/DOC--Umfeld). Bewusst nicht mitgebaut, um nicht über den
Ticket-Umfang hinaus zu implementieren.
Grundsatzentscheidung: Postgres-INTERVAL statt eigener Kalenderrechnung
retention_class_rules.duration ist ein natives Postgres-INTERVAL
(z. B. '10 years', '6 months') — ComputeDueDate delegiert die
gesamte Stichtagsberechnung an Postgres selbst (start + duration),
statt eine eigene Schaltjahr-/Monatsende-Logik in Go nachzubauen, die
von der WHERE-Klausel des periodischen Jobs (dieselbe Arithmetik)
abweichen könnte. Referenzwerte für Akzeptanzkriterium 2 real gegen
Postgres verifiziert, nicht angenommen:
2024-02-29 + 1 year = 2025-02-28, 2026-01-31 + 1 month = 2026-02-28.
Umsetzung
migrations/0004_retention_class_rules.up.sql/.down.sql.internal/retentionengine.ConfigureClassRule— eine Regel je Klasse (UPSERT).internal/retentionengine.ComputeDueDate— delegiert an Postgres.internal/retentionengine.ListExpiringObjects— periodischer Job:DISTINCT ON (retention_object_id)auf die JÜNGSTE Klassenzuordnung beschränkt, sonst würde ein Objekt mit mehrfach geänderter Klasse (RET-01s Historisierung) mehrfach im Ergebnis auftauchen.
Prüfungen
| # | Prüfung | Ergebnis |
|---|---|---|
| 1 | Fristberechnung an Referenzdaten mit bekannten Ablaufdaten geprüft | bestanden — TestComputeDueDate_KnownReferenceDates: Schaltjahr (29.02.2024 + 1 Jahr → 28.02.2025) und Monatsende (31.01.2026 + 1 Monat → 28.02.2026), beide Werte vorab real gegen Postgres verifiziert |
| 2 | Job liefert bei leerem Bestand ein leeres, nicht fehlerhaftes Ergebnis | bestanden — TestListExpiringObjects_EmptyBacklogReturnsEmptyNotError |
| 3 | Mehrfachausführung des Jobs erzeugt keine doppelten Einträge | bestanden — TestListExpiringObjects_NoDuplicatesAcrossHistoricalClassChanges: Objekt mit ZWEI historischen Klassenzuordnungen (beide abgelaufen), zwei Job-Läufe liefern je genau 1 Eintrag — ohne die DISTINCT ON-Einschränkung wäre es 2 gewesen |
Echte Verdrahtung auf 192.168.1.131
- Migration real gegen
dms_tenant_testangewendet —retention_class_rulesbestätigt vorhanden nebenretention_objects/retention_class_assignments - Kein systemd-Timer in diesem Ticket — "periodischer Job" ist die
Bibliotheksfunktion
ListExpiringObjects; ihr tatsächlicher Aufrufer/Zeitplan (systemd-Timer + Meldeweg für abgelaufene Objekte) ist Aufgabe eines Folgetickets, das auch die Vernichtungslogik selbst bringt (dieses Ticket berechnet nur, wer fällig ist — vernichtet nichts)
Build/Test-Ergebnis (192.168.1.131, make check)
go build ./... -> clean
go vet ./... -> clean
golangci-lint run ./... -> 0 issues
go test ./... -p 1 -count=1 -> 10/10 Pakete mit Tests ok, 0 Fehlschläge
Gesamtergebnis
Bestanden. Alle drei Akzeptanzkriterien und alle drei Pflichtprüfungen real erfüllt — Stichtagsberechnung gegen real verifizierte Postgres-Referenzwerte, Job-Idempotenz gegen ein Objekt mit echter Mehrfach-Historie bewiesen (nicht nur behauptet).