From c6de214ac8fb9aaa078648e7bf9e6e3a1344c20d Mon Sep 17 00:00:00 2001 From: DonVoo Date: Thu, 16 Jul 2026 23:38:33 +0200 Subject: [PATCH] Review: Snapshot gruen, normalizeMessageID-Aenderung ist Rollout-Blocker Snapshot (14-snapshot.go) korrekt: VACUUM INTO vor der Wasserlinie, N aus dem Schnappschuss, MAX(file_offset+frame_len), kein indexed_bytes, stat nur als Notbremse. Datensatz-Kreuzpruefung (Zeile 145) war nicht verlangt und ist gut. Reindex-Erhaltung sauber: Position ist Schluessel, Body-Hash ist Bedingung. Real geprueft: 228 Dateien, mbox_index 121.861 = Produktion, integrity ok. BEFUND 1 (Blocker): normalizeMessageID aendert die Identitaet fuer Werte mit verirrtem '>'. Gemessen: 3 Zeilen copied, 2 mbox_index. Die Reindex-Erhaltung schuetzt den Index, nicht den Live-Pfad - LegacyMessageID ist ebenfalls neu-normalisiert. Erster Nachtlauf nach Rollout erzeugt 3 lautlose Dubletten. BEFUND 2: Snapshot-DB ist 0644 und enthaelt 46 Klartext-Passwoerter; die mbox-Dateien daneben sind 0600. Co-Authored-By: Claude Opus 4.8 --- review-snapshot-2026-07-16.md | 139 ++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 review-snapshot-2026-07-16.md diff --git a/review-snapshot-2026-07-16.md b/review-snapshot-2026-07-16.md new file mode 100644 index 0000000..f780906 --- /dev/null +++ b/review-snapshot-2026-07-16.md @@ -0,0 +1,139 @@ +# Review — Snapshot (14-snapshot.go) + Streaming-Reindex + +Geprueft von Claude am 2026-07-16 gegen den nicht-committeten Arbeitsstand. + +## Urteil + +**Der Snapshot ist richtig gebaut und darf so committet werden.** +**Die Aenderung an `normalizeMessageID` darf NICHT ohne Fix ausgerollt werden.** + +## Was gepasst hat + +Die zwei Stellen, an denen ich Fehler erwartet habe, sind beide korrekt: + +```go +104: DB.Exec(`VACUUM INTO ?`, databasePath) // erst der Schnappschuss +120: collectSnapshotWatermarks(snapshotDB, Cfg.MboxRoot) // dann N DARAUS + MAX(i.file_offset+i.frame_len) // richtige Wasserlinie +327: os.Stat(...) -> info.Size() < watermark.Bytes // stat nur als Notbremse +``` + +- `indexed_bytes` kommt nirgends vor (die Falle aus backup-brief.md). +- Reihenfolge stimmt; `stat` leitet N nicht ab, sondern verweigert nur. +- Zeile 145 bricht ab, wenn indizierte und abgebildete Datensaetze auseinander- + laufen. **Das stand nicht im Brief und ist besser als verlangt.** +- Pfad-Ausbruchspruefung (`relative == ".."`) vorhanden. +- `snapshotPathKey` senkt nur unter Windows auf Kleinschreibung — richtig herum: + auf Linux sind `Drafts` und `drafts` zwei echte Dateien und duerfen nicht + kollidieren, unter Windows wuerden sie es. +- Streaming-Reindex (`06-mbox.go:293`) liest nachrichtenweise ueber `bufio` + statt 9,4 GB in den RAM. + +Die Message-ID-Erhaltung im Reindex ist entgegen meinem ersten Verdacht sauber: + +```go +if storedIdentity.bodySHA256 != "" && storedIdentity.bodySHA256 == entry.BodySHA256 { + entry.MessageID = storedIdentity.messageID +} +``` +Die Position ist nur der Nachschlage-Schluessel, der **Body-Hash ist die +Bedingung**. Eine falsche Zuordnung kann keine falsche Uebernahme ausloesen. + +Nachgeprueft am realen Snapshot `archive-20260716-2250`: +228 Archivdateien, `mbox_index` 121.861 im Snapshot **und** in Produktion, +121.861 praezise `copied`-Identitaeten, `integrity_check=ok`. +`go vet` sauber, `go test -count=1 ./backend/` gruen (0,366s). + +--- + +## BEFUND 1 — Identitaetsaenderung, Dublettenrisiko (Blocker fuer Rollout) + +```go + func normalizeMessageID(id string) string { +- return strings.Trim(strings.TrimSpace(id), "<>") ++ if start := strings.IndexByte(id, '<'); start >= 0 { ... } +``` + +`normalizeMessageID` speist `message_id` = die halbe Identitaet. Alt und neu +gegen die **echten gespeicherten Werte** verglichen (lauffaehig nachgestellt): + +``` +<476BDFDB019289BE@mail21.bluewin.ch> (added by postmaster@bluewin.ch) + alt: [476BDFDB019289BE@mail21.bluewin.ch> (added by postmaster@bluewin.ch)] + neu: [476BDFDB019289BE@mail21.bluewin.ch] <<< WEICHT AB + +<22578196653698419217.BB8A0E8F48F3F290@dr-gold.de>+D271B1409C3C6028 + alt: [22578196653698419217.BB8A0E8F48F3F290@dr-gold.de>+D271B1409C3C6028] + neu: [22578196653698419217.BB8A0E8F48F3F290@dr-gold.de] <<< WEICHT AB + +<1280874184.3726.1662111901550.JavaMail."LOCAL SERVICE"@DESV226350> gleich +<476FE4C5.2060001@yahoo.com;Evy O'Brien> gleich +<13140-22021132765855760@strepack.de> gleich +13140-22021132765855760@strepack.de gleich +``` + +Nur Werte mit einem **verirrten `>` im Inneren** aendern sich. Umfang gemessen: +**3 Zeilen in `copied`, 2 in `mbox_index`** von 121.861. + +**Warum die Reindex-Erhaltung nicht reicht:** Sie schuetzt den Index, nicht den +Live-Pfad. Dort gilt: + +1. Praezise Abfrage `message_id=NEU AND body_sha256=X` → gespeichert ist ALT → + **kein Treffer**. +2. Die Alias-Schleife hilft nicht: `LegacyMessageID` wird in + `identityForRawMessage` ebenfalls mit der **neuen** Funktion gebildet + (`id := normalizeMessageID(...); legacyID := id`) — sie traegt die alte + Normalisierung nicht. +3. Ergebnis: `CopyState{}` → nicht kopiert → **erneutes mbox.Append UND + erneutes dst.Append**. + +**Beim ersten Nachtlauf nach dem Rollout entstehen so 3 Dubletten in beiden +Haelften — lautlos.** Klein, aber es ist dieselbe Klasse wie die 4.024 Waisen, +und Dubletten sind unsere Stopp-Bedingung. + +### Zwei Wege, Empfehlung (a) + +**(a) Alte Normalisierung als zusaetzlichen Alias fuehren.** `identityForRawMessage` +bekommt zusaetzlich den alt-normalisierten Wert; die Alias-Schleife in +`GetCopyIdentityStateWithFolderAlias` muss ihn dann auch gegen **praezise** +Zeilen pruefen, nicht nur gegen `body_sha256=''`. +*Additiv, mutiert keine Geschichte, nutzt vorhandene Maschinerie.* Passt zur +Linie „Aliase sind Rueckrollschutz". + +**(b) Einmal-Migration der 5 Zeilen** auf den neuen Wert. Simpler, aber schreibt +Geschichte um — und die Regel lautet: Aliase nicht ohne gesonderte, geprueefte +Bereinigung anfassen. + +### Abnahme fuer den Fix + +1. Lauf gegen ein Konto mit einer der beiden IDs: `archived=0`. Ein zweiter + Lauf danach ebenfalls `archived=0`. +2. `SELECT COUNT(*) FROM mbox_index WHERE message_id LIKE '%>%'` bleibt bei 2 — + nicht 4. +3. Test mit genau diesen zwei Roh-Headern als Fixture. + +--- + +## BEFUND 2 — Snapshot-DB world-readable (klein, aber schnell zu beheben) + +``` +600 root:root .../backup/colak@dr-gold.de/INBOX.mbox <- richtig +644 root:root .../data/mail-graveyard.db <- zu offen +``` + +Die Snapshot-DB enthaelt **23 Quell- und 23 Ziel-Passwoerter im Klartext**. +Heute schuetzt nur die `0700`-Wurzel darueber — ein `chmod` von der Preisgabe +entfernt, und inkonsistent zu den mbox-Dateien daneben. Bitte `0600`. + +--- + +## Nicht deins + +`gofmt -l` meldet `backend/11-folders.go`. Die Datei ist im Diff unveraendert +(Altbestand), reine Ausrichtung einer Map. Kein Rollout-Hindernis. + +## Weiterhin offen (unveraendert aus backup-brief.md) + +- Der Snapshot liegt auf **derselben Platte** — er ist die Vorstufe, nicht das + Backup. Raeumlich getrennte, verschluesselte Kopie fehlt. +- Restore-Test gehoert **wiederholt** gefahren, nicht einmal.