Skip to content

feat(api): drive the route-spec ratchet to zero and delete the baseline (#9531) - #9581

Merged
JSONbored merged 4 commits into
mainfrom
feat/route-ratchet-9531
Jul 28, 2026
Merged

feat(api): drive the route-spec ratchet to zero and delete the baseline (#9531)#9581
JSONbored merged 4 commits into
mainfrom
feat/route-ratchet-9531

Conversation

@JSONbored

@JSONbored JSONbored commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Part of #9531. #9575 and #9579 have merged, so this now stacks directly on . Delivers the ratchet-to-zero deliverable, the ORB surface, / on every operation, and the parallel-security-model deletion; the remaining #9531 deliverables are listed at the bottom and I have not closed the issue.

The ratchet is at zero

The published document described 151 of the 244 operations createApp() actually serves. It now describes all 244, and src/openapi/unspecced-routes-baseline.json is deleted — there is no longer anywhere to record an exception, which is the point.

The 93 newly-described routes include the whole surface #9522 needs: /v1/orb/*, /v1/internal/orb/*, the fleet kill-switch and config-push, the per-repo BYO-key routes, and the 32 internal job endpoints. Each carries a stable operationId, real tags, and an auth level that derives its security stanza instead of having one bolted on afterwards by path prefix.

The review's blockers, fixed — and they were right twice over

The AI review flagged that the generated document still emitted the identical LoopOverBearer+LoopOverSessionCookie stanza on every operation regardless of its declared auth — a bearer requirement on the ORB ingress whose own comment says it takes a shared-secret header. Correct, and the cause was layered:

applySecurityMetadata overwrote whatever an operation declared. The seam produced differentiated stanzas; the post-pass clobbered all of them with the generic pair. It now fills in only operations that declared nothing — which required public to emit [] (OpenAPI's explicit "no credential") rather than undefined, so a deliberately open route is distinguishable from a legacy registerPath call that never said.

The fill-in now derives from the real gate. requiresApiToken — the predicate the app actually gates on — moved to src/auth/route-auth.ts (pure, no cloudflare: imports, so the plain-Node spec builder can load it), and isProtectedPath is deleted: it was a second path-prefix model of the same policy that had already drifted, publishing the whole /v1/public/decision-ledger/* family as bearer-gated when every one of those routes answers 200 anonymously.

The stanzas now split six ways instead of one: the generic pair (136), bearer-only internal (63), legacy-undeclared (28), explicitly public (11), the ORB instance bearer (5), the webhook body signature (1).

Deriving from the real gate surfaced a route both former models had wrong. POST /v1/auth/github/token 403s a bearer-only caller — the handler checks identity.kind !== "session" — yet one model published it as bearer-or-cookie and the other would publish it as open. It now declares LoopOverSessionCookie alone.

The new parity meta-test (test/unit/openapi-security-parity.test.ts) asserts against the real gate: no ungated route advertises the ordinary credential, no gated route claims to be open, the auth levels stay distinct rather than collapsing back to one stanza, and every referenced scheme is declared. Its own first two drafts each misread an exempt family as ungated (/v1/internal/* has its own INTERNAL_JOB_TOKEN middleware; the session-only route above is gated in its handler) — recorded in the test, because those exemptions are the map of where auth is enforced other than the token gate.

operationId + tags on all 244

The document previously emitted tags: [] everywhere and an operationId nowhere — one flat namespace for a generated client, and every path edit a breaking rename. All 244 operations now carry a unique id and a real tag; the five table-registered families derive theirs in-loop from the same rules, so adding a sibling route stays a one-line change.

Three fixes the work forced

RouteAuth gained orb and webhook. The ORB ingress genuinely does not authenticate the way the rest of the API does — requiresApiToken() exempts it — and the old model therefore published it as needing no credential at all. It needs a different one: an ORB-issued bearer for the relay and token endpoints, an HMAC signature header for the webhook. Both now have their own security scheme in the document.

registerRouteSpec never emitted path parameters. Every templated segment it registered was a Cloudflare 30046 schema-validation warning and a hole in any generated client — it had no {owner}/{repo}/{id} to fill. Now derived from the path itself, since there is no case where a path parameter is optional. Caught by the repo's own existing parameter test the moment the first templated route went through the seam.

A test was requiring the document to stay wrong. test/unit/openapi.test.ts listed /v1/internal/jobs/generate-signal-snapshots/run among paths that must be absent as removed routes. src/api/routes.ts:5167 registers it. Removed from that list with the reason recorded inline.

The ratchet's other direction earned its keep

It rejected two job routes I had assumed existed. I had modelled the internal jobs as uniformly having both an enqueue form and a /run sibling; two of them — backfill-contributor-gate-history and refresh-installation-health — are operator-triggered repairs with only the /run form. Publishing an operation no route serves is strictly worse than omitting one, because a generated client compiles a call that 404s at runtime. That direction never had a baseline and now never will.

I also replaced the deleted baseline's two tests with one that asserts the surface is non-trivial (>200 live routes, >200 operations). Both remaining assertions are satisfied by two empty lists; this is what stops them from passing by describing nothing.

Still open on #9531 — not done here

I am not closing the issue. What remains:

  • 143 of the 244 operations still lack operationId and tags — the pre-existing hand-registered ones in spec.ts. The 101 that went through the seam have both.
  • Requirement 5, deleting isProtectedPath/applySecurityMetadata, is blocked on the above: the parallel security model can only go once every operation declares its own auth.
  • Requirement 2/3, relocating the 38 inline request schemas and the response schemas into @loopover/contract.
  • Requirement 6, retiring check-openapi-settings-parity.ts, which becomes obsolete by construction only after requirement 3.
  • Requirement 7, the control-plane spec and the four self-host infra endpoints.

Those are sequenced — 5 depends on 4, 6 depends on 3 — so they want their own PRs rather than one that changes 244 operations at once.

Validation

  • npx vitest run test/unit test/contract — 23,521 passed, 6 skipped, 0 failed.
  • npm run typecheck clean across root and all three stricter packages.
  • ui:openapi:check, ui:openapi:settings-parity, docs:drift-check, dead-source-files:check, import-specifiers:check green.
  • The route↔spec diff is empty in both directions, asserted without a baseline.

@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 19:09:10 UTC

12 files · 1 AI reviewer · 3 blockers · CI failing · unstable

🛑 Suggested Action - Manual Review

Review summary
This PR adds ~700 lines of new OpenAPI route-spec entries (internal jobs, ORB ingress, control-panel, per-repo BYO-key routes) plus a new MCP dispatch-telemetry chokepoint and shared telemetry contract in packages/loopover-contract/src/telemetry.ts. The spec-entry code itself is clean and well-organized (table-driven job routes, explicit auth-level rationale in comments), and the telemetry redaction logic (secret-key/value stripping, depth cap, byte cap) is careful and well-reasoned. However, the generated apps/loopover-ui/public/openapi.json shown as the post-change artifact does not reflect the auth-level differentiation the PR claims to deliver: every route in the document — regardless of its declared `auth` (public, internal, orb, session, token) — emits the identical `security: [{LoopOverBearer:[]},{LoopOverSessionCookie:[]}]` stanza, contradicting the description's central claim that auth now 'derives its security stanza instead of having one bolted on afterwards.'

Blockers

  • apps/loopover-ui/public/openapi.json shows `/v1/ams/ingest` (declared `auth: "orb"` in src/openapi/internal-and-public-route-specs.ts, with an explicit comment saying it uses 'its own shared-secret header... not a LoopOver bearer') rendered with `security: [{LoopOverBearer:[]},{LoopOverSessionCookie:[]}]` — the exact scheme the code comment says it must not use — indicating registerRouteSpec/define-route.ts is not actually deriving a distinct security scheme for the new `orb`/`webhook` RouteAuth values.
  • The same generated document attaches an identical `security: [{LoopOverBearer:[]},{LoopOverSessionCookie:[]}]` stanza to routes declared `auth: "public"` (e.g. `/v1/public/subnet-interface`, `/v1/public/repos/{owner}/{repo}/badge.json`, `/v1/mcp/finding-taxonomy`, `/v1/drafts`) as well as to `auth: "internal"` routes (e.g. `/v1/internal/status`) — every auth level compiles to the same requirement, so the document still does not accurately describe which routes need no credential vs. an internal token vs. a session, which is the defect this PR states it fixes.
Nits — 5 non-blocking
  • The 'leaked private key' flags at packages/loopover-contract/src/telemetry.ts:137 and the test file are false positives — that line is the `SECRET_VALUE_PATTERN` regex used to detect PEM headers for redaction, not an actual key; worth a one-line dismissal in the PR discussion so reviewers don't chase it.
  • src/mcp/dispatch-telemetry.ts:37-38 reportedly add `console.*` calls; if these are on a telemetry catch/fire-and-forget path that's a defensible failure-swallow pattern, but confirm it isn't leftover debug output since the file content wasn't available to verify.
  • JOB_PAIRS in internal-and-public-route-specs.ts has 13 entries (26 routes) plus 2 run-only and 2 single-form jobs = 30, while the PR description says 'nineteen internal job routes exist in two forms' — the prose doesn't match the table; not a code defect but worth reconciling the description.
  • Several magic numbers (retry depth cap of 6, HTTP 502/503/504, issue numbers embedded as literals) are flagged by the external brief as unexplained; most already carry adjacent comments in the shown code, but scripts/lib/validate-mcp/synthesize-input.ts:45 and src/openapi/define-route.ts:53/58/72 weren't visible to check.
  • Before merging, spot-check define-route.ts's security-schema derivation for each RouteAuth value (public/internal/session/token/orb/webhook) against a route of each kind in the generated openapi.json — the current output suggests it may be a no-op that always emits the same two schemes.

Why this is blocked

  • apps/loopover-ui/public/openapi.json shows `/v1/ams/ingest` (declared `auth: "orb"` in src/openapi/internal-and-public-route-specs.ts, with an explicit comment saying it uses 'its own shared-secret header... not a LoopOver bearer') rendered with `security: [{LoopOverBearer:[]},{LoopOverSessionCookie:[]}]` — the exact scheme the code comment says it must not use — indicating registerRouteSpec/define-route.ts is not actually deriving a distinct security scheme for the new `orb`/`webhook` RouteAuth values.
  • No linked issue detected: No closing reference or linked issue number was found in the PR metadata/body. — If this PR is intended to solve an issue, link it explicitly in the PR body.
  • Maintainer requires a linked issue: This repo's maintainer focus manifest requires every PR to reference a tracked issue. — Link the relevant issue (for example Closes #123) before opening the PR.
📋 Copy for AI agents — paste into your coding agent
Fix the following blocker(s) from this PR review:

1. apps/loopover-ui/public/openapi.json shows \`/v1/ams/ingest\` \(declared \`auth: "orb"\` in src/openapi/internal-and-public-route-specs.ts, with an explicit comment saying it uses 'its own shared-secret header... not a LoopOver bearer'\) rendered with \`security: \[\{LoopOverBearer:\[\]\},\{LoopOverSessionCookie:\[\]\}\]\` — the exact scheme the code comment says it must not use — indicating registerRouteSpec/define-route.ts is not actually deriving a distinct security scheme for the new \`orb\`/\`webhook\` RouteAuth values.

2. No linked issue detected: No closing reference or linked issue number was found in the PR metadata/body. — If this PR is intended to solve an issue, link it explicitly in the PR body.

3. Maintainer requires a linked issue: This repo's maintainer focus manifest requires every PR to reference a tracked issue. — Link the relevant issue (for example `Closes #123`) before opening the PR.

CI checks failing

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

Decision drivers

  • ❌ Code review — 3 blockers (1 reviewer)
  • ❌ Gate result — Blocking (Repo-configured hard blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ⚠️ Missing No linked issue or no-issue rationale found.
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 (no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 300 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 300 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is registered but has no active allocation in the current snapshot.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 300 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 3 steps in the Signals table above.
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: missing_linked_issue
  • config: 81651757bad46e936c609a14a8f84cb23e592b9cf1213bca06cbd1ac1c165272 · pack: oss-anti-slop · ci: failed
  • model: claude-code · prompt: 7ac46a3f8ed0bb19bf9274163ef5821c41a4b11125e4c2ffc182d26d1c35f412 · confidence: 0.55
  • record: 8fab9edd5d6207502248027ec015a61f6137349b06021f23e031e711a73ab79a (schema v5, head 72de26e)
Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
/ mobile before / (mobile)
before / (mobile)
after / (mobile)
after / (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy.

Scroll preview
Route Before (production) After (this PR's preview)
/ before / (scroll)
before / (scroll)
after / (scroll)
after / (scroll)

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.

  • Re-run LoopOver review

@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 72de26e Commit Preview URL

Branch Preview URL
Jul 28 2026, 06:53 PM

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 120.83kB (1.58%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.78MB 120.83kB (1.58%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-Bueq10z1.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-Dz1usDiC.js (New) 910.04kB 910.04kB 100.0% 🚀
openapi.json 76.88kB 707.77kB 12.19% ⚠️
assets/docs.fumadocs-spike-api-reference-D8q7JLq3.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-VUSetV3t.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-Bcgvj0R1.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-CI2BJeOy.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/maintainer-panel-BaNiXs2E.js (New) 78.99kB 78.99kB 100.0% 🚀
assets/routes-C4oRzryg.js (New) 35.96kB 35.96kB 100.0% 🚀
assets/owner-panel-CR_1IkpK.js (New) 27.92kB 27.92kB 100.0% 🚀
assets/app-DFwa7a0Q.js (New) 25.78kB 25.78kB 100.0% 🚀
assets/ui-vendor-Odg_b3if.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-plzTY0iR.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-DmjT5ktn.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-PfLn-7gs.js (New) 17.57kB 17.57kB 100.0% 🚀
assets/self-hosting-docs-audit-LFnoeIzG.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-BOn_fvbe.js (New) 15.52kB 15.52kB 100.0% 🚀
assets/playground-panel-5j-Y5CJG.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/fairness-MaFmb9mq.js (New) 10.73kB 10.73kB 100.0% 🚀
assets/app.audit-DkbT4BuX.js (New) 10.08kB 10.08kB 100.0% 🚀
assets/app.config-generator-Dqrwn8dY.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-MftX9dRB.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-Ch_VIRSv.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-CC4BW5P3.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-BKGJU-rc.js (New) 6.65kB 6.65kB 100.0% 🚀
assets/maintainer-workflow-BDyMjb7r.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel--RJ769Q_.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-21Q14ZuI.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-D15vEzQT.js (New) 6.01kB 6.01kB 100.0% 🚀
assets/docs.index-DdwLmGYA.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-DNIlptyk.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-CQp-VLwD.js (New) 2.7kB 2.7kB 100.0% 🚀
assets/api-DKczDQbX.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-C-LpNHzF.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-O8BZxgat.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-XZ2A2vFC.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-DjK18NVp.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-Dlq6sP1o.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-DyqwPSBQ.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-CV-rwx4X.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-DPigy3BK.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-DlxO052j.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-3BU27Bfu.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-BPUbCDPw.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-ceXaOLYM.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-vJCrwaFV.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-AUWu4-T-.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-B6ghVaUM.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-D-vtB7Gl.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-DVdC7d1k.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-7Vwq_ner.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-BxJJXgjI.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-CLg1qJ6K.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-Cr1lVvvZ.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-CsRTdNB6.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history---TrEOl8.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-Be01fpAp.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-w9WVo1FS.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-Bc56yiGl.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-DHLgnNGZ.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-CPvbb16N.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-DnFhXXQs.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-CzsZjBp5.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-hPfft2uo.js (Deleted) -866.09kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-DHav1n6d.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-DFDx2O-b.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-8qWQUoIw.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-DmBLXNTN.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-Bsu8XqZS.js (Deleted) -78.99kB 0 bytes -100.0% 🗑️
assets/routes-Cd37ugs0.js (Deleted) -35.96kB 0 bytes -100.0% 🗑️
assets/owner-panel-DZ3CdbSu.js (Deleted) -27.92kB 0 bytes -100.0% 🗑️
assets/app-DzVGOipY.js (Deleted) -25.78kB 0 bytes -100.0% 🗑️
assets/ui-vendor-BAHx250m.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-BWK8-jZu.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-z_xaFKMf.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-z-wjf_bn.js (Deleted) -17.57kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-B5B_yM6S.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-Did43G6J.js (Deleted) -15.52kB 0 bytes -100.0% 🗑️
assets/playground-panel-Be8Qq7cF.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/fairness-Gssv-L_I.js (Deleted) -10.73kB 0 bytes -100.0% 🗑️
assets/app.audit-bJWPBjpj.js (Deleted) -10.08kB 0 bytes -100.0% 🗑️
assets/app.config-generator-OrpLyF70.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-BwwyBuso.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-D9abaPSS.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-Cbko_XPo.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-hzW2QsD1.js (Deleted) -6.65kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-CDtNg06P.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-CGbdDTAN.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-BkeU3YDt.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-CqMI2HEk.js (Deleted) -6.01kB 0 bytes -100.0% 🗑️
assets/docs.index-DsKXlk90.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-BUGpIFMe.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-9p-EgvPH.js (Deleted) -2.7kB 0 bytes -100.0% 🗑️
assets/api-DLvc8siE.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-6ltADNVN.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-Bt2a1yvO.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-FzOV4FmA.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-D9WqkRhd.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-hJVqMI59.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-BBVu6eB7.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-CFtQ2eQQ.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-CF-pfgPl.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-BVqT76ea.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-D9HW4azP.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-DinkEwKf.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-DAoJyv1a.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-CmjfHjtL.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-0F2NgXk-.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-SVapCkQ9.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-D1BvCWlX.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-9knDs4Xx.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-C2jfgChb.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-D34r8_wp.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-CVMzKg7L.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-D-sBWpDk.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-KliiXcC6.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-HQeJWkuG.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-GlAmpI37.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-BG-cmgYH.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-BS6c1Sbv.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-B7zBtKkU.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-s8b4d_Z5.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-W96kUT38.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.83721% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 89.03%. Comparing base (0e990a3) to head (72de26e).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
src/openapi/spec.ts 91.66% 0 Missing and 1 partial ⚠️

❌ Your patch check has failed because the patch coverage (98.83%) is below the target coverage (99.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9581      +/-   ##
==========================================
- Coverage   89.84%   89.03%   -0.81%     
==========================================
  Files         875      878       +3     
  Lines      110980   111029      +49     
  Branches    26402    26409       +7     
==========================================
- Hits        99706    98856     -850     
- Misses       9992    11155    +1163     
+ Partials     1282     1018     -264     
Flag Coverage Δ
backend 94.16% <98.83%> (-1.47%) ⬇️

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

Files with missing lines Coverage Δ
src/api/routes.ts 95.54% <ø> (-0.06%) ⬇️
src/auth/route-auth.ts 100.00% <100.00%> (ø)
src/openapi/define-route.ts 97.22% <100.00%> (-2.78%) ⬇️
src/openapi/internal-and-public-route-specs.ts 100.00% <100.00%> (ø)
src/openapi/orb-and-control-route-specs.ts 100.00% <100.00%> (ø)
src/openapi/spec.ts 99.30% <91.66%> (-0.35%) ⬇️

... and 3 files with indirect coverage changes

@superagent-security

Copy link
Copy Markdown
Contributor

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

The published document described 151 of the 244 operations createApp() actually
serves. It now describes all 244, and src/openapi/unspecced-routes-baseline.json
is gone -- there is no longer anywhere to record an exception.

The 93 newly-described routes include the entire ORB management surface
(/v1/orb/*, /v1/internal/orb/*), the fleet kill-switch and config-push, the
per-repo BYO-key routes, and the 32 internal job endpoints. Each carries a
stable operationId, real tags, and an auth level that DERIVES its security
stanza rather than having one bolted on afterwards by path prefix.

Three fixes the work forced:

- RouteAuth gained 'orb' and 'webhook'. The ORB ingress genuinely does not
  authenticate the way the rest of the API does, and the old model published it
  as needing no credential at all. It needs a different one: an ORB-issued
  bearer for the relay and token endpoints, an HMAC signature header for the
  webhook. Both now have their own security scheme.
- registerRouteSpec never emitted path parameters, so every templated segment it
  registered was a Cloudflare 30046 schema-validation warning and a hole in any
  generated client. Derived from the path now, because there is no case where a
  path parameter is optional.
- test/unit/openapi.test.ts asserted /v1/internal/jobs/generate-signal-snapshots
  /run must be ABSENT from the document as a removed route. It is live. The
  assertion was requiring the document to stay wrong.

The ratchet's other direction earned its keep immediately: it rejected two job
routes I had assumed existed, because two of the internal jobs are
operator-triggered repairs with a /run form and no enqueue sibling.
The document emitted tags: [] on all 244 operations and an operationId on none,
which collapses every one of them into a single flat namespace for a generated
client or a doc explorer -- and makes every path edit a breaking rename, since
the id would otherwise be slugified from method+path.

126 literal registerPath calls carry both inline. The five families registered
from a table derive them in-loop from the same rules, because those tables exist
so that adding a sibling route is a one-line change and a second hand-written
entry per route would give that up.

All 244 now carry both, with no duplicate ids. That is the precondition for
deleting isProtectedPath/applySecurityMetadata: the parallel security model can
only go once every operation declares enough to derive its own stanza.
…e the parallel model

Review blockers on #9581, both correct: the document still emitted the identical
LoopOverBearer+SessionCookie pair on every operation regardless of its declared
auth -- a bearer requirement on the ORB ingress whose own comment says it takes
a shared-secret header, and on public routes that answer 200 anonymously.

Two causes, both fixed:

- applySecurityMetadata OVERWROTE whatever an operation declared. The seam was
  producing differentiated stanzas and the post-pass clobbered all of them with
  the generic pair. It now fills in only operations that declared nothing --
  which required `public` to emit `[]` (OpenAPI's explicit "no credential")
  rather than undefined, so a deliberately open route is distinguishable from a
  legacy registerPath call that never said.

- The fill-in itself now derives from requiresApiToken, the predicate the app
  gates on, extracted to src/auth/route-auth.ts (pure, no cloudflare: imports,
  so the plain-Node spec builder can load it). isProtectedPath -- the second,
  path-prefix model that had already drifted (it published the whole
  /v1/public/decision-ledger/* family as bearer-gated) -- is deleted.

The published stanzas now split six ways instead of one: the generic pair (136),
bearer-only internal (63), legacy-undeclared (28), explicitly public (11), the
ORB instance bearer (5), and the webhook body signature (1).

Deriving from the real gate surfaced one route BOTH former models had wrong:
POST /v1/auth/github/token 403s a bearer-only caller (the handler checks
identity.kind !== "session"), yet was published as bearer-or-cookie by one model
and would be published as open by the other. It now declares
LoopOverSessionCookie alone.

The new parity meta-test asserts, against the real gate: no ungated route
advertises the ordinary credential, no gated route claims to be open, the auth
levels stay distinct rather than collapsing back to one stanza, and every
referenced scheme is declared. Its own first two drafts each misread an exempt
family as ungated -- /v1/internal/* has its own INTERNAL_JOB_TOKEN middleware,
and the session-only route above is gated in its handler -- which is recorded in
the test because those exemptions ARE the map of where auth is enforced other
than the token gate.
@JSONbored
JSONbored merged commit 6e9c34e into main Jul 28, 2026
10 of 11 checks passed
@JSONbored
JSONbored deleted the feat/route-ratchet-9531 branch July 28, 2026 19:08
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.

1 participant