Ursache: Session-Writes (writeFile + atomic rename) liefen ohne fsync. Waehrend eines
Downloads feuert persistSoon alle 700ms-3s, die Session-Datei bleibt damit dauerhaft
dirty im OS-Cache. Bei hartem Stromausfall auf NTFS ist die rename-Metadatentransaktion
journaled (durable), aber die Datenbloecke der temp-Datei sind nicht geflusht -> primary
zeigt nach Reboot auf Null/Garbage. Die .bak-Kopie stammt per copyFileSync aus einer
ebenfalls ungeflushten primary -> ebenfalls korrupt. loadSession faellt durch
primary -> bak -> temp auf emptySession() durch, und der naechste persistSoon speichert
diese leere Session ueber die Platte -> dauerhaft leer. Tritt nur bei UNSAUBEREM Neustart
auf (sauberes Beenden flusht ohnehin).
Fix in drei Schichten:
- Durable atomic write: temp wird vor dem rename gefsynct. Reihenfolge zwingend
write -> fsync -> close -> rename (NTFS kann eine Datei mit offenem Handle nicht renamen).
Sync-Pfad via openSync/writeSync/fsyncSync/closeSync, Async-Pfad via FileHandle.sync()
(laeuft auf dem libuv-Threadpool, blockiert den Hot-Path nicht). Kein Throttle: ein
throttle-skip wuerde eine ungeflushte temp ueber die durable primary renamen und das
Korruptionsfenster wieder oeffnen.
- Read-Retry: readSessionFile wiederholt bei transienten Sperren (EBUSY/EPERM/EAGAIN, z.B.
Virenscanner/Disk-not-ready beim Boot) 5x mit Backoff. EACCES und JSON-Parse-Fehler
werden nicht wiederholt.
- Empty-Clobber-Guard: loadSessionWithStatus meldet, ob alle Tiers unlesbar waren (Status
empty-unreadable). In dem Fall blockiert der DownloadManager das Speichern einer leeren
Session ueber vorhandene Daten, bis wieder echte Daten vorliegen; die erste nicht-leere
Speicherung hebt den Schutz auf.
Tests: tests/session-restart-loss.test.ts um Status-Klassifizierung, fsync-Nachweis,
async-Roundtrip (close-before-rename), EBUSY-Retry und Guard-Clear-Pfad erweitert.
Suite 929 gruen, tsc unveraendert bei 6 Baseline-Fehlern.