You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of #9515, and the follow-on to #9519. That issue builds the defineRoute seam (runtime zod validation + spec emission in one registration) and the CI ratchet that fails on any route without a spec entry, with a committed baseline of the currently-unspecced routes that may only shrink. This issue is the work of driving that baseline to zero.
The measured starting point: createApp() (src/api/routes.ts:1154) registers 242 routes; buildOpenApiSpec() (src/openapi/spec.ts:141) hand-registers 128 paths / 151 operations. 91 live routes have no spec entry, including every ORB management surface (POST /v1/orb/token, /v1/orb/relay*, /v1/orb/webhook, /v1/orb/oauth/callback, all six /v1/internal/orb/*, /v1/internal/fleet/analytics, /v1/internal/ops/stats, /v1/app/fleet/config-push, /v1/app/kill-switch, the DLQ admin quartet, PUT /v1/app/installations/:id/agent/bulk-settings, PUT /v1/repos/:owner/:repo/settings, the ai-key/linear-key routes, and ~20 /v1/internal/jobs/*).
Alongside that, 38 inline z.object(...) request schemas live in src/api/routes.ts (from :479) with ~70 .safeParse/.parse call sites, and ~150 response schemas sit in src/openapi/schemas.ts where they describe the spec but never validate a response.
Requirements
Migrate every remaining route through the defineRoute seam, in reviewable batches by route family (public → auth → app/control-panel → repos → contributors → lint/preflight/local/agent → webhooks/ORB ingress → /v1/internal/*). Each batch shrinks the ratchet baseline; the baseline file is deleted in the final batch.
Relocate request schemas to @loopover/contract as each batch lands, so the 38 inline schemas in routes.ts are gone at the end and any MCP tool wrapping the same route references the same schema object rather than a copy.
Every migrated operation carries tags and a stable operationId. The spec currently emits tags: [] and no systematic operationIds, so a client generator or an MCP server deriving tools from the spec would get one flat namespace with slugified names.
Delete the parallel security implementation.isProtectedPath() / applySecurityMetadata() (src/openapi/spec.ts:2062-2090) are a second, path-prefix-based model of the real gates (requiresApiToken, isPublicNoCredentialRoute, requireAppRole, the /v1/internal/* middleware) and already disagree with them. Once every route declares its auth, the stanzas derive from that declaration and these go away, with a meta-test asserting the declared level matches the enforcing middleware for every route class.
Retire scripts/check-openapi-settings-parity.ts once RepositorySettingsSchema/RepoSettingsPreviewSchema are z.infer-derived from the same source the handlers serialize. Its regex key-set diff covers 2 of ~150 schemas and becomes obsolete by construction, not by being deleted early.
Same treatment for the two surfaces outside the main app:control-plane/src/http-app.ts (no zod, no spec at all today; its only contract is prose plus the hardcoded fetches in packages/loopover-miner/lib/tenant-client.ts) gets zod + the seam + its own committed spec and drift check, and packages/loopover-miner/lib/tenant-client.ts types itself from those schemas. The four self-host infra endpoints in src/server.ts (/health, /ready, /metrics, /setup*) get spec entries under a selfhost-infra tag.
Fix, don't route around, what the migration surfaces. Known already: LocalBranchAnalysisSchema does not declare the predictedGate field its route emits (the PredictedGateVerdict schema authored in contract: create @loopover/contract — the single zod source for tool/API schemas, with a six-tool pilot #9517 is the fix). Expect more of this class — a route whose response schema and real payload disagree is a published-spec lie and gets fixed in its batch.
Full branch-counted patch coverage per the house bar on every batch; each batch keeps test:ci green.
Spec-vs-payload defects found during migration fixed in-batch, each noted in its PR
Expected outcome
apps/loopover-ui/public/openapi.json describes every route the app actually serves, with correct auth metadata, stable operationIds, and real tags. No route can exist without a schema. The ORB management surface — the precondition for #9522's management tools — is fully described, and the UI and clients derive their types from the same schemas the server validates against.
References
Part of #9515. Blocked by #9519 (needs the seam and the ratchet). Unblocks the full completion of #9282 and gives #9522 a specced management surface to bind to.
Context
Part of #9515, and the follow-on to #9519. That issue builds the
defineRouteseam (runtime zod validation + spec emission in one registration) and the CI ratchet that fails on any route without a spec entry, with a committed baseline of the currently-unspecced routes that may only shrink. This issue is the work of driving that baseline to zero.The measured starting point:
createApp()(src/api/routes.ts:1154) registers 242 routes;buildOpenApiSpec()(src/openapi/spec.ts:141) hand-registers 128 paths / 151 operations. 91 live routes have no spec entry, including every ORB management surface (POST /v1/orb/token,/v1/orb/relay*,/v1/orb/webhook,/v1/orb/oauth/callback, all six/v1/internal/orb/*,/v1/internal/fleet/analytics,/v1/internal/ops/stats,/v1/app/fleet/config-push,/v1/app/kill-switch, the DLQ admin quartet,PUT /v1/app/installations/:id/agent/bulk-settings,PUT /v1/repos/:owner/:repo/settings, theai-key/linear-keyroutes, and ~20/v1/internal/jobs/*).Alongside that, 38 inline
z.object(...)request schemas live insrc/api/routes.ts(from:479) with ~70.safeParse/.parsecall sites, and ~150 response schemas sit insrc/openapi/schemas.tswhere they describe the spec but never validate a response.Requirements
defineRouteseam, in reviewable batches by route family (public → auth → app/control-panel → repos → contributors → lint/preflight/local/agent → webhooks/ORB ingress →/v1/internal/*). Each batch shrinks the ratchet baseline; the baseline file is deleted in the final batch.@loopover/contractas each batch lands, so the 38 inline schemas inroutes.tsare gone at the end and any MCP tool wrapping the same route references the same schema object rather than a copy.src/openapi/schemas.tsinto the contract package progressively, so the UI and generated clients canz.inferthem (ui: derive API response types from the backend's real zod/OpenAPI schemas, stop hand-duplicating them #9282's mechanism) instead of hand-typing interfaces.tagsand a stableoperationId. The spec currently emitstags: []and no systematic operationIds, so a client generator or an MCP server deriving tools from the spec would get one flat namespace with slugified names.isProtectedPath()/applySecurityMetadata()(src/openapi/spec.ts:2062-2090) are a second, path-prefix-based model of the real gates (requiresApiToken,isPublicNoCredentialRoute,requireAppRole, the/v1/internal/*middleware) and already disagree with them. Once every route declares itsauth, the stanzas derive from that declaration and these go away, with a meta-test asserting the declared level matches the enforcing middleware for every route class.scripts/check-openapi-settings-parity.tsonceRepositorySettingsSchema/RepoSettingsPreviewSchemaarez.infer-derived from the same source the handlers serialize. Its regex key-set diff covers 2 of ~150 schemas and becomes obsolete by construction, not by being deleted early.control-plane/src/http-app.ts(no zod, no spec at all today; its only contract is prose plus the hardcoded fetches inpackages/loopover-miner/lib/tenant-client.ts) gets zod + the seam + its own committed spec and drift check, andpackages/loopover-miner/lib/tenant-client.tstypes itself from those schemas. The four self-host infra endpoints insrc/server.ts(/health,/ready,/metrics,/setup*) get spec entries under aselfhost-infratag.LocalBranchAnalysisSchemadoes not declare thepredictedGatefield its route emits (thePredictedGateVerdictschema authored in contract: create @loopover/contract — the single zod source for tool/API schemas, with a six-tool pilot #9517 is the fix). Expect more of this class — a route whose response schema and real payload disagree is a published-spec lie and gets fixed in its batch.test:cigreen.Non-goals
Deliverables
createApp()registered through the seam, in batches@loopover/contract; response schemas progressively relocatedtags+ stableoperationIdon every operationisProtectedPath/applySecurityMetadatadeleted; auth-parity meta-test in their placecheck-openapi-settings-parity.tsretiredcontrol-planespec + drift check + typed tenant client; self-host infra endpoints speccedExpected outcome
apps/loopover-ui/public/openapi.jsondescribes every route the app actually serves, with correct auth metadata, stable operationIds, and real tags. No route can exist without a schema. The ORB management surface — the precondition for #9522's management tools — is fully described, and the UI and clients derive their types from the same schemas the server validates against.References
Part of #9515. Blocked by #9519 (needs the seam and the ratchet). Unblocks the full completion of #9282 and gives #9522 a specced management surface to bind to.