feat(npm): merge the collected documents into the read (S31 plan: 13/19)
Split into 4 stacked MRs to keep each within the review size limit (≈800 ideal, 1000 acceptable). Each part targets the previous (part 1 → main), reviewed/merged bottom-up.
Part 4 of 4 of docs/plans/2026-08-21-s31-npm-virtual.md — Step 13: metadata merge and serve surface (Part 2: collection over positions, and the outcome arms).
Reopens every collected blob and streams the cross-upstream merge over the collection's verdict: npmMergeCollector.merge, runMerge, mergedOutcome, applyMergeOmissions, openMergeInput, logOmissions. This is the top of the stack — with this part, virtual_collect.go and its test file are byte-identical to how they converged through Stage 2 code review before this 4-part re-cut.
Spec: S31 (npm-virtual) Plan: docs/plans/2026-08-21-s31-npm-virtual.md
Related to #894 (closed)
Size
This MR's diff is 1952 reviewable LOC, past development-model.md's 500-line guideline (guardrail 18). Split by file group: about 175 production and docs LOC, about 613 new test LOC, and about 1,160 LOC of unchanged tests relocated by the 4-part stack's split (87 of 90 top-level declarations in virtual_collect_internal_test.go are byte-identical to how they converged in review before the re-cut). merge and its tests are kept in one MR rather than split further, since they are a single concern.
The own-length bound on each merge input's MaxBytes (doc.maxBytes, not npm.max_remote_packument_size) is a deliberate choice recorded in this MR's own review thread: the fill's URL rewrite can grow a stored body past the fetch cap, so clamping to the config value would wrongly omit a legitimate rewrite-grown packument. No separate spec or plan amendment exists for it yet.
Non-applicable guardrails
- Guardrail 12 (e2e scenario catalogs,
docs/testing/): no route buildsnpmMergeCollectoryet, so no scenario is reachable or affected. - Guardrail 24 (
docs/user/): same reason — nothing customer-visible changes until a route wires this collector in.
Stacked MRs (review/merge bottom-up)
- feat(npm): collection plumbing seams (S31 Step ... (!3018 - merged) • David Fernandez • 19.5
- feat(npm): collect over remote positions (S31 S... (!3021 - merged) • David Fernandez • 19.5
- feat(npm): hosted positions and read vetoes (S3... (!3022 - merged) • David Fernandez • 19.5
- feat(npm): merge the collected documents into t... (!3023 - merged) • David Fernandez • 19.5
👈