Der in rettungslauf-fixes.md beschriebene Befund "~109 byte-verschiedene Mails mit gleicher Message-ID" war eine HYPOTHESE, die faelschlich als Befund formuliert wurde. Messung nach Einbau von body_sha256 (petarus/INBOX.Sent): 518 Quell-Mails, davon nur 463 eindeutige Identitaeten - exakt der Archivbestand. Die 55 "fehlenden" sind bitgenaue Duplikate an der Quelle (eine Identitaet 10x, zwei je 6x, fuenf je 4x - Re-Import-Signatur). Es fehlte nie etwas. Der body_sha256-Schluessel war dennoch richtig: er ist der Beweis der Bitgleichheit. Waeren die Mails verschieden gewesen, waeren sie beim kontrollierten Nachlauf archiviert worden. Lehre im Brief ergaenzt: Eine Zaehlung taeuscht in beide Richtungen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
154 lines
6.7 KiB
Markdown
154 lines
6.7 KiB
Markdown
# 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 <id>: 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.
|