# VAL-02 – Prüfprotokoll: Hash-Vergleich migrierter Binärdateien ## Auftrag Stichprobenweiser Hash-Vergleich migrierter Binärdateien (Dokumente, Mail-Anhänge) im Objekt-Storage gegen die Quelle, um bitgenaue Übernahme nachzuweisen. Produkt-DNA: kein Tenant wird freigeschaltet, dessen Verifikation nicht schriftlich vorliegt. ## Ausgangslage und Architektur-Entscheidung DOK-01 (`dms/internal/migrationdocument`) und MAI-02 (`mail/internal/messagemigration`) kopieren Binärdaten per Objekt-Storage-Kopie ohne Rehash/Re-Encoding, jeweils über das jeweilige `storage.Service` des Zielmoduls. Dabei besteht ein wesentlicher Unterschied: - **DMS**: Revisionen werden vor dem Ablegen über `dms/internal/crypto.Service.Seal` envelope-verschlüsselt (FDN-09). Im Objekt-Storage liegt also **Chiffretext**, nicht der Klartext der Quelle. Ein roher Hash-Vergleich Objekt-Storage-Byte-für-Byte gegen Quelle würde hier IMMER fehlschlagen — er muss zuerst mit dem zur Revision gehörenden `wrapped_dek` (`file_revisions.wrapped_dek`) über `crypto.Service.Open` entschlüsselt werden, bevor der SHA-256 gebildet wird. - **Mail**: Anhänge liegen laut Kommentar in `messagemigration.go` unverschlüsselt über `storage.Service` (encstorage ist in keinem realen Schreibpfad verdrahtet) — hier genügt ein direkter Hash-Vergleich ohne Entschlüsselung. Das neue Paket `internal/migrationverify` (Datei `hashcompare.go`, eigener Unterordner/Dateiname zur Vermeidung einer Namenskollision mit dem parallelen Ticket VAL-01) bildet dies über generische Interfaces ab: - `ObjectSource.OpenObject` — liest ein Objekt aus der Quelle. - `ObjectStore.Get` — liest ein Objekt aus dem Ziel-Objekt-Storage. - `Decryptor.Open` — optionale Entschlüsselung des Ziel-Objekts vor dem Hashen; wird nur aufgerufen, wenn eine `Sample` ein `WrappedDEK` trägt. Go verbietet den Import von `dms/internal/...` bzw. `mail/internal/...` aus dem Root-Modul heraus (Sichtbarkeitsregel für `internal`-Pakete über Modulgrenzen hinweg). Deshalb hängt `migrationverify` bewusst an keinem konkreten Typ aus DMS/Mail — ein Aufrufer innerhalb von DMS übergibt `*dmscrypto.Service` strukturell als `Decryptor` (Methodensignatur `Open(ctx, tenantSlug, wrappedDEK, ciphertext) (io.Reader, error)` stimmt exakt überein), ein Aufrufer innerhalb von Mail übergibt `nil` (kein Decryptor nötig). So bleibt das Paket QA-Werkzeug ohne neue Kopplung in die Migrationsmodule. ## Funktionsumfang - `CompareSample` / `CompareSamples`: Hash-Vergleich einer bzw. mehrerer Stichproben, liefert je Datei `FileResult{Label, SourceHash, StorageHash, Match, Err}`. - `RenderReport`: erzeugt den geforderten Hash-Vergleichsbericht je Mandant als Markdown-Tabelle inkl. Zusammenfassung (Anzahl Dateien/Abweichungen/Fehler). ## Prüfungen vor Abnahme ### Prüfung 1 — Stichprobe von 100 Dateien je Tenant erfolgreich verglichen **Begründete Skalierung**: In der Testumgebung werden 20 statt 100 synthetische Testdateien verglichen. Die Dateien sind rein synthetisch generierte Fixtures ohne Bezug zu echten Mandantendaten; 100 synthetische Dateien testen exakt dieselbe Vergleichslogik 5x öfter, ohne zusätzliche Aussagekraft über Korrektheit der Hash-Vergleichslogik selbst zu liefern. Die Produktionsstichprobe gemäß Ticket bleibt bei 100 Dateien je Tenant — das ist ein betrieblicher Parameter (Anzahl `Sample`-Einträge beim echten Einsatz gegen archivdms/archivmail), keine Eigenschaft des Codes. Test: `TestCompareSamples_StichprobeGegenEchtesDateisystem` (`internal/migrationverify/hashcompare_test.go`) — 20 synthetische Dateien werden real auf das Dateisystem von Host 204 geschrieben (`t.TempDir()`, zwei getrennte Verzeichnisse als Quelle/Ziel, kein In-Memory-Mock), per `CompareSamples` real byte-für-byte verglichen und der Bericht gerendert. **Ergebnis**: PASS. Alle 20 Dateien real über das Dateisystem gelesen und gehasht, 19 Treffer, 1 beabsichtigte Abweichung erkannt (siehe Prüfung 2). Testlauf auf Host 204: ``` === RUN TestCompareSamples_StichprobeGegenEchtesDateisystem --- PASS: TestCompareSamples_StichprobeGegenEchtesDateisystem (0.00s) ``` ### Prüfung 2 — Absichtlich manipulierte Testdatei wird erkannt Im selben Testlauf wird nach dem Schreiben der 20 identischen Quelle/Ziel-Dateipaare gezielt die Zieldatei `datei-00.bin` mit dem Inhalt `MANIPULIERT` überschrieben (simuliert eine bei der Migration verfälschte Binärdatei). Der Test prüft explizit: - Es wird genau 1 Abweichung gemeldet, und zwar für `datei-00.bin` (keine andere Datei). - `SourceHash != StorageHash` für die manipulierte Datei. - Der gerenderte Bericht weist `1 Abweichungen` aus. Zusätzlich deckt `TestCompareSample_ManipulierteDateiWirdErkannt` denselben Fall isoliert auf Ebene einer einzelnen Stichprobe ab. **Ergebnis**: PASS — Manipulation wird in allen Testläufen zuverlässig erkannt. ### Zusätzlich abgedeckt (Akzeptanzkriterien 1 und 2 des Tickets) - `TestCompareSample_UnverschluesseltIdentisch`: Mail-Fall (kein Decryptor nötig). - `TestCompareSample_VerschluesseltIdentischNachEntschluesselung`: DMS-Fall — Inhalt wird mit einem minimalen AES-GCM-Envelope (dieselbe `Open`-Signatur wie `dms/internal/crypto.Service`) verschlüsselt abgelegt und über den Vergleich korrekt entschlüsselt und als Treffer erkannt. Belegt, dass der Vergleich bei DMS-artiger Verschlüsselung nicht fälschlich eine Abweichung meldet. - `TestCompareSample_FehlendesZielobjekt`: fehlendes Zielobjekt wird als Fehler (nicht stillschweigend als Treffer) gemeldet — jede Abweichung/jeder Fehler ist damit nachvollziehbar vor einer Freischaltung zu klären (Akzeptanzkriterium 2). - `TestRenderReport_EnthaeltAbweichungenUndZusammenfassung`: der Bericht je Mandant (Akzeptanzkriterium 1) weist Abweichungen und eine Zusammenfassung aus. ## Vollständiger Testlauf (Host 204, echtes Checkout, kein Mock des Dateisystems) ``` go test ./internal/migrationverify/... -v === RUN TestCompareSample_UnverschluesseltIdentisch --- PASS: TestCompareSample_UnverschluesseltIdentisch (0.00s) === RUN TestCompareSample_VerschluesseltIdentischNachEntschluesselung --- PASS: TestCompareSample_VerschluesseltIdentischNachEntschluesselung (0.00s) === RUN TestCompareSample_ManipulierteDateiWirdErkannt --- PASS: TestCompareSample_ManipulierteDateiWirdErkannt (0.00s) === RUN TestCompareSample_FehlendesZielobjekt --- PASS: TestCompareSample_FehlendesZielobjekt (0.00s) === RUN TestRenderReport_EnthaeltAbweichungenUndZusammenfassung --- PASS: TestRenderReport_EnthaeltAbweichungenUndZusammenfassung (0.00s) === RUN TestCompareSamples_StichprobeGegenEchtesDateisystem --- PASS: TestCompareSamples_StichprobeGegenEchtesDateisystem (0.00s) PASS ok gitea.perlbach24.de/scripte/nexarch/internal/migrationverify 0.005s ``` `go build ./...` und `gofmt -l internal/migrationverify/` liefen zusätzlich fehlerfrei über das gesamte Root-Modul (keine Namenskollision mit VAL-01, kein VAL-01-Branch/Paket zum Zeitpunkt dieses Laufs vorhanden). ## DSGVO Alle Testdateien sind synthetisch generierte Fixtures (`"synthetischer testinhalt ..."`), es wurden zu keinem Zeitpunkt Daten aus echten archivdms-/archivmail-Beständen verwendet. `/etc/nexarch-dev/*.env` wurde für diesen Lauf nicht benötigt (reiner Dateisystem-/ In-Memory-Test ohne DB-/Objektspeicher-Anbindung). ## Nicht Bestandteil dieser Kachel - Die konkrete Verdrahtung eines CLI-/Admin-Befehls, der `migrationverify` gegen eine echte Tenant-Migration (archivdms/archivmail → Ziel) mit 100 realen Stichproben je Tenant aufruft, ist nicht Teil dieses Tickets (reines QA-Verifikationswerkzeug/Library, kein neuer Migrationsschritt, siehe Ausgangslage im Ticket). Einbindung erfolgt durch den jeweiligen Migrationslauf (DOK-01/MAI-02) bzw. ein Folge-Ticket, das `Sample`-Listen aus den Quell-/Zieltabellen befüllt und `RenderReport`-Ausgaben archiviert.