feat(maven): sidecar synthesis from a warm remote parent (S14 Step 12)

What

Lands checksum-sidecar synthesis for an already-cached Maven remote parent — Step 12 of the S14 Maven remote vertical slice. serveRemoteSidecar replaces Step 10's 501 stub: it is a pure column read against Step 8/9's remote cache row, never consulting the parent's freshness and never issuing an upstream request. An uncached parent still returns 501 until Step 16 lands the cold-parent fill.

The digest-selection table sidecar_checksum.go already held for the hosted PUT path (checksumSpecs, keyed by SidecarAlgorithm) is retargeted from MavenFile onto a small digest carrier both MavenFile and the remote cache row project into, and gains the SidecarMD5 entry it lacked. sidecarBody collapses its parallel switch onto that table and keeps the 404/500 error writes: the NULL-md5 404, and the errChecksumNoColumn / errChecksumColumnLength 500 mapping with its sentinel logging.

One table now serves hosted GET synthesis, remote GET synthesis, and PUT validation, so a hosted and a remote parent holding the same digests synthesize byte-identical 200 bodies for all four extensions. The 200 body is the scope of that claim: on a conditional request the two kinds diverge, because serveSidecar (hosted) never reads If-None-Match and always reaches its 200, while serveRemoteSidecar answers 304 with no body for a matching validator. Same URL shape, behavior forking on kind — a deliberate divergence, documented at sidecar.go's hosted-path comment.

Alongside the synthesis path:

  • handler.go gains RemoteCacheRowFinder, Deps.RemoteCache, and Handler.remoteCache — a coordinate-keyed lookup seam, since threading Step 8's path-string RemoteCacheStore wrapper through NewDispatchHandler would force a re-parse of a coordinate this handler already holds parsed. It is a consumer-side interface in the spirit of DownloadStore/DigestStore, but not the same shape: those two are sub-interfaces cut from the single Store aggregate, while RemoteCacheRowFinder is a separately wired optional Deps field with its own datastore concrete. An additive Deps field, not a signature change.
  • wire_maven.go wires Deps.RemoteCache to datastore.NewMavenRemoteCacheStore(client), which satisfies RemoteCacheRowFinder with no adapter.
  • remoteLookupCoords is the one constructor both cache readers use (RemoteCacheStore.Lookup and serveRemoteSidecar), so the two readers' coordinate mappings cannot drift as MavenRemoteLookupCoords grows.
  • handler_test.go narrows four tests that asserted Step 10's now-superseded 501-stub behavior for the checksum-sidecar class; the artifact/metadata rows they otherwise cover are untouched.

Tests

Every cited acceptance clause has a named asserting test:

Acceptance clause Test
All four extensions synthesize (incl. NULL md5 → 404) TestServeRemoteSidecar_AllFourExtensions (one matrix over parent class × extension), TestServeRemoteSidecar_MD5NullColumn404
FIPS .md5 short-circuit, non-md5 unaffected TestServeRemoteSidecar_FIPSShortCircuit, TestServeRemoteSidecar_FIPSOnNonMD5StillSynthesizes, TestServeRemoteSidecar_FIPSGate_DirectCall_422
No upstream sidecar fetch Structural: serveRemoteSidecar's row.Found path never references an upstream client; the integration suite wires none
No counter mutation TestServeRemoteSidecar_NeverBumpsCounters
Sidecar conditional request (weak ETag, 304 + validator, GET and HEAD) TestServeRemoteSidecar_ConditionalRequest304, TestServeRemoteSidecar_NonMatchingIfNoneMatch200, TestServeRemoteSidecar_IfNoneMatchAcrossFieldLines304, unit-backed by TestIfNoneMatchSatisfied / TestWeakETagEqual
HEAD parity HEAD subtests inside the extension matrix (both parent classes, full GET header set), the FIPS tests, and the NULL-md5 test above
Cache-generation consistency (artifact and metadata parents) TestServeRemoteSidecar_CacheGenerationConsistency_ArtifactParent / _MetadataParent
Uncached parent still 501 TestServeRemoteSidecar_UncachedParent501
Hosted and remote parents synthesize byte-identical bodies TestServeRemoteSidecar_HostedAndRemoteSameDigests_ByteIdentical (HTTP), TestSidecarSynthesis_HostedAndRemoteCarriersAgree (DB-free)
Table-driven digest selection, all four algorithms plus every error sentinel TestSidecarSynthesis, TestChecksumSpecsFlags

The existing sidecar_test.go and sidecar_extensions_test.go are the refactor's regression guard for the retargeted table and are unchanged apart from one stale comment.

The two SQL statements this MR first makes production-reachable already carry partition-pruning EXPLAIN coverage from Step 8 (TestMavenRemoteCacheStore_VersionLevelStmt_PrunesToOnePartition and _PackageLevelStmt_PrunesToOnePartition in internal/datastore/maven_remote_cache_explain_integration_test.go). Those assert pruning, not index choice; adding an index-scan assertion would touch Step 8's files and is left to the datastore owner.

Size

~2,675 changed lines, over the 500 ceiling in development-model.md, justified here per that document rather than split. Measured at 4e801a4a after three review rounds (+2,225/−450 against base 73ab9b62):

Part Lines
Test churn ~1,605
Production Go ~870
Plan text ~180
CLAUDE.md 20

The integration suite alone is 754 lines, driven by the acceptance matrix: four extensions against two parent classes, GET and HEAD, FIPS on and off. Of the production Go, much of sidecar_checksum.go's share is comment rewriting for the carrier retarget rather than new logic. A split is available — the carrier plus the checksumSpecs retarget and the sidecarBody collapse in one MR, the synthesis path stacked on it — and is not taken here: the collapse's motivation is only checkable with its second caller in the same diff, and the overage is test churn that a split redistributes rather than reduces. The plan's Step 12 size row records the same breakdown.

Rollout

During a rolling deploy the same kind=2 sidecar URL answers 501 on an old pod and 200/304/404/422 on a new one, and a client can take an ETag from a new pod and send If-None-Match to an old one, getting 501 rather than 304. Both self-heal on the next request and the direction is 501 → real behavior, so no client contract breaks; flagged so a mixed-status dashboard during the deploy window reads as expected.

Nothing writes maven_remote_files yet and no endpoint creates a kind=2 Maven repository, so the warm branch is unreachable in production until Steps 13 to 15 land. That reachability rests on one production gate: the kind check in internal/managementapi/create.go, which rejects kind != hosted with 422 before the datastore, while RepositoryStore.Create, insertFormatChild, and the check_repositories_kind constraint all admit kind = 2. The resulting merge-order obligation — creation or a backfill must not land ahead of Steps 14 to 16 — is recorded in the plan's Reachability of the remote read paths section and tracked in #604 (closed), where the creation work will see it.

Review follow-ups

  • Both automated-review findings are fixed in ddd1d421: the self-contained FIPS gate in serveRemoteSidecar (AppSec) and the ETag threaded into writeSidecarOK instead of recomputed (Duo).
  • A branch review raised a decision on weakETagEqual renormalizing the response ETag once per If-None-Match candidate. Deferred deliberately so the fix covers the Maven and npm copies together rather than drifting them: #569.

E2E scenario impact

None. No Maven e2e catalog exists yet (docs/testing/e2e/ holds only README.md, docker.md, and oci.md); Step 18's hermetic harness is the automated coverage meanwhile, per the plan's Testing Strategy.

The Maven conformance suite does exist and runs on this MR: conformance:maven:s3-garage is anchored on internal/format/maven/**/* and cmd/artifact-registry/**/*, both of which this MR touches, and it exercises the hosted sidecar GET path this MR refactors. It has no kind=2 target for the new remote path: its fixture scripts/conformance/maven-provision/main.go seeds the repository row by direct INSERT with kind=0, so the provisioner is what a remote conformance case has to change first, not the 422 in internal/managementapi/create.go that the Rollout section names.

Related to #286 (closed)

Edited by Sylvia Shen

Merge request reports

Loading
Loading