Behebt drei Fehler derselben Klasse: an jeder Stelle wurde derselbe Wert zweimal berechnet, statt einmal berechnet und weitergereicht - und die beiden Berechnungen liefen auseinander. 1. Instabiler Ersatzschluessel (bug-hashkey-instabil.md) Mails ohne Message-ID bekamen sha256 ueber die IMAP-Rohbytes, der Reindex hashte dieselbe Mail ueber die mbox-gespeicherten Bytes (>From-Quoting, andere Zeilenenden) -> zwei Schluessel, 4.024 Doppel-Eintraege in copied. Fix: canonicalMessageBytes() bringt beide Seiten auf eine Form (>From zurueckdrehen, CRLF->LF, Trailing-Newlines weg). Alle Pfade (Index, Migration, Viewer, Dedup) nutzen dieselbe Funktion. body_sha256 in copied + mbox_index, UNIQUE erweitert. 2. Index-Drift (bug-index-drift.md) Der plain-mbox-Reader las nach Datei-Position statt nach dem gespeicherten file_offset. Weil SQLITE_BUSY (busy_timeout=0) je Ordner einen Index-Eintrag verschluckt hatte, war ab dieser Luecke alles um 1 verschoben: Klick auf Mail X zeigte Mail X+1. Betroffen genau die 6 Ordner mit Busy-Fehler beim Rettungslauf. Fix: busy_timeout=10000, Lesen ueber file_offset, --reindex --rebuild. 3. HTML-Vorschau-Panic (bug-htmltotext-panic.md) replaceCaseInsensitive/stripHTMLBlock indizierten mit Offsets aus strings.ToLower(s) in s - ToLower ist nicht byte-laengen-erhaltend (z.B. U+0130). ~0,1% der Mails brachten die Vorschau zum Absturz. Fix: asciiFoldIndex() sucht direkt auf den Original-Bytes; zusaetzlich Rohtext-Fallback, damit keine archivierte Mail unsichtbar wird. Weiter: Archiv-zuerst-Reihenfolge (mbox_done/target_done) - ein sterbendes Ziel kostet keine Archiv-Kopie mehr; Dedup vergleicht zusaetzlich den Inhalt und schuetzt byte-verschiedene Varianten. WICHTIG: Die 62.073 alten Alias-Zeilen in copied bleiben bewusst erhalten. Sie sehen wie Muell aus, sind aber die Zeilen, auf die der alte Schluessel matcht - ein Loeschen wuerde Mails erneut in fremde Postfaecher kopieren. Verifiziert: go test ./... gruen; 58.051 Archivmails, 0 ohne copied-Zeile; Drift 0 ueber alle 69 Ordner; 2.000 HTTP-Stichproben ueber 8 Ordner: 0 falsche Zuordnung, 0 Panics; Watcher 220 + 60 Mails archived=0 target=0 errors=0. Live als Image fix-identity2-20260716. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.2 KiB
🟠 Instabiler Ersatzschlüssel: 4.024 Doppel-Einträge in copied
Kein Datenverlust, keine Dubletten in Postfächern. Aber die Buchhaltung stimmt nicht — und beim Aufräumen lauert eine Falle (siehe „⚠️ NICHT TUN").
Befund
Datei-Records: 58.051 ═ mbox_index: 58.051 ✓ (Drift 0)
copied: 62.073 ← 4.022 mehr
copied-Zeilen ohne passenden mbox_index-Eintrag: 4.024, davon
4.021 mit sha256:-Schlüssel. Verteilung exakt auf die Konten mit
--reindex --rebuild: gb +2533, rb +719, byrne +564, crystal +112, roby +92.
(gb hatte gemessen 2.537 Mails ohne Message-ID — passt auf ~4 genau.)
Ursache
Mails ohne Message-ID-Header bekommen sha256:<hash> als Ersatzschlüssel.
Der Hash wird aber über unterschiedliche Bytes gebildet:
| Pfad | hasht |
|---|---|
Migration (07-migrate.go) |
die IMAP-Rohbytes der Mail |
Reindex/Rebuild (06-mbox.go) |
den mbox-Record aus der Datei |
Die mbox-Fassung ist nicht byte-gleich mit der IMAP-Fassung: Der Writer setzt
>From -Quoting und normalisiert Zeilenenden. → anderer Hash → anderer
Schlüssel → dieselbe Mail steht zweimal in copied.
Betroffen sind alle 6.623 Mails ohne Message-ID (so viele hat mbox_index
mit sha256:-Schlüssel).
⚠️ NICHT TUN: die „Waisen" einfach löschen
Die 4.024 Waisen sind nicht der Müll — sie sind die Zeilen, auf die der Migrationspfad tatsächlich matcht (er rechnet den IMAP-Hash aus). Die vom Rebuild ergänzten Zeilen (mbox-Hash) sind die redundanten.
Werden die Waisen gelöscht, hält die Migration diese 4.021 Mails für unkopiert und schreibt sie erneut ins Ziel-Postfach → echte Dubletten in fremden Postfächern. Genau der Schaden, den wir vermeiden wollen.
Aus demselben Grund darf die Schlüsselberechnung nicht einfach umgestellt werden: Für Konten ohne Rebuild (kolmer, palamari, colak, …) existiert nur der IMAP-Hash. Ein Wechsel auf den mbox-Hash lässt dort alle Message-ID-losen Mails als „neu" erscheinen → Massen-Neukopie.
Fix (in dieser Reihenfolge, sonst knallt es)
- Schlüssel vereinheitlichen: Der Ersatzschlüssel muss in beiden Pfaden
über dieselben Bytes gehen. Sauberste Wahl: über die mbox-normalisierte
Fassung (also das, was tatsächlich im Archiv steht) — dann liefern
Migration und Reindex/Rebuild dauerhaft denselben Wert, auch nach jedem
künftigen Rebuild.
→ In
07-migrate.goden Hash nach der mbox-Normalisierung bilden, nicht über die IMAP-Rohbytes. - Einmalige
copied-Migration (zwingend zusammen mit 1., nicht danach): Für jedes Konto/Ordnercopiedausmbox_indexneu ableiten (message_idaus dem Index,mbox_done=1),target_doneaus dem bestehenden Stand übernehmen (die Mails sind im Ziel — nicht auf 0 zurücksetzen!). Anschließend die alten IMAP-Hash-Zeilen entfernen. In einer Transaktion, mit Zählung vorher/nachher. - Abnahme:
copied==mbox_index(58.051 == 58.051), und der erste Watch-/Migrationslauf danach meldetarchived=0 target=0 pending=0— also keine einzige Neukopie. Meldet er mehr, sofort stoppen: dann matcht der Schlüssel nicht und es entstehen gerade Dubletten.
Alternativ (wenn 1.+2. zu heikel erscheinen)
Zustand so lassen. Er ist funktional korrekt: Der Migrationspfad matcht auf
die IMAP-Hash-Zeilen, es wird nichts doppelt kopiert (live bestätigt:
complete=0 archived=0 target=0 pending=0 errors=0). Kosten: copied ist um
~7 % aufgebläht und die Zahl taugt nicht als Beleg für „so viel ist archiviert".
Dann aber in INSTALL.md dokumentieren, warum die Zahlen auseinandergehen —
sonst rätselt in zwei Jahren jemand daran.
Hängt zusammen mit
Der noch offenen body_sha256-Bereinigung (byte-verschiedene Mails mit gleicher
Message-ID, ~109 Nachzügler): Beide drehen sich um „was ist die Identität einer
Mail". Am besten in einem Zug lösen und eine Schlüsselregel festlegen:
- Message-ID vorhanden →
message_id+body_sha256(fängt die byte-verschiedenen Fälle), - keine Message-ID → nur
body_sha256, body_sha256immer über die mbox-normalisierte Fassung.