Skip to content

Route audit capstone: a strict reverse guard — every client URL must match a route some surface mounts #3642

Description

@os-zhuang

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:

SurfaceLedgerTranche
Dispatcherpackages/runtime/src/route-ledger.ts#3563
REST serverpackages/rest/src/rest-route-ledger.ts#3587
Autonomous service mountsservice-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:

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:

  1. 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, :param segments 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.
  2. 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.
  3. Static extraction of template literals in index.ts. No fixtures, but brittle against the client's getRoute() 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 ObjectStackClient method 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions