feat(npm): hosted positions and read vetoes (S31 plan: 13/19)

Split into 4 stacked MRs to keep each within a reviewable size, split by file group (see below). Each part targets the previous (part 1 → main), reviewed/merged bottom-up.

Part 3 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).

Adds hosted positions and their vetoes on top of part 2's remote-only collector: npmMergeContributor.fromHosted, closeHostedMergeBody, npmMergeDeadlineWriter, queuedHostedHitVeto, and Contribute's hosted dispatch arm — removing part 2's temporary hosted-rejection guard. Covers both the refused-rebuild veto and the queued-behind-budget-expiry veto (the package-substitution hazard named in the spec and resolve_budget.md).

Spec: S31 (npm-virtual) Plan: docs/plans/2026-08-21-s31-npm-virtual.md

Related to #894 (closed)

Diff size: ~1183 reviewable LOC across 6 files (AGENTS.md guardrail 15) — 920 in tests (virtual_collect_internal_test.go 910, virtual_read_errors_test.go 10), 178 in production Go, 85 in docs. Split by file group per guardrail 15 rather than shaved to a round number.

No docs/testing/ update: no production route builds an npmMergeCollector yet, and docs/testing/e2e/npm.md:33 already scopes virtual npm reads out (AGENTS.md guardrail 12). No docs/user/ update: this part changes no customer-visible surface (AGENTS.md guardrail 24).

Plan and spec amendments

Review on this MR widened S31-npm-virtual.md's hosted-Hit veto contract: it previously named the refused rebuild as the only veto arm over a hosted Hit (a DB error, a cache row-read fault, or a missing packument blob answered 200 merged_partial from the remotes). It now vetoes the whole read (500, or 503 for a budget expiry) on any hosted-Hit read failure whose own context has not already ended from outside. This is a widened contract, not a transcription fix — it flips the client answer for those failure shapes.

Stacked MRs (review/merge bottom-up)

Edited by David Fernandez

Merge request reports

Loading
Loading