Skip to content

SCRUM-226 — Patch: manifest endpoint with ETag / 304

Plan ref: PAT-B5 (docs/11-admin-plane-plan.md). Stacked on SCRUM-225.

Behaviour — GET /patch/v1/{channel}/manifest (HEAD too)

Case Response
channel ∉ dev/staging/live 404 not_found "no such channel"
dev / staging without a valid staff token 401 with the verifier's code (re-checked here; the player gateway also gates them)
staff token with no role 403 insufficient_role
channel not loaded yet 503 not_ready "manifest for is not loaded"
If-None-Match matches (*, a list, weak comparison ignoring W/) 304, empty body
otherwise 200, the canonical manifest bytes verbatim

200 and 304 both carry ETag: "<manifest_sha256>", Cache-Control: no-cache (the client must revalidate; a silently stale manifest is a client on the wrong build) and X-Min-Client-Version. The read path is one atomic load and a map lookup: no locks, no JSON encoding. Metric patch_manifest_requests_total{channel,result="200|304"}; auth rejections reuse patch_token_rejected_total.

How to verify

cd services/patch
export PATCH_TEST_DATABASE_URL='postgres://USER:PASS@127.0.0.1:5433/config_test?sslmode=disable'
go vet ./... && go test -race -count=1 ./...
go test -count=1 -v -run 'Manifest|IfNoneMatch' ./internal/api/ ./internal/server/
Test Proves
TestIfNoneMatch exact, list, spaces, W/ either side, *, no match, malformed members ignored, empty header
TestManifestLiveServesBytesAndValidators / …Revalidation / …HeadServesNoBody body byte-for-byte, three headers; 304 with headers and no body; HEAD no body
TestManifestErrors 404 unknown channel, 503 not loaded
TestManifestRestrictedChannelAuth dev: no token 401, player-issuer token 401, roleless staff 403, viewer 200
TestManifestRouteServesAndCounts the metric counts 200 and 304 separately
TestManifestServesBootstrappedLive (DB) the real live manifest; ETag = "+manifest_sha256+"; 304 on revalidation

Live check done during review (real binary, test DB):

GET /patch/v1/live/manifest  -> 200, Etag "476ad8d1…07f4" (= channel_head's manifest_sha256),
                                Cache-Control: no-cache, X-Min-Client-Version: 0.0.0,
                                {"channel":"live","config":{},"format":1,"min_client_version":"0.0.0","packs":[],"release_id":3}
  + If-None-Match: <that etag> -> 304, 0 body bytes
GET /patch/v1/staging/manifest (no token) -> 401 missing_token
patch_manifest_requests_total{channel="live",result="200"} 3, {…,result="304"} 1

Results at time of writing

  • go vet, go test -race (6 packages) against Postgres 16: pass; live check above.

How it was built

DeepSeek run scoped (Landlock) to services/patch (161 s, ~29k output tokens). Claude review: handler and If-None-Match parser read; curl check. No changes needed.