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>
84 lines
4.2 KiB
Markdown
84 lines
4.2 KiB
Markdown
# 🟠 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)
|
|
|
|
1. **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.go` den Hash **nach** der mbox-Normalisierung bilden, nicht
|
|
über die IMAP-Rohbytes.
|
|
2. **Einmalige `copied`-Migration** (zwingend zusammen mit 1., nicht danach):
|
|
Für jedes Konto/Ordner `copied` aus `mbox_index` neu ableiten
|
|
(`message_id` aus dem Index, `mbox_done=1`), `target_done` aus 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.
|
|
3. **Abnahme:** `copied` == `mbox_index` (58.051 == 58.051), und der erste
|
|
Watch-/Migrationslauf danach meldet **`archived=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_sha256` immer über die mbox-normalisierte Fassung.**
|