⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
⚠️ Required pattern: mirror how GET /v1/repos/{owner}/{repo}/gate-config/effective is documented
in src/openapi/spec.ts (schema registered via registry.register(...), then attached to a
registry.registerPath({ path, method, ... }) call) — issue #6611 fixed the exact same class of gap
for that route.
Context
src/api/routes.ts has two live, tested /v1/scoring/* POST routes, each already backed by an MCP
tool with its own Zod output shape in src/mcp/server.ts:
POST /v1/scoring/eligibility-plan → loopover_get_eligibility_plan (outputSchema: eligibilityPlanOutputSchema)
POST /v1/scoring/explain-breakdown → loopover_explain_score_breakdown (outputSchema: scoreBreakdownOutputSchema)
Neither route appears anywhere in src/openapi/spec.ts — grep for "/v1/scoring/eligibility-plan"
and "/v1/scoring/explain-breakdown" in that file returns nothing, and neither route is reachable
from the generated apps/loopover-ui/public/openapi.json. Their sibling /v1/scoring/* routes
(/v1/scoring/model, /v1/scoring/preview) ARE documented. This is the same class of gap #6611
fixed for /v1/repos/{owner}/{repo}/gate-config/effective ("OpenAPI spec is missing GET
.../gate-config/effective (documented sibling: live-gate-thresholds)").
Requirements
- In
src/openapi/schemas.ts, define EligibilityPlanResponseSchema and
ScoreBreakdownResponseSchema as z.object({...}) Zod schemas matching the actual response shape
— use eligibilityPlanOutputSchema and scoreBreakdownOutputSchema (the raw Zod-shape objects
already validated in src/mcp/server.ts for this exact data) as the source of truth for field
names/types, not a fresh guess at the shape.
- In
src/openapi/spec.ts, registry.register(...) both new schemas as components, then add a
registry.registerPath({...}) entry for each route (POST /v1/scoring/eligibility-plan,
POST /v1/scoring/explain-breakdown), following the existing pattern used for
/v1/repos/{owner}/{repo}/gate-config/effective (request body schema + 200 response schema +
appropriate error responses).
- Schema is the contract — regenerate and commit the generated artifact. After editing
src/openapi/schemas.ts/src/openapi/spec.ts, run npm run ui:openapi (this repo's build command
for this artifact — scripts/write-ui-openapi.ts) to regenerate apps/loopover-ui/public/openapi.json,
and commit the regenerated file in this same PR. CI enforces this via npm run ui:openapi:check
(part of npm run test:ci) — a stale openapi.json fails CI.
Deliverables
All deliverables are required in this one PR — there is no follow-up issue.
Test Coverage Requirements
99%+ Codecov patch target (codecov/patch, unsharded via npm run test:coverage) on any new schema
helper code. Add a regression test (or extend an existing OpenAPI-spec snapshot/shape test) asserting
both new paths are present in buildOpenApiSpec()'s output with the correct method and a response
schema whose keys match eligibilityPlanOutputSchema/scoreBreakdownOutputSchema.
Expected Outcome
GET /openapi.json (and the committed apps/loopover-ui/public/openapi.json) documents both
scoring routes, matching what their MCP tool counterparts already validate — closing the drift
between "this data contract is already enforced by the MCP tool" and "this data contract is
discoverable via the public API spec."
Links & Resources
Context
src/api/routes.tshas two live, tested/v1/scoring/*POST routes, each already backed by an MCPtool with its own Zod output shape in
src/mcp/server.ts:POST /v1/scoring/eligibility-plan→loopover_get_eligibility_plan(outputSchema: eligibilityPlanOutputSchema)POST /v1/scoring/explain-breakdown→loopover_explain_score_breakdown(outputSchema: scoreBreakdownOutputSchema)Neither route appears anywhere in
src/openapi/spec.ts— grep for"/v1/scoring/eligibility-plan"and
"/v1/scoring/explain-breakdown"in that file returns nothing, and neither route is reachablefrom the generated
apps/loopover-ui/public/openapi.json. Their sibling/v1/scoring/*routes(
/v1/scoring/model,/v1/scoring/preview) ARE documented. This is the same class of gap #6611fixed for
/v1/repos/{owner}/{repo}/gate-config/effective("OpenAPI spec is missing GET.../gate-config/effective (documented sibling: live-gate-thresholds)").
Requirements
src/openapi/schemas.ts, defineEligibilityPlanResponseSchemaandScoreBreakdownResponseSchemaasz.object({...})Zod schemas matching the actual response shape— use
eligibilityPlanOutputSchemaandscoreBreakdownOutputSchema(the raw Zod-shape objectsalready validated in
src/mcp/server.tsfor this exact data) as the source of truth for fieldnames/types, not a fresh guess at the shape.
src/openapi/spec.ts,registry.register(...)both new schemas as components, then add aregistry.registerPath({...})entry for each route (POST /v1/scoring/eligibility-plan,POST /v1/scoring/explain-breakdown), following the existing pattern used for/v1/repos/{owner}/{repo}/gate-config/effective(request body schema + 200 response schema +appropriate error responses).
src/openapi/schemas.ts/src/openapi/spec.ts, runnpm run ui:openapi(this repo's build commandfor this artifact —
scripts/write-ui-openapi.ts) to regenerateapps/loopover-ui/public/openapi.json,and commit the regenerated file in this same PR. CI enforces this via
npm run ui:openapi:check(part of
npm run test:ci) — a staleopenapi.jsonfails CI.Deliverables
EligibilityPlanResponseSchemaandScoreBreakdownResponseSchemaadded tosrc/openapi/schemas.ts, matching the MCP tools' existing output shapes field-for-field.src/openapi/spec.ts.apps/loopover-ui/public/openapi.jsonregenerated vianpm run ui:openapiand committed.npm run ui:openapi:checkpasses in CI.All deliverables are required in this one PR — there is no follow-up issue.
Test Coverage Requirements
99%+ Codecov patch target (
codecov/patch, unsharded vianpm run test:coverage) on any new schemahelper code. Add a regression test (or extend an existing OpenAPI-spec snapshot/shape test) asserting
both new paths are present in
buildOpenApiSpec()'s output with the correct method and a responseschema whose keys match
eligibilityPlanOutputSchema/scoreBreakdownOutputSchema.Expected Outcome
GET /openapi.json(and the committedapps/loopover-ui/public/openapi.json) documents bothscoring routes, matching what their MCP tool counterparts already validate — closing the drift
between "this data contract is already enforced by the MCP tool" and "this data contract is
discoverable via the public API spec."
Links & Resources
src/api/routes.ts:POST /v1/scoring/eligibility-plan,POST /v1/scoring/explain-breakdownsrc/mcp/server.ts:eligibilityPlanOutputSchema,scoreBreakdownOutputSchema(source of truthfor the new schemas' fields)
src/openapi/spec.ts:/v1/repos/{owner}/{repo}/gate-config/effectiveregistration (pattern tomirror)