Snapshot (14-snapshot.go) korrekt: VACUUM INTO vor der Wasserlinie, N aus dem Schnappschuss, MAX(file_offset+frame_len), kein indexed_bytes, stat nur als Notbremse. Datensatz-Kreuzpruefung (Zeile 145) war nicht verlangt und ist gut. Reindex-Erhaltung sauber: Position ist Schluessel, Body-Hash ist Bedingung. Real geprueft: 228 Dateien, mbox_index 121.861 = Produktion, integrity ok. BEFUND 1 (Blocker): normalizeMessageID aendert die Identitaet fuer Werte mit verirrtem '>'. Gemessen: 3 Zeilen copied, 2 mbox_index. Die Reindex-Erhaltung schuetzt den Index, nicht den Live-Pfad - LegacyMessageID ist ebenfalls neu-normalisiert. Erster Nachtlauf nach Rollout erzeugt 3 lautlose Dubletten. BEFUND 2: Snapshot-DB ist 0644 und enthaelt 46 Klartext-Passwoerter; die mbox-Dateien daneben sind 0600. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
139 lines
5.7 KiB
Markdown
139 lines
5.7 KiB
Markdown
# Review — Snapshot (14-snapshot.go) + Streaming-Reindex
|
|
|
|
Geprueft von Claude am 2026-07-16 gegen den nicht-committeten Arbeitsstand.
|
|
|
|
## Urteil
|
|
|
|
**Der Snapshot ist richtig gebaut und darf so committet werden.**
|
|
**Die Aenderung an `normalizeMessageID` darf NICHT ohne Fix ausgerollt werden.**
|
|
|
|
## Was gepasst hat
|
|
|
|
Die zwei Stellen, an denen ich Fehler erwartet habe, sind beide korrekt:
|
|
|
|
```go
|
|
104: DB.Exec(`VACUUM INTO ?`, databasePath) // erst der Schnappschuss
|
|
120: collectSnapshotWatermarks(snapshotDB, Cfg.MboxRoot) // dann N DARAUS
|
|
MAX(i.file_offset+i.frame_len) // richtige Wasserlinie
|
|
327: os.Stat(...) -> info.Size() < watermark.Bytes // stat nur als Notbremse
|
|
```
|
|
|
|
- `indexed_bytes` kommt nirgends vor (die Falle aus backup-brief.md).
|
|
- Reihenfolge stimmt; `stat` leitet N nicht ab, sondern verweigert nur.
|
|
- Zeile 145 bricht ab, wenn indizierte und abgebildete Datensaetze auseinander-
|
|
laufen. **Das stand nicht im Brief und ist besser als verlangt.**
|
|
- Pfad-Ausbruchspruefung (`relative == ".."`) vorhanden.
|
|
- `snapshotPathKey` senkt nur unter Windows auf Kleinschreibung — richtig herum:
|
|
auf Linux sind `Drafts` und `drafts` zwei echte Dateien und duerfen nicht
|
|
kollidieren, unter Windows wuerden sie es.
|
|
- Streaming-Reindex (`06-mbox.go:293`) liest nachrichtenweise ueber `bufio`
|
|
statt 9,4 GB in den RAM.
|
|
|
|
Die Message-ID-Erhaltung im Reindex ist entgegen meinem ersten Verdacht sauber:
|
|
|
|
```go
|
|
if storedIdentity.bodySHA256 != "" && storedIdentity.bodySHA256 == entry.BodySHA256 {
|
|
entry.MessageID = storedIdentity.messageID
|
|
}
|
|
```
|
|
Die Position ist nur der Nachschlage-Schluessel, der **Body-Hash ist die
|
|
Bedingung**. Eine falsche Zuordnung kann keine falsche Uebernahme ausloesen.
|
|
|
|
Nachgeprueft am realen Snapshot `archive-20260716-2250`:
|
|
228 Archivdateien, `mbox_index` 121.861 im Snapshot **und** in Produktion,
|
|
121.861 praezise `copied`-Identitaeten, `integrity_check=ok`.
|
|
`go vet` sauber, `go test -count=1 ./backend/` gruen (0,366s).
|
|
|
|
---
|
|
|
|
## BEFUND 1 — Identitaetsaenderung, Dublettenrisiko (Blocker fuer Rollout)
|
|
|
|
```go
|
|
func normalizeMessageID(id string) string {
|
|
- return strings.Trim(strings.TrimSpace(id), "<>")
|
|
+ if start := strings.IndexByte(id, '<'); start >= 0 { ... }
|
|
```
|
|
|
|
`normalizeMessageID` speist `message_id` = die halbe Identitaet. Alt und neu
|
|
gegen die **echten gespeicherten Werte** verglichen (lauffaehig nachgestellt):
|
|
|
|
```
|
|
<476BDFDB019289BE@mail21.bluewin.ch> (added by postmaster@bluewin.ch)
|
|
alt: [476BDFDB019289BE@mail21.bluewin.ch> (added by postmaster@bluewin.ch)]
|
|
neu: [476BDFDB019289BE@mail21.bluewin.ch] <<< WEICHT AB
|
|
|
|
<22578196653698419217.BB8A0E8F48F3F290@dr-gold.de>+D271B1409C3C6028
|
|
alt: [22578196653698419217.BB8A0E8F48F3F290@dr-gold.de>+D271B1409C3C6028]
|
|
neu: [22578196653698419217.BB8A0E8F48F3F290@dr-gold.de] <<< WEICHT AB
|
|
|
|
<1280874184.3726.1662111901550.JavaMail."LOCAL SERVICE"@DESV226350> gleich
|
|
<476FE4C5.2060001@yahoo.com;Evy O'Brien> gleich
|
|
<13140-22021132765855760@strepack.de> gleich
|
|
13140-22021132765855760@strepack.de gleich
|
|
```
|
|
|
|
Nur Werte mit einem **verirrten `>` im Inneren** aendern sich. Umfang gemessen:
|
|
**3 Zeilen in `copied`, 2 in `mbox_index`** von 121.861.
|
|
|
|
**Warum die Reindex-Erhaltung nicht reicht:** Sie schuetzt den Index, nicht den
|
|
Live-Pfad. Dort gilt:
|
|
|
|
1. Praezise Abfrage `message_id=NEU AND body_sha256=X` → gespeichert ist ALT →
|
|
**kein Treffer**.
|
|
2. Die Alias-Schleife hilft nicht: `LegacyMessageID` wird in
|
|
`identityForRawMessage` ebenfalls mit der **neuen** Funktion gebildet
|
|
(`id := normalizeMessageID(...); legacyID := id`) — sie traegt die alte
|
|
Normalisierung nicht.
|
|
3. Ergebnis: `CopyState{}` → nicht kopiert → **erneutes mbox.Append UND
|
|
erneutes dst.Append**.
|
|
|
|
**Beim ersten Nachtlauf nach dem Rollout entstehen so 3 Dubletten in beiden
|
|
Haelften — lautlos.** Klein, aber es ist dieselbe Klasse wie die 4.024 Waisen,
|
|
und Dubletten sind unsere Stopp-Bedingung.
|
|
|
|
### Zwei Wege, Empfehlung (a)
|
|
|
|
**(a) Alte Normalisierung als zusaetzlichen Alias fuehren.** `identityForRawMessage`
|
|
bekommt zusaetzlich den alt-normalisierten Wert; die Alias-Schleife in
|
|
`GetCopyIdentityStateWithFolderAlias` muss ihn dann auch gegen **praezise**
|
|
Zeilen pruefen, nicht nur gegen `body_sha256=''`.
|
|
*Additiv, mutiert keine Geschichte, nutzt vorhandene Maschinerie.* Passt zur
|
|
Linie „Aliase sind Rueckrollschutz".
|
|
|
|
**(b) Einmal-Migration der 5 Zeilen** auf den neuen Wert. Simpler, aber schreibt
|
|
Geschichte um — und die Regel lautet: Aliase nicht ohne gesonderte, geprueefte
|
|
Bereinigung anfassen.
|
|
|
|
### Abnahme fuer den Fix
|
|
|
|
1. Lauf gegen ein Konto mit einer der beiden IDs: `archived=0`. Ein zweiter
|
|
Lauf danach ebenfalls `archived=0`.
|
|
2. `SELECT COUNT(*) FROM mbox_index WHERE message_id LIKE '%>%'` bleibt bei 2 —
|
|
nicht 4.
|
|
3. Test mit genau diesen zwei Roh-Headern als Fixture.
|
|
|
|
---
|
|
|
|
## BEFUND 2 — Snapshot-DB world-readable (klein, aber schnell zu beheben)
|
|
|
|
```
|
|
600 root:root .../backup/colak@dr-gold.de/INBOX.mbox <- richtig
|
|
644 root:root .../data/mail-graveyard.db <- zu offen
|
|
```
|
|
|
|
Die Snapshot-DB enthaelt **23 Quell- und 23 Ziel-Passwoerter im Klartext**.
|
|
Heute schuetzt nur die `0700`-Wurzel darueber — ein `chmod` von der Preisgabe
|
|
entfernt, und inkonsistent zu den mbox-Dateien daneben. Bitte `0600`.
|
|
|
|
---
|
|
|
|
## Nicht deins
|
|
|
|
`gofmt -l` meldet `backend/11-folders.go`. Die Datei ist im Diff unveraendert
|
|
(Altbestand), reine Ausrichtung einer Map. Kein Rollout-Hindernis.
|
|
|
|
## Weiterhin offen (unveraendert aus backup-brief.md)
|
|
|
|
- Der Snapshot liegt auf **derselben Platte** — er ist die Vorstufe, nicht das
|
|
Backup. Raeumlich getrennte, verschluesselte Kopie fehlt.
|
|
- Restore-Test gehoert **wiederholt** gefahren, nicht einmal.
|