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 to main as 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.gobase64StringReader exposes 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.goboundedWriter decouples the CPU-cheap producer (base64 decode + hash) from the slower consumer (gzip + tar) with an optional bufio buffer; a zero size reproduces the unbuffered pipe behavior (used by the backpressure regression test in a later part).
  • sink.gomultiHash computes SHA-256 / SHA-1 / SHA-512 in one pass and tracks the byte count; sizeLimitWriter enforces 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 issues
  • GOEXPERIMENT=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.gosizeLimitWriter.Write now returns the bytes actually written on the cap-exceeded path (the io.Writer contract) instead of n=0, so a wrapping writer's accounting stays accurate. (A separate int64int narrowing in buffer.go was raised; it only affects 32-bit builds, which this 64-bit-only service does not target, and the suggested > math.MaxInt guard is an always-false comparison that staticcheck rejects on 64-bit — so buffer.go stays verbatim.)

One test-only deviation (efc7566):

  • Added primitive error-path coverage in primitives_test.go: a failing io.Writer drives the boundedWriter (unbuffered/buffered/flush) and sizeLimitWriter (under-cap, room≤0, and portion-that-fits) error returns, plus base64StringReader's buffer-fill and idempotent-EOF paths. base64reader.go, buffer.go, and sink.go reach 100% except multiHash's write-error branch, which is structurally unreachable (io.MultiWriter over hash.Hash never errors). The reference leaves these branches cold (it covers the primitives only through MR6 integration); this is extra coverage, not a functional change. Because primitives_test.go is removed in Part 6/8, it has no effect on the final merged tree.
Edited by David Fernandez

Merge request reports

Loading
Loading