Review: Snapshot gruen, normalizeMessageID-Aenderung ist Rollout-Blocker

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>
This commit is contained in:
DonVoo 2026-07-16 23:38:33 +02:00
parent e5aacf9b3f
commit c6de214ac8

View file

@ -0,0 +1,139 @@
# 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.