fix(openapi): register each internal job once, with the status its handler returns (#9706) - #9759
Conversation
…ndler returns (#9706) buildOpenApiSpec registered the /v1/internal/jobs/* family TWICE -- five explicit blocks plus a nine-entry loop in spec.ts, and jobRoutes() in internal-and-public-route-specs.ts -- so 11 POST operations were contributed twice with nothing detecting it. zod-to-openapi keeps the last, which silently discarded the more accurate half: backfill-pr-details published an operation with no 400, while the spec.ts block declaring the 400 its handler really returns never reached the document. The ratchet cannot catch this. It compares SETS, so a path registered twice still balances in both directions of its diff. The guard added here consumes SPEC_REGISTRARS -- the same list buildOpenApiSpec iterates -- rather than restating the registrar modules, because a guard that has to be kept in sync with what it guards goes quiet exactly when a new one is added. Statuses now match the handlers, verified route by route. Every SINGLE_JOBS entry ENQUEUES and answers 202; the table published a 200 that was unreachable for all five, with summaries claiming the job ran. rag-index declares the 404 it returns when retrieval is disabled and no 400, since an unparseable body becomes {} rather than a rejection; regate-pr declares both. The nine routes that do validate a body declare a 400 through a per-entry override -- not by widening the shared QUEUED and RAN constants, which would have attached a 400 to every route that accepts any body. Both arms are asserted, since an override nobody exercises the negative side of is an override nobody tested. build-contributor-evidence, build-burden-forecasts, and repair-data-fidelity move into SINGLE_JOBS with the rest of the family. They were the three loop entries with no /run sibling to be tabled with, and being legacy registerPath calls that declared no auth, they published a 401 with no scheme that could produce it -- requiresApiToken returns false for /v1/internal/*, which has its own middleware. Every operation under that prefix now declares its bearer, asserted exhaustively.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
loopover-ui | 916f778 | Commit Preview URL Branch Preview URL |
Jul 29 2026, 07:04 AM |
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-29 07:23:32 UTC
Review summary Nits — 8 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Decision record
Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. Scroll preview
A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Bundle ReportChanges will increase total bundle size by 1.29kB (0.02%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: loopover-uiAssets Changed:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## fix/openapi-internal-security-9707 #9759 +/- ##
======================================================================
- Coverage 89.45% 89.45% -0.01%
======================================================================
Files 904 904
Lines 113210 113206 -4
Branches 26859 26861 +2
======================================================================
- Hits 101275 101271 -4
Misses 10846 10846
Partials 1089 1089
Flags with carried forward coverage won't be shown. Click here to find out more.
|


Closes #9706.
The duplication
buildOpenApiSpec()registered the/v1/internal/jobs/*family twice — five explicit blocks plus a nine-entry loop inspec.ts, andjobRoutes()ininternal-and-public-route-specs.ts. 11 POST operations were contributed twice with nothing detecting it.zod-to-openapikeeps the last registration, which silently discarded the more accurate half. Verified against the committed document:POST /v1/internal/jobs/backfill-pr-detailspublishedqueueBackfillPrDetailsJobwith responses["202","401"], while thespec.tsblock declaringpostInternalJobsBackfillPrDetailsand a 400 was gone entirely. The route does return 400.The ratchet cannot catch this. It compares sets, so a path registered twice still balances in both directions of its diff. The guard added here consumes
SPEC_REGISTRARS— the same listbuildOpenApiSpeciterates — rather than restating the registrar modules, because a guard that must be kept in sync with what it guards goes quiet exactly when a new registrar is added.The statuses
Checked route by route against the handlers:
SINGLE_JOBSentry enqueues and answers 202. The table published a200that was unreachable for all five, with summaries claiming the job ran. Now 202, and "Queue a job to …".rag-indexdeclares the404it returns when retrieval is disabled, and no 400 — an unparseable body becomes{}rather than a rejection.regate-prdeclares both: it validates its body and 404s an uninstalled repo.400, through a per-entry override rather than by widening the sharedQUEUED/RANconstants — which would have attached a 400 to every route that happily accepts any body. Both arms are asserted, since an override whose negative side nobody exercises is an override nobody tested.build-contributor-evidence,build-burden-forecasts, andrepair-data-fidelityjoinSINGLE_JOBSwith the rest of the family. They were the three loop entries with no/runsibling to be tabled with — and, being legacyregisterPathcalls declaring no auth, they published a 401 with no scheme that could produce it, sincerequiresApiTokenreturns false for/v1/internal/*(that family has its own middleware). Every operation under that prefix now declares its bearer, asserted exhaustively.Validation
rag-index/regate-prstatus cases.grep -c 'internal/jobs' src/openapi/spec.ts→ 0.npx vitest run --changed: 1934 passed, 142 files.tsc --noEmitclean; the ratchet still passes in both directions.