Files
homelab-frontend/tasks/8.2-x-service-dispatcher.md
T

2.9 KiB

8.2 — X-Service/X-Resource dispatcher, capability auth, blind 5xx retry (GREEN)

Phase: 8 — ServiceAdapter CRD rollout Stage: RED Depends on: 8.1 (CRD, informer, in-memory registry)

Design contract: API_ROUTING_HYBRID_DESIGN.md §2, §4.

  • internal/server/router.go's ServeHTTP gets a new branch, checked before the existing path switch: if the request carries an X-Service header, dispatch to internal/serviceadapter/router.go, regardless of r.URL.Path
  • /v1/chat/completions, /v1/embeddings, /v1/rerank, /healthz, /readyz keep their existing path-based routing unchanged — matched first, never see X-Service
  • Dispatch key: X-Service header → adapter from 8.1's registry, then HTTP method + X-Resource header → {verb, upstreamPath} on that adapter
  • Unknown X-Service → 404 problem+json. Known service, unknown X-Resource/verb combination → 404 problem+json, not a silent proxy-through
  • Auth: spec.auth.capability is the default per adapter; a method's own auth.capability overrides it; auth.required: false at either level skips the capability check entirely. Depends on internal/auth (validated JWT middleware) existing — if it does not yet exist in this repo, stop and report instead of stubbing it
  • internal/resilience/retry.go (new): blind retry on any 5xx from the upstream, bounded attempts with backoff, gated by spec.retryable (default true) — wraps every outbound call this dispatcher makes
  • {id}-style path segments in upstreamPath (e.g. /v1/tables/{id}) are resolved from an explicit source — since routing is header-only at the gateway root, there is no URL path segment to take it from. Decide and document the actual source (query param, extra header, or body field) in this task's own notes before implementing; do not guess silently
  • Legacy /workflow* path stays mounted, delegating internally to this same dispatcher, per §2

Verify

# unknown service
curl -s -o /dev/null -w '%{http_code}\n' https://api.riotpiao.com/ \
  -H 'X-Service: does-not-exist' -H 'X-Resource: whatever'
# expected: 404

# known service, wrong verb+resource combination
curl -s -o /dev/null -w '%{http_code}\n' -X PATCH https://api.riotpiao.com/ \
  -H 'Authorization: Bearer <jwt>' -H 'X-Service: iam' -H 'X-Resource: role'
# expected: 404 (role only defines GET/POST in §3)

# capability override enforced
curl -s -o /dev/null -w '%{http_code}\n' https://api.riotpiao.com/ \
  -H 'Authorization: Bearer <jwt-with-memory:read-only>' \
  -H 'X-Service: memory' -H 'X-Resource: ingest' -X POST -d '{}'
# expected: 403 — ingest requires memory:write, token only has memory:read

# blind retry: point smoke-test adapter (from 8.1) at an upstream returning 503 twice then 200,
# confirm the caller sees 200 and the upstream log shows 3 attempts