feat(npm): the virtual read's fetch options 1/3 (S31 plan: 12/19)
The stack
Step 12 is three MRs. They must merge in this order: part 3's Resolve
calls part 1's virtualFetchOptions, and its composed suite asserts envelopes
through part 2's mapping, so part 3 does not compile on main without both.
Parts 1 and 2 are independent of each other; the stack is linear only because
each MR targets the one before it, which keeps every diff to its own files.
| Order | MR | Part | Contents | LOC |
|---|---|---|---|---|
| 1 | !2325 (merged) | 1/3 | the read's remote.FetchOptions |
114 |
| 2 | !2326 (merged) | 2/3 | the error mapping and its taxonomy | 1574 |
| 3 | !2255 (merged) | 3/3 | the resolution facade and the composed suite | 1762 |
3450 LOC across the three. 3442 of those were the whole of what !2255 (merged) carried
before the split, and nothing was added or dropped in the cut: part 3's tree
reproduced the reviewed head file for file. The other 8 are part 1's review
round (11dd3cd2b), which parts 2 and 3 pick up when they rebase onto this
branch's new head.
What this MR does
The request-global remote.FetchOptions one kind=virtual npm read runs its
winner's Fetch under: the body-kind class and the size cap every position of
one read shares, with the Transform deliberately left unset.
Part 1 of the three this step was split into. It is the smallest piece and has no dependency on the other two.
The two decisions in 25 lines
A tarball takes the blob class. npmremote.TarballFetchOptions leaves
MaxBodyBytes unset, which remote.FetchOptions reads as the blob class's own
configured cap. Carrying npm.max_remote_packument_size there instead would
clamp a tarball to a metadata-class number. Every other kind is a metadata
document, an undefined one included: the metadata class is the fail-small one,
and Target.Validate rejects an undefined kind before a read reaches here.
No fill-time rewriter is set here, and that is the load-bearing half.
virtual.Request.Fetch is request-global: virtual.Resolver.fetchWinner copies
it and hands the one Transform to whichever position wins. A single
request-global rewriter built on position 1's base would rewrite position 2's
fill inside the shared flight, and position 2's npm_remote_metadata_files row
would land carrying position 1's tarball URLs; a later standalone read of
position 2's repository then serves that row verbatim, and clients pull position
1's tarball for position 2's package.
A single-remote fixture cannot see any of that, which is why
TestVirtualFetchOptions_CarryNoTransform asserts the absence directly. The
rewriter is built where the position's own identity is still in scope, in the
per-position fetcher the upstream assembly (Step 11, merged) already owns.
Reachability
virtualFetchOptions is unexported and has no production caller until part 3
lands the facade that calls it. Its test is what exercises it here, which is
also why unused does not fire.
Reviewable LOC
114 reviewable LOC (virtual_fetch_options.go 25, virtual_fetch_options_test.go
89), measured at 11dd3cd2b against origin/main at e0de46676. Under the 500
ceiling; no justification needed.
Spec coverage
Spec: docs/specs/S31-npm-virtual.md Plan: docs/plans/2026-08-21-s31-npm-virtual.md, Step 12
| # | Claim | Tests |
|---|---|---|
| SEC-7, SEC-8 | No new outbound surface; streaming bounds are the upstream's | TestVirtualFetchOptions_SelectsTheSizeCapClassPerTarget |
| P-1, SEC-5 | The re-point is per position, not request-global | TestVirtualFetchOptions_CarryNoTransform; the fill-time half is part 3's TestVirtualResolution_FillRewritesOntoTheWinningPositionsOwnBase |
e2e scenarios
No scenario in docs/testing/e2e/npm.md is added or affected. That catalog carries no virtual section, and no route reaches this code, so no end-to-end flow can exercise it. The virtual npm scenarios land with the serve surface (Steps 13, 15 to 17); the round trip is Step 19.
Conformance
No format-handler behaviour changes: internal/handlers/{oci,maven,npm}/ is
untouched and no route is registered, so the npm conformance harness has nothing
new to exercise.
Related to #893 (closed)