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:
ResolutiongainsRemoteRepository, themaven_remote_repositoriesrow Step 6's finder returns, attached at resolution so no handler needs a second round-trip forurl, 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.gogains the two codes the S14 spec's error map consumes,upstream_unavailableandupstream_rejected, which is why Step 13 names this step as a predecessor.api-style.mdgains 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.gothreads the remote finder intoNewDispatchHandlerbehind a small adapter; no later step wires a handler.NewResolveris 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)