Foundation unit for the cache/API design (#36).
Depends on the download-plumbing unit (the fsync atomic writer). This is a
precondition for precaching originals and for the GUI viewer, per the design.
Goal
Stop buffering a whole decrypted file in RAM. Stream the decrypted plaintext
straight to the temp file, chunk by chunk, then rename it into place. A
multi-gigabyte video must not need ~2× its size in memory, and five concurrent
originals must not mean five whole files resident.
Current state (grounded, src/download/index.ts)
streamDecrypt accumulates every plaintext chunk in plainChunks[] and
returns one Uint8Array; downloadFile/downloadThumbnail then hand that
whole buffer to the atomic writer. RAM cost scales with file size (this is #21).
The TAG_FINAL truncation guarantee must be preserved: a body cut short must
never leave a complete-looking file on disk.
Scope
Add a streaming decrypt-to-temp path: pull each secretstream chunk and write
its plaintext to the temp file as it is produced (reuse the exported fsync
atomic writer's temp-then-rename discipline). The rename happens only after
the stream authenticates as terminated on TAG_FINAL; a truncated stream
deletes the temp file and throws exactly as today.
Preserve retry semantics: a retry restarts from byte zero (secretstream pull
state is not resumable) and must truncate/replace the temp file cleanly.
Keep the per-chunk progress hook working through the streaming path.
Memory during a download must be bounded by the chunk size, not the file size.
A whole-buffer helper may remain for small payloads (thumbnails, metadata) if
it keeps the code simpler, but originals must stream.
Definition of done
Originals decrypt-and-write with memory bounded by chunk size (tested with a
multi-chunk fixture; assert the temp file grows and the buffer is not retained
— e.g. via a spy on writes, or a large synthetic stream under a heap check if
practical).
Truncation still rejected and leaves no destination file; TAG_FINAL check
intact; retry restarts cleanly. All covered by tests.
Files: src/download/index.ts, src/crypto/stream.ts (if the pull API needs a
streaming accessor), test/download/download.test.ts, test/crypto/stream.test.ts.
Subsumes #21.
Dispatch notes: TDD; no scripted edits; no interactive questions. Squash subject
ends (closes #<this issue>). End every message with Model: opus-4-8.
Model: opus-4-8
Foundation unit for the cache/API design (https://git.eeqj.de/sneak/quak/issues/36).
Depends on the download-plumbing unit (the fsync atomic writer). This is a
precondition for precaching originals and for the GUI viewer, per the design.
## Goal
Stop buffering a whole decrypted file in RAM. Stream the decrypted plaintext
straight to the temp file, chunk by chunk, then rename it into place. A
multi-gigabyte video must not need ~2× its size in memory, and five concurrent
originals must not mean five whole files resident.
## Current state (grounded, `src/download/index.ts`)
- `streamDecrypt` accumulates every plaintext chunk in `plainChunks[]` and
returns one `Uint8Array`; `downloadFile`/`downloadThumbnail` then hand that
whole buffer to the atomic writer. RAM cost scales with file size (this is
https://git.eeqj.de/sneak/quak/issues/21).
- The `TAG_FINAL` truncation guarantee must be preserved: a body cut short must
never leave a complete-looking file on disk.
## Scope
- Add a streaming decrypt-to-temp path: pull each secretstream chunk and write
its plaintext to the temp file as it is produced (reuse the exported fsync
atomic writer's temp-then-rename discipline). The rename happens only after
the stream authenticates as terminated on `TAG_FINAL`; a truncated stream
deletes the temp file and throws exactly as today.
- Preserve retry semantics: a retry restarts from byte zero (secretstream pull
state is not resumable) and must truncate/replace the temp file cleanly.
- Keep the per-chunk progress hook working through the streaming path.
- Memory during a download must be bounded by the chunk size, not the file size.
A whole-buffer helper may remain for small payloads (thumbnails, metadata) if
it keeps the code simpler, but originals must stream.
## Definition of done
- Originals decrypt-and-write with memory bounded by chunk size (tested with a
multi-chunk fixture; assert the temp file grows and the buffer is not retained
— e.g. via a spy on writes, or a large synthetic stream under a heap check if
practical).
- Truncation still rejected and leaves no destination file; `TAG_FINAL` check
intact; retry restarts cleanly. All covered by tests.
- `make check` green. Closes https://git.eeqj.de/sneak/quak/issues/21.
## Grounding
Files: `src/download/index.ts`, `src/crypto/stream.ts` (if the pull API needs a
streaming accessor), `test/download/download.test.ts`, `test/crypto/stream.test.ts`.
Subsumes https://git.eeqj.de/sneak/quak/issues/21.
Dispatch notes: TDD; no scripted edits; no interactive questions. Squash subject
ends ` (closes #<this issue>)`. End every message with `Model: opus-4-8`.
Model: opus-4-8
Decrypted downloads now stream straight to disk: each secretstream chunk is written to the staged temp file as it decrypts and dropped, and the file is renamed into place only after the stream authenticates on TAG_FINAL. Peak memory is one 4 MiB chunk regardless of file size. Truncation is still rejected with no destination file left behind, and a retry restarts from byte zero into a fresh temp file. Also closes #21.
Model: opus-4-8
Implemented in https://git.eeqj.de/sneak/quak/pulls/59 (base `next`, needs-review).
Decrypted downloads now stream straight to disk: each secretstream chunk is written to the staged temp file as it decrypts and dropped, and the file is renamed into place only after the stream authenticates on `TAG_FINAL`. Peak memory is one 4 MiB chunk regardless of file size. Truncation is still rejected with no destination file left behind, and a retry restarts from byte zero into a fresh temp file. Also closes https://git.eeqj.de/sneak/quak/issues/21.
Model: opus-4-8
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Foundation unit for the cache/API design (#36).
Depends on the download-plumbing unit (the fsync atomic writer). This is a
precondition for precaching originals and for the GUI viewer, per the design.
Goal
Stop buffering a whole decrypted file in RAM. Stream the decrypted plaintext
straight to the temp file, chunk by chunk, then rename it into place. A
multi-gigabyte video must not need ~2× its size in memory, and five concurrent
originals must not mean five whole files resident.
Current state (grounded,
src/download/index.ts)streamDecryptaccumulates every plaintext chunk inplainChunks[]andreturns one
Uint8Array;downloadFile/downloadThumbnailthen hand thatwhole buffer to the atomic writer. RAM cost scales with file size (this is
#21).
TAG_FINALtruncation guarantee must be preserved: a body cut short mustnever leave a complete-looking file on disk.
Scope
its plaintext to the temp file as it is produced (reuse the exported fsync
atomic writer's temp-then-rename discipline). The rename happens only after
the stream authenticates as terminated on
TAG_FINAL; a truncated streamdeletes the temp file and throws exactly as today.
state is not resumable) and must truncate/replace the temp file cleanly.
A whole-buffer helper may remain for small payloads (thumbnails, metadata) if
it keeps the code simpler, but originals must stream.
Definition of done
multi-chunk fixture; assert the temp file grows and the buffer is not retained
— e.g. via a spy on writes, or a large synthetic stream under a heap check if
practical).
TAG_FINALcheckintact; retry restarts cleanly. All covered by tests.
make checkgreen. Closes #21.Grounding
Files:
src/download/index.ts,src/crypto/stream.ts(if the pull API needs astreaming accessor),
test/download/download.test.ts,test/crypto/stream.test.ts.Subsumes #21.
Dispatch notes: TDD; no scripted edits; no interactive questions. Squash subject
ends
(closes #<this issue>). End every message withModel: opus-4-8.Model: opus-4-8
Implemented in #59 (base
next, needs-review).Decrypted downloads now stream straight to disk: each secretstream chunk is written to the staged temp file as it decrypts and dropped, and the file is renamed into place only after the stream authenticates on
TAG_FINAL. Peak memory is one 4 MiB chunk regardless of file size. Truncation is still rejected with no destination file left behind, and a retry restarts from byte zero into a fresh temp file. Also closes #21.Model: opus-4-8