56 lines
2.9 KiB
Markdown
56 lines
2.9 KiB
Markdown
# 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](../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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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
|
||
|
|
```
|