Repository navigation
Route audit capstone: a strict reverse guard — every client URL must match a route some surface mounts #3642
Description
The capstone #3636 anticipated ("a client method whose target route exists on no surface can be flagged mechanically — file that as the capstone if it doesn't fall out naturally"). It did not fall out naturally, and tranche 3 showed why it matters.
Where the audit now stands
All three server surfaces the SDK reaches are ledgered and CI-guarded:
| Surface | Ledger | Tranche |
|---|---|---|
| Dispatcher | packages/runtime/src/route-ledger.ts |
#3563 |
| REST server | packages/rest/src/rest-route-ledger.ts |
#3587 |
| Autonomous service mounts | service-storage/src/storage-route-ledger.ts, service-i18n/src/i18n-route-ledger.ts |
#3636 |
Each guard runs server → client: enumerate what the surface mounts, demand a reviewed disposition, and for sdk rows demand the named client method exists.
The hole all three share
The method exists is not the method can be called. No guard compares the URL the client builds against the patterns any server mounts, so a client method can name a real function, carry a green ledger row, and still 404 on every surface.
That is not hypothetical — it is the recurring bug this audit keeps rediscovering:
analytics.explaincalled/explain; nothing served it (Reconcile the four dispatcher↔client shape mismatches (analytics ×2, storage ×2) — #3563 follow-up #3584)analytics.metacalled/meta/:cube; no server mounted that shape (Reconcile the four dispatcher↔client shape mismatches (analytics ×2, storage ×2) — #3563 follow-up #3584)meta.getViewsent?type=; REST mounts only/ui/view/:object/:type(UI view route speaks two dialects — REST's path-param form is unreachable by the SDK's query-param form (#3587 finding) #3611)i18n.getTranslationssent/translations?locale=xxandi18n.getFieldLabelssent/labels/:object?locale=xx; every surface mounts only the path form (Route audit tranche 3: autonomously-mounted service routes (service-storage / service-i18n) — #3587 follow-up #3636)
Four instances, four tranches, one class. The i18n pair is the sharpest: route-ledger.ts has carried sdk rows for those two routes since tranche 1, and both methods were wire-level 404s the whole time. The dispatcher's own domain body happily accepts the query dialect — but nothing routes a bare /translations to that body, so the tolerance was unreachable.
What the guard should do
Union the three ledgers into one set of mounted patterns, then assert every URL the SDK constructs matches one of them.
Getting the client's URLs mechanically is the real design question. Options:
- Drive the client with a recording
fetch. A fixture calls every namespace method with dummy args and captures the URL; match each against the union,:paramsegments as wildcards. Pro: tests the real code path, catches dialect bugs exactly as they ship. Con: needs a call fixture per method, and the fixture list itself needs a completeness guard (every method on every namespace is invoked) or coverage rots silently — the same failure mode this whole audit is about. - Declare the target route per client method and assert both ends. Cheap and total, but it is an assertion about the code rather than of it: a method could still be edited away from its declared route.
- Static extraction of template literals in
index.ts. No fixtures, but brittle against the client'sgetRoute()indirection and dynamic segments.
Option 1 is the honest one; option 2 is a reasonable stepping stone if the fixture cost bites. Whichever lands, the completeness check ("no client method escaped the sweep") is the part that must not be skipped.
Note the union must stay a union — a route mounted by only one surface is still legitimately reachable, so the guard cannot demand a pattern appear in all three ledgers.
Acceptance
- Every URL an
ObjectStackClientmethod builds matches a pattern in some surface's ledger. - Every client method is provably included in the sweep.
- Wired into CI at zero, like the tranche-1/2/3 ratchets.
Refs: #3563 (tranche 1), #3587 (tranche 2), #3636 (tranche 3), #3584 / #3611 (prior instances of the class), docs/audits/2026-07-dispatcher-client-route-coverage.md.
Activity
fix(client,service-i18n): ledger the autonomous service-mount surface…
test(plugin-auth): enumerate better-auth's route table — the `/auth/*…
test(client,runtime): the last wildcard was wrong evidence, not weak …
feat(client)!: delete `projects.listTemplates()` — nothing has ever m…
test(client): close the route audit's reverse direction — every SDK U…
feat(client,spec)!: express the AI surface that exists, retire the de…
fix(rest,runtime): mount six ledgered-but-dead routes and gate the cl…