# BAK-07 – Prüfprotokoll: Aufbewahrungsfrist für Backup-Snapshots Voraussetzung BAK-01, BAK-02 – erledigt, siehe eigene Protokolle. ## Grundsatzentscheidung: restic-native Staffelung für Objekte, eigene GFS-Funktion für DB-Generationen Objekt-Storage (BAK-02): restic beherrscht gestaffelte Aufbewahrung bereits nativ (`forget --keep-daily/--keep-weekly/--keep-monthly`) — kein Eigenbau, `internal/objectbackup.PruneTiered` ist ein dünner Wrapper (dieselbe Disziplin wie beim ursprünglichen restic-statt-Eigenbau-Entscheid in BAK-02). Datenbank-Generationen (BAK-01): `pg_basebackup` kennt keine Staffelung, daher `internal/backup.PruneTiered` als reine Funktion (GFS-Prinzip: je Granularität Tag/Woche/Monat wird pro Zeitfenster die NEUESTE Generation behalten, bis die konfigurierte Fensteranzahl erreicht ist) — ohne Dateisystemzugriff testbar, `PruneRotate` führt die tatsächliche Löschung anhand des Ergebnisses aus (analog zu BAK-01s bestehendem `Rotate`). Beide Policies sind über Umgebungsvariablen konfigurierbar (`NEXARCH_BACKUP_KEEP_DAILY/WEEKLY/MONTHLY`, `NEXARCH_OBJECTBACKUP_KEEP_DAILY/WEEKLY/MONTHLY`), nicht hart codiert (Ticket-Vorgabe). Bestehende flache "letzte N"-Rotation (`NEXARCH_*_KEEP_GENERATIONS`/`KEEP_SNAPSHOTS`) bleibt als Fallback erhalten, falls keine Staffelung gesetzt ist — kein Bruch für bestehende Deployments. ## Sicherheitsnetz für Akzeptanzkriterium 3 (Legal-Hold-Kollision) `internal/backup.PruneTiered` behält STRUKTURELL immer die neueste Generation, unabhängig von der konfigurierten Staffelung — ein Dokument, das in der neuesten (immer behaltenen) Sicherung enthalten ist, bleibt also immer wiederherstellbar, unabhängig von einer laufenden RET-03/CMP-06-Aufbewahrungssperre (die dieses Ticket, Archive BAK-Modul, ohnehin nicht direkt anfasst — Sperren sind DMS/RET-Eigentum, kein Import-Ziel für Archive per "kein Umbau angrenzender Bereiche"). Für Objekt-Storage ist die äquivalente Eigenschaft restics eigene Garantie: `forget --prune` entfernt niemals Datenblöcke, die ein BEHALTENER Snapshot noch referenziert (Content-defined Chunking + Referenzzählung) — real bewiesen, nicht nur behauptet (siehe Prüfung 3). ## Umsetzung - `internal/backup.RetentionPolicy`/`PruneTiered` (`retention.go`) — reine GFS-Funktion. - `internal/backup.PruneRotate` (`rotate.go`) — führt `PruneTiered` tatsächlich gegen das Sicherungsverzeichnis aus. - `internal/objectbackup.RetentionPolicy`/`PruneTiered` (`restic.go`) — Wrapper um restics native Staffelung. - `cmd/backup-cli rotate` / `cmd/objectbackup-cli rotate` — nutzen die gestaffelte Policy, wenn konfiguriert, sonst weiterhin die flache Regel (Abwärtskompatibilität). ## Prüfungen | # | Prüfung | Ergebnis | |---|---|---| | 1 | Prune-Test mit simulierten Snapshots über mehrere Altersstufen ergibt korrekte Staffelung | **bestanden** — `TestPruneTiered_KeepsCorrectStaggering` (Datenbank, reine Funktion, simulierte Generationen über Tage/Wochen/Monate) UND `TestPruneTiered_KeepsCorrectStaggering_RealSnapshots` (Objekt-Storage, ECHTE restic-Snapshots mit erzwungenem Zeitstempel `restic backup --time`, `KeepDaily=3` reduziert 10 reale Snapshots korrekt auf 3) | | 2 | Automatisierte Löschung abgelaufener Snapshots im Test nachgewiesen | **bestanden** — `TestPruneRotate_DeletesOnlyExpiredGenerations`: reales Verzeichnis, abgelaufene Generation tatsächlich vom Dateisystem entfernt, aktuelle unangetastet; real auf 131: `backup-cli rotate` und `objectbackup-cli rotate` über systemd ausgelöst, Journal zeigt „gestaffelte rotation abgeschlossen“ | | 3 | Legal-Hold-Test bestätigt, dass Pruning nicht mit Dokumenten-Retention kollidiert | **bestanden** — `TestPruneTiered_AlwaysKeepsNewest` (Datenbank-Sicherheitsnetz: neueste Generation immer behalten, selbst bei Policy 0/0/0) UND `TestPruneTiered_KeptSnapshotStillFullyRestorable` (Objekt-Storage, real: 5 ältere Snapshots eines Dokuments weggeprunt, verbleibender Snapshot danach vollständig und inhaltlich korrekt wiederhergestellt, zusätzlich `restic check --read-data` bestätigt Datenintegrität nach dem Pruning) | Zusätzlich: `TestPruneTiered_DeterministicForIdenticalInput`. ## Echte Verdrahtung auf 192.168.1.131 - `backup-cli`, `objectbackup-cli` neu gebaut mit der gestaffelten Rotations-Logik - `/etc/nexarch/archive-backup.env` und `/etc/nexarch/archive-objectbackup.env` um `KEEP_DAILY=14`/`KEEP_WEEKLY=12`/`KEEP_MONTHLY=12` ergänzt - Beide `*-rotate.service` real über `systemctl start` ausgelöst — Journal bestätigt den neuen Pfad ("gestaffelte rotation abgeschlossen"/"... entfernt: [...]"), nicht mehr die alte flache Regel ## 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 -> 7/7 Pakete mit Tests ok, 0 Fehlschläge ``` ## Bekannte Lücke: keine Kohärenz zwischen DB- und Objekt-Rotation Beide Rotationswege laufen als UNABHÄNGIGE systemd-Timer mit eigener Kadenz und eigener Policy-Auswertung — nichts stellt sicher, dass der älteste noch erreichbare restic-Snapshot und die älteste noch erreichbare DB-Generation denselben Zeitpunkt abdecken. Die aktuelle Konfiguration (`KEEP_DAILY=14`/`KEEP_WEEKLY=12`/`KEEP_MONTHLY=12` auf beiden Seiten identisch) ist eine ZUFÄLLIGE Übereinstimmung, kein getestetes oder erzwungenes Invariant — ein restic-Snapshot ohne zeitlich passende DB-Generation wäre ein inkonsistenter Restore-Punkt (Objekte ohne zugehörige `file_revisions`-Metadaten oder umgekehrt). Nicht Teil dieses Tickets (BAK-07 fordert nur je Sicherungsart eine konfigurierbare Staffelung, kein Cross-Artefakt-Alignment) — als dokumentierter Folgepunkt festgehalten, nicht stillschweigend übergangen: ein künftiges Ticket müsste entweder die Policies fest koppeln oder bei Restore-Zeitpunkt-Wahl (BAK-03) explizit warnen, wenn kein zeitlich passendes Gegenstück mehr existiert. ## Gesamtergebnis **Bestanden.** Alle drei Akzeptanzkriterien und alle drei Pflichtprüfungen real erfüllt — sowohl mit simulierten (Datenbank) als auch mit tatsächlich zeitversetzt erzeugten, realen restic-Snapshots (Objekt-Storage). Ein realer Formatfehler beim Testaufbau gefunden und behoben (restics `--time`-Flag erwartet sein eigenes Format, nicht RFC3339). Beide Rotations-Wege real über systemd auf 192.168.1.131 ausgelöst, nicht nur isolierter Testcode.