package backup import ( "fmt" "sort" "time" ) // RetentionPolicy ist BAK-07s gestaffelte Aufbewahrungsfrist für // Datenbank-Sicherungsgenerationen — unabhängig von RET-02s // Dokumenten-Retention (Akzeptanzkriterium 1). Ein Feldwert 0 bedeutet // "diese Stufe nicht anwenden", dieselbe Konvention wie // objectbackup.RetentionPolicy (BAK-02), damit beide Backup-Arten // gleich konfiguriert werden. type RetentionPolicy struct { KeepDaily int KeepWeekly int KeepMonthly int } // PruneTiered wählt aus generations (IDs im NewGenerationID-Format) die // zu BEHALTENDEN nach gestaffelter GFS-Regel: je Granularität // (Tag/Woche/Monat) wird pro Zeitfenster die NEUESTE Generation // behalten, bis die konfigurierte Anzahl Fenster erreicht ist — reine // Funktion, keine Dateisystem-/Netzwerkzugriffe, dadurch ohne echte // Infrastruktur testbar (Rotate in rotate.go führt die tatsächliche // Löschung anhand des Ergebnisses aus). // // Sicherheitsnetz für Akzeptanzkriterium 3 ("mindestens ein aktueller // Snapshot bleibt erhalten"): die NEUESTE Generation wird IMMER // behalten, unabhängig von der konfigurierten Staffelung — selbst bei // versehentlich auf 0 gesetzter Policy geht nie die letzte Sicherung // verloren. func PruneTiered(generations []string, policy RetentionPolicy) (keep, remove []string) { type parsed struct { id string t time.Time } var items []parsed for _, id := range generations { t, err := time.Parse("20060102T150405Z", id) if err != nil { continue // unbekanntes Format wird ignoriert, nicht geloescht } items = append(items, parsed{id: id, t: t}) } sort.Slice(items, func(i, j int) bool { return items[i].t.After(items[j].t) }) // neueste zuerst keepSet := make(map[string]bool) if len(items) > 0 { keepSet[items[0].id] = true // Sicherheitsnetz: immer die neueste } applyTier := func(bucketKey func(time.Time) string, n int) { if n <= 0 { return } seen := make(map[string]bool) count := 0 for _, it := range items { if count >= n { break } key := bucketKey(it.t) if seen[key] { continue } seen[key] = true count++ keepSet[it.id] = true } } applyTier(func(t time.Time) string { return t.Format("2006-01-02") }, policy.KeepDaily) applyTier(func(t time.Time) string { y, w := t.ISOWeek() return fmt.Sprintf("%04d-W%02d", y, w) }, policy.KeepWeekly) applyTier(func(t time.Time) string { return t.Format("2006-01") }, policy.KeepMonthly) for _, it := range items { if keepSet[it.id] { keep = append(keep, it.id) } else { remove = append(remove, it.id) } } return keep, remove }