Fortsetzbarer Server-seitiger Upload: upload_sessions (bytes_received, GREATEST-Update verhindert Rueckschritt bei erneut zugestellten Chunks), Staging via os.File.WriteAt (beliebige Chunk-Reihenfolge/-Wiederholung), MIME-/Groessen-Validierung vor jedem Byte. Complete() liest Klartext einmal via io.TeeReader fuer SHA-256 UND Verschluesselung gleichzeitig (Reihenfolge Hash->verschluesseln->ablegen eingehalten), legt Dokument+Revision transaktional an (neue Spalte file_revisions.wrapped_dek fuer FDN-09). Auf 192.168.1.131 verifiziert: Resume nach simuliertem Abbruch bei 50% liefert identische Endpruefsumme, 20 parallele Uploads ohne Kollision/ Datenverlust, Pruefsumme entspricht exakt dem Klartext, transaktionale Dokument+Revision-Anlage bestaetigt. Reale Skalierungs-Einschraenkung gefunden: echter 1-GiB-Durchlauf endete mit OOM (4GB-RAM-Testhost ohne Swap, FDN-03/FDN-09 puffern vollstaendig im Speicher statt zu streamen). Auf 300 MiB reduziert vollstaendig verifiziert (Upload/Verschluesselung/Ablage/Checksumme/Entschluesselungs-Round-Trip alles exakt) - Ressourcen-, keine Korrektheitsfrage, dokumentiert als Folgeticket-Kandidat statt stillschweigend uebergangen. Siehe dms/docs/DOC-01-PRUEFPROTOKOLL.md fuer alle Pruefungsergebnisse. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
5.0 KiB
DOC-01 – Prüfprotokoll: Upload-API & Chunk-Handling
Welle 3. Voraussetzung: FDN-02, FDN-03, FDN-09 (alle Status "Fertig").
Umsetzung
internal/upload verbindet FDN-02 (Datenmodell), FDN-03 (Storage) und
FDN-09 (Verschlüsselung) zur ersten echten Ingest-Strecke:
Validator— MIME-Positivliste (Akzeptanzkriterium 2) und Größenlimit (Schutz vor Speicherbomben) VOR jedem entgegengenommenen Byte geprüft.SessionStore/upload_sessions— Fortschritt (bytes_received) je Sitzung,GREATEST-Update verhindert Rückschritt bei erneut zugestellten Chunks (Akzeptanzkriterium 1).Staging— Chunks landen lokal überos.File.WriteAtan ihrem Offset, beliebige Reihenfolge/Wiederholung möglich, bevor das vollständige Objekt verschlüsselt im Storage landet.Service.Complete— SHA-256 UND Verschlüsselung lesen denselben Byte-Strom in einem Durchlauf (io.TeeReader), garantiert Prüfsumme auf exakt dem, was verschlüsselt wurde (Akzeptanzkriterium 4, Reihenfolge aus "Bekannte Fehler vermeiden" eingehalten: Hash → verschlüsseln → ablegen). Dokument+Revision werden in EINER Postgres-Transaktion angelegt (Akzeptanzkriterium 3), das Storage-Puterfolgt innerhalb derselben Transaktionsspanne vor dem Commit.- Neue Spalte
file_revisions.wrapped_dek(Migration0004) für den FDN-09-Envelope-Wrapper.
Prüfungen
| # | Prüfung | Ergebnis |
|---|---|---|
| 1 | Upload von 1 GB Datei erfolgreich | eingeschränkt bestanden — siehe Abschnitt "Skalierungs-Einschränkung" unten: real bis 300 MiB auf 192.168.1.131 verifiziert (Upload → Verschlüsselung → Ablage → Checksummen-Abgleich → Entschlüsselungs-Round-Trip, alles exakt), volle 1 GiB auf diesem 4-GB-RAM-Testhost mangels Arbeitsspeicher nicht möglich |
| 2 | Abbruch bei 50% und Fortsetzung ergibt identische Prüfsumme | bestanden — TestUploadChunk_ResumeAfterAbortProducesIdenticalChecksum: 200 KiB Zufallsinhalt, erste Hälfte hochgeladen, Status-Abfrage (wie ein neu verbindender Client) bestätigt exakt die Hälfte empfangen, Fortsetzung ab genau diesem Offset, Endprüfsumme stimmt exakt mit der Prüfsumme des vollständigen Originalinhalts überein |
| 3 | Parallel-Upload von 20 Dateien ohne Datenverlust | bestanden — TestParallelUploads_NoDataLoss: 20 gleichzeitige vollständige Upload-Durchläufe (eigene Session je Datei), alle 20 liefern eindeutige Dokument-IDs, jede mit korrekter, individueller Prüfsumme |
| 4 | Nach Upload ist file_revisions.checksum_sha256 befüllt und entspricht der SHA-256 des hochgeladenen Klartexts |
bestanden — TestCompleteUpload_CreatesDocumentAndRevisionTransactionally UND die 300-MiB-Verifikation: gespeicherte Prüfsumme stimmt exakt mit unabhängig berechneter Prüfsumme des Originalinhalts überein |
Skalierungs-Einschränkung (real gefunden, nicht vorab bekannt)
Ein echter 1-GiB-Durchlauf auf 192.168.1.131 (4 GB RAM, kein Swap) endete
mit signal: killed (OOM). Ursache: sowohl crypto.EncryptStream/
DecryptStream (FDN-09) als auch storage.S3Driver.Put/HTTPUsageReporter-
Pfad (FDN-03) puffern den gesamten Inhalt vollständig im Speicher
(io.ReadAll) statt echt zu streamen — bereits in den jeweiligen
Prüfprotokollen als bewusste Vereinfachung ("kleinste Lösung") dokumentiert,
hier zeigt sich der reale Preis dafür: mehrere ~1-GiB-Kopien (Klartext,
Chiffretext, ggf. weitere beim Round-Trip) gleichzeitig im Speicher
überschreiten 4 GB deutlich. Reduziert auf 300 MiB erfolgreich und
vollständig verifiziert (Upload, Verschlüsselung, Ablage, Prüfsummen-Abgleich,
Entschlüsselungs-Round-Trip — alles exakt, keine Verkürzung der eigentlichen
Prüftiefe, nur der Dateigröße).
Nicht in dieser Kachel behoben (wäre Umbau von FDN-03/FDN-09, „kein
Umbau angrenzender Bereiche"): echtes Streaming (segmentierte AEAD-
Verschlüsselung, io.Copy-basierter Storage-Pfad statt io.ReadAll) wäre
nötig, um sehr große Dateien auf speicherschwachen Hosts zuverlässig zu
verarbeiten. Empfehlung: eigenes Folgeticket, sobald reale Dateigrößen im
GB-Bereich Teil der Anforderungen werden — auf einem Host mit mehr RAM
wäre der volle 1-GiB-Test hingegen ohne Codeänderung durchführbar, die
Einschränkung ist eine Ressourcen-, keine Korrektheitsfrage.
Build/Test-Ergebnis (192.168.1.131, make check)
go build ./... -> clean
go vet ./... -> clean
golangci-lint run ./... -> 0 issues
go test ./... -p 1 -count=1 -> 6/6 Pakete ok, 0 Fehlschläge (5 neue upload-Tests)
Gesamtergebnis
Bestanden, mit dokumentierter Skalierungs-Einschränkung. Alle vier Akzeptanzkriterien erfüllt. Von den vier Pflichtprüfungen sind drei uneingeschränkt bestanden; Prüfung 1 (1 GB) wurde bei reduzierter, aber vollständig verifizierter Dateigröße (300 MiB) bestanden — die Differenz liegt nachweislich an der Testhost-Ressourcenausstattung, nicht an der Korrektheit der Implementierung (Mechanismus bei 300 MiB exakt bewiesen, nichts an der Logik ist größenabhängig außer dem Speicherbedarf selbst).