Skip to content

api: drive the route↔spec ratchet to zero — migrate every route through the seam, retire the parallel security model #9531

Description

@JSONbored

Context

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

  1. 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.
  2. 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.
  3. Relocate/re-export response schemas from src/openapi/schemas.ts into the contract package progressively, so the UI and generated clients can z.infer them (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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. Full branch-counted patch coverage per the house bar on every batch; each batch keeps test:ci green.

Non-goals

Deliverables

  • Every route in createApp() registered through the seam, in batches
  • Ratchet baseline driven to zero and the baseline file deleted
  • 38 inline request schemas relocated to @loopover/contract; response schemas progressively relocated
  • tags + stable operationId on every operation
  • isProtectedPath/applySecurityMetadata deleted; auth-parity meta-test in their place
  • check-openapi-settings-parity.ts retired
  • control-plane spec + drift check + typed tenant client; self-host infra endpoints specced
  • 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.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.orbGittensory Orb related - maintainer self-hosting analytics.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions