docs: spec limit semantics, pricing caveats, manifest hedge

I2 (controller ruling): docs/superpowers/specs/2026-08-19-traefik-llmgateway-design.md
described limits as "user overrides group" / "user limit if set, else
group limit, else unlimited" — stale relative to the actual, already-correct
implementation (buildLimitScopes/checkAndCount evaluate every configured
scope and return the first violation). Amended §2 and §7 to state both
scopes are enforced together and the tighter bound wins.

I4 (manifest hedge): .traefik.yml's groups.default was a bare "{}",
the shape empirically reproduced as a decode failure outside this
repo's own (necessarily simplified) yaegi-check harness. Changed to
"providers: []" — same zero semantics, no longer a bare empty object.
manifest_test.go's JSON mirror and drift-check token updated to match;
make yaegi-check still passes (its YAML-subset parser already handles
an inline "[]" under a nested map key).

M9 (pricing provenance): pricing.go's built-in table comment replaced
"verify before release" with a real source note (each provider's own
published list prices, recorded 2026-08, approximate). README's limits
section gained the matching caveat and points to the "pricing" config
override for billing-grade accuracy.

Doc nits: README's Kubernetes section now says the example manifest
applies cleanly once Traefik's CRDs (Middleware, IngressRoute) are
installed, rather than claiming kubectl apply -f works unconditionally.
README's Security notes section no longer claims "no auth-event logging
at all" (see the paired fix: commit) — it now describes what is logged
and that key material never appears.

This commit and its sibling fix: commit are the final wave closing out
the whole-branch review findings for docs/superpowers/sdd/2026-08-20-
traefik-llmgateway.

Gates: go test ./... -count=1 (-race) green, gofmt -l . empty, go vet
clean, make yaegi-check OK, golangci-lint 0 issues.
This commit is contained in:
2026-08-20 14:59:57 +01:00
parent 4b849fd6c7
commit c9ae14adf7
5 changed files with 32 additions and 19 deletions
+16 -8
View File
@@ -358,7 +358,10 @@ kept — a bad edit to the file never breaks already-authenticated traffic.
built-in per-model table (`pricing.go`) or a configured `pricing`
override. An unpriced model costs 0 and logs a once-per-model warning
(capped at 128 distinct unpriced model names per process lifetime, then
one summary warning).
one summary warning). The built-in table is **approximate** — list
prices as each provider published them, recorded 2026-08, and not kept
in sync automatically. Set `pricing` overrides for billing-grade
accuracy.
- **Storage**: `redis` configured wires a hand-rolled, stdlib-only RESP2
client (`resp.go`) — no `go-redis`, which is not Yaegi-interpretable —
against Redis, Valkey, or Dragonfly. Semantics are deliberately
@@ -421,10 +424,14 @@ kept — a bad edit to the file never breaks already-authenticated traffic.
provider response content into a log line; today's only caller
(`writeProviderUpstreamError`) reads that body directly and puts it in
the client-facing response, never in a log.
- **There is no auth-event logging at all** — `auth.go` has zero logging
calls of any kind. A key that fails lookup gets a plain 401 JSON
envelope and nothing else happens; there is no log line, successful or
failed, for a request to appear in.
- **Every authentication attempt is logged**, through one shared helper
(`logAuthEvent`, `logger.go`) called at each of the six routes that
authenticate a request (`llmgateway.go`'s `ServeHTTP`, and
`routes_unified.go`'s `handleModels`). A failed attempt logs the request
method, path, and remote address, and returns the usual 401 JSON
envelope. A successful attempt logs the resolved user's name and the
route. Neither line ever includes the presented API key — only the
identity it resolved to, never the key material itself.
## Known limitations
@@ -470,9 +477,10 @@ kept — a bad edit to the file never breaks already-authenticated traffic.
## Kubernetes
See [`examples/kubernetes.yaml`](examples/kubernetes.yaml) for a complete,
YAML-validated example manifest that `kubectl apply -f` runs as-is: a
`Secret` holding the users file, a `Middleware` custom resource carrying
the plugin's own configuration (validated against the real `Config`
YAML-validated example manifest that `kubectl apply -f` applies cleanly
once Traefik's CRDs (`Middleware`, `IngressRoute`) are installed in the
cluster: a `Secret` holding the users file, a `Middleware` custom resource
carrying the plugin's own configuration (validated against the real `Config`
struct — every field in that example round-trips through `New()`
cleanly), a dummy backing `Deployment`/`Service` (Traefik requires a real
router backend even though this middleware never actually reaches it —