fix(archive): BAK-05 Report liefert existing_in_storage fuer BAK-08
Report enthielt bisher nur Abweichungen. BAK-08 braucht eine deterministisch sortierte Liste bestaetigt existierender Objekte als Stichprobengrundlage, nicht nur "keine Abweichung". Ergaenzt vor BAK-08-Start, real neu getestet (9/9) und auf 131 erneut ausgeloest.
This commit is contained in:
@@ -12,11 +12,22 @@ echtes Dateisystem). Beide Seiten liefern nur SCHLÜSSEL – niemals Inhalt
|
||||
– dadurch bleibt BAK-05 sauber getrennt von BAK-08 (Inhalts-/Prüfsummen-
|
||||
verifikation, eigene Fehlerklasse, eigenes Ticket).
|
||||
|
||||
Report-Format bewusst deterministisch: beide Ergebnislisten
|
||||
(`missing_in_storage`, `orphaned_in_storage`) nach `storage_key`
|
||||
aufsteigend sortiert, damit BAK-08 später dieselbe Objektliste als
|
||||
stabile Stichprobenquelle weiterverarbeiten kann, ohne selbst neu zu
|
||||
sortieren/filtern (Nutzervorgabe).
|
||||
Report-Format bewusst deterministisch: alle drei Ergebnislisten
|
||||
(`missing_in_storage`, `orphaned_in_storage`, `existing_in_storage`)
|
||||
nach `storage_key` aufsteigend sortiert.
|
||||
|
||||
**Nachtrag (nach Rückfrage vor BAK-08-Start):** Der ursprüngliche Report
|
||||
enthielt nur die beiden Abweichungslisten – keine Liste der bestätigt
|
||||
existierenden Objekte. Für BAK-08 als Stichprobengrundlage reicht
|
||||
"keine Abweichung" nicht, es braucht die tatsächliche, deterministisch
|
||||
sortierte Liste. Ergänzt: `Report.ExistingInStorage` – DB-Eintrag UND
|
||||
Storage-Objekt beide vorhanden, reine Existenzbestätigung (keine
|
||||
Inhaltsprüfung, Scope-Trennung zu BAK-08 bleibt gewahrt), aufsteigend
|
||||
nach `storage_key` sortiert. BAK-08 zieht seine Stichprobe daraus, ohne
|
||||
selbst zu sortieren/filtern. Neuer Test
|
||||
`TestReconcile_ExistingInStorageIsStableSamplingBasis` beweist Inhalt
|
||||
und Sortierung. Real neu gebaut, getestet (9/9) und auf 131 erneut
|
||||
ausgelöst – Journal zeigt das Feld `existing_in_storage` im Report.
|
||||
|
||||
Meldeweg über OPS-05 (wie später BAK-08) wurde als offene Design-Frage
|
||||
aufgeworfen, aber nicht zur Vorbedingung gemacht – hier bewusst noch
|
||||
@@ -94,7 +105,7 @@ go test ./... -p 1 -count=1 -> 3/3 Pakete mit Tests ok (internal/backup, intern
|
||||
```
|
||||
|
||||
`internal/reconcile`-Tests separat mit gesetzter `TEST_TENANT_DSN` gegen
|
||||
`dms_tenant_test` verifiziert: 8/8 Tests bestanden (5 reine
|
||||
`dms_tenant_test` verifiziert: 9/9 Tests bestanden (6 reine
|
||||
`Reconcile`-Tests + 3 `sources.go`-Integrationstests).
|
||||
|
||||
## Gesamtergebnis
|
||||
|
||||
@@ -37,6 +37,13 @@ type Report struct {
|
||||
// OrphanedInStorage: Objekt im Storage vorhanden, kein Datenbankeintrag
|
||||
// (Akzeptanzkriterium 2).
|
||||
OrphanedInStorage []Finding `json:"orphaned_in_storage"`
|
||||
// ExistingInStorage: Datenbankeintrag UND Storage-Objekt beide
|
||||
// vorhanden — reine Existenzbestätigung, KEINE Inhaltsprüfung. Dient
|
||||
// Archive BAK-08 als stabile, deterministisch sortierte
|
||||
// Stichprobengrundlage (nach StorageKey aufsteigend, siehe Report-
|
||||
// Dokumentation oben) — BAK-08 muss dafür selbst nicht mehr
|
||||
// sortieren/filtern.
|
||||
ExistingInStorage []Finding `json:"existing_in_storage"`
|
||||
}
|
||||
|
||||
// IsClean liefert true, wenn der Lauf keine Abweichungen fand (Pflicht-
|
||||
@@ -71,10 +78,12 @@ func Reconcile(dbEntries []DBEntry, storageKeys []string) Report {
|
||||
dbSet[e.StorageKey] = e
|
||||
}
|
||||
|
||||
var missing []Finding
|
||||
var missing, existing []Finding
|
||||
for _, e := range dbEntries {
|
||||
if !storageSet[e.StorageKey] {
|
||||
missing = append(missing, Finding(e))
|
||||
} else {
|
||||
existing = append(existing, Finding(e))
|
||||
}
|
||||
}
|
||||
var orphaned []Finding
|
||||
@@ -86,10 +95,12 @@ func Reconcile(dbEntries []DBEntry, storageKeys []string) Report {
|
||||
|
||||
sort.Slice(missing, func(i, j int) bool { return missing[i].StorageKey < missing[j].StorageKey })
|
||||
sort.Slice(orphaned, func(i, j int) bool { return orphaned[i].StorageKey < orphaned[j].StorageKey })
|
||||
sort.Slice(existing, func(i, j int) bool { return existing[i].StorageKey < existing[j].StorageKey })
|
||||
|
||||
return Report{
|
||||
GeneratedAt: time.Now().UTC(),
|
||||
MissingInStorage: missing,
|
||||
OrphanedInStorage: orphaned,
|
||||
ExistingInStorage: existing,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,38 @@ func TestReconcile_ExistingButCorruptedObjectProducesNoFinding(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestReconcile_ExistingInStorageIsStableSamplingBasis ist der Nachweis,
|
||||
// dass Reconcile eine deterministisch sortierte Liste ALLER bestaetigt
|
||||
// existierenden Objekte liefert (DB-Eintrag UND Storage-Objekt vorhanden)
|
||||
// - dies ist die Stichprobengrundlage, die Archive BAK-08 weiterverwendet,
|
||||
// ohne selbst neu zu sortieren/filtern.
|
||||
func TestReconcile_ExistingInStorageIsStableSamplingBasis(t *testing.T) {
|
||||
db := []DBEntry{
|
||||
{StorageKey: "documents/z/revisions/r1", DocumentID: "z", RevisionID: "r1"},
|
||||
{StorageKey: "documents/a/revisions/r1", DocumentID: "a", RevisionID: "r1"},
|
||||
{StorageKey: "documents/fehlt/revisions/r1", DocumentID: "fehlt", RevisionID: "r1"},
|
||||
}
|
||||
storage := []string{
|
||||
"documents/z/revisions/r1",
|
||||
"documents/a/revisions/r1",
|
||||
}
|
||||
|
||||
report := Reconcile(db, storage)
|
||||
|
||||
want := []string{"documents/a/revisions/r1", "documents/z/revisions/r1"}
|
||||
if len(report.ExistingInStorage) != len(want) {
|
||||
t.Fatalf("erwartet %d bestaetigt existierende objekte, habe %d: %+v", len(want), len(report.ExistingInStorage), report.ExistingInStorage)
|
||||
}
|
||||
for i, w := range want {
|
||||
if report.ExistingInStorage[i].StorageKey != w {
|
||||
t.Fatalf("sortierreihenfolge falsch: %v, want beginnend mit %v", report.ExistingInStorage, want)
|
||||
}
|
||||
}
|
||||
if len(report.MissingInStorage) != 1 || report.MissingInStorage[0].StorageKey != "documents/fehlt/revisions/r1" {
|
||||
t.Fatalf("missing_in_storage unerwartet: %+v", report.MissingInStorage)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReconcile_DeterministicOrdering ist der Nachweis fuer die
|
||||
// Stabilitaets-Anforderung: gleiche Eingabe liefert bei mehreren Laeufen
|
||||
// IMMER dieselbe Reihenfolge (Voraussetzung dafuer, dass Archive BAK-08
|
||||
|
||||
Reference in New Issue
Block a user