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 <noreply@anthropic.com>
5.7 KiB
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:
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_byteskommt nirgends vor (die Falle aus backup-brief.md).- Reihenfolge stimmt;
statleitet 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. snapshotPathKeysenkt nur unter Windows auf Kleinschreibung — richtig herum: auf Linux sindDraftsunddraftszwei echte Dateien und duerfen nicht kollidieren, unter Windows wuerden sie es.- Streaming-Reindex (
06-mbox.go:293) liest nachrichtenweise ueberbufiostatt 9,4 GB in den RAM.
Die Message-ID-Erhaltung im Reindex ist entgegen meinem ersten Verdacht sauber:
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)
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:
- Praezise Abfrage
message_id=NEU AND body_sha256=X→ gespeichert ist ALT → kein Treffer. - Die Alias-Schleife hilft nicht:
LegacyMessageIDwird inidentityForRawMessageebenfalls mit der neuen Funktion gebildet (id := normalizeMessageID(...); legacyID := id) — sie traegt die alte Normalisierung nicht. - 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
- Lauf gegen ein Konto mit einer der beiden IDs:
archived=0. Ein zweiter Lauf danach ebenfallsarchived=0. SELECT COUNT(*) FROM mbox_index WHERE message_id LIKE '%>%'bleibt bei 2 — nicht 4.- 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.