docs: document the non-OpenAPI routes carve-out (R11)
10 routes are registered manually on the chi router in server.go rather
than generated from openapi.yaml. Added a 'Non-OpenAPI routes' comment
block at the top of NewHandler listing each route with its structural
reason for the carve-out:
- Auth/infra: /healthz, /api/v1/auth/oidc-*, /oidc-callback — bypass
auth middleware or aren't JSON API
- SSE override: /api/v1/events/stream — re-registered for Flush()
- Ad-hoc aggregations: /knowledge/recent, /knowledge/content/{id},
/activity/recent, /activity/session/{id}, /learning/timeline,
/learning/trend — derived shapes with no schema type yet
Updated .agents/dev/CONTRIBUTING.md §OpenAPI codegen with the carve-out
policy: if an ad-hoc route stabilizes, promote it to openapi.yaml with a
proper schema and migrate the serve* function to a strict handler.
This commit is contained in:
@@ -113,6 +113,23 @@ current phase status). To add a new capability:
|
|||||||
implement it in `internal/httpapi/impl.go`
|
implement it in `internal/httpapi/impl.go`
|
||||||
- Problem+JSON errors via `internal/httpapi/problem.go` — RFC 9457 format
|
- Problem+JSON errors via `internal/httpapi/problem.go` — RFC 9457 format
|
||||||
- Cursor pagination, If-Match/ETag, idempotency keys, SSE streaming
|
- Cursor pagination, If-Match/ETag, idempotency keys, SSE streaming
|
||||||
|
- **Non-OpenAPI routes carve-out:** ~10 routes are registered manually on
|
||||||
|
the chi router in `internal/httpapi/server.go` rather than generated from
|
||||||
|
`openapi.yaml`. These fall into three categories:
|
||||||
|
1. **Auth/infra** (`/healthz`, `/api/v1/auth/oidc-*`, `/oidc-callback`) —
|
||||||
|
must bypass the auth middleware or aren't JSON API endpoints.
|
||||||
|
2. **SSE override** (`/api/v1/events/stream`) — in the spec but
|
||||||
|
re-registered manually because the strict handler can't `Flush()` per
|
||||||
|
event.
|
||||||
|
3. **Ad-hoc aggregations** (`/api/v1/knowledge/recent`,
|
||||||
|
`/api/v1/knowledge/content/{id}`, `/api/v1/activity/recent`,
|
||||||
|
`/api/v1/activity/session/{id}`, `/api/v1/learning/timeline`,
|
||||||
|
`/api/v1/learning/trend`) — return derived/aggregate shapes that don't
|
||||||
|
map cleanly to a schema type. If one of these stabilizes, promote it
|
||||||
|
to `openapi.yaml` with a proper schema and migrate the `serve*`
|
||||||
|
function to a strict handler.
|
||||||
|
The full list with reasons is in the "Non-OpenAPI routes" comment block
|
||||||
|
at the top of `NewHandler` in `server.go`.
|
||||||
|
|
||||||
## Testing philosophy
|
## Testing philosophy
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,25 @@ func NewHandler(ctx context.Context, pool *db.Pool, cfg config.Config) http.Hand
|
|||||||
MaxAge: 86400,
|
MaxAge: 86400,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// ─── Non-OpenAPI routes (carve-out) ────────────────────────────────
|
||||||
|
//
|
||||||
|
// These routes are registered manually on the chi router rather than
|
||||||
|
// generated from api/openapi.yaml. Each has a structural reason it
|
||||||
|
// can't go through the strict-server codegen:
|
||||||
|
//
|
||||||
|
// /healthz — infra liveness probe, no auth, no /api/v1 prefix
|
||||||
|
// /api/v1/auth/oidc-* — auth flow, must run before auth middleware
|
||||||
|
// /oidc-callback — standalone HTML page, not a JSON API
|
||||||
|
// /api/v1/events/stream — in OpenAPI but re-registered for SSE Flush()
|
||||||
|
// /api/v1/knowledge/recent — ad-hoc aggregation, no schema type yet
|
||||||
|
// /api/v1/knowledge/content/{id} — returns raw markdown, not a gen type
|
||||||
|
// /api/v1/activity/recent — recency-ordered, not paginated
|
||||||
|
// /api/v1/activity/session/{id} — session-scoped aggregation
|
||||||
|
// /api/v1/learning/timeline — derived view, no backing schema type
|
||||||
|
// /api/v1/learning/trend — derived view, no backing schema type
|
||||||
|
//
|
||||||
|
// See .agents/dev/CONTRIBUTING.md §OpenAPI codegen for the policy.
|
||||||
|
|
||||||
// Liveness — no auth, no audit (plan SG18). Not exposed via Caddy.
|
// Liveness — no auth, no audit (plan SG18). Not exposed via Caddy.
|
||||||
r.Get("/healthz", func(w http.ResponseWriter, req *http.Request) {
|
r.Get("/healthz", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx, cancel := context.WithTimeout(req.Context(), 2*time.Second)
|
ctx, cancel := context.WithTimeout(req.Context(), 2*time.Second)
|
||||||
@@ -173,23 +192,27 @@ func NewHandler(ctx context.Context, pool *db.Pool, cfg config.Config) http.Hand
|
|||||||
// Custom (non-OpenAPI) route: recency-ordered knowledge + stats for the
|
// Custom (non-OpenAPI) route: recency-ordered knowledge + stats for the
|
||||||
// Knowledge page's "what the system has learned" view. Registered after
|
// Knowledge page's "what the system has learned" view. Registered after
|
||||||
// HandlerWithOptions so it wins over any generated catch-all.
|
// HandlerWithOptions so it wins over any generated catch-all.
|
||||||
|
// (See "Non-OpenAPI routes" carve-out block above.)
|
||||||
r.With(combinedAuth(cfg, false)).Get("/api/v1/knowledge/recent", s.serveRecentKnowledge)
|
r.With(combinedAuth(cfg, false)).Get("/api/v1/knowledge/recent", s.serveRecentKnowledge)
|
||||||
|
|
||||||
// Custom (non-OpenAPI) route: full markdown content for a knowledge
|
// Custom (non-OpenAPI) route: full markdown content for a knowledge
|
||||||
// entity (document/investigation/runbook) by its own id or slug — the
|
// entity (document/investigation/runbook) by its own id or slug — the
|
||||||
// generated /api/v1/knowledge/{id} route (GetEntityKnowledge) answers a
|
// generated /api/v1/knowledge/{id} route (GetEntityKnowledge) answers a
|
||||||
// different question (knowledge referencing this entity), not this one.
|
// different question (knowledge referencing this entity), not this one.
|
||||||
|
// (See "Non-OpenAPI routes" carve-out block above.)
|
||||||
r.With(combinedAuth(cfg, false)).Get("/api/v1/knowledge/content/{id}", s.serveKnowledgeContent)
|
r.With(combinedAuth(cfg, false)).Get("/api/v1/knowledge/content/{id}", s.serveKnowledgeContent)
|
||||||
|
|
||||||
// Custom (non-OpenAPI) routes: the global activity feed (recency-ordered,
|
// Custom (non-OpenAPI) routes: the global activity feed (recency-ordered,
|
||||||
// unlike ListExecutions which sorts by target for pagination) and the
|
// unlike ListExecutions which sorts by target for pagination) and the
|
||||||
// per-session "what did this session do" digest.
|
// per-session "what did this session do" digest.
|
||||||
|
// (See "Non-OpenAPI routes" carve-out block above.)
|
||||||
r.With(combinedAuth(cfg, false)).Get("/api/v1/activity/recent", s.serveRecentActivity)
|
r.With(combinedAuth(cfg, false)).Get("/api/v1/activity/recent", s.serveRecentActivity)
|
||||||
r.With(combinedAuth(cfg, false)).Get("/api/v1/activity/session/{id}", s.serveSessionDigest)
|
r.With(combinedAuth(cfg, false)).Get("/api/v1/activity/session/{id}", s.serveSessionDigest)
|
||||||
|
|
||||||
// Learning view: capability timeline + success trend, both derived from
|
// Learning view: capability timeline + success trend, both derived from
|
||||||
// executions (real, growing data) rather than the patterns/skills tables,
|
// executions (real, growing data) rather than the patterns/skills tables,
|
||||||
// which are correctly modeled but have no writers anywhere yet.
|
// which are correctly modeled but have no writers anywhere yet.
|
||||||
|
// (See "Non-OpenAPI routes" carve-out block above.)
|
||||||
r.With(combinedAuth(cfg, false)).Get("/api/v1/learning/timeline", s.serveLearningTimeline)
|
r.With(combinedAuth(cfg, false)).Get("/api/v1/learning/timeline", s.serveLearningTimeline)
|
||||||
r.With(combinedAuth(cfg, false)).Get("/api/v1/learning/trend", s.serveLearningTrend)
|
r.With(combinedAuth(cfg, false)).Get("/api/v1/learning/trend", s.serveLearningTrend)
|
||||||
|
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ the gitignore comment); `build` target ensures `bin/` exists; `clean` removes `b
|
|||||||
| R8 | Add `eslint`+`prettier`+`vitest` to `web/`; wire `svelte-check`+`tsc` into CI; add `web/` CI job | M | Low | ✅ done |
|
| R8 | Add `eslint`+`prettier`+`vitest` to `web/`; wire `svelte-check`+`tsc` into CI; add `web/` CI job | M | Low | ✅ done |
|
||||||
| R9 | Define `OikosEvent` discriminated union; eliminate ~15 `any` sites in web | S | Low |
|
| R9 | Define `OikosEvent` discriminated union; eliminate ~15 `any` sites in web | S | Low |
|
||||||
| R10 | Replace `<svelte:component>` in `ActivityTimeline.svelte:103`; fix `state_referenced_locally` warnings | S | Low | ✅ done |
|
| R10 | Replace `<svelte:component>` in `ActivityTimeline.svelte:103`; fix `state_referenced_locally` warnings | S | Low | ✅ done |
|
||||||
| R11 | Add the 8 manually-registered `serve*` routes to `openapi.yaml` (or document the carve-out) | S | Low |
|
| R11 | Add the 8 manually-registered `serve*` routes to `openapi.yaml` (or document the carve-out) | S | Low | ✅ done — documented the carve-out |
|
||||||
| R12 | Add `docs/mbse/README.md` "Last verified" header + scheduled re-verification; normalize ADR 0013/0014 template | S | Low |
|
| R12 | Add `docs/mbse/README.md` "Last verified" header + scheduled re-verification; normalize ADR 0013/0014 template | S | Low |
|
||||||
| R13 | Reconcile on-client path (`/opt/homelab/` vs `/opt/homelab-context/`) across AGENTS.md + CLIENTS.md | S | Low |
|
| R13 | Reconcile on-client path (`/opt/homelab/` vs `/opt/homelab-context/`) across AGENTS.md + CLIENTS.md | S | Low |
|
||||||
| R14 | Install `golangci-lint`/`staticcheck`/`govulncheck` locally + in CI | S | Low |
|
| R14 | Install `golangci-lint`/`staticcheck`/`govulncheck` locally + in CI | S | Low |
|
||||||
|
|||||||
Reference in New Issue
Block a user