Skip to content

SCRUM-214 — Config: draft validation endpoint

Plan ref: CFG-B4 (docs/11-admin-plane-plan.md). Stacked on SCRUM-213.

What changed

POST /api/admin/config/namespaces/{ns}/draft/validate (live_ops), body {"document": {…}} (same strict reading and 1 MiB cap as the draft PUT; the body reader is now shared). Validates against the namespace's latest schema and saves nothing (no audit row, draft revision unchanged).

{"valid": false, "schema_version": 2,
 "errors": [{"pointer": "/weapons/0/damage", "message": "got string, want integer"}]}
  • errors is [] when valid, never null; capped at 100, library order.
  • Pointers are RFC 6901 and name the failing instance location, so the UI can mark the exact field; a missing required property points at its parent object and the message names the property.
  • Compiled validators are cached per (namespace, schema_version) in schema.Cache (added to api.Handlers; versions are immutable, so no invalidation). A stored schema that no longer compiles → 500.
  • Numbers are decoded exactly (jsonschema.UnmarshalJSON), so integer checks are not fooled by float conversion.

How to verify

cd services/config
export CONFIG_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 'Validate' ./internal/schema/ ./internal/server/
Test Proves
schema.TestValidateAcceptsAValidDocument valid → no issues
schema.TestValidateNamesTheFailingInstance damage:"lots" → /weapons/0/damage
schema.TestValidateRequiredPointsAtTheParent missing required → parent pointer, property named
schema.TestValidateCapsIssues >100 failures → exactly 100
server.TestDraftValidateRoutesThroughTheLiveServer real schema PUT then: valid → valid:true, errors:[]; "lots" and -1 → /weapons/0/damage; viewer 403; unknown ns 404; draft revision unchanged

By hand (live_ops token $T, after giving ui.motd a schema):

curl -s -H "Authorization: Bearer $T" -d '{"document":{"weapons":[{"damage":"lots"}]}}' \
  localhost:8080/api/admin/config/namespaces/balance.weapons/draft/validate

Results at time of writing

  • go vet, go test -race ./... against Postgres 16: pass (7 packages).

How it was built

DeepSeek run scoped (Landlock) to services/config (106 s, ~18k output tokens). Claude review: handler and cache wiring read, suite re-run. No changes needed.