test(npm): streampub fixtures, fuzz, and coherence suite (part 8/8)

📦 What this MR does

Part 8/8 — the final part of the stack splitting the streampub streaming publish-envelope library (S11 Step 13). See Part 1/8 (!432) for the stack overview.

  • Targets 10io/npm-local-step-13-part7of8 (Part 7/8). GitLab retargets to main as the parent merges.
  • Depends on Part 7/8 (set as a merge-request dependency).

Lands the checked-in tarball corpus, the fixture-driven tests, and the fuzz target — completing Step 13.

  • testdata/ — the generated tarball corpus (8 .tgz) + generate.go (one-shot generator, //go:build ignore) + README. .gitattributes marks the .tgz as generated so GitLab collapses them.
  • streampub_test.gofixture() reader, TestProcess_FixtureDigestsDistinct (guards against content-addressed fixture collapse), FuzzProcess (untrusted-input parsing must never panic — only succeed or return a typed *streampub.ProcessError).
  • coherence_test.goTestProcess_CoherenceFailures: malformed gzip/tar, missing manifest, path traversal (dotdot + escape), oversized package.json, each driven by a corpus fixture.
  • lifecycle_test.goTestProcess_DrainOnError_BoundedTime and TestProcess_BackpressureBufferWired.
  • .gitlab-ci.yml — the fuzz:npm job (mirrors fuzz:oci), the first fuzz target under internal/format/npm.

🧪 Tests

  • golangci-lint (repo-pinned v2.12) → 0 issues
  • GOEXPERIMENT=jsonv2 go test -race -count=1 ./internal/format/npm/streampub/... → pass (23 tests + FuzzProcess seed corpus)

Stack byte-identity verification

This is the merge point of the stack. git diff <this branch> <reference> over internal/format/npm/ + the wired config shows only the reviewed deltas accumulated across Parts 1–8, and nothing else:

  • error.go / streampub.goProcessError API (renamed type, unexported code / wrapped) + six-cap Limits.validate (Parts 1–2, from Duo/AppSec review).
  • sink.gosizeLimitWriter reports bytes written on the cap-exceeded path (io.Writer contract, Part 5).
  • pipeline.go — deferred pipe-close panic backstop (S11 coherence-sink contract, Part 6).
  • inspector.go — non-regular tar entry skip at the manifest path (go-secure-coding.md, Part 6).
  • Test files — the *ProcessError rename, the "forbidden rune" case label, the corrected test-author-phase comments, and the Part 8 review fixes below.

Part 8 review fixes (Duo/AppSec, in this MR):

  • lifecycle_test.goBackpressureBufferWired drops t.Parallel() (runs serially, matching its //nolint:paralleltest timing rationale); DrainOnError_BoundedTime widens the context deadline to 10s so a broken drain trips the time.After diagnostic rather than a misleading context-deadline error.
  • streampub_test.goFuzzProcess drops the dead context.Canceled carve-out (the fuzz uses a non-cancelable context), enforcing the full typed-*ProcessError-or-success contract like the OCI fuzz target.

The testdata corpus, .mise.toml GOEXPERIMENT, and .gitattributes are byte-identical to the reference.

One forced .gitlab-ci.yml deviation: the fuzz:npm job runs go test -fuzz=FuzzProcess ./internal/format/npm/streampub/, not the reference's ./internal/format/npm/.... On this stack's (newer) base, that glob matches two packages (the npm handler + streampub) and go test -fuzz rejects multiple packages; scoping to the package that contains FuzzProcess fixes it. (The reference's glob worked only on its older base, before the handler package existed alongside streampub.)

🚩 Deviations from the reference (!420 (closed))

The deltas listed under Stack byte-identity verification above — each a Duo/AppSec review fix or a propagated rename, documented in the part where it landed. No others.

Edited by David Fernandez

Merge request reports

Loading
Loading