feat(maven): send checksums and ETag on a metadata cold fill (S14 plan: 20/21)

Summary

Implements issue #732 item 1: a metadata cold fill no longer streams headers-first. Both ServeStream dispatch arms in the Maven remote metadata fill now route through drainMetadataFill — drain into the cache fill, let the row commit, read it back, and serve through the shared cached-serve contract. The first 200 for a maven-metadata.xml carries everything a later hit carries, so Maven Resolver verifies the document against the response's checksum headers instead of issuing a follow-up .sha1 GET on every cold fill and every revalidation 200.

Artifacts keep the stream-first fill; streamMetadataFill and logMetadataFillCopyOutcome are deleted.

Behavior changes

  • Cold metadata GET / revalidation 200: served from the committed row with Content-Type, Content-Disposition, Content-Length, ETag: "sha256:<hex>", X-Checksum-Sha1, and X-Checksum-Md5 (proxy mode, FIPS rules).
  • Client conditional on a cold fill: evaluated against the just-committed representation — matching If-None-Match answers a bodyless 304 (operator-approved design decision).
  • Range on a cold metadata GET: now honored (206 slice of the committed generation) because the serve runs through the hit path; the fetch itself is never range-partitioned. Surfaced by the integration suite during development.
  • Mid-fill failure: truncation, body-cap breach, or transport failure answers structurally (mapped error or degraded fallback) instead of an interrupted 200.
  • Counters: delivery-gated last_downloaded_at bumps ride serveRemoteCacheRow as before; nothing bumps on a failed fill.

Process deviations (deliberate, operator-directed)

This single MR bundles the spec amendment, the plan step, and the implementation. Two defaults are set aside, both recorded in the plan's Step 20 Amendment note:

  1. The S14 amendment normally lands in its own Approved MR before plan work opens.
  2. A step MR normally does not edit the plan file.

The spec-amendment deviation rests on the operator direction plus the bundling itself: the amendment and its implementation are one behavior slice. The step-MR rule guards parallel steps against colliding on shared files; this plan has exactly one open step, so there is no second writer.

Verification

  • go test ./internal/format/maven/ — pass.
  • Full go test -tags=integration ./internal/format/maven/ — pass (~195s), including the Maven Resolver conformance sequence.
  • golangci-lint run ./internal/format/maven/ — 0 issues; tagged pass clean for every file this MR touches.
  • scripts/ci/check-comment-caps.sh --base origin/main — OK.
  • Live end-to-end against the running service (driver.sh up, namespace seeded with a real Central upstream):
    • Cold maven-metadata.xml GET → 200 with ETag + both checksum headers; sha256/sha1/md5 of the served bytes match the advertised headers.
    • Second GET → cache hit, identical validator; If-None-Match → bodyless 304.
    • .sha1 sidecar equals the cold response's checksum header.
    • Cold artifact jar still carries no ETag (stream-first unchanged); jar hit carries the full set.
    • Real mvn 3.9.16 with AR as mirror: exact-version resolve and LATEST resolve succeed; the LATEST flow shows one upstream metadata GET and no follow-up .sha1 request in the proxy wide events, while artifact fills keep their sidecar fallbacks.
    • last_downloaded_at bumped on the delivered fill; an untouched seed row stayed null.

e2e scenario catalog

No scenario added: docs/testing/e2e/maven.md scopes itself to hosted repositories and lists "Virtual and remote (proxy/cache) Maven repositories" as out of scope until that capability ships remotely.

Diff size

437 insertions / 392 deletions across eight files: ~175 lines deleted production code (the streaming helper), ~200 test churn flipping pinned assertions to the new contract plus the new dispatch unit suite, ~140 docs (spec amendment + plan Step 20), and six review-fix commits (log-message attribution and arm naming, dead dupl-directive pruning with doc reattachment, CAS-miss divergence record, stale comment refreshes, spec Range scoping, corrected wait-bound wording). Splitting would separate the spec text from the code that must match it word-for-word; not split.

Post-Deploy Monitoring & Validation

  • Watch maven_remote_proxy wide events for operation="metadata": expect cache=miss events at 200 with byte counts, followed by no sidecar event for the same path from Resolver clients.
  • Latency: cold metadata TTFB now includes the upstream transfer plus commit. The fetch is bounded by request_total_timeout (30s default) and the body by the 16 MB cap; the flush and commit run on the detached fill session, whose only bound today is the S06 session TTL (#396). Alert on absolute p99 duration, not against a byte-cap-derived number.
  • Rollback: revert reverts behavior; no schema, config, or API-surface change.

Related to #732

Edited by Moaz Khalifa

Merge request reports

Loading
Loading