From 11647d9bea7dc569fba23c5d2b7bd554ef638941 Mon Sep 17 00:00:00 2001 From: cleanjunc Date: Wed, 15 Jul 2026 09:14:33 +0000 Subject: [PATCH] docs(api): add operation-level summaries to all OpenAPI routes (#5810) --- apps/loopover-ui/public/openapi.json | 306 ++++++++++++++++++--------- src/openapi/spec.ts | 142 ++++++++++--- test/unit/openapi.test.ts | 14 ++ 3 files changed, 335 insertions(+), 127 deletions(-) diff --git a/apps/loopover-ui/public/openapi.json b/apps/loopover-ui/public/openapi.json index a45d231414..ebf818b5c8 100644 --- a/apps/loopover-ui/public/openapi.json +++ b/apps/loopover-ui/public/openapi.json @@ -13960,7 +13960,8 @@ } } } - } + }, + "summary": "Service liveness probe" } }, "/v1/mcp/compatibility": { @@ -13976,7 +13977,8 @@ } } } - } + }, + "summary": "Public-safe API and MCP client compatibility metadata" } }, "/v1/public/stats": { @@ -13998,7 +14000,8 @@ "503": { "description": "Public stats are temporarily unavailable" } - } + }, + "summary": "Public homepage aggregate stats" } }, "/v1/public/github/repos/{owner}/{repo}/stats": { @@ -14038,7 +14041,8 @@ "503": { "description": "GitHub repository stats are unavailable" } - } + }, + "summary": "Public GitHub stars and forks for an allowlisted repository" } }, "/v1/public/repos/{owner}/{repo}/quality": { @@ -14078,7 +14082,8 @@ "503": { "description": "Public quality metrics are temporarily unavailable" } - } + }, + "summary": "Public repository quality summary for an opted-in repository" } }, "/v1/registry/snapshot": { @@ -14102,7 +14107,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Latest Gittensor registry snapshot" } }, "/v1/registry/changes": { @@ -14126,7 +14132,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Diff between the two latest registry snapshots" } }, "/v1/scoring/model": { @@ -14150,7 +14157,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Latest scoring model snapshot" } }, "/v1/upstream/status": { @@ -14174,7 +14182,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Upstream Gittensor source and ruleset drift status" } }, "/v1/upstream/ruleset": { @@ -14201,7 +14210,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Latest normalized upstream Gittensor ruleset snapshot" } }, "/v1/upstream/drift": { @@ -14244,7 +14254,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Open and historical upstream drift reports" } }, "/v1/scoring/preview": { @@ -14271,7 +14282,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Generate a scoring preview artifact for a candidate contribution" } }, "/v1/sync/status": { @@ -14295,7 +14307,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Repository and installation sync status" } }, "/v1/readiness": { @@ -14319,7 +14332,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Operational readiness summary for the hosted API" } }, "/v1/installations": { @@ -14364,7 +14378,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "List GitHub App installations and their health" } }, "/v1/installations/{id}/health": { @@ -14401,7 +14416,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "GitHub App installation health detail" } }, "/v1/installations/{id}/repair": { @@ -14438,7 +14454,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "GitHub App installation repair diagnostics" } }, "/v1/installations/{id}/repair/refresh": { @@ -14475,7 +14492,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Recompute GitHub App installation repair diagnostics" } }, "/v1/app/notification-model": { @@ -14614,7 +14632,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Opt-in notification model and PWA-readiness metadata" } }, "/v1/repos": { @@ -14641,7 +14660,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "List known repositories" } }, "/v1/repos/{owner}/{repo}": { @@ -14686,7 +14706,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Repository detail" } }, "/v1/repos/{owner}/{repo}/intelligence": { @@ -14728,7 +14749,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Canonical repository intelligence bundle" } }, "/v1/repos/{owner}/{repo}/issue-quality": { @@ -14773,7 +14795,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Repository issue quality report" } }, "/v1/repos/{owner}/{repo}/outcome-patterns": { @@ -14818,7 +14841,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Accepted and rejected pull request outcome patterns for a repository" } }, "/v1/repos/{owner}/{repo}/registration-readiness": { @@ -14860,7 +14884,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Gittensor registration readiness signal for repository owners" } }, "/v1/repos/{owner}/{repo}/gittensor-config-recommendation": { @@ -14902,7 +14927,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Recommended Gittensor configuration for a repository" } }, "/v1/repos/{owner}/{repo}/focus-manifest": { @@ -14950,7 +14976,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Repository focus manifest and compiled policy" }, "put": { "parameters": [ @@ -14999,7 +15026,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Persist an API-backed focus manifest for a repository" } }, "/v1/repos/{owner}/{repo}/focus-manifest/refresh": { @@ -15047,7 +15075,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Refresh the persisted focus manifest from the repository file" } }, "/v1/repos/{owner}/{repo}/agent/audit-feed": { @@ -15193,7 +15222,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Maintainer-scoped agent audit feed of executed actions and approval decisions" } }, "/v1/repos/{owner}/{repo}/pulls/{number}/incident-reports": { @@ -15317,7 +15347,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Record a post-merge incident report for a pull request" } }, "/v1/app/incident-reports": { @@ -15427,7 +15458,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Record a post-merge incident report from the operator side" } }, "/v1/app/self-dogfood/registration-pack": { @@ -15457,7 +15489,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Self-dogfood registration pack for the LoopOver repository" } }, "/v1/repos/{owner}/{repo}/self-dogfood-registration-pack": { @@ -15505,7 +15538,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Self-dogfood registration pack when the repository matches the configured target" } }, "/v1/repos/{owner}/{repo}/onboarding-pack/preview": { @@ -15556,7 +15590,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Preview the onboarding pack for an accepted repository" } }, "/v1/repos/{owner}/{repo}/contributor-issue-drafts/generate": { @@ -15607,7 +15642,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Generate maintainer-reviewed contributor issue drafts" } }, "/v1/repos/{owner}/{repo}/settings": { @@ -15649,7 +15685,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Repository automation settings" } }, "/v1/repos/{owner}/{repo}/settings-preview": { @@ -15694,7 +15731,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Dry-run the public surface decision for a sample pull request" } }, "/v1/repos/{owner}/{repo}/pulls/{number}/maintainer-packet": { @@ -15744,7 +15782,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Maintainer review packet for a pull request" } }, "/v1/repos/{owner}/{repo}/pulls/{number}/reviewability": { @@ -15794,7 +15833,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Pull request reviewability score and maintainer action" } }, "/v1/contributors/{login}/profile": { @@ -15828,7 +15868,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Contributor evidence profile" } }, "/v1/contributors/{login}/decision-pack": { @@ -15872,7 +15913,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Canonical contributor decision pack" } }, "/v1/contributors/{login}/open-pr-monitor": { @@ -15906,7 +15948,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Contributor open-PR monitor with classifications and next-step packets" } }, "/v1/contributors/{login}/repos/{owner}/{repo}/decision": { @@ -15966,7 +16009,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Repository-specific contributor decision" } }, "/v1/preflight/pr": { @@ -15993,7 +16037,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Run submission preflight for a pull request" } }, "/v1/preflight/local-diff": { @@ -16020,7 +16065,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Run preflight against a local diff" } }, "/v1/local/branch-analysis": { @@ -16050,7 +16096,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Analyze a local branch for MCP clients" } }, "/v1/agent/runs": { @@ -16080,7 +16127,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue an agent run" }, "get": { "parameters": [ @@ -16142,7 +16190,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "List persisted agent runs for an actor" } }, "/v1/agent/runs/{id}": { @@ -16179,7 +16228,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Persisted agent run bundle" } }, "/v1/agent/plan-next-work": { @@ -16219,7 +16269,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Rank the next work items for an agent run" } }, "/v1/agent/preflight-branch": { @@ -16259,7 +16310,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Preflight an agent branch before submission" } }, "/v1/agent/prepare-pr-packet": { @@ -16299,7 +16351,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Prepare a pull request packet for an agent run" } }, "/v1/agent/explain-blockers": { @@ -16339,7 +16392,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Explain an agent run's current blockers" } }, "/v1/bounties": { @@ -16366,7 +16420,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "List known bounty records" } }, "/v1/bounties/{id}/advisory": { @@ -16403,7 +16458,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Bounty lifecycle advisory" } }, "/v1/bounties/{id}/lifecycle": { @@ -16440,7 +16496,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Bounty lifecycle transition history" } }, "/v1/github/webhook": { @@ -16452,7 +16509,8 @@ "401": { "description": "Invalid webhook signature" } - } + }, + "summary": "Receive a GitHub webhook delivery" } }, "/v1/orb/ingest": { @@ -16472,7 +16530,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Ingest a batch of Orb events" } }, "/v1/auth/github/start": { @@ -16484,7 +16543,8 @@ "503": { "description": "GitHub OAuth app secret is not configured" } - } + }, + "summary": "Start GitHub web OAuth" } }, "/v1/auth/github/callback": { @@ -16493,7 +16553,8 @@ "302": { "description": "Completes GitHub web OAuth and redirects to the app" } - } + }, + "summary": "Complete GitHub web OAuth and redirect to the app" } }, "/v1/auth/github/device/start": { @@ -16514,7 +16575,8 @@ "429": { "description": "Rate limited" } - } + }, + "summary": "Start GitHub device-flow authentication" } }, "/v1/auth/github/device/poll": { @@ -16535,7 +16597,8 @@ "429": { "description": "Rate limited" } - } + }, + "summary": "Poll a pending GitHub device-flow authorization" } }, "/v1/auth/github/session": { @@ -16556,7 +16619,8 @@ "429": { "description": "Rate limited" } - } + }, + "summary": "Exchange a GitHub token for a LoopOver session" } }, "/v1/auth/logout": { @@ -16577,7 +16641,8 @@ "429": { "description": "Rate limited" } - } + }, + "summary": "End the current session" } }, "/v1/auth/extension/session": { @@ -16606,7 +16671,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Create an extension-scoped session" } }, "/v1/auth/session": { @@ -16615,7 +16681,8 @@ "200": { "description": "Current auth session, or signed_out when no app session is present" } - } + }, + "summary": "Current authentication session" } }, "/v1/app/overview": { @@ -16648,7 +16715,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Live app overview assembled from backend data" } }, "/v1/app/roles": { @@ -16678,7 +16746,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "App roles granted to the current session" } }, "/v1/app/miner-dashboard": { @@ -16708,7 +16777,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Miner dashboard data" } }, "/v1/app/maintainer-dashboard": { @@ -16738,7 +16808,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Maintainer dashboard data" } }, "/v1/app/operator-dashboard": { @@ -16768,7 +16839,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Operator dashboard data" } }, "/v1/app/commands": { @@ -16798,7 +16870,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "@loopover command catalog" } }, "/v1/app/commands/usefulness": { @@ -16828,7 +16901,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "@loopover command usefulness rollup" } }, "/v1/app/digest": { @@ -16858,7 +16932,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Maintainer digest content" } }, "/v1/app/analytics/daily-rollups": { @@ -16888,7 +16963,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Daily analytics rollups" } }, "/v1/app/analytics/mcp-compatibility": { @@ -16918,7 +16994,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "MCP client compatibility analytics" } }, "/v1/app/selfhost/queue/dead/{id}/replay": { @@ -16972,7 +17049,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Replay a dead-letter queue job" } }, "/v1/app/selfhost/queue/dead/{id}": { @@ -17026,7 +17104,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Delete a dead-letter queue job" } }, "/v1/app/selfhost/queue/dead": { @@ -17062,7 +17141,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Purge all dead-letter queue jobs" }, "get": { "parameters": [ @@ -17121,7 +17201,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "List dead-letter queue jobs" } }, "/v1/app/analytics/weekly-value-report": { @@ -17200,7 +17281,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Weekly value report" } }, "/v1/app/skipped-pr-audit": { @@ -17284,7 +17366,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Audit of pull requests the review agent skipped" } }, "/v1/app/commands/preview": { @@ -17320,7 +17403,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Dry-run a sanitized @loopover command response" } }, "/v1/app/commands/feedback": { @@ -17366,7 +17450,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Submit feedback on an @loopover command response" } }, "/v1/app/digest/subscriptions": { @@ -17412,7 +17497,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Manage maintainer digest subscriptions" } }, "/v1/extension/pull-context": { @@ -17483,7 +17569,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Pull request context for the browser extension" } }, "/v1/internal/jobs/refresh-registry": { @@ -17503,7 +17590,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a registry refresh job" } }, "/v1/internal/jobs/backfill-registered-repos": { @@ -17523,7 +17611,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a registered-repository backfill job" } }, "/v1/internal/jobs/backfill-repo-segment": { @@ -17546,7 +17635,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a repository segment backfill job" } }, "/v1/internal/jobs/backfill-pr-details": { @@ -17569,7 +17659,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue an open pull request detail backfill job" } }, "/v1/internal/jobs/generate-review-recap": { @@ -17592,7 +17683,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a maintainer review recap digest job" } }, "/v1/internal/jobs/refresh-scoring-model": { @@ -17612,7 +17704,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a scoring model refresh job" } }, "/v1/internal/jobs/refresh-upstream-drift": { @@ -17632,7 +17725,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue an upstream drift refresh job" } }, "/v1/internal/jobs/file-upstream-drift-issues": { @@ -17652,7 +17746,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a job that files upstream drift issues" } }, "/v1/internal/jobs/build-contributor-evidence": { @@ -17672,7 +17767,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a contributor evidence build job" } }, "/v1/internal/jobs/build-contributor-decision-packs": { @@ -17692,7 +17788,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a contributor decision pack build job" } }, "/v1/internal/jobs/build-burden-forecasts": { @@ -17712,7 +17809,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a burden forecast build job" } }, "/v1/internal/jobs/generate-signal-snapshots": { @@ -17732,7 +17830,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a signal snapshot generation job" } }, "/v1/internal/jobs/generate-weekly-value-report": { @@ -17752,7 +17851,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a weekly value report job" } }, "/v1/internal/jobs/repair-data-fidelity": { @@ -17772,7 +17872,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Queue a data fidelity repair job" } }, "/v1/internal/bounties/import": { @@ -17792,7 +17893,8 @@ { "LoopOverSessionCookie": [] } - ] + ], + "summary": "Import a bounty snapshot" } } }, diff --git a/src/openapi/spec.ts b/src/openapi/spec.ts index 41f5dcc6fe..8fde005f59 100644 --- a/src/openapi/spec.ts +++ b/src/openapi/spec.ts @@ -160,6 +160,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/health", + summary: "Service liveness probe", responses: { 200: { description: "Service health", content: { "application/json": { schema: HealthSchema } } }, }, @@ -167,6 +168,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/mcp/compatibility", + summary: "Public-safe API and MCP client compatibility metadata", responses: { 200: { description: "Public-safe API and MCP compatibility metadata", content: { "application/json": { schema: McpCompatibilitySchema } } }, }, @@ -174,6 +176,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/public/stats", + summary: "Public homepage aggregate stats", responses: { 200: { description: "Public-safe homepage stats: lifetime PRs handled/merged/closed, gate + slop blocks, and reversal-grounded accuracy. Aggregate counts only.", content: { "application/json": { schema: PublicStatsSchema } } }, 404: { description: "Public stats are disabled (LOOPOVER_PUBLIC_STATS off)" }, @@ -183,6 +186,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/public/github/repos/{owner}/{repo}/stats", + summary: "Public GitHub stars and forks for an allowlisted repository", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Public GitHub repository stars/forks for the website chrome; PUBLIC_REPO_STATS_ALLOWLIST must explicitly include the owner/repo.", content: { "application/json": { schema: PublicRepoStatsSchema } } }, @@ -193,6 +197,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/public/repos/{owner}/{repo}/quality", + summary: "Public repository quality summary for an opted-in repository", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { @@ -207,6 +212,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/registry/snapshot", + summary: "Latest Gittensor registry snapshot", responses: { 200: { description: "Latest Gittensor registry snapshot", content: { "application/json": { schema: RegistrySnapshotSchema } } }, }, @@ -214,6 +220,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/registry/changes", + summary: "Diff between the two latest registry snapshots", responses: { 200: { description: "Diff between latest registry snapshots", content: { "application/json": { schema: RegistryChangeReportSchema } } }, }, @@ -221,6 +228,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/scoring/model", + summary: "Latest scoring model snapshot", responses: { 200: { description: "Latest private scoring model snapshot", content: { "application/json": { schema: ScoringModelSnapshotSchema } } }, }, @@ -228,6 +236,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/upstream/status", + summary: "Upstream Gittensor source and ruleset drift status", responses: { 200: { description: "Upstream Gittensor source/ruleset drift status", content: { "application/json": { schema: UpstreamStatusSchema } } }, }, @@ -235,6 +244,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/upstream/ruleset", + summary: "Latest normalized upstream Gittensor ruleset snapshot", responses: { 200: { description: "Latest normalized upstream Gittensor ruleset snapshot", content: { "application/json": { schema: UpstreamRulesetSnapshotSchema } } }, 404: { description: "No upstream ruleset snapshot has been built yet" }, @@ -243,6 +253,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/upstream/drift", + summary: "Open and historical upstream drift reports", responses: { 200: { description: "Open and historical upstream drift reports", @@ -261,6 +272,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/scoring/preview", + summary: "Generate a scoring preview artifact for a candidate contribution", responses: { 200: { description: "Private scoring preview artifact", content: { "application/json": { schema: ScorePreviewSchema } } }, 400: { description: "Invalid scoring preview input" }, @@ -269,6 +281,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/sync/status", + summary: "Repository and installation sync status", responses: { 200: { description: "Repository and installation sync status", content: { "application/json": { schema: SyncStatusSchema } } }, }, @@ -276,6 +289,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/readiness", + summary: "Operational readiness summary for the hosted API", responses: { 200: { description: "Operational readiness summary for hosted API, signal fidelity, and public-review preparation", content: { "application/json": { schema: ReadinessSchema } } }, }, @@ -283,6 +297,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/installations", + summary: "List GitHub App installations and their health", responses: { 200: { description: "GitHub App installations and health", @@ -300,6 +315,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/installations/{id}/health", + summary: "GitHub App installation health detail", request: { params: z.object({ id: z.string() }) }, responses: { 200: { description: "GitHub App installation health", content: { "application/json": { schema: InstallationHealthSchema } } }, @@ -309,6 +325,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/installations/{id}/repair", + summary: "GitHub App installation repair diagnostics", request: { params: z.object({ id: z.string() }) }, responses: { 200: { description: "GitHub App installation repair diagnostics", content: { "application/json": { schema: InstallationRepairSchema } } }, @@ -318,6 +335,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/installations/{id}/repair/refresh", + summary: "Recompute GitHub App installation repair diagnostics", request: { params: z.object({ id: z.string() }) }, responses: { 200: { description: "Refreshed GitHub App installation repair diagnostics", content: { "application/json": { schema: InstallationRepairSchema } } }, @@ -327,6 +345,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/app/notification-model", + summary: "Opt-in notification model and PWA-readiness metadata", responses: { 200: { description: "Opt-in notification model and PWA-readiness metadata for control-panel routes", @@ -366,6 +385,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos", + summary: "List known repositories", responses: { 200: { description: "Known repositories", content: { "application/json": { schema: RepositorySchema.array() } } }, }, @@ -373,6 +393,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}", + summary: "Repository detail", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Repository detail", content: { "application/json": { schema: RepositorySchema } } }, @@ -382,6 +403,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/intelligence", + summary: "Canonical repository intelligence bundle", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Canonical repository intelligence bundle", content: { "application/json": { schema: RepoIntelligenceSchema } } }, @@ -390,6 +412,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/issue-quality", + summary: "Repository issue quality report", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Cached or computed issue quality report for the repo", content: { "application/json": { schema: IssueQualityResponseSchema } } }, @@ -399,6 +422,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/outcome-patterns", + summary: "Accepted and rejected pull request outcome patterns for a repository", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Cached or freshly-computed per-repo accepted/rejected PR outcome patterns with freshness envelope and explicit evidence-completeness", content: { "application/json": { schema: RepoOutcomePatternsResponseSchema } } }, @@ -408,6 +432,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/registration-readiness", + summary: "Gittensor registration readiness signal for repository owners", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Gittensor registration readiness signal for repo owners", content: { "application/json": { schema: RegistrationReadinessSchema } } }, @@ -416,6 +441,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/gittensor-config-recommendation", + summary: "Recommended Gittensor configuration for a repository", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Private Gittensor config recommendation for repo owners", content: { "application/json": { schema: GittensorConfigRecommendationSchema } } }, @@ -424,6 +450,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/focus-manifest", + summary: "Repository focus manifest and compiled policy", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Repo focus manifest and compiled policy for maintainers", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, @@ -433,6 +460,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/repos/{owner}/{repo}/focus-manifest/refresh", + summary: "Refresh the persisted focus manifest from the repository file", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Refresh the persisted focus manifest cache from the repo file", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, @@ -442,6 +470,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "put", path: "/v1/repos/{owner}/{repo}/focus-manifest", + summary: "Persist an API-backed focus manifest for a repository", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Persist API-backed focus manifest for a repo", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, @@ -452,6 +481,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/agent/audit-feed", + summary: "Maintainer-scoped agent audit feed of executed actions and approval decisions", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { @@ -498,6 +528,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/repos/{owner}/{repo}/pulls/{number}/incident-reports", + summary: "Record a post-merge incident report for a pull request", request: { params: z.object({ owner: z.string(), repo: z.string(), number: z.string() }), body: { @@ -527,6 +558,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/app/incident-reports", + summary: "Record a post-merge incident report from the operator side", request: { body: { content: { @@ -557,6 +589,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/app/self-dogfood/registration-pack", + summary: "Self-dogfood registration pack for the LoopOver repository", responses: { 200: { description: "Private self-dogfood registration pack for the LoopOver repo", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, 403: { description: "Insufficient role for maintainer-only self-dogfood report" }, @@ -565,6 +598,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/self-dogfood-registration-pack", + summary: "Self-dogfood registration pack when the repository matches the configured target", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Private self-dogfood registration pack when repo matches configured LoopOver target", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, @@ -574,6 +608,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/onboarding-pack/preview", + summary: "Preview the onboarding pack for an accepted repository", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Preview-only repo onboarding pack for accepted repositories", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, @@ -584,6 +619,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/repos/{owner}/{repo}/contributor-issue-drafts/generate", + summary: "Generate maintainer-reviewed contributor issue drafts", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Generate maintainer-reviewed contributor issue drafts from repo policy (dry-run by default)", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, @@ -594,6 +630,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/settings", + summary: "Repository automation settings", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "LoopOver repository automation settings", content: { "application/json": { schema: RepositorySettingsSchema } } }, @@ -602,6 +639,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/repos/{owner}/{repo}/settings-preview", + summary: "Dry-run the public surface decision for a sample pull request", request: { params: z.object({ owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Maintainer dry-run preview of the public surface decision for a sample PR (no GitHub mutation)", content: { "application/json": { schema: RepoSettingsPreviewSchema } } }, @@ -611,6 +649,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/pulls/{number}/maintainer-packet", + summary: "Maintainer review packet for a pull request", request: { params: z.object({ owner: z.string(), repo: z.string(), number: z.string() }) }, responses: { 200: { description: "PR-specific maintainer review packet", content: { "application/json": { schema: PullRequestMaintainerPacketSchema } } }, @@ -619,6 +658,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/pulls/{number}/reviewability", + summary: "Pull request reviewability score and maintainer action", request: { params: z.object({ owner: z.string(), repo: z.string(), number: z.string() }) }, responses: { 200: { description: "Private PR reviewability score and maintainer action", content: { "application/json": { schema: PullRequestReviewabilitySchema } } }, @@ -627,6 +667,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/contributors/{login}/profile", + summary: "Contributor evidence profile", request: { params: z.object({ login: z.string() }) }, responses: { 200: { description: "Contributor evidence profile", content: { "application/json": { schema: ContributorProfileSchema } } }, @@ -635,6 +676,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/contributors/{login}/decision-pack", + summary: "Canonical contributor decision pack", request: { params: z.object({ login: z.string() }) }, responses: { 200: { @@ -647,6 +689,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/contributors/{login}/open-pr-monitor", + summary: "Contributor open-PR monitor with classifications and next-step packets", request: { params: z.object({ login: z.string() }) }, responses: { 200: { @@ -658,6 +701,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/contributors/{login}/repos/{owner}/{repo}/decision", + summary: "Repository-specific contributor decision", request: { params: z.object({ login: z.string(), owner: z.string(), repo: z.string() }) }, responses: { 200: { description: "Repo-specific contributor decision from decision pack. May carry freshness 'stale' or 'rebuilding'.", content: { "application/json": { schema: RepoDecisionResponseSchema } } }, @@ -667,6 +711,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/preflight/pr", + summary: "Run submission preflight for a pull request", responses: { 200: { description: "Submission preflight result", content: { "application/json": { schema: PreflightResultSchema } } }, 400: { description: "Invalid preflight input" }, @@ -675,6 +720,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/preflight/local-diff", + summary: "Run preflight against a local diff", responses: { 200: { description: "Local diff preflight result", content: { "application/json": { schema: LocalDiffPreflightResultSchema } } }, 400: { description: "Invalid local diff preflight input" }, @@ -683,6 +729,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/local/branch-analysis", + summary: "Analyze a local branch for MCP clients", responses: { 200: { description: "Private local branch analysis for MCP clients", content: { "application/json": { schema: LocalBranchAnalysisSchema } } }, 400: { description: "Invalid local branch analysis input" }, @@ -692,6 +739,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/agent/runs", + summary: "Queue an agent run", responses: { 202: { description: "Copilot-only agent run queued", content: { "application/json": { schema: AgentRunBundleSchema } } }, 400: { description: "Invalid agent run request" }, @@ -701,6 +749,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/agent/runs", + summary: "List persisted agent runs for an actor", request: { query: z.object({ actorLogin: z.string().min(1).openapi({ @@ -732,16 +781,23 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/agent/runs/{id}", + summary: "Persisted agent run bundle", request: { params: z.object({ id: z.string() }) }, responses: { 200: { description: "Persisted agent run bundle", content: { "application/json": { schema: AgentRunBundleSchema } } }, 404: { description: "Agent run not found" }, }, }); - for (const path of ["/v1/agent/plan-next-work", "/v1/agent/preflight-branch", "/v1/agent/prepare-pr-packet", "/v1/agent/explain-blockers"]) { + for (const [path, summary] of [ + ["/v1/agent/plan-next-work", "Rank the next work items for an agent run"], + ["/v1/agent/preflight-branch", "Preflight an agent branch before submission"], + ["/v1/agent/prepare-pr-packet", "Prepare a pull request packet for an agent run"], + ["/v1/agent/explain-blockers", "Explain an agent run's current blockers"], + ] as const) { registry.registerPath({ method: "post", path, + summary, responses: { 200: { description: "Agent run completed with deterministic ranked actions", content: { "application/json": { schema: AgentRunBundleSchema } } }, 202: { description: "Agent run needs snapshot refresh", content: { "application/json": { schema: AgentRunBundleSchema } } }, @@ -753,6 +809,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/bounties", + summary: "List known bounty records", responses: { 200: { description: "Known bounty records", content: { "application/json": { schema: BountySchema.array() } } }, }, @@ -760,6 +817,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/bounties/{id}/advisory", + summary: "Bounty lifecycle advisory", request: { params: z.object({ id: z.string() }) }, responses: { 200: { description: "Bounty lifecycle advisory", content: { "application/json": { schema: BountyAdvisorySchema } } }, @@ -769,6 +827,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/bounties/{id}/lifecycle", + summary: "Bounty lifecycle transition history", request: { params: z.object({ id: z.string() }) }, responses: { 200: { description: "Bounty lifecycle transition history", content: { "application/json": { schema: BountyLifecycleEventsSchema } } }, @@ -778,6 +837,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/github/webhook", + summary: "Receive a GitHub webhook delivery", responses: { 202: { description: "Webhook queued" }, 401: { description: "Invalid webhook signature" }, @@ -786,6 +846,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/orb/ingest", + summary: "Ingest a batch of Orb events", responses: { 200: { description: "Batch accepted; returns { accepted: number }" }, 400: { description: "Malformed JSON or invalid payload shape" }, @@ -794,6 +855,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/auth/github/start", + summary: "Start GitHub web OAuth", responses: { 302: { description: "Redirects to GitHub web OAuth" }, 503: { description: "GitHub OAuth app secret is not configured" }, @@ -802,14 +864,22 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/auth/github/callback", + summary: "Complete GitHub web OAuth and redirect to the app", responses: { 302: { description: "Completes GitHub web OAuth and redirects to the app" }, }, }); - for (const path of ["/v1/auth/github/device/start", "/v1/auth/github/device/poll", "/v1/auth/github/session", "/v1/auth/logout", "/v1/auth/extension/session"]) { + for (const [path, summary] of [ + ["/v1/auth/github/device/start", "Start GitHub device-flow authentication"], + ["/v1/auth/github/device/poll", "Poll a pending GitHub device-flow authorization"], + ["/v1/auth/github/session", "Exchange a GitHub token for a LoopOver session"], + ["/v1/auth/logout", "End the current session"], + ["/v1/auth/extension/session", "Create an extension-scoped session"], + ] as const) { registry.registerPath({ method: "post", path, + summary, responses: { 200: { description: "Auth request completed" }, 201: { description: "Auth session created" }, @@ -822,6 +892,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/auth/session", + summary: "Current authentication session", responses: { 200: { description: "Current auth session, or signed_out when no app session is present" }, }, @@ -829,26 +900,28 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/app/overview", + summary: "Live app overview assembled from backend data", responses: { 200: { description: "Live app overview assembled from backend data", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, 401: { description: "Unauthorized" }, 403: { description: "Insufficient role" }, }, }); - for (const path of [ - "/v1/app/roles", - "/v1/app/miner-dashboard", - "/v1/app/maintainer-dashboard", - "/v1/app/operator-dashboard", - "/v1/app/commands", - "/v1/app/commands/usefulness", - "/v1/app/digest", - "/v1/app/analytics/daily-rollups", - "/v1/app/analytics/mcp-compatibility", - ]) { + for (const [path, summary] of [ + ["/v1/app/roles", "App roles granted to the current session"], + ["/v1/app/miner-dashboard", "Miner dashboard data"], + ["/v1/app/maintainer-dashboard", "Maintainer dashboard data"], + ["/v1/app/operator-dashboard", "Operator dashboard data"], + ["/v1/app/commands", "@loopover command catalog"], + ["/v1/app/commands/usefulness", "@loopover command usefulness rollup"], + ["/v1/app/digest", "Maintainer digest content"], + ["/v1/app/analytics/daily-rollups", "Daily analytics rollups"], + ["/v1/app/analytics/mcp-compatibility", "MCP client compatibility analytics"], + ] as const) { registry.registerPath({ method: "get", path, + summary, responses: { 200: { description: "Live app API response", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, 401: { description: "Unauthorized" }, @@ -858,6 +931,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/app/selfhost/queue/dead/{id}/replay", + summary: "Replay a dead-letter queue job", request: { params: z.object({ id: z.string().openapi({ param: { description: "Dead-letter job id." }, example: "812" }), @@ -875,6 +949,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "delete", path: "/v1/app/selfhost/queue/dead/{id}", + summary: "Delete a dead-letter queue job", request: { params: z.object({ id: z.string().openapi({ param: { description: "Dead-letter job id." }, example: "812" }), @@ -892,6 +967,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "delete", path: "/v1/app/selfhost/queue/dead", + summary: "Purge all dead-letter queue jobs", responses: { 200: { description: "Dead-letter jobs purged", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, 401: { description: "Unauthorized" }, @@ -902,6 +978,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/app/selfhost/queue/dead", + summary: "List dead-letter queue jobs", request: { query: z.object({ limit: z.string().optional().openapi({ @@ -925,6 +1002,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/app/analytics/weekly-value-report", + summary: "Weekly value report", request: { query: z.object({ variant: z.enum(["public", "operator"]).optional().openapi({ @@ -964,6 +1042,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/app/skipped-pr-audit", + summary: "Audit of pull requests the review agent skipped", request: { query: z.object({ limit: z.string().optional().openapi({ @@ -994,6 +1073,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/app/commands/preview", + summary: "Dry-run a sanitized @loopover command response", responses: { 200: { description: "Maintainer dry-run preview of a sanitized @loopover command response (no GitHub mutation)", content: { "application/json": { schema: CommandPreviewResponseSchema } } }, 400: { description: "Invalid request" }, @@ -1002,10 +1082,14 @@ export function buildOpenApiSpec() { 404: { description: "Command not found" }, }, }); - for (const path of ["/v1/app/commands/feedback", "/v1/app/digest/subscriptions"]) { + for (const [path, summary] of [ + ["/v1/app/commands/feedback", "Submit feedback on an @loopover command response"], + ["/v1/app/digest/subscriptions", "Manage maintainer digest subscriptions"], + ] as const) { registry.registerPath({ method: "post", path, + summary, responses: { 200: { description: "Live app mutation or preview response", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, 201: { description: "Created", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, @@ -1017,6 +1101,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "get", path: "/v1/extension/pull-context", + summary: "Pull request context for the browser extension", request: { query: z.object({ owner: z.string().min(1).openapi({ param: { description: "Repository owner" }, example: "JSONbored" }), @@ -1034,6 +1119,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/internal/jobs/refresh-registry", + summary: "Queue a registry refresh job", responses: { 202: { description: "Registry refresh queued" }, 401: { description: "Invalid internal token" }, @@ -1042,6 +1128,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/internal/jobs/backfill-registered-repos", + summary: "Queue a registered-repository backfill job", responses: { 202: { description: "Registered repo backfill queued" }, 401: { description: "Invalid internal token" }, @@ -1050,6 +1137,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/internal/jobs/backfill-repo-segment", + summary: "Queue a repository segment backfill job", responses: { 202: { description: "Repository segment backfill queued" }, 400: { description: "Invalid segment request" }, @@ -1059,6 +1147,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/internal/jobs/backfill-pr-details", + summary: "Queue an open pull request detail backfill job", responses: { 202: { description: "Open PR detail backfill queued" }, 400: { description: "Invalid PR detail backfill request" }, @@ -1068,26 +1157,28 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/internal/jobs/generate-review-recap", + summary: "Queue a maintainer review recap digest job", responses: { 202: { description: "Maintainer review recap digest queued (#1963)" }, 400: { description: "Missing repoFullName" }, 401: { description: "Invalid internal token" }, }, }); - for (const path of [ - "/v1/internal/jobs/refresh-scoring-model", - "/v1/internal/jobs/refresh-upstream-drift", - "/v1/internal/jobs/file-upstream-drift-issues", - "/v1/internal/jobs/build-contributor-evidence", - "/v1/internal/jobs/build-contributor-decision-packs", - "/v1/internal/jobs/build-burden-forecasts", - "/v1/internal/jobs/generate-signal-snapshots", - "/v1/internal/jobs/generate-weekly-value-report", - "/v1/internal/jobs/repair-data-fidelity", - ]) { + for (const [path, summary] of [ + ["/v1/internal/jobs/refresh-scoring-model", "Queue a scoring model refresh job"], + ["/v1/internal/jobs/refresh-upstream-drift", "Queue an upstream drift refresh job"], + ["/v1/internal/jobs/file-upstream-drift-issues", "Queue a job that files upstream drift issues"], + ["/v1/internal/jobs/build-contributor-evidence", "Queue a contributor evidence build job"], + ["/v1/internal/jobs/build-contributor-decision-packs", "Queue a contributor decision pack build job"], + ["/v1/internal/jobs/build-burden-forecasts", "Queue a burden forecast build job"], + ["/v1/internal/jobs/generate-signal-snapshots", "Queue a signal snapshot generation job"], + ["/v1/internal/jobs/generate-weekly-value-report", "Queue a weekly value report job"], + ["/v1/internal/jobs/repair-data-fidelity", "Queue a data fidelity repair job"], + ] as const) { registry.registerPath({ method: "post", path, + summary, responses: { 202: { description: "Internal job queued" }, 401: { description: "Invalid internal token" }, @@ -1097,6 +1188,7 @@ export function buildOpenApiSpec() { registry.registerPath({ method: "post", path: "/v1/internal/bounties/import", + summary: "Import a bounty snapshot", responses: { 200: { description: "Bounty snapshot imported" }, 401: { description: "Invalid internal token" }, diff --git a/test/unit/openapi.test.ts b/test/unit/openapi.test.ts index 754fa1db66..dfbba5ba05 100644 --- a/test/unit/openapi.test.ts +++ b/test/unit/openapi.test.ts @@ -130,6 +130,20 @@ describe("OpenAPI contract", () => { expect(spec.paths["/v1/auth/extension/session"]?.post?.security).toEqual([{ LoopOverBearer: [] }, { LoopOverSessionCookie: [] }]); }); + // #5810: every operation needs a title in the generated spec and the rendered API browser. Iterating the built + // document (rather than counting `summary:` lines in the source) also covers the paths registered from a loop, + // and fails loudly when a future route is added without one. + it("gives every operation a non-empty summary", () => { + const spec = buildOpenApiSpec(); + for (const [path, methods] of Object.entries(spec.paths ?? {})) { + for (const [method, operation] of Object.entries(methods as Record)) { + const label = `${method.toUpperCase()} ${path}`; + expect(typeof operation.summary, `${label} is missing an operation-level summary`).toBe("string"); + expect(operation.summary?.trim(), `${label} has an empty operation-level summary`).not.toBe(""); + } + } + }); + it("declares an `in: path` parameter for every {templated} path segment (Cloudflare schema-validation warning 30046)", () => { const spec = buildOpenApiSpec(); for (const [path, methods] of Object.entries(spec.paths ?? {})) {