feat(api): add session route shell#81
Conversation
PR was building, leaving #24 conflicting on nearly every file. The route shell itself is now redundant, but two pieces of #24 are genuinely net-new and absent from main — salvage them here, rebuilt on top of #47's merged code: - Code-first OpenAPI: apispec/specgen reflects the controllers' request/response types and project DTOs (the same types the handlers use at runtime) into openapi.yaml via swaggest. `cmd/genspec` + `go:generate` regenerate the committed, embedded spec; a drift test (TestBuild_MatchesEmbedded) and a route parity test (TestRouteSpecParity) fail CI if the spec and the code disagree. This replaces main's hand-maintained openapi.yaml so the "single source of truth" claim is actually enforced, not aspirational. - Typed frontend client: frontend/src/api/schema.d.ts is generated from that spec via openapi-typescript (`npm run gen:api`), consumed by a small openapi-fetch client. The frontend now gets its types from the daemon contract instead of hand-maintaining them. specgen lives outside apispec (which controllers import for the 501 stub) to avoid an import cycle. Handlers now encode named response DTOs (controllers/dto.go) instead of map[string]any so the generator reflects the real wire shapes. A gen-verify CI job regenerates both artifacts and fails on a stale commit. Tradeoff: the generated spec drops the hand-authored examples / x-rest-audit notes from #47's openapi.yaml; those can be re-added as operation metadata in specgen if wanted. Behaviour-only patch (no handler logic changes). Supersedes the codegen + frontend parts of #24. Refs #20, #47.
…chema names Resolve the four review comments on #59: - ProjectOrDegraded.MarshalJSON now errors when neither Project nor Degraded is set instead of silently emitting `{"project": null}`, which would breach the required oneOf[Project, Degraded] contract. - requestBody.required: true is now set for addProject and updateProjectConfig via WithCustomize — swaggest left it absent (== optional) before. - schemaName replaces the TrimPrefix catch-all with an exhaustive default→clean map; an unrecognised type is returned verbatim so it surfaces in the diff rather than silently colliding with an existing schema. - nonNullableSlices strips the spurious "null" swaggest unions into every Go slice, so `projects` is `Summary[]` not `Summary[] | null`; the list handler normalises a nil slice to [] so the wire matches the non-nullable schema. Regenerated openapi.yaml + frontend schema.d.ts. Refs #59.
Addresses the P1 follow-up on #59: returning an error from ProjectOrDegraded.MarshalJSON does not "surface" the contract violation — envelope.WriteJSON discards the encode error after the 200 status and a partial JSON frame have already been flushed, leaving the client with a truncated, unparseable 200 (worse than the previous null). Validate the invariant upstream instead: newGetProjectResponse now returns an error when the GetResult sets neither Project nor Degraded, and the get handler maps that to a 500 envelope before any status/body is written. MarshalJSON keeps the error branch only as an unreachable last-resort backstop, with the comment corrected to say so. Adds TestProjectsAPI_GetEmptyResultIs500 to lock the clean-500 behavior. Refs #59.
…pec API, regen) Rebased onto main, which landed several changes that intersect this branch: - writeProjectError is now 3-arg (status derived from project.Error.Kind); the get handler's degenerate-GetResult guard now calls the 3-arg form. - apispec.Spec dropped its YAML() method; add apispec.Embedded() returning the raw embedded bytes, used by the generator's drift + route-parity tests. - #62 removed Runtime/Reactions/ReactionConfig from the project types, so the regenerated openapi.yaml (and frontend schema.d.ts) drop those, and the dead ReactionConfig entry is removed from the schema-name map. - CI go.yml: keep main's new golangci-lint `lint` job alongside the gen-verify job, both reading the Go version from go.mod. go build/vet/test all pass; spec + TS regenerate with no drift; gofmt clean.
Three findings from the lint CI job (golangci-lint v2.12.2, which can't run locally against the go1.25 module): - gosec G306: genspec writes openapi.yaml with 0o600 instead of 0o644. - revive: add the missing doc comment on ProjectOrDegraded.MarshalJSON. - staticcheck SA1019: r.InterceptDefName is deprecated; pass jsonschema.InterceptDefName(schemaName) via DefaultOptions instead. No behavior change — the regenerated spec is byte-identical (the InterceptDefName move produces the same output). build/vet/test pass; gofmt clean.
Greptile SummaryThis PR adds the first session route-shell slice on top of the OpenAPI code-generation branch, mounting 501-backed handlers for list, spawn, get, restore, and send, and regenerating
Confidence Score: 5/5All five session handlers are 501 stubs that never touch real data; no existing behavior is changed. Every route returns NOT_IMPLEMENTED today, so there is no runtime path through the new session code that can fail, corrupt state, or expose data. The spec and DTO additions are straightforward and covered by the existing drift test. The only gap — missing enum constraints on domain string fields — is a spec quality concern for future client generators, not a correctness issue in the running service. No files require special attention; the generated openapi.yaml is produced deterministically by the drift-checked build pipeline. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant Router as chi Router (/api/v1)
participant SC as SessionsController
participant Spec as apispec.NotImplemented
Client->>Router: GET /api/v1/sessions
Router->>SC: list(w, r)
SC->>Spec: NotImplemented
Spec-->>Client: 501 NOT_IMPLEMENTED
Client->>Router: POST /api/v1/sessions
Router->>SC: spawn(w, r)
SC->>Spec: NotImplemented
Spec-->>Client: 501 NOT_IMPLEMENTED
Client->>Router: "GET /api/v1/sessions/{id}"
Router->>SC: get(w, r)
SC->>Spec: NotImplemented
Spec-->>Client: 501 NOT_IMPLEMENTED
Client->>Router: "POST /api/v1/sessions/{id}/restore"
Router->>SC: restore(w, r)
SC->>Spec: NotImplemented
Spec-->>Client: 501 NOT_IMPLEMENTED
Client->>Router: "POST /api/v1/sessions/{id}/send"
Router->>SC: send(w, r)
SC->>Spec: NotImplemented
Spec-->>Client: 501 NOT_IMPLEMENTED
Reviews (3): Last reviewed commit: "fix(api): remove unused session schema n..." | Re-trigger Greptile |
064c188 to
019c985
Compare
|
Closing this as superseded by #68, which merged the newer service-layer session API work on main. Any remaining useful pieces from this branch, like generated OpenAPI LF normalization or dropped-route test coverage, should be carried forward as a fresh small PR from main if still needed. |
Summary
Testing
Notes
This PR is stacked on #59 because OpenAPI is generated from Go contract code there. It intentionally does not hand-edit OpenAPI YAML beyond the generated artifact.