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-
|
– dadurch bleibt BAK-05 sauber getrennt von BAK-08 (Inhalts-/Prüfsummen-
|
||||||
verifikation, eigene Fehlerklasse, eigenes Ticket).
|
verifikation, eigene Fehlerklasse, eigenes Ticket).
|
||||||
|
|
||||||
Report-Format bewusst deterministisch: beide Ergebnislisten
|
Report-Format bewusst deterministisch: alle drei Ergebnislisten
|
||||||
(`missing_in_storage`, `orphaned_in_storage`) nach `storage_key`
|
(`missing_in_storage`, `orphaned_in_storage`, `existing_in_storage`)
|
||||||
aufsteigend sortiert, damit BAK-08 später dieselbe Objektliste als
|
nach `storage_key` aufsteigend sortiert.
|
||||||
stabile Stichprobenquelle weiterverarbeiten kann, ohne selbst neu zu
|
|
||||||
sortieren/filtern (Nutzervorgabe).
|
**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
|
Meldeweg über OPS-05 (wie später BAK-08) wurde als offene Design-Frage
|
||||||
aufgeworfen, aber nicht zur Vorbedingung gemacht – hier bewusst noch
|
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
|
`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).
|
`Reconcile`-Tests + 3 `sources.go`-Integrationstests).
|
||||||
|
|
||||||
## Gesamtergebnis
|
## Gesamtergebnis
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ type Report struct {
|
|||||||
// OrphanedInStorage: Objekt im Storage vorhanden, kein Datenbankeintrag
|
// OrphanedInStorage: Objekt im Storage vorhanden, kein Datenbankeintrag
|
||||||
// (Akzeptanzkriterium 2).
|
// (Akzeptanzkriterium 2).
|
||||||
OrphanedInStorage []Finding `json:"orphaned_in_storage"`
|
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-
|
// 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
|
dbSet[e.StorageKey] = e
|
||||||
}
|
}
|
||||||
|
|
||||||
var missing []Finding
|
var missing, existing []Finding
|
||||||
for _, e := range dbEntries {
|
for _, e := range dbEntries {
|
||||||
if !storageSet[e.StorageKey] {
|
if !storageSet[e.StorageKey] {
|
||||||
missing = append(missing, Finding(e))
|
missing = append(missing, Finding(e))
|
||||||
|
} else {
|
||||||
|
existing = append(existing, Finding(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var orphaned []Finding
|
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(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(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{
|
return Report{
|
||||||
GeneratedAt: time.Now().UTC(),
|
GeneratedAt: time.Now().UTC(),
|
||||||
MissingInStorage: missing,
|
MissingInStorage: missing,
|
||||||
OrphanedInStorage: orphaned,
|
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
|
// TestReconcile_DeterministicOrdering ist der Nachweis fuer die
|
||||||
// Stabilitaets-Anforderung: gleiche Eingabe liefert bei mehreren Laeufen
|
// Stabilitaets-Anforderung: gleiche Eingabe liefert bei mehreren Laeufen
|
||||||
// IMMER dieselbe Reihenfolge (Voraussetzung dafuer, dass Archive BAK-08
|
// IMMER dieselbe Reihenfolge (Voraussetzung dafuer, dass Archive BAK-08
|
||||||
|
|||||||
Reference in New Issue
Block a user