Baseline for the Kong replacement on api.riotpiao.com. Brings the working tree under version control for the first time: gateway source, the task board that drives the agent runs, test fixtures, and K8s manifests. Anchor the gateway ignore rule to the repo root. Unanchored, "gateway" also matched the cmd/gateway/ source directory, so the program entrypoint was excluded from every commit. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
8.0 KiB
Task board — homelab-frontend
The Go API gateway replacing Kong OSS on api.riotpiao.com.
Contract: REQUIREMENTS.md. Why: ADR-0001. What Kong does today and the cutover order: docs/MIGRATION-kong.md.
Rules carried from the ADR and requirements
- G1 — ingress-nginx owns TLS. The gateway never terminates TLS.
- G2 — the gateway holds no Kubernetes credentials. Config comes from git, not a CRD.
- 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.
- G6 — every timeout, body cap and concurrency limit is explicit in config.
- G7 — deployment flows through git and Argo. No
kubectl apply, nohelm upgrade.
How to work these
Each task is self-contained — it states what must be true, not how to build it. Reason out the implementation; the acceptance criteria are the contract.
Stages follow red-green-refactor. A task marked RED means the test comes first and must fail for the right reason before any implementation exists.
Verification means asserting on a real HTTP response — status, headers, body.
"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.
Phase 0 — Foundations
| Task | Description |
|---|---|
| 0.1 | Go module, entrypoint, graceful shutdown |
| 0.2 | Declarative route/upstream config from git, fail-loud validation |
| 0.3 | /healthz and /readyz |
| 0.4 | Run with no cluster, no kubeconfig, no credentials — stub upstreams |
| 0.5 | Structured logs, no secrets or bodies |
| 0.6 | CI: build, vet, test, govulncheck |
Phase 1 — Proxy core
| Task | Description |
|---|---|
| 1.1 | Reverse proxy to a configured upstream, connection reuse |
| 1.2 | SSE and chunked responses pass through unbuffered |
| 1.3 | Client disconnect cancels the upstream request |
| 1.4 | Explicit connect/read/write timeouts per route |
| 1.5 | Hop-by-hop stripping, X-Forwarded-* from nginx |
| 1.6 | WebSocket upgrade — agent-pod/console needs it |
| 1.7 | Per-route request body limits |
Phase 2 — LLM surfaces (/v1/* and /llm/*)
Two protocol dialects over the same models and the same slot controller. Wire formats are documented in docs/API-llm.md.
OpenAI dialect — /v1/*
| Task | Description |
|---|---|
| 2.1 | Model → upstream map from config |
| 2.2 | POST /v1/chat/completions routes on the body's model — the reason this project exists |
| 2.3 | Unknown/missing model → RFC 9457 problem+json listing valid values |
| 2.4 | Keep /v1/{reasoning,ornith,qwen}/chat/completions working during cutover |
| 2.5 | GET /v1/models derived from config, never hardcoded |
| 2.6 | POST /v1/embeddings — no rewrite needed |
| 2.7 | POST /v1/rerank → upstream /rerank |
| 2.8 | Gateway and Kong return equivalent responses for every migrated route |
Anthropic dialect — /llm/*
| Task | Description |
|---|---|
| 2.9 | Dialect-neutral internal request both surfaces translate into |
| 2.10 | POST /llm/v1/messages request → canonical; system, blocks, required max_tokens |
| 2.11 | Upstream response → Messages shape; reasoning_content becomes a thinking block |
| 2.12 | Named-event SSE with block indices — the hardest task in the phase |
| 2.13 | Anthropic error shape, not RFC 9457 — same rejection, two renderings |
| 2.14 | Custom event: queue before message_start — deliberate non-standard extension |
| 2.15 | Enforce what is deliberately unimplemented: tools, images, caching, batch |
Phase 3 — Authentication (Authentik)
| Task | Description |
|---|---|
| 3.1 | Fetch and cache Authentik JWKS, handle rotation without a runbook |
| 3.2 | Validate Authorization: Bearer — the thing Kong OSS could not do |
| 3.3 | Service account + client_credentials provider in Authentik |
| 3.4 | Auth defaults off; enabling it is deliberate |
| 3.5 | A queue token must not invoke a GPU |
| 3.6 | Move pi off the apikey header onto Bearer |
Phase 4 — Limits and budgets
| Task | Description |
|---|---|
| 4.1 | Cap concurrent reasoning requests below 8 slots, bounded queue |
| 4.2 | Request budget per identified caller per window |
| 4.3 | RFC 9457 rejections with Retry-After |
Phase 5 — Observability
| Task | Description |
|---|---|
| 5.1 | Match the retiring Kong plugin: rate, latency, status, bandwidth, upstream health |
| 5.2 | In-flight per upstream, queue depth, slot occupancy, rejections by reason |
| 5.3 | ServiceMonitor so Prometheus scrapes it |
Phase 6 — Deploy and cutover
| Task | Description |
|---|---|
| 6.1 | Distroless, non-root, read-only rootfs, no shell, SHA tags |
| 6.2 | Deployment, Service, NetworkPolicy |
| 6.3 | Argo Application in the homelab-root GitOps repo |
| 6.4 | Deploy unexposed, verify in-cluster against the real upstreams |
| 6.5 | Repoint the nginx Ingress from kong-proxy to the gateway — reversible |
| 6.6 | Delete kong Ingresses, plugins, Helm release. Irreversible |
Phase 7 — Additional capability prefixes
Deliberately after cutover. Each is additive and must not disturb /v1/*.
| Task | Description |
|---|---|
| 7.1 | /cluster/* → atlas (riotpiao-backend) |
| 7.2 | /sqs/* → kmsvc management-service, Kafka |
| 7.3 | /workflow/* → Temporal |
| 7.4 | /db/* → CloudNativePG, MinIO, monitoring reads |
Progress
Status as of 2026-08-19: scaffolded, nothing implemented. Kong is still serving all
live traffic on api.riotpiao.com, currently unauthenticated.
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.
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.
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.