Test with actual JWT from Authentik (poimen-memory app):
- ✅ Decodes real Authentik JWT
- ✅ Validates signature against Authentik JWKS
- ✅ Verifies claims (iss, aud, sub)
- ✅ Correctly rejects tokens with wrong audience
Token from: https://authentik.riotpiao.com
Issuer: https://authentik.riotpiao.com/application/o/poimen-memory/
Audience: poimen-memory
Algorithm: RS256 (signed by Authentik)
Proves JWT validation works with real Authentik tokens.
For SQS: Need to create SQS OAuth2 app in Authentik and grant
homelab-admin user SQS permissions.
Comprehensive end-to-end testing:
SQS (JWT required):
✅ Reject without Authorization header (403)
✅ Reject with invalid JWT (403)
Memory (no JWT):
✅ POST query (200)
✅ GET projects (200)
✅ POST create (200)
S3 (no JWT):
✅ GET list-objects (200)
✅ PUT create-object (201)
IAM (no JWT):
✅ GET list-roles (200)
✅ POST create-user (201)
Error handling:
✅ Unknown service returns 404
All tests pass with real HTTP traffic through gateway.
Proves routing, auth, and proxying work correctly.
Tests that verify actual service operations:
- SQS send-message routing
- S3 list-objects with JWT pass-through
- Memory query routing
- IAM with JWT
- Authorization header pass-through to services
Tests gracefully skip if services unreachable (expected behavior).
Tests get real JWT from Authentik if credentials provided.
Run: GATEWAY_URL=http://localhost:8080 ./scripts/test-integration.sh
Or: GATEWAY_URL=https://api.riotpiao.com \
AUTHENTIK_CLIENT_ID=xxx AUTHENTIK_CLIENT_SECRET=yyy \
./scripts/test-integration.sh
Adapters defined in config.yaml alongside routes and models.
Parsed by existing config loader, populated into registry at startup.
Removed: client-go deps, REST loader, informer, nginx proxy,
CiliumNetworkPolicy, apis/gateway/v1/ (duplicate types).
Kept: merged CI pipeline, imagePullPolicy Always, CA certs in Dockerfile.
http.Server.WriteTimeout is an absolute deadline over the whole
response, not an inactivity timeout -- 15s was cutting off SSE streams
from the reasoning model mid-generation, surfacing to clients as a
"terminated" error well before the model finished. Switch to
ReadHeaderTimeout (protects against slow headers without capping body
duration) and raise WriteTimeout to match the edge nginx Ingress's
proxy-read/send-timeout of 3600s.
Creates:
- cmd/worker/main.go: Worker that registers workflows and activities
- cmd/test-workflow/main.go: Test client to trigger workflows
Adds go.temporal.io/sdk dependency to go.mod.
- 2.3: unknown model errors (400 + RFC 9457 problem+json with valid_models)
- 2.5: GET /v1/models endpoint (derived from config, not hardcoded)
- 2.6: POST /v1/embeddings passthrough (body-based dispatch, no rewrite)
- 2.7: POST /v1/rerank with path rewrite (/v1/rerank → /rerank)
- wire proxy.Handler in main.go (was using dummy handler)
- 140+ tests passing, race detector clean
- all requests: client → nginx → gateway → upstreams
- ready for config deployment to go live
- Dockerfile: multi-stage, distroless nonroot, CGO_ENABLED=0 static, commit
SHA stamped via VERSION build arg.
- .forgejo/workflows/ci.yaml: Forgejo reads .forgejo/, not .github/, and the
runner declares only the "docker" label. Verify job on every push; image
build and push gated to main.
- Drop .github/workflows/ci.yml — this remote is Forgejo, so it never ran.
- deployment.yaml: image from the Forgejo registry, forgejo-registry pull
secret, runAsUser 65532 to match distroless nonroot.
- kustomization.yaml: pin the tag in one place. Promoting a build is a
one-line newTag bump, never :latest.
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]>