Skip to content

feat(api): add the route-registration seam and a route-to-spec ratchet - #9533

Merged
JSONbored merged 1 commit into
mainfrom
feat/route-seam-ratchet-9519
Jul 28, 2026
Merged

feat(api): add the route-registration seam and a route-to-spec ratchet#9533
JSONbored merged 1 commit into
mainfrom
feat/route-seam-ratchet-9519

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

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: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.

src/openapi/route-inventory.ts reads Hono's own routes array and diffs it against the generated document in both directions. Nothing hand-maintained: a route added with app.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:

  • a new unspecced route fails immediately rather than joining a growing pile;
  • an entry that has since been specced must be deleted from the baseline, so the file keeps describing the real remaining work rather than rotting into a stale allowlist;
  • the reverse direction — an operation with no live route, strictly worse since a generated client compiles a call that 404s — is zero today and gets no baseline at all.

I verified the gate actually bites by temporarily adding GET /v1/ratchet-canary-not-real and confirming the test fails, then restoring.

src/openapi/define-route.ts registers a route and its OpenAPI operation from one definition, with the same zod schemas validating the request at runtime. The handler receives already-parsed body/query, so a migrated route cannot forget to validate — parsing is the only path to the handler. operationId and tags are mandatory: the document emits tags: [] 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 declared auth, which is what lets isProtectedPath() — 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 plain app.get(...) registrations so 241 routes migrate in batches, whereas adopting the library means rewriting createApp() wholesale and re-coupling the zod version. The emit path reuses the @asteasolutions/zod-to-openapi registry 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

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (e.g. Closes #123) — a linked open issue is required for every contributor PR.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally (scoped — see note)
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

30 tests green across define-route (11), route-spec-ratchet (9), and the existing openapi suite (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:

  • No existing route changes behavior: nothing is migrated through the seam yet, so openapi.json is byte-identical (ui:openapi:check passes unchanged) and no handler is touched. Unchecked items cover surfaces this diff does not touch. CI runs them in full.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. (No auth enforcement changes; the seam's auth field only drives the emitted security stanza today, and all four levels are asserted.)
  • API/OpenAPI/MCP behavior is updated and tested where needed.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks. (n/a)
  • Visible UI changes include a UI Evidence section below. (n/a — no visible change)
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs.

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.

@loopover-orb

loopover-orb Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Caution

🛑 LoopOver review result - fixes required

Review updated: 2026-07-28 07:23:43 UTC

5 files · 1 AI reviewer · no blockers · CI failing · unstable

🛑 Suggested Action - Manual Review

Review summary
This PR adds a route-registration seam (`defineRoute`) that couples Hono route handlers to OpenAPI spec emission via shared zod schemas, a pure `route-inventory.ts` diffing utility, and a ratchet test that pins the 90 currently-unspecced live routes into a baseline that may only shrink. The seam itself is unused by any actual route in this diff (no migration happens here — that's deferred to #9531), so this PR is purely infrastructure plus the enforcement mechanism. The logic is sound: `defineRoute` validates body/query before invoking the handler, returns a 400 in the existing error shape on failure, and `registerRouteSpec` correctly derives the OpenAPI operation from the same schemas. The ratchet test's three-way check (no new gaps, no stale baseline entries, no phantom spec-only operations) is well-designed and the author's claim of having verified it bites (temporarily adding a canary route) is a good practice, though unverifiable from the diff alone.

Nits — 6 non-blocking
  • The `codecov/patch` check failed at 95.83% vs a 99% target — likely from a few untested branches in `define-route.ts` (e.g., the `options.description` and `options.request` optional-block combinations), worth checking which lines codecov flagged.
  • src/openapi/define-route.ts:67 — `c.req.json().catch(() => undefined)` swallows all JSON parse errors into `undefined`, which then fails zod validation as a 400; this is intentional per the comment but is worth a one-line note that this also masks genuinely malformed non-JSON content-types the same way.
  • The PR description states 91 routes were missing when route-inventory.ts landed but the baseline file only lists 90 entries — worth confirming that's an intentional off-by-one in the prose (openapi.json itself being one of the 90) rather than a stale count.
  • Consider naming the `400` status literal via a small local constant in define-route.ts since it's repeated three times, though this is purely stylistic.
  • No route in this diff is migrated through `defineRoute` yet — confirm the PR title/description's framing as 'seam + ratchet only' matches maintainer expectations before api: drive the route↔spec ratchet to zero — migrate every route through the seam, retire the parallel security model #9531 lands, since a large ratchet file with zero adoption could stall if the follow-up doesn't materialize.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

CI checks failing

  • codecov/patch — 95.83% of diff hit (target 99.00%)

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9519
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 326 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 326 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Partially addressed
The PR delivers the shim (defineRoute/registerRouteSpec) and the route↔spec ratchet with a shrinking baseline wired into a new test file, matching requirement 2 and building the core seam of requirement 1, but it does not migrate any of the 5 pilot routes through the shim (no ORB or /v1/app/* route is actually registered via defineRoute in the diff), does not touch isProtectedPath()/applySecurityM

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 326 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask 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.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Decision record
  • action: hold · clause: success
  • config: 44e43511dcf9e39d3a7765f971d87ef322795e0672d3d83c0d61d9a82c93713c · pack: oss-anti-slop · ci: failed
  • record: 67bd7bc0bddf2b08994a1cd375aa329a7bc4edbd9e144a96e8f4a0871e95755b (schema v5, head 93fa57a)

🟩 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.

  • Re-run LoopOver review

@JSONbored JSONbored self-assigned this Jul 28, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@JSONbored
JSONbored force-pushed the feat/route-seam-ratchet-9519 branch from e195cd3 to 93fa57a Compare July 28, 2026 06:29
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.83333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.67%. Comparing base (d6c4334) to head (fc8b1dd).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/openapi/route-inventory.ts 90.00% 0 Missing and 2 partials ⚠️
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     
Flag Coverage Δ
backend 93.64% <95.83%> (-1.64%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/openapi/define-route.ts 100.00% <100.00%> (ø)
src/openapi/route-inventory.ts 90.00% <90.00%> (ø)

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 28, 2026
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
@JSONbored
JSONbored force-pushed the feat/route-seam-ratchet-9519 branch from 93fa57a to fc8b1dd Compare July 28, 2026 07:05
@JSONbored
JSONbored merged commit 97ab8cb into main Jul 28, 2026
7 of 8 checks passed
@JSONbored
JSONbored deleted the feat/route-seam-ratchet-9519 branch July 28, 2026 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

api: one route-registration seam — runtime zod validation + spec emission, with a route↔spec ratchet to zero unspecced routes

1 participant