feat(api): add the route-registration seam and a route-to-spec ratchet - #9533
Conversation
|
Caution 🛑 LoopOver review result - fixes requiredReview updated: 2026-07-28 07:23:43 UTC
Review summary Nits — 6 non-blocking
CI checks failing
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionPartially addressed 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
🟩 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. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
loopover-ui | fc8b1dd | Commit Preview URL Branch Preview URL |
Jul 28 2026, 07:07 AM |
e195cd3 to
93fa57a
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #9533 +/- ##
==========================================
- Coverage 89.56% 88.67% -0.90%
==========================================
Files 843 845 +2
Lines 110207 110255 +48
Branches 26233 26250 +17
==========================================
- Hits 98712 97767 -945
- Misses 10232 11514 +1282
+ Partials 1263 974 -289
Flags with carried forward coverage won't be shown. Click here to find out more.
|
createApp() registers 241 operations; buildOpenApiSpec() describes 151. Nothing compared the two, so 90 live routes had no spec entry at all -- including every ORB management surface (/v1/orb/*, /v1/internal/orb/*, fleet config-push, kill-switch, the DLQ admin quartet). ui:openapi:check only verifies the committed file matches its own generator, and test/unit/openapi.test.ts is a hand-written allowlist of paths that must exist; neither can see a route that exists in the app and nowhere in the document. route-inventory.ts reads Hono's own routes array and diffs it against the generated document in both directions. The ratchet test holds the 90 known gaps in a committed baseline that may only SHRINK: a new unspecced route fails immediately rather than joining the pile, and an entry that has since been specced must be removed, so the file keeps describing the real remaining work. Verified by temporarily adding a route and confirming the gate fails. The reverse direction -- an operation with no live route, which would make a generated client compile a call that 404s -- is zero today and has no baseline at all. define-route.ts registers a route AND its operation from one definition, with the same zod schemas validating at runtime. The handler receives already-parsed body and query, so a migrated route cannot forget to validate. operationId and tags are mandatory: the document emits tags: [] everywhere today, which collapses every operation into one namespace for generated clients, and slugified operation ids make every path edit a breaking change for consumers. Security stanzas derive from the declared auth level, which is what will let isProtectedPath() -- a second, already-disagreeing model of the same policy -- be deleted in #9531. A local shim rather than @hono/zod-openapi, per the decision recorded on #9519: it coexists with plain app.get() registrations so 241 routes can migrate in batches. Closes #9519
93fa57a to
fc8b1dd
Compare
Summary
Builds the seam and the enforcement that #9531 will use to describe every route the API actually serves.
The gap, measured.
createApp()(src/api/routes.ts:1154) registers 241 operations;buildOpenApiSpec()(src/openapi/spec.ts:141) describes 151. 90 live routes have no spec entry — including every ORB management surface (/v1/orb/*, all six/v1/internal/orb/*,/v1/app/fleet/config-push,/v1/app/kill-switch, the DLQ admin quartet, ~20/v1/internal/jobs/*). Nothing could detect this:ui:openapi:checkonly verifies the committed file matches its own generator, andtest/unit/openapi.test.tsis a hand-written allowlist of paths that must exist — neither can see a route that exists in the app and nowhere in the document.src/openapi/route-inventory.tsreads Hono's ownroutesarray and diffs it against the generated document in both directions. Nothing hand-maintained: a route added withapp.get(...)and forgotten everywhere else shows up.The ratchet (
test/unit/route-spec-ratchet.test.ts) holds the 90 known gaps in a committed baseline that may only shrink:I verified the gate actually bites by temporarily adding
GET /v1/ratchet-canary-not-realand confirming the test fails, then restoring.src/openapi/define-route.tsregisters a route and its OpenAPI operation from one definition, with the same zod schemas validating the request at runtime. The handler receives already-parsedbody/query, so a migrated route cannot forget to validate — parsing is the only path to the handler.operationIdandtagsare mandatory: the document emitstags: []on every operation today (collapsing generated clients into one flat namespace), and leaving ids to be slugified from method+path makes every path edit a breaking change for consumers. Security stanzas derive from the declaredauth, which is what letsisProtectedPath()— a second, already-disagreeing model of the same policy — be deleted in #9531.Decision recorded on the issue
A local shim, not
@hono/zod-openapi: it coexists with plainapp.get(...)registrations so 241 routes migrate in batches, whereas adopting the library means rewritingcreateApp()wholesale and re-coupling the zod version. The emit path reuses the@asteasolutions/zod-to-openapiregistry the spec is already built with, so the generated document's shape is unchanged.Scope
The bulk migration of the remaining routes is deliberately not here — I split it into #9531 so this stays one reviewable PR per issue. This PR is the mechanism plus the enforcement; #9531 drives the baseline to zero and deletes it.
Closes #9519
Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally (scoped — see note)npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderate30 tests green across
define-route(11),route-spec-ratchet(9), and the existingopenapisuite (10). The seam's tests cover both sides of every branch: body present/absent, valid/invalid, unparseable JSON, query valid/invalid, all four auth levels, responses with and without a schema, and the spec-only registration path.If any required check was skipped, explain why:
openapi.jsonis byte-identical (ui:openapi:checkpasses unchanged) and no handler is touched. Unchecked items cover surfaces this diff does not touch. CI runs them in full.Safety
authfield only drives the emitted security stanza today, and all four levels are asserted.)UI Evidencesection below. (n/a — no visible change)Notes
Part of #9515. Unblocks #9531 (drive the baseline to zero), which in turn gives #9522 a fully specced ORB management surface to bind its tools to.