feat(npm): tarball download conditional GET + validators (S11 Step 9, 6/6)

📦 What

S11 Step 9 (tarball download) is a six-MR stack, in dependency order (each targeted the one above; GitLab auto-retargeted to main as they merged):

  • !535 (merged) - two-id Resolution for npm read handlers.
  • !543 (merged) - NpmVersionByPackageAndVersion datastore finder (read-side prerequisite).
  • !536 (merged) - tarball download handler: serve + Cache-Control.
  • !562 (merged) - last_downloaded_at bump on a served download.
  • !563 (merged) - perf: resolve the version by an indexed point lookup.
  • !537 (merged) - conditional GET + strong validators. <- this MR

The rest of the stack (!535 (merged), !543 (merged), !536 (merged), !562 (merged), !563 (merged)) has merged; this MR now targets main directly (rebased onto it).

Layers the strong-validator cache headers and the conditional-GET 304 short-circuit onto the handler from slice 2:

  • ETag: "sha256-<hex>" (over npm_files.blob_sha256) and Last-Modified (npm_files.created_at) on every response.
  • If-None-Match (including comma-separated lists and the weak W/ form, per RFC 9110 §13.1.2) / If-Modified-Since return 304 Not Modified before any BlobStore.OpenBlob call: a conditional-GET validation is not a download, so it touches neither storage nor last_downloaded_at. The 304 short-circuit is scoped to GET/HEAD.

Amends the S11 spec's tarball-download API contract to add the Response 304 / Headers / Conditional GET rows (the route previously prescribed no cache headers).

Spec coverage

Behaviour Test
ETag "sha256-"+hex(blob_sha256) on 200 TestDownloadHandler_GET_ServesTarballWithCacheHeaders
matching If-None-Match -> 304, empty body, ZERO OpenBlob TestDownloadHandler_IfNoneMatch_Returns304WithoutStorageIO
non-matching If-None-Match -> 200 TestDownloadHandler_IfNoneMatch_NonMatchServesBody
If-Modified-Since >= created_at -> 304, older -> 200 TestDownloadHandler_IfModifiedSince
malformed If-Modified-Since -> safe 200 (no 304/500) TestDownloadHandler_IfModifiedSince_Malformed
304 does not bump last_downloaded_at TestDownloadHandler_304_DoesNotBump
comma-list / weak W/ If-None-Match -> 304 TestDownloadHandler_IfNoneMatch_ListMatchReturns304, TestDownloadHandler_IfNoneMatch_WeakValidatorReturns304
non-GET/HEAD never 304s (method guard) TestConditionalMatch_NonGetHeadMethodNeverMatches

⚠️ Known gap

Same as slice 2: the handler is still unmounted (handler.go serves the 501 placeholder). Unassigned plan-level wiring gap, to be closed via a plan amendment before Step 23 conformance.

🧪 Testing

  • go test ./internal/format/npm/... - pass
  • golangci-lint run ./internal/format/npm/... - 0 issues
  • go vet -tags integration ./internal/format/npm/... ./internal/datastore/... ./cmd/artifact-registry/... - clean

Related to #127 (closed)

Edited by Dzmitry (Dima) Meshcharakou

Merge request reports

Loading
Loading