# 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 **Befund:** Bei mehreren Konten ist `copied < total` **ohne** Fehler (neubauer 1, simsek 5, palamari 8, gb u. a. โ€” zusammen ~25 Mails). 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.