Skip to content

SCRUM-224 — Patch: load manifests into a lock-free in-memory set

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

What changed

New internal/manifest:

Piece Behaviour
Loader.LoadAll one query (channel_head ⋈ release), every channel; builds a new immutable Set and swaps it in
Loader.Reload(channel) copies the current set, replaces one channel, swaps (used by SCRUM-225's NOTIFY/poll)
Holder atomic.Pointer[Set]; readers do one Load() and a map lookup — no locks on the read path; Ready() is manifests not loaded until the first publish
Entry canonical Body, ETag = "<manifest_sha256>", ReleaseID, MinClientVersion
hash check release.manifest is jsonb, which does not keep the text manifest_sha256 was computed over (Postgres even reorders keys by length). Patch re-derives canonical bytes and refuses a row whose sha256 differs; that channel keeps its last good entry
Canonical identical rule to Config's schema.Canonical (SCRUM-215): keys sorted, no whitespace, floats canonicalised, integers exact
wiring patch.go runs LoadAll at startup (failure logged, startup continues, /readyz stays 503 so an empty DB doesn't crash-loop Patch); /readyz now uses holder.Ready
metric patch_current_release_id{channel}

How to verify

cd services/patch
go vet ./... && go test -race -count=1 ./...
# DB tests read Config's schema (apply services/config migrations to this DB first)
PATCH_TEST_DATABASE_URL='postgres://USER:PASS@127.0.0.1:5433/config_test?sslmode=disable' \
  go test -race -count=1 -v ./internal/manifest/
Test Proves
TestCanonical* order/whitespace independence, nested sort, 2^53+1 kept, idempotent, duplicate keys / trailing data rejected, the bootstrap migration's body is already canonical, float/int parity with Config
TestHolderReady, TestSetGet readiness before/after; missing channel
TestPublishedSetIsImmutable an old pointer still returns the old entry after a swap
TestConcurrentLoadAndSwap (-race) 8 readers vs 1000 swaps: every read is a complete entry whose ETag matches its body's sha256
TestLoadAllServesBootstrappedChannels dev/staging/live from the real DB; ETag = "+manifest_sha256+" = sha256(body)
TestLoadAllRejectsTamperedHash a row with a wrong sha is not published (done in a rolled-back tx)
TestLoadAllKeepsLastGoodEntryForARejectedChannel a reload that finds a tampered live keeps serving the previous live

Results at time of writing

  • go vet, go test -race (5 packages) against Postgres 16 with Config's migrations: pass.

How it was built

DeepSeek run scoped (Landlock) to services/patch (173 s, ~32k output tokens). Claude review: LoadAll changed to keep a rejected channel's last good entry (it dropped it), plus that test and a Config-parity test for Canonical.