v1.0.14
Stream job results from disk instead of buffering them. handleGetJobResult read the whole masked result file (up to 50MB) into memory with os.ReadFile before writing the response, so the download path had none of the memory ceilings that guard synchronous masking (PII_MASKER_MAX_CONCURRENT_SYNC) and asynchronous jobs (PII_MASKER_MAX_CONCURRENT_JOBS) — memory grew linearly with the number of concurrent downloads. The handler now uses os.Open + file.Stat() + http.ServeContent to stream the file without buffering it, which also brings Accept-Ranges/Range support (resumable downloads) and conditional requests for free. If the result file has disappeared — swept by the retention worker or removed out of band — the response is now 404 job_result_not_found instead of 500 result_read_failed (errors.Is(err, fs.ErrNotExist)). Content-Type is only set when the job metadata carries one, leaving ServeContent's extension sniffing in place otherwise.
Verified with two new integration tests (a completed PDF job advertises Accept-Ranges: bytes and answers Range: bytes=8-23 with 206, a matching Content-Range and a body equal to that slice of the full response; deleting the result file from disk yields 404 with job_result_not_found), plus gofmt -l (no output), go vet ./..., go build ./..., go test -count=1 ./... and go test -race -count=1 ./... all passing.