# Codex-Brief — Fixes aus dem echten Rettungslauf (14.07., 15 GB / 58k Mails) Der Lauf gegen die 18 echten `@dr-gold.de`-Postfaecher hat vier Schwaechen freigelegt, die im Testbetrieb nie sichtbar waren. **Fix 1 und 2 sind Pflicht, bevor wir den Nachschlag fahren** — sonst reisst es an derselben Stelle wieder. Kontext: Der Quell-Hoster verliert derzeit Mails. Das lokale mbox-Archiv ist das Sicherheitsnetz. **Alles, was das Archiv gefaehrdet, ist ein Notfall.** --- ## 1. 🔴 `busy_timeout = 0` — SQLite scheitert sofort statt zu warten **Befund:** `PRAGMA busy_timeout` steht auf **0**. Bei Lock-Konflikten (Watcher + Web-App + Migration auf derselben DB) bricht die Abfrage **sofort** ab: ``` mbox index error : database is locked (5) (SQLITE_BUSY) ``` Sechsmal im Rettungslauf passiert. **Fix:** In `ConnectDB` (02-database.go) neben `journal_mode=WAL` setzen: ```sql PRAGMA busy_timeout = 10000; -- 10s warten statt sofort scheitern ``` Einzeiler, grosse Wirkung. --- ## 2. 🔴 Das Archiv haengt am Ziel — und das ist verkehrt herum **Der schwerste Befund.** Heutige Reihenfolge in `migrateAccount`: ``` AlreadyCopied -> dst.Append (All-Inkl) -> mbox.Append (lokal) -> MarkCopied ^^^^^^^^^^ scheitert das hier, wird per `return nil` abgebrochen -> die Mail landet AUCH NICHT im lokalen Archiv ``` **Real passiert bei `petarus`:** Die Ziel-Verbindung starb mitten im Lauf (`use of closed network connection`). Danach schlug jedes `EnsureFolder`/`Append` fehl → **~620 von 1135 Mails wurden weder ins Ziel noch ins Archiv geschrieben.** Das Sicherheitsnetz hat genau dann gerissen, als es gebraucht wurde. **Das Archiv muss zuerst kommen und vom Ziel unabhaengig sein.** Dafuer braucht `copied` getrennte Zustaende — sonst erzeugt ein Retry mbox-Dubletten: ```sql ALTER TABLE copied ADD COLUMN mbox_done INTEGER NOT NULL DEFAULT 1; ALTER TABLE copied ADD COLUMN target_done INTEGER NOT NULL DEFAULT 1; -- Default 1: Bestandszeilen sind beides-fertig, das stimmt. ``` **Neuer Ablauf:** ``` 1. Zeile vorhanden und mbox_done=1 und target_done=1 -> ueberspringen 2. mbox.Append (ZUERST — das Archiv ist das Sicherheitsnetz) scheitert -> Fehler zaehlen, Mail ueberspringen, nichts markieren 3. Zeile schreiben/aktualisieren: mbox_done=1, target_done=0 4. dst.Append (Ziel) OK -> target_done=1 scheitert -> Fehler zaehlen, WEITERMACHEN. Die Mail IST archiviert. ``` **Was das loest:** - Ein sterbendes Ziel kostet nie wieder eine Archiv-Kopie. - Ein zweiter Lauf sieht `mbox_done=1, target_done=0` → **ueberspringt den mbox-Append** (keine Dublette!) und **holt nur das Ziel nach**. `MarkCopied` bleibt sinngemaess das Letzte je Stufe. Ein Absturz zwischen mbox-Write und Zeilen-Update kopiert die Mail beim naechsten Lauf erneut ins Archiv — dieselbe bewusste Entscheidung wie bisher, nur jetzt pro Stufe. --- ## 3. 🟠 Kein Reconnect zum Ziel Bei einem 2-GB-Postfach haelt All-Inkl die IMAP-Sitzung nicht durch. Stirbt sie, scheitert **jede** weitere Operation — der Rest des Kontos faellt weg. **Fix:** Verbindungsfehler erkennen (`use of closed network connection`, EOF, `broken pipe`) und **die Ziel-Verbindung neu aufbauen** (1–2 Versuche mit kurzer Pause). Gelingt das nicht: - **nicht abbrechen**, sondern fuer den Rest des Kontos im **Archiv-Only-Modus** weiterlaufen (dank Fix 2 ist das gefahrlos — die Mails landen sicher im mbox, `target_done=0`), und - am Ende **laut melden**: `ZIEL NICHT ERREICHBAR — N Mails nur archiviert, Nachlauf noetig`. Lieber ein vollstaendiges Archiv mit offenem Ziel-Nachlauf als beides halb. --- ## 4. 🟠 Gleiche Message-ID im selben Ordner → Mail wird still verworfen > **NACHTRAG 16.07. — dieser Befund war eine HYPOTHESE, keine Messung.** > Nach dem Einbau von `body_sha256` und einem kontrollierten Nachlauf steht fest: > Die vermuteten „~109 byte-verschiedenen Mails" **existieren nicht**. Messung an > `petarus/INBOX.Sent`: 518 Quell-Mails, aber nur **463 eindeutige Identitäten** > (`message_id` + kanonischer `body_sha256`) — exakt der Archivbestand. Die 55 > „fehlenden" sind **bitgenaue Duplikate an der Quelle** (eine Identität liegt > 10×, zwei je 6×, fünf je 4× vor — Re-Import-Signatur). Das Archiv hat korrekt > zusammengefasst; es fehlte nie etwas. > > **Der Fix war trotzdem richtig:** `body_sha256` ist der *Beweis* der > Bitgleichheit. Wären die Mails verschieden gewesen, wären sie beim Nachlauf > archiviert worden. Erst der Schlüssel machte die Aussage überhaupt prüfbar. > > **Lehre:** Eine Zählung täuscht in beide Richtungen — gleiche Anzahl kann > verschiedene Mails verbergen, und eine Abweichung bedeutet nicht automatisch > fehlende Mail. Belastbar ist nur der Identitäts-Vergleich. **Ursprünglicher (vermuteter) Befund:** Bei mehreren Konten ist `copied < total` **ohne** Fehler (neubauer 1, simsek 5, palamari 8, gb u. a. — zusammen ~25 Mails). Vermutete Ursache: Die gleiche Message-ID kommt im selben Ordner zweimal vor (Re-Import, kaputte Mailer), und der Dedup wirft die zweite weg. Fuer ein **Beweis-Archiv ist das nicht hinnehmbar**: Zwei Mails mit derselben Message-ID koennen **unterschiedlichen Inhalt** haben. Wir duerfen nie still eine byte-verschiedene Mail verwerfen. **Fix:** `copied` um `body_sha256` erweitern, Unique-Key auf `(account_id, folder, message_id, body_sha256)`. - Gleiche ID **und** gleiche Bytes → echte Dublette, ueberspringen (wie bisher). - Gleiche ID, **andere** Bytes → **andere Mail**, archivieren. - Bestandszeilen: `body_sha256` leer lassen und beim Vergleich als Wildcard behandeln (kein Reindex-Zwang, keine Re-Kopie des Bestands). --- ## 5. 🟢 Phantom-Ordner tolerieren `LIST` meldet `INBOX.Posteingang`, `SELECT` sagt `NO Mailbox doesn't exist`. Kaputte Quelle (passt zum Hoster-Problem). Heute: Fehler. Kuenftig: **Warnung**, Ordner ueberspringen, Lauf sauber weiterfuehren — das ist kein Fehler unseres Tools. --- ## Abnahme 1. `go test ./...` gruen. 2. `PRAGMA busy_timeout` → 10000. 3. **Der entscheidende Test — Ziel-Ausfall simulieren:** Migration eines Testkontos starten, waehrend des Laufs das Ziel unerreichbar machen (z. B. falscher `dst_host`/Port). Erwartung: - **Alle** Quell-Mails landen trotzdem **vollstaendig im lokalen mbox**. - `target_done=0` fuer diese Mails. - Lauf meldet laut „Ziel nicht erreichbar, Nachlauf noetig". - **Zweiter Lauf mit erreichbarem Ziel:** kopiert **nichts** ins mbox (keine Dublette!) und holt **nur** das Ziel nach. Das ist die Regression, die uns bei `petarus` ~620 Mails gekostet haette. 4. Nachschlag `petarus` (`--folders` ordnerweise) → alle 1135 Mails im Archiv. Ich (Claude) fahre danach den Verifikationslauf: Quell-Anzahl vs. Archiv-Anzahl je Konto, und ziehe alle Luecken nach.