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, andX-Checksum-Md5(proxy mode, FIPS rules). - Client conditional on a cold fill: evaluated against the just-committed representation — matching
If-None-Matchanswers a bodyless304(operator-approved design decision). - Range on a cold metadata GET: now honored (
206slice 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_atbumps rideserveRemoteCacheRowas 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:
- The S14 amendment normally lands in its own Approved MR before plan work opens.
- 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.xmlGET → 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. .sha1sidecar 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.16with AR as mirror: exact-version resolve andLATESTresolve succeed; theLATESTflow shows one upstream metadata GET and no follow-up.sha1request in the proxy wide events, while artifact fills keep their sidecar fallbacks. last_downloaded_atbumped on the delivered fill; an untouched seed row stayed null.
- Cold
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_proxywide events foroperation="metadata": expectcache=missevents 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