Files
nike/.claude/rules/fastapi-dashboard.md
Robert Helewka 9f1d85f04b
All checks were successful
CVE Scan & Docker Build / security-scan (push) Successful in 33s
CVE Scan & Docker Build / build-and-push (push) Successful in 1m36s
docs: add Claude rules and workspace configuration
2026-07-14 13:34:05 -04:00

47 lines
2.5 KiB
Markdown

---
description: dashboard /api routes, tool-runner, metrics middleware, health routes, HAProxy edge, mount order
paths:
- "nike/server.py"
---
# FastAPI dashboard, routing & edge
- **Mount order matters and is load-bearing.** `dashboard.mount("/mcp", _mcp_app)`
goes **before** the SPA `StaticFiles` mount at `/` (which is greedy with
`html=True` SPA fallback). MCP must be mounted first or `/` swallows `/mcp`. The
MCP ASGI app is built early (`mcp.http_app(path="/")`) so its lifespan can be
nested inside the FastAPI lifespan — keep that wiring.
- **`/api/*` routes are thin façades** over `db`, `sportsdb`, and `mcp`. `/api/status`
(health cards), `/api/tools` (derived from `mcp.list_tools()` — the single source
the dashboard reads), `/api/logs` (the in-memory request log), `/api/cache/invalidate`
(flush both caches), `/api/run` (tool-runner: `mcp.get_tool``tool.fn(**args)`),
`/api/v1/telemetry` (browser error sink). Keep `/api/tools` derived, never
hand-maintained.
- **Prometheus middleware skips health/MCP paths.** `_SKIP_METRICS_PREFIXES =
("/live","/ready","/metrics","/mcp")` — probes and MCP transport must not inflate
`nike_http_requests_total`. It also normalises UUIDs in paths to `{id}` to bound
label cardinality. Keep both behaviours; add new health-ish prefixes to the skip
list.
- **`/metrics` is Prometheus exposition, IP-gated in Python.** `_METRICS_ALLOWED_NETS`
= the standard four ranges (`10.10.0.0/24`, `172.16.0.0/12`, `127.0.0.0/8`,
`::1/128`), no auth — HAProxy/Prometheus can't authenticate (`red_panda_standards.md`).
Don't widen it or add app-side auth.
- **Health routes register both slash forms** (`/live` + `/live/`, `/ready` +
`/ready/`). `/ready` returns 503 when `db.check_connection()` reports down. `/live`
is a bare `{"status": "ok"}`.
- **HAProxy is the edge.** `uvicorn.run(..., proxy_headers=True,
forwarded_allow_ips=config.TRUSTED_PROXY_IPS, ws="wsproto")`. The write routes
(`/api/run`, `/api/cache/invalidate`) are **not individually authenticated** —
access control is at HAProxy; the code comment says so. If you add a write route,
keep that assumption explicit; if Nike could be exposed directly, it needs auth.
`/api/v1/telemetry` stays unprotected on purpose (`sendBeacon` can't set headers).
- **Structured logging is re-applied in the lifespan** (`configure_logging` after
`create_pool`) because uvicorn overwrites handlers on `config.load()`. Don't drop
the second call, or JSON logging silently reverts to uvicorn's default.