feat(npm): streampub attachment stream primitives (part 5/8)
📦 What this MR does
Part 5/8 of the stack splitting the streampub streaming publish-envelope library (S11 Step 13). See Part 1/8 (!432) for the stack overview and the byte-identity contract against the reference, !420 (closed).
- Targets
10io/npm-local-step-13-part4of8(Part 4/8). GitLab retargets tomainas the parent merges. - Depends on Part 4/8 (set as a merge-request dependency).
This part adds the standalone leaf components the streaming attachment hand-off composes, ahead of wiring them into the walk (Part 6/8):
base64reader.go—base64StringReaderexposes a JSON string body as a byte stream, stopping at the unescaped closing quote and rejecting a backslash (the base64 payload never legitimately escapes).buffer.go—boundedWriterdecouples the CPU-cheap producer (base64 decode + hash) from the slower consumer (gzip + tar) with an optionalbufiobuffer; a zero size reproduces the unbuffered pipe behavior (used by the backpressure regression test in a later part).sink.go—multiHashcomputes SHA-256 / SHA-1 / SHA-512 in one pass and tracks the byte count;sizeLimitWriterenforces the tarball-size cap, writing what fits under the cap before failing.
These use only local sentinels (errBase64Escape, errTarballTooLarge) and the stdlib, so error.go is untouched here — the producer that maps errTarballTooLarge to a spec code lands with the pipeline in Part 6/8.
🧪 Tests
primitives_test.go is throwaway scaffolding (package streampub, internal). The components have no production caller until the pipeline wires them, so these unit tests exercise them directly to satisfy the unused linter. Part 6/8 wires them into Process, covers them black-box (happy-path digests, tarball-size cap), and deletes this file — the file header says so explicitly.
golangci-lint(repo-pinned v2.12) → 0 issuesGOEXPERIMENT=jsonv2 go test -race -count=1 ./internal/format/npm/streampub/...→ pass
🚩 Deviations from the reference (!420 (closed))
base64reader.go and buffer.go are verbatim from the reference. The throwaway primitives_test.go is not in the reference (the reference covers these primitives black-box via Process); it is removed in Part 6/8.
One production deviation from review:
sink.go—sizeLimitWriter.Writenow returns the bytes actually written on the cap-exceeded path (theio.Writercontract) instead ofn=0, so a wrapping writer's accounting stays accurate. (A separateint64→intnarrowing inbuffer.gowas raised; it only affects 32-bit builds, which this 64-bit-only service does not target, and the suggested> math.MaxIntguard is an always-false comparison thatstaticcheckrejects on 64-bit — sobuffer.gostays verbatim.)
One test-only deviation (efc7566):
- Added primitive error-path coverage in
primitives_test.go: a failingio.Writerdrives theboundedWriter(unbuffered/buffered/flush) andsizeLimitWriter(under-cap, room≤0, and portion-that-fits) error returns, plusbase64StringReader's buffer-fill and idempotent-EOF paths.base64reader.go,buffer.go, andsink.goreach 100% exceptmultiHash's write-error branch, which is structurally unreachable (io.MultiWriteroverhash.Hashnever errors). The reference leaves these branches cold (it covers the primitives only through MR6 integration); this is extra coverage, not a functional change. Becauseprimitives_test.gois removed in Part 6/8, it has no effect on the final merged tree.
🔗 References
- Reference MR (read-only): !420 (closed)
- Related work item: #131 (closed) (not closed by this MR)