feat(npm): collect over remote positions (S31 Step 13 Part 2, part 2/4)
Split into 4 stacked MRs (review/merge bottom-up, part 1 → main) because a single MR for the whole step would be far larger. This part is 1020 reviewable lines — 359 in virtual_collect.go, 640 in virtual_collect_internal_test.go, 21 in virtual_read_errors.md — over docs/dev/development-model.md's 500-line ceiling. A smaller cut would not help: collect and its contributor family (Contribute/ContributeFromCache/fetchOnce/fromFetch/fromBlob/fromStream/leaderLost) is one dependency-ordered unit, and splitting it from its own test file would violate the rule that a part's tests travel with the code they cover.
Part 2 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).
Collects over remote positions only: npmMergeCollector.collect, resolveCachedBlobSizes, and npmMergeContributor.Contribute/ContributeFromCache/fetchOnce/fromFetch/fromBlob/fromStream/leaderLost. A hosted position reaching this collector build is rejected by a temporary guard (errNpmMergeHostedPositionUnsupported) — hosted support and its veto logic land in part 3, which also removes this guard.
Spec: S31 (npm-virtual) Plan: docs/plans/2026-08-21-s31-npm-virtual.md
Related to #894 (closed)
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