From 8100fa3d14dd623a97d2d05c2a5d663c339c07d3 Mon Sep 17 00:00:00 2001 From: sysops Date: Sat, 29 Aug 2026 23:41:36 +0200 Subject: [PATCH] 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. --- archive/docs/BAK-05-PRUEFPROTOKOLL.md | 23 ++++++++++---- archive/internal/reconcile/reconcile.go | 13 +++++++- archive/internal/reconcile/reconcile_test.go | 32 ++++++++++++++++++++ 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/archive/docs/BAK-05-PRUEFPROTOKOLL.md b/archive/docs/BAK-05-PRUEFPROTOKOLL.md index ff71fbd..6c432ea 100644 --- a/archive/docs/BAK-05-PRUEFPROTOKOLL.md +++ b/archive/docs/BAK-05-PRUEFPROTOKOLL.md @@ -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 diff --git a/archive/internal/reconcile/reconcile.go b/archive/internal/reconcile/reconcile.go index 06d3d18..665346e 100644 --- a/archive/internal/reconcile/reconcile.go +++ b/archive/internal/reconcile/reconcile.go @@ -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, } } diff --git a/archive/internal/reconcile/reconcile_test.go b/archive/internal/reconcile/reconcile_test.go index 30be1e4..68f0e62 100644 --- a/archive/internal/reconcile/reconcile_test.go +++ b/archive/internal/reconcile/reconcile_test.go @@ -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