S31 Step 6: remote upstream lookup and prober
Part of #290, S31 npm virtual. This slice composes code that is already merged: S11's hosted npm reads, S15's remote legs, and S13's resolution engine in internal/virtual, which has had no caller until now. The three npm_virtual_* tables land in internal/datastore/migrations/, the resolution reads in internal/datastore/, and the loading slice, the serve surface, and the three read handlers in internal/format/npm/.
References: S31 spec · S31 plan (see "Step 6").
Type: feat | Depends on: nothing. This step is a root and can start immediately.
Scope
The batched cache reads for a remote position and the Phase 2 prober the flight registry cannot supply.
Files
-
internal/datastore/npm_virtual_remote_lookup.go(Create): the batchedIN-list reads overnpm_remote_metadata_filesandnpm_remote_fileswith S13's freshness model, one statement whatever the remote position count. TheINlist is the remote-position count, so it inherits Step 4'sLIMITon the list read rather than carrying its own; this step names that dependency instead of assuming it, because nothing in the read's own signature bounds the slice it is handed.The metadata read takes the document kind and filters on it per position.
npm_remote_metadata_filesis kind-keyed and S15's remote route already writes abbreviated (kind=2) rows, so a read that ignores the column answers an abbreviatedAcceptfrom a fresh full-kind row. The kind comes from the target Step 12 builds. -
internal/format/npm/virtual_remote_upstream.go(Create): theremote.Upstreamadapter, and the prober composed fromnpmremote.NewLiveUpstreamConfigSource,npmremote.NewUpstreamRequestBuilder, and theupstreamhttpdoer. The Doer is a constructor parameter, never built here. Composing separately opens a second path to the upstream client, and the security covers are split across the two collaborators: the base-URL gate and the bearer live inBuildRequest, but the resolved-address deny-list and the own-interface check live in the Doer's dialer. A prober handed a plainhttp.Clientcompiles and passes every test in this step, so the assertion that production wires the composition root's sharedupstreamhttpDoer is Step 14's, and this step's doc comment says so. -
internal/datastore/query_names.go(Modify): the remote-lookup query names, in their own group. -
the paired
*_integration_test.goand*_test.gofiles (Create).
Acceptance
Contributes the remote half of Spec AC, Resolution #15: the batched read issues one statement for 2 and for 20 remote positions. A remote Lookup returns fresh, stale, and miss verdicts off the cache rows, and a prober built here issues a bodyless HEAD against the upstream base the remote repository's row names.
Tests
Integration over seeded cache rows in each freshness state and in both packument kinds, including the cross-variant negative: a fresh full-kind row must not answer an abbreviated read, and the reverse. Unit tests for the prober against an httptest upstream, including a 304 answer mapping to ProbeFresh and a 200 to ProbeHit.
Size
Source ~380 · Test ~840 · Total ~1,220