feat(maven): route the virtual read classes through the dispatch slot (S30 plan: 9/25)

Summary

Implements Step 9 of the S30 plan: the kind=1 arm of serveDownload routes each parsed class — checksum sidecar, package-level maven-metadata.xml, version-level maven-metadata.xml, primary file — through one virtual-read handler dependency on the dispatcher. The slot defaults to the interim 501. The FIPS .md5 short-circuit moves ahead of the dispatch, exactly as the kind=2 arm orders it. The write path is unchanged and pinned against kind=1.

Depends on: Step 8 (!2737 (merged), open), so this MR targets the step-8 branch.

References: S30 spec · S30 plan (see "Step 9: virtual dispatch slot")

Reviewable LOC: 630 insertions — past the 500 ceiling. By file group: routing-table tests 563 (handler_test.go 561, download_test.go threading 2), slot and seams 67 (handler.go 61, export_test.go 5, remote_stub.go 1); 81 deletions, mostly the compressed test-constructor comment and the stub doc. A split would not help: the slot and its routing table are one surface, and the tests are the slot's only coverage. The shape mirrors npm's Step 10 suites minus the handler-label surface Maven does not carry, per the plan.

Scope

The diff carries three paths beyond Step 9's Files entry. All three are required ripples, confirmed by the pre-push coherence check:

  • export_test.go: the test constructor takes opts ...DispatchOption, because the new suites inject the slot through it.
  • download_test.go: the opts threading carries the injected spy into the dispatcher (newVirtualReadDispatcher calls through it).
  • remote_stub.go: the writeNotImplemented doc compresses to the writers that remain, as the Scope prose mandates.

Governing ADRs

  • ADR-009 (API design) — no new route; client URLs are unchanged. PUT on kind=1 still answers 405 with Allow: GET, HEAD before any body byte is read. The FIPS .md5 refusal answers 422 before any resolution query.
  • ADR-001 (organizations as anchor) — untouched; authorization still answers before the kind gate, and the suspended-namespace 403 still precedes both. Both orderings are pinned against an injected denying decision.

No deviation in either ADR.

Testing

  • go test -count=1 ./internal/format/maven/ green, plus ./internal/datastore/ and ./internal/authz/ (parent-slice cover).
  • New suites: every read class reaches the slot (TestDispatchHandler_VirtualKind_ReadClasses_ReachTheVirtualSlot); the unwired slot answers the interim 501 (TestDispatchHandler_VirtualKind_UnwiredSlot_ServesInterim501); hosted and kind=2 reads never reach the slot (TestDispatchHandler_VirtualSlot_NotReachedByHostedOrRemoteReads); the 405 / 403 / auth-ordering pins; parse rejections precede the kind branch; the FIPS .md5 422.
  • A five-category pre-push coherence check ran on this branch: no blockers. The 13 out-of-Files rows from that check belong to the parent slice (!2737 (merged)); the 3 in-slice rows are dispositioned under Scope above.

e2e scenarios

No scenario added or affected: the slot still answers the interim 501 until Step 17 swaps it, so no route serves virtual reads and no client-visible behavior changes.

User documentation

No user-facing surface changes: every status path answers exactly as before, so docs/user/** is unaffected by this MR.

Related to #289 (closed)

Merge request reports

Loading
Loading