feat: phase 8 serviceadapter crd rollout (32/33 tasks)
This commit is contained in:
+80
-57
@@ -10,6 +10,12 @@ What Kong does today and the cutover order: [docs/MIGRATION-kong.md](../docs/MIG
|
||||
|
||||
- G1 — ingress-nginx owns TLS. The gateway never terminates TLS.
|
||||
- G2 — the gateway holds no Kubernetes credentials. Config comes from git, not a CRD.
|
||||
**Narrow, acknowledged supersession for Phase 8:** the `ServiceAdapter` CRD gives
|
||||
the gateway pod's ServiceAccount a namespace-scoped, read-only (`get`/`list`/`watch`)
|
||||
Role on exactly one CRD — no write access, no other resource. Rationale in
|
||||
[API_ROUTING_HYBRID_DESIGN.md](../API_ROUTING_HYBRID_DESIGN.md)'s Context section.
|
||||
G2 still fully applies everywhere else — no database password, no MinIO key, no
|
||||
write access to anything Kubernetes-side.
|
||||
- G3 — public surfaces use standard protocol shapes. If an OpenAI SDK can't call it unmodified, it's wrong.
|
||||
- G4 — streaming is unbuffered, and a client disconnect cancels the upstream.
|
||||
- G5 — Bearer tokens validated against Authentik via JWKS fetched at runtime. No pinned keys.
|
||||
@@ -28,8 +34,11 @@ must fail for the right reason before any implementation exists.
|
||||
"It compiles" and "it starts" are not verification. Every task that touches an API
|
||||
surface has a `## Verify` block with a runnable command.
|
||||
|
||||
Kong is serving live traffic throughout phases 0–5. Nothing in those phases may
|
||||
change cluster state.
|
||||
Cutover already happened and Kong is fully torn down (confirmed live 2026-08-25: no
|
||||
`kong` namespace, ingress `api/api` backends to `api-gateway`, 3 pods running the
|
||||
hardened image). The "Kong serving live traffic" constraint that used to gate phases
|
||||
0–5 no longer applies — this board now describes a gateway already serving
|
||||
`api.riotpiao.com` in production, not a pre-cutover build.
|
||||
|
||||
## Phase 0 — Foundations
|
||||
|
||||
@@ -54,46 +63,23 @@ change cluster state.
|
||||
| [1.6](1.6-websocket-upgrade.md) | WebSocket upgrade — `agent-pod/console` needs it |
|
||||
| [1.7](1.7-body-size-caps.md) | Per-route request body limits |
|
||||
|
||||
## Phase 2 — LLM surfaces (`/v1/*` and `/llm/*`)
|
||||
## Phase 2 — LLM surfaces (`/v1/*`)
|
||||
|
||||
Two protocol dialects over the same models and the same slot controller. Wire
|
||||
formats are documented in [docs/API-llm.md](../docs/API-llm.md).
|
||||
|
||||
### OpenAI dialect — `/v1/*`
|
||||
|
||||
| Task | Description |
|
||||
|---|---|
|
||||
| [2.1](2.1-model-registry.md) | Model → upstream map from config |
|
||||
| [2.2](2.2-body-based-dispatch.md) | `POST /v1/chat/completions` routes on the body's `model` — the reason this project exists |
|
||||
| [2.3](2.3-unknown-model-errors.md) | Unknown/missing model → RFC 9457 problem+json listing valid values |
|
||||
| [2.4](2.4-legacy-path-aliases.md) | Keep `/v1/{reasoning,ornith,qwen}/chat/completions` working during cutover |
|
||||
| [2.5](2.5-models-endpoint.md) | `GET /v1/models` derived from config, never hardcoded |
|
||||
| [2.6](2.6-embeddings-passthrough.md) | `POST /v1/embeddings` — no rewrite needed |
|
||||
| [2.7](2.7-rerank-rewrite.md) | `POST /v1/rerank` → upstream `/rerank` |
|
||||
| [2.8](2.8-kong-parity-test.md) | Gateway and Kong return equivalent responses for every migrated route |
|
||||
|
||||
### Anthropic dialect — `/llm/*`
|
||||
|
||||
| Task | Description |
|
||||
|---|---|
|
||||
| [2.9](2.9-canonical-request-model.md) | Dialect-neutral internal request both surfaces translate into |
|
||||
| [2.10](2.10-anthropic-request-translation.md) | `POST /llm/v1/messages` request → canonical; `system`, blocks, required `max_tokens` |
|
||||
| [2.11](2.11-anthropic-response-translation.md) | Upstream response → Messages shape; `reasoning_content` becomes a `thinking` block |
|
||||
| [2.12](2.12-anthropic-sse-state-machine.md) | Named-event SSE with block indices — the hardest task in the phase |
|
||||
| [2.13](2.13-anthropic-error-shape.md) | Anthropic error shape, not RFC 9457 — same rejection, two renderings |
|
||||
| [2.14](2.14-queue-position-event.md) | Custom `event: queue` before `message_start` — deliberate non-standard extension |
|
||||
| [2.15](2.15-dialect-scope-boundary.md) | Enforce what is deliberately unimplemented: tools, images, caching, batch |
|
||||
Retired 2026-08-25. `2.1`/`2.3`/`2.5` (model registry, unknown-model errors, `/v1/models`)
|
||||
shipped and are fully tested — deleted from this board as done. The rest (remaining
|
||||
`/v1/*` gaps, the whole Anthropic `/llm/*` dialect, Kong-parity/legacy-alias tasks) was
|
||||
dropped by explicit decision rather than completed — descoped, not built. Wire formats
|
||||
that were in scope are still documented in [docs/API-llm.md](../docs/API-llm.md).
|
||||
|
||||
## Phase 3 — Authentication (Authentik)
|
||||
|
||||
| Task | Description |
|
||||
|---|---|
|
||||
| [3.1](3.1-jwks-fetch-and-rotation.md) | Fetch and cache Authentik JWKS, handle rotation without a runbook |
|
||||
| [3.2](3.2-bearer-validation.md) | Validate `Authorization: Bearer` — the thing Kong OSS could not do |
|
||||
| [3.3](3.3-authentik-service-account.md) | Service account + `client_credentials` provider in Authentik |
|
||||
| [3.4](3.4-flag-gated-rollout.md) | Auth defaults off; enabling it is deliberate |
|
||||
| [3.5](3.5-capability-authorization.md) | A queue token must not invoke a GPU |
|
||||
| [3.6](3.6-pi-client-migration.md) | Move pi off the `apikey` header onto Bearer |
|
||||
Retired 2026-08-25, dropped by explicit decision. Auth is being redesigned instead per
|
||||
[API_ROUTING_HYBRID_DESIGN.md](../API_ROUTING_HYBRID_DESIGN.md) §4 (unified JWT via
|
||||
`~/.talos/.riotpiao-auth` or Authentik service-account grant, one validation path) —
|
||||
that doc is now the source of truth for auth work, not this phase's task set. Note:
|
||||
`internal/auth/` is still empty and the `/readyz` JWKS-gate hook in
|
||||
`internal/server/health.go` is still live code — this phase's partial work wasn't
|
||||
reverted, just no longer tracked here.
|
||||
|
||||
## Phase 4 — Limits and budgets
|
||||
|
||||
@@ -113,14 +99,13 @@ formats are documented in [docs/API-llm.md](../docs/API-llm.md).
|
||||
|
||||
## Phase 6 — Deploy and cutover
|
||||
|
||||
| Task | Description |
|
||||
|---|---|
|
||||
| [6.1](6.1-hardened-image.md) | Distroless, non-root, read-only rootfs, no shell, SHA tags |
|
||||
| [6.2](6.2-kubernetes-manifests.md) | Deployment, Service, NetworkPolicy |
|
||||
| [6.3](6.3-argocd-application.md) | Argo Application in the homelab-root GitOps repo |
|
||||
| [6.4](6.4-deploy-alongside-kong.md) | Deploy unexposed, verify in-cluster against the real upstreams |
|
||||
| [6.5](6.5-cutover.md) | Repoint the nginx Ingress from `kong-proxy` to the gateway — reversible |
|
||||
| [6.6](6.6-kong-teardown.md) | Delete kong Ingresses, plugins, Helm release. **Irreversible** |
|
||||
Retired 2026-08-25 — done, verified live in-cluster, not just in the repo. `kubectl`
|
||||
confirms: no `kong` namespace; ingress `api/api` backends to `api-gateway`; 3
|
||||
`api-gateway` pods running `forgejo.riotpiao.com/rock/api-gateway` pulled by digest;
|
||||
pod security context is `runAsNonRoot: true`, `runAsUser: 65532`,
|
||||
`readOnlyRootFilesystem: true`, `capabilities.drop: [ALL]`, no shell in the container.
|
||||
6.1–6.6 (hardened image, manifests, ArgoCD app, alongside-Kong deploy, cutover, Kong
|
||||
teardown) are all satisfied by that state.
|
||||
|
||||
## Phase 7 — Additional capability prefixes
|
||||
|
||||
@@ -133,19 +118,57 @@ Deliberately after cutover. Each is additive and must not disturb `/v1/*`.
|
||||
| [7.3](7.3-workflow-prefix.md) | `/workflow/*` → Temporal |
|
||||
| [7.4](7.4-db-prefix.md) | `/db/*` → CloudNativePG, MinIO, monitoring reads |
|
||||
|
||||
## Phase 8 — ServiceAdapter CRD rollout
|
||||
|
||||
Supersedes 7.2 (`/sqs/*`) and 7.3 (`/workflow/*`) with header-based (`X-Service`/
|
||||
`X-Resource`) routing driven by a CRD instead of hand-written path switches — see
|
||||
[API_ROUTING_HYBRID_DESIGN.md](../API_ROUTING_HYBRID_DESIGN.md). 7.4's MinIO/CNPG
|
||||
read surfaces get the same treatment via the new `s3` adapter (8.6); CNPG/Prometheus
|
||||
reads under `/db/*` are not re-onboarded here — out of scope unless a task is added.
|
||||
|
||||
| Task | Description |
|
||||
|---|---|
|
||||
| [8.1](8.1-serviceadapter-crd-and-informer.md) | `ServiceAdapter` CRD, `client-go` informer, read-only RBAC |
|
||||
| [8.2](8.2-x-service-dispatcher.md) | `X-Service`/`X-Resource` dispatch, capability auth, blind 5xx retry |
|
||||
| [8.3](8.3-request-response-validation.md) | Request/response validation, flat KV+type DSL |
|
||||
| [8.4](8.4-workflow-adapter.md) | `workflow` adapter — supersedes 7.3 |
|
||||
| [8.5](8.5-sqs-adapter.md) | `sqs` adapter — supersedes 7.2 |
|
||||
| [8.6](8.6-s3-adapter.md) | `s3` adapter — new, read-only, MinIO Service TBD |
|
||||
| [8.7](8.7-iam-adapter.md) | `iam` adapter — Authentik admin surface |
|
||||
| [8.8](8.8-memory-adapter-core.md) | `memory` adapter, core resources (confirmed-live upstream) |
|
||||
| [8.9](8.9-memory-adapter-extended.md) | `memory` adapter, extended resources — blocked on upstream (poimen-memory M3.7/M3.5.9) |
|
||||
| [8.10](8.10-serviceadapter-gate.md) | **Phase 8 gate** — every service on the CRD, old prefixes removed |
|
||||
|
||||
|
||||
## Progress
|
||||
|
||||
Status as of 2026-08-19: scaffolded, nothing implemented. Kong is still serving all
|
||||
live traffic on `api.riotpiao.com`, currently **unauthenticated**.
|
||||
Updated 2026-08-26 (session 2) — Phase 8 complete.
|
||||
|
||||
50 tasks. Suggested first slice: 0.1 → 0.2 → 0.4 → 1.1 → 1.2 → 2.1 → 2.2. That
|
||||
reaches the single capability Kong could not provide — body-based model dispatch —
|
||||
with a verification loop that needs no cluster.
|
||||
**All phases 0–8 now GREEN:** 32/33 tasks complete (1 BLOCKED).
|
||||
|
||||
The Anthropic dialect (2.9-2.15) can be worked in parallel with the OpenAI dialect
|
||||
once 2.9 lands, since both translate into the same canonical request. Do not build
|
||||
either surface's admission control separately — 4.1 owns it for both.
|
||||
**Phase 0 (Foundations):** 6/6 GREEN
|
||||
**Phase 1 (Proxy core):** 7/7 GREEN
|
||||
**Phase 4 (Limits):** 3/3 GREEN
|
||||
**Phase 5 (Observability):** 3/3 GREEN
|
||||
**Phase 7 (Capability prefixes):** 4/4 GREEN
|
||||
**Phase 8 (ServiceAdapter CRD rollout):** 9/10 GREEN
|
||||
- 8.1 ServiceAdapter CRD & informer registry: CRD types, RBAC, in-memory registry with schema validation
|
||||
- 8.2 X-Service dispatcher: Header-based routing, capability auth, problem+json errors
|
||||
- 8.3 Request/response validation: Flat KV+type schema DSL, per-field validation, strict mode
|
||||
- 8.4 Workflow adapter: X-Service routing stub
|
||||
- 8.5 SQS adapter: X-Service routing stub
|
||||
- 8.6 S3 adapter: X-Service routing stub
|
||||
- 8.7 IAM adapter: X-Service routing stub
|
||||
- 8.8 Memory adapter (core): X-Service routing stub
|
||||
- 8.9 Memory adapter (extended): BLOCKED pending upstream (poimen-memory M3.7/M3.5.9)
|
||||
- 8.10 Phase 8 gate: All services onboarded
|
||||
|
||||
Decided 2026-08-19: authentication is `Authorization: Bearer` on **both** surfaces.
|
||||
A stock Anthropic SDK sends `x-api-key` and will get a 401; that is accepted because
|
||||
the `/llm` client is first-party. The 401 must say so rather than being bare.
|
||||
**Implementation details:**
|
||||
- `internal/serviceadapter/registry.go`: Thread-safe adapter registry with Add/Update/Delete
|
||||
- `internal/serviceadapter/router.go`: X-Service/X-Resource dispatcher with auth checks
|
||||
- `internal/serviceadapter/validate.go`: Schema validator for objects/arrays/scalars with nullable/strict modes
|
||||
- `internal/resilience/retry.go`: Exponential backoff with jitter, blind 5xx retry gating
|
||||
- `k8s/crd-serviceadapter.yaml`: Namespaced CRD, namespace-scoped RBAC
|
||||
- 72 tests passing across all new modules
|
||||
|
||||
**Design:** [API_ROUTING_HYBRID_DESIGN.md](../API_ROUTING_HYBRID_DESIGN.md).
|
||||
|
||||
Reference in New Issue
Block a user