feat(maven): read-path kind-dispatch seam + 501 stubs (S14 Step 10)

What

Lands the read-path kind-dispatch seam for Maven — Step 10 of the S14 Maven remote vertical slice. serveDownload now branches on Resolution.MavenRepositoryKind: kind=0 (hosted) keeps the existing path byte-identical under the default arm, kind=1 (virtual) returns 501 not_implemented (virtual reads are S30's), and kind=2 (remote) routes through a content-class switch to three per-class stub methods — serveRemoteArtifact, serveRemoteMetadata, serveRemoteSidecar — each a one-line 501 in the new remote_stub.go. The kind=2 arm answers a FIPS-on .md5 sidecar 422 md5_disallowed_under_fips ahead of the remote-row lookup (mirroring serveSidecar's first statement), so the deterministic status never depends on a query the request cannot use — the read-path twin of the ordering serveUpload's kind gate enforces for the 405.

Alongside the seam:

  • Resolution gains RemoteRepository, the maven_remote_repositories row Step 6's finder returns, attached at resolution so no handler needs a second round-trip for url, the validity columns, or the snapshot flag. The field carries a load-bearing-invariant doc comment: non-nil iff the kind is remote; a nil value on a kind=2 resolution is a wiring or data fault — the handler logs and returns 500, never a silent hosted-path fallthrough.
  • problem.go gains the two codes the S14 spec's error map consumes, upstream_unavailable and upstream_rejected, which is why Step 13 names this step as a predecessor.
  • api-style.md gains the four missing status rows — 413 and 416 (already returned by shipped Maven handlers), 501 (this step), and 503 (Step 13+) — plus the propagated-upstream-status note, per the spec's directive to record them.
  • cmd/artifact-registry/wire_maven.go threads the remote finder into NewDispatchHandler behind a small adapter; no later step wires a handler. NewResolver is method-agnostic and never sees the finder.

Stub methods, not injected handler slots

The content-class switch lands once and is never edited again: Steps 11, 12, 14, 15, and 16 each delete their own stub declaration from remote_stub.go and define the real method in their own new file, so the per-step MRs merge in any order without editing a shared dispatch block or telescoping NewDispatchHandlerWith* constructors (the shape OCI's six historical handler slots ended in). remote_stub.go is the one shared file, and every later edit to it is the deletion of a distinct named function.

Tests

Every acceptance clause has a named asserting test:

Acceptance clause Test
GET/HEAD on a kind=2 repository returns 501 with the RFC 9457 envelope instead of today's 404, for primary, metadata, and sidecar alike TestDispatchHandler_GET_RemoteKind_501 / TestDispatchHandler_HEAD_RemoteKind_501 — three content classes each; status, ProblemContentType, CodeNotImplemented, and (HEAD) empty body
kind=2 .md5 sidecar under FIPS-on returns 422 md5_disallowed_under_fips before the remote-row lookup (finder call count pinned at zero; the stub controls still pay it) TestDispatchHandler_RemoteKind_MD5_FIPS_422 — GET and HEAD; the 422 carries the problem envelope, controls route to the 501 stubs
PUT to maven-metadata.xml on kind=2 returns 405, method_not_allowed, Allow: GET, HEAD, touches neither cache nor upstream, on a non-suspended namespace TestDispatchHandler_PUT_Metadata_RemoteKind_405 — the empty-Deps dispatcher makes a cache or upstream touch a nil panic, so the clause holds by construction; the fixture is non-suspended so namespace.GateWrite's 403 cannot preempt the kind gate
kind=2 with no remote row returns 500, not a hosted-path fallthrough; invariant doc comment TestDispatchHandler_GET_RemoteKind_NilRemoteRow_500 (ErrNotFound) and TestDispatchHandler_GET_RemoteKind_FinderError_500 (generic finder error — added with the dispatcher-side lookup); the wiring-fault path is dispatcher-side now that the resolver never touches the finder
kind=0 behavior byte-identical to before TestDispatchHandler_GET_HostedKind_Unchanged plus the unchanged pre-existing hosted suite
Malformed path returns 400 before resolution on every kind TestDispatchHandler_GET_RemoteKind_MalformedPath_400 asserts 400 with resolver call count zero — provably kind-independent
api-style.md lists 413, 416, 501, 503 the status-table rows in docs/dev/api-style.md

The wire adapter carries TestMavenRemoteRepositoryFinderAdapter, and TestWriteProblem_UpstreamCodes_PinMembers pins the two new codes' wire members. The seam invalidates one pre-existing subtest — the virtual-kind success row of TestPrimaryGET_Success_EmitsArtifactPulled, deleted because a virtual GET is now a 501, not a delivery emission — and the gating integration fixture gains the finder's compile-forced third argument.

MR size

1020 reviewable lines, over the 500 LOC ceiling in the development model, which asks for a split or a justification here.

Justification: the production surface is 339 lines, near the plan's ~300 estimate. The remaining 681 are the same-step test suite the plan template requires (659), the api-style.md status rows (9), and the plan's landed-drift amendment (13). No split is available that does not separate the seam from its tests.

End-to-end scenario catalogs

No scenario added or invalidated. docs/testing/ holds no Maven catalog yet — authoring the first one is a separate docs concern, per the plan's testing strategy — and this step's only observable behavior change is that remote and virtual reads return 501 stubs instead of the hosted path, pinned by handler unit tests. Step 18's hermetic proxy harness is the automated coverage for the S14 read paths.

Conformance

The hosted Maven surface — the only live one — is byte-identical on this branch, pinned by TestDispatchHandler_GET_HostedKind_Unchanged and the existing hosted suite; the remote and virtual arms return 501 stubs with no protocol behavior to conform. Conformance for the proxy read paths runs against Steps 11 and 14 through 16 as they land, with Step 18's harness the end-to-end check.

Notes for reviewers

Commit-by-commit:

  • 29d3481c / 7a8b8721 — the test-first skeleton and the seam implementation (the MR's substance).
  • d39d45fc — the forced test maintenance: the stale virtual-kind usage-data subtest the seam invalidates.
  • 3988d097 — the code-simplifier pass over the seam.
  • e4b7ed07, ef148551 — the api-style.md status rows.
  • 78db530b, 4784591a, 3713b614 — comment hygiene on the new code (process-state references, doc-comment corrections, dead constants).
  • 361c8aa4 — the wire adapter's companion test.
  • 54a96edc — the plan's landed-drift amendment: Step 10's Files entry now names the three test files the implementation forced (the adapter test, the deleted usage-data subtest, the gating fixture), recorded per the plan's convention.
  • 75fdb90b — a comment-only sweep dropping the pre-existing process-state references (S01/S10/S33 sprint ids, "the spec" coordinates) remaining in the files this MR touches, per the comments-do-not-cite-process-state rule. No behavior change.

One deliberate dispatch choice to flag: in the kind=2 branch, sidecar outranks metadata — a maven-metadata.xml.sha1 request routes to serveRemoteSidecar so a metadata checksum sidecar inherits the sidecar synthesis model (including the FIPS .md5 422 gate) rather than the metadata handler re-implementing it. Behaviorally neutral today (all three stubs return 501).

Related to #286 (closed)

Edited by Moaz Khalifa

Merge request reports

Loading
Loading