Loading
feat(npm): streampub CAS sink + attachment gate (S11 Step 15, 1/3)
📦 What
S11 Step 15 (publish handler - stream + coherence + CAS) ships as a three-MR stack, in dependency order:
- !695 (merged) - streampub CAS sink + attachment gate (library seam + unit tests). <- this MR
- !696 (merged) - publish handler streaming/CAS staging (Spec Step 3): gate-driven pre-check, lazy session, commit.
- !697 (merged) - publish Step 4 coherence check (shasum / integrity / manifest).
This MR is the bottom of the stack and targets main. Slices 2/3 and 3/3 build on it.
Slice 1 - the streampub seam the publish handler stages tarballs through. Library-only: no handler wiring yet, so Step 14's existing Process call is unchanged (a nil gate keeps the two-arm fan-out).
WithCASSink(io.Writer)threads the decoded tarball to a thirdio.MultiWriterarm; theResultdigests are byte-for-byte the digests of what the sink received (so the server-computed SHA-256 is the commit authority).WithAttachmentGate(GateFunc)fires at the attachment hand-off - once the envelope identity is parsed and before any tarball bytes stream - and returns the sink or an error that aborts the pass. This is what lets slice 2/3 run the version-exists / quota pre-check before opening a session (spec Step 2).WithCASSinkis the unconditional form of the gate.casSinkWritercaptures the sink's first write error verbatim (errors.Isfidelity, precedence over the cascaded inspector error); a short write is treated as a storage error.
✅ Spec coverage
| Behaviour | Test |
|---|---|
| CAS sink receives the decoded tarball byte-for-byte; digests match those bytes | TestProcess_WithCASSink_ThreadsDecodedTarball |
Nil sink preserves the two-arm behavior (Step 14 Process unchanged) |
TestProcess_NilCASSink_PreservesTwoArmBehavior |
CAS sink is transparent to digests / captured package.json |
TestProcess_CASSink_Transparent |
CAS-write error surfaces from Process via errors.Is |
TestProcess_CASSink_WriteError_Surfaces |
| CAS-write error wins precedence over a coherence/size error | TestProcess_CASSink_WriteError_WinsPrecedence |
| Gate receives the parsed identity; the returned sink streams the tarball | TestProcess_AttachmentGate_ReceivesIdentity |
| Gate error aborts the pass before any bytes stream | TestProcess_AttachmentGate_AbortsBeforeStreaming |
Gate receives a clone of dist-tags; a gate mutation can't corrupt the Result |
TestProcess_AttachmentGate_ClonesDistTags |
BenchmarkProcess_WithCASSink / _NoSink record the per-publish allocation baseline.
🧪 Testing
go test ./internal/format/npm/...- passgolangci-lint run ./internal/format/npm/...- 0 issuesgo vet -tags integration ./internal/format/npm/...- clean- Unit-level only (no DB); the gate/sink are exercised end-to-end against the real storage
Sessionin !696 (merged) / !697 (merged).
Related to #122 (closed)
Edited by Dzmitry (Dima) Meshcharakou