feat(maven): sidecar cold-parent fill (S14 plan: 16/19)
Summary
Step 16 of the Maven remote plan: the cold-parent arm of the checksum-sidecar path, replacing Step 12's fillRemoteSidecar stub. A sidecar GET whose parent is uncached resolves the parent by the same layout rules, runs the parent fill with the body draining to cache only (the client asked for a digest, not the file), then synthesizes from the committed row through the shared digest-selection table. The parent fill enters single-flight under the parent's path key, so a sidecar arriving mid-fill coalesces behind the leader with exactly one upstream GET.
Carries the operator-approved freshness-symmetric amendment (recorded in the plan's Step 16 Amendment and mirrored in S14): serveRemoteSidecar now reads through the same freshness-aware RemoteCacheStore lookup as the parent arms. A stale parent revalidates under the parent's path key, conditional on the stored upstream ETag; a 304 keeps the row's digests, a transport failure with a stale row serves that row's digest degraded, and snapshot_metadata_always_revalidate makes version-level snapshot metadata ForceStale on every sidecar request. The now-unused Deps.RemoteCache seam is removed; the serve arm answers the fill's 501 on a partially wired handler.
Governing ADRs
- ADR-007 — synthesis reads the compute-at-fill digest columns; freshness and conditional revalidation run through
upstream_checked_at, the validity windows, andupstream_etag, exactly as the ADR decides. No schema change in this MR. - ADR-009 — no route, verb, or payload-contract change; the problem-details error surface is unchanged.
- ADR-023 — flat
internal/format/mavenpackage; format → datastore/remote import direction only; nodatabase/sql. - Checked and not engaged: ADR-001, 002, 005, 008, 010, 012, 022.
Per Mirror freshness and internal ADRs, the local mirror is not the authority; no deviation is introduced here.
Spec coverage
Spec: docs/specs/S14-maven-remote.md
Specs live under docs/specs/; acceptance criteria, error cases, and security considerations are the source of truth for what the tests assert (docs/dev/go-testing.md).
Acceptance criteria (Sidecar synthesis)
| # | Criterion | Tests |
|---|---|---|
| AC-1 | All four extensions | TestServeRemoteSidecar_AllFourExtensions, TestRemoteSidecarCold_AllFourExtensions_SynthesizeFromFilledParent |
| AC-2 | FIPS short-circuit | TestServeRemoteSidecar_FIPSShortCircuit, TestServeRemoteSidecar_FIPSGate_DirectCall_422, TestServeRemoteSidecar_FIPSOnNonMD5StillSynthesizes |
| AC-3 | No upstream sidecar fetch, any cache state | assertNoUpstreamSidecarPath helper invoked by all 10 cold-fill and 5 revalidate cases; TestRemoteSidecarCold_ArtifactParent_FillsThenSynthesizes (sole upstream GET targets the parent path), TestRemoteSidecarRevalidate_Stale304_KeepsRowDigests (zero upstream calls on the follow-up request); the warm suite wires no upstream client at all |
| AC-4 | Cold parent filled, then synthesized | TestRemoteSidecarCold_ArtifactParent_FillsThenSynthesizes, TestRemoteSidecarCold_MetadataParent_FillsThenSynthesizes |
| AC-5 | Mid-fill sidecar coalesces | TestRemoteSidecarCold_MidFillSidecarCoalesces, TestRemoteSidecarCold_TwoSidecarsShareOneParentFill |
| AC-6 | Stale parent revalidates, then synthesizes | TestRemoteSidecarRevalidate_Stale304_KeepsRowDigests, TestRemoteSidecarRevalidate_Stale200_SynthesizesNewDigest, TestRemoteSidecarRevalidate_StaleNoEtag_RefetchesFull, TestRemoteSidecarRevalidate_StaleTransportFailure_ServesCachedDigest, TestRemoteSidecarRevalidate_ForceStaleSnapshotMetadata_RevalidatesEveryRequest |
| AC-7 | No counters | TestServeRemoteSidecar_NeverBumpsCounters |
| AC-8 | Sidecar conditional request | TestServeRemoteSidecar_ConditionalRequest304, TestServeRemoteSidecar_IfNoneMatchAcrossFieldLines304, TestServeRemoteSidecar_NonMatchingIfNoneMatch200 |
| AC-9 | HEAD on a sidecar | warm-suite HEAD subtests (TestServeRemoteSidecar_AllFourExtensions), TestRemoteSidecarCold_HeadFillsThenReturnsSynthesisHeaders |
| AC-10 | Sidecar cache-generation consistency | TestServeRemoteSidecar_CacheGenerationConsistency_ArtifactParent, TestServeRemoteSidecar_CacheGenerationConsistency_MetadataParent, revalidate suite digest stability |
Error mapping
| # | Rule | Tests |
|---|---|---|
| E-1 | Upstream 404 propagates | TestRemoteSidecarCold_ParentUpstream404_Propagates (404, not_found, no cache row) |
| E-2 | Transport failure with cache: serve cached copy | TestRemoteSidecarRevalidate_StaleTransportFailure_ServesCachedDigest (degraded stale-row digest, no upstream reach, no new row) |
| E-3 | Transport failure without cache: 503 upstream_unavailable + Retry-After |
TestRemoteSidecarCold_ParentTransportFailure_503, TestRemoteSidecarCold_ParentDrainFailure_Maps503 |
| E-4 | Upstream 5xx not a fallback trigger | TestRemoteSidecarCold_ParentUpstream5xx_Mapped (500 → internal_server_error without Retry-After; 503 → upstream_unavailable with Retry-After; status propagated verbatim) |
| E-5 | Path traversal rejected | Parent-arm parse, unchanged in this MR; covered by the artifact/metadata suites (Steps 14/15) |
| E-6 | Outbound segment safety | Parent-arm URL construction, unchanged in this MR; covered by the Steps 14/15 suites |
| E-7 | Upstream non-404 4xx rejected | Shared MapFetchOutcome branch reused unchanged; covered by the Step 13/14 mapping suites — no sidecar-specific branch |
| E-8 | Upstream body exceeds cap → transport-failure treatment | S13 fetch-owned; mid-transfer failure classification asserted by TestRemoteSidecarCold_ParentDrainFailure_Maps503 |
E-5–E-8 route through parent-arm and S13 code this MR does not modify; the rows are listed so the sidecar arm's reuse is explicit, per go-testing.md's dependency-owned-rows rule.
Security considerations
| Concern | Tests |
|---|---|
| Checksum trust model | TestServeRemoteSidecar_CacheGenerationConsistency_ArtifactParent, TestServeRemoteSidecar_CacheGenerationConsistency_MetadataParent, TestServeRemoteSidecar_HostedAndRemoteSameDigests_ByteIdentical, revalidate suite |
| FIPS | TestServeRemoteSidecar_FIPSShortCircuit, TestServeRemoteSidecar_FIPSGate_DirectCall_422, TestServeRemoteSidecar_FIPSOnNonMD5StillSynthesizes |
| Error-payload hygiene | S13-owned (no blanket header copy; this arm asserts status/code/Retry-After only — TestRemoteSidecarCold_ParentUpstream5xx_Mapped) |
| Credential hygiene / at rest, SSRF, redirect stripping | S13/S17-owned. Not tested in this MR. |
docs/testing/ (Guardrail 12)
No e2e scenario is added or affected: the Maven e2e catalog (docs/testing/e2e/maven.md) covers hosted local repositories only, and no catalog row asserts remote sidecar behavior. The remote sidecar path is pinned by the two new integration suites instead (see Spec coverage).
Diff footprint (Guardrail 18)
+1301 −304 total; the plan carries this work as one step (Step 16), and the diff is one request-path behavior with its coverage. Splitting would separate a single fill→revalidate→synthesize flow from the tests that pin it. By file group:
- Implementation:
remote_sidecar.go(+344 −23),remote_stub.go(+20 −37),handler.go(+21 −36),remote_store.go(+7 −8),wire_maven.go(+13 −23) — +405 −127 - Integration and unit tests:
remote_sidecar_cold_integration_test.go(+499),remote_sidecar_revalidate_integration_test.go(+211),remote_sidecar_integration_test.go(+136 −20),remote_sidecar_test.go(+9 −148),remote_artifact_integration_test.go(+8 −1),handler_test.go(+1 −1) — +864 −170 - Docs:
docs/specs/S14-maven-remote.md(+10 −7),docs/plans/2026-07-27-maven-remote.md(+22) — +32 −7
Tests dominate the diff (two-thirds of the added lines); the production change is one function family in remote_sidecar.go.
Related to #286 (closed)