Skip to content

SCRUM-223 — Patch service foundation

Plan ref: PAT-B1 (docs/11-admin-plane-plan.md). Replaces the fmt.Println stub in services/patch (module path fixed to github.com/tanukifurhire/otomo/services/patch).

What exists

Ported from services/config's skeleton (same code, PATCH_* names):

Piece Notes
patch serve (default) no migrate: Patch owns no schema; it reads Config's DB as patch_ro
Config listen/metrics addrs, PATCH_DATABASE_URL, PATCH_BLOB_ROOT (read-only mount), staff JWKS URL/issuer/audience/refresh + clock skew (for dev/staging manifests), PATCH_POLL_INTERVAL (60s, for SCRUM-225), timeouts, log level
internal/auth Config's staff verifier, copied verbatim; constructed and counted in readiness, no route uses it yet
internal/blob read-only root check only (a directory stat); Patch never writes blobs
Public listener only a COM-5 404 catch-all
Internal listener /healthz, /readyz, /metrics (patch_*), pprof
/readyz Postgres and staff JWKS has keys and blob root is a directory and manifests loaded. The manifest check is a placeholder returning manifests not loaded until SCRUM-224, so 503 is expected for now
dockerfile, .env.example, README.md distroless nonroot, serve

How to verify

cd services/patch
go vet ./... && go test -race -count=1 ./...
PATCH_TEST_DATABASE_URL='postgres://USER:PASS@127.0.0.1:5433/config_test?sslmode=disable' go test -count=1 ./internal/store/

go build -o /tmp/patch . && PATCH_DATABASE_URL=… PATCH_BLOB_ROOT=/some/dir \
  PATCH_STAFF_JWKS_URL=http://127.0.0.1:18180/.well-known/jwks.json /tmp/patch &
curl -i localhost:9090/readyz     # 503 until SCRUM-224 (first failing check is named)
curl -i localhost:8080/healthz    # 404 COM-5 (internal only)

Results at time of writing

  • go vet, go test -race (4 packages, incl. DB readiness) pass.
  • Binary: /readyz 503 naming the first failing check (with no JWKS reachable: staff jwks not fetched yet …); /healthz 404 on the public port; 16 patch_* series. The manifests not loaded branch is covered by internal/server/server_test.go with injected checks.

How it was built

DeepSeek run scoped (Landlock) to services/patch (167 s, ~33k output tokens), adapting services/config. Claude review: diffed against Config, binary run.