Skip to content

Commit 1b717e5

Browse files
authored
test(client): close the route audit's reverse direction — every SDK URL must match a route some surface mounts (#3642) (#3658)
The capstone of the #3563 audit. The dispatcher (#3563), REST (#3587) and service-mount (#3636) ledgers all run server → client and never asked whether the URL the client BUILDS matches anything a server MOUNTS — so a method could name a real function, carry a green ledger row, and 404 everywhere. That shipped four times, found one at a time by hand (#3584 x2, #3611, #3636 x2). client-url-conformance.test.ts drives every method on a real client with a recording fetch and matches each captured URL against the union of all four ledgers. A real drive rather than a hand-written mapping table, which would be an assertion ABOUT the code that the code can drift away from. Mutation-checked: re-injecting the #3636 dialect bug fails the suite. The sweep's own completeness is asserted, because that is what rots silently — every method is driven or declared NON_HTTP with a reason; a driven method emitting zero requests fails; a URL containing undefined fails; and the __api-endpoint `(unmatched)` catch-all is excluded so it cannot match everything and make the suite vacuous. 196 of ~219 methods matched. Two bounds are reported rather than papered over: /api/v1/cloud/* belongs to the sibling cloud repo and is exempt by prefix, bounded so no other namespace can use it (#3655); and 60 matched calls rest only on a `**` prefix claim, 54 of them on `* /auth/**` — ratcheted so it can only shrink (#3656). No runtime change. Closes #3642.
1 parent 1986594 commit 1b717e5

4 files changed

Lines changed: 466 additions & 7 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
"@objectstack/client": patch
3+
---
4+
5+
test(client): close the route audit's reverse direction — every SDK URL must match a route some surface mounts (#3642)
6+
7+
The capstone of the #3563 route audit. The dispatcher (#3563), REST (#3587) and
8+
service-mount (#3636) ledgers all run server → client: enumerate what a surface
9+
mounts, demand a reviewed disposition, and for `sdk` rows demand the named
10+
client method exists. None of them asked the reverse question — does the URL
11+
the client *builds* match anything a server *mounts*? — so a method could name
12+
a real function, carry a green ledger row, and 404 everywhere.
13+
14+
That shipped four times, found one at a time by hand: `analytics.explain` and
15+
`analytics.meta` (#3584), `meta.getView` (#3611), and `i18n.getTranslations` /
16+
`getFieldLabels` (#3636) — the last pair having carried green `sdk` rows since
17+
tranche 1.
18+
19+
`client-url-conformance.test.ts` drives every method on a real client with a
20+
recording `fetch` and matches each captured URL against the union of all four
21+
ledgers. A real drive rather than a hand-written "method X targets route Y"
22+
table, because such a table is an assertion *about* the code that the code can
23+
drift away from — the exact failure being fixed. Mutation-checked: re-injecting
24+
the #3636 dialect bug fails the suite.
25+
26+
The sweep's own completeness is asserted, since that is what rots silently — a
27+
new method must be driven or declared `NON_HTTP` with a reason; a driven method
28+
emitting zero requests fails (stale placeholder args are how a sweep quietly
29+
stops covering anything); a URL containing `undefined` fails; and the
30+
`__api-endpoint` `(unmatched)` catch-all is excluded from the pattern set so it
31+
cannot match everything and make the suite vacuous.
32+
33+
196 of ~219 methods matched. Two bounds are reported rather than papered over:
34+
`/api/v1/cloud/*` (23 `projects.*` methods) belongs to the sibling `cloud` repo
35+
and is exempt by prefix, bounded so no other namespace can use it (#3655); and
36+
60 of ~196 matched calls rest only on a `**` prefix claim rather than a
37+
resolvable route — 54 of those on `* /auth/**` — a count the guard ratchets so
38+
it can only shrink (#3656).
39+
40+
No runtime change: this is a guard plus the ledger-header and audit-doc notes
41+
recording what it does and does not cover.

docs/audits/2026-07-dispatcher-client-route-coverage.md

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,47 @@ decided by which plugin mounted the route.
195195
Also filed, not fixed: `GET {base}/_local/file/:key` is built by three call
196196
sites and mounted by none (#3641).
197197

198-
**The gap all three ledgers still share** is the reverse direction — no guard
199-
compares the URL a client method *builds* against the patterns any surface
200-
*mounts*. Four instances of that class have now been found one at a time
201-
(#3584 ×2, #3611, #3636 ×2). Mechanizing it is the capstone, #3642.
198+
**The gap all three ledgers shared** was the reverse direction — no guard
199+
compared the URL a client method *builds* against the patterns any surface
200+
*mounts*. Four instances of that class were found one at a time
201+
(#3584 ×2, #3611, #3636 ×2). Mechanized in #3642, below.
202+
203+
## 10. The reverse direction, mechanized (#3642)
204+
205+
`packages/client/src/client-url-conformance.test.ts` drives **every** method on
206+
a real `ObjectStackClient` with a recording `fetch` and matches each captured
207+
URL against the **union** of all four ledgers (a union, not an intersection —
208+
a route mounted by one surface is still reachable). A real drive, not a
209+
declaration table: "method X targets route Y" written by hand is an assertion
210+
*about* the code that the code can drift away from, which is the very failure
211+
being fixed.
212+
213+
Result at landing: 196 of ~219 methods matched; the only unmatched family was
214+
`projects.*`, which targets the control plane (below). Mutation-checked — the
215+
#3636 dialect bug, re-injected, fails the suite.
216+
217+
**The sweep's own completeness is asserted**, because that is the part that
218+
rots silently:
219+
220+
| Assertion | What it stops |
221+
|---|---|
222+
| every method is driven or declared `NON_HTTP` **with a reason** | a new SDK method escaping coverage |
223+
| a driven method emitting **zero** requests fails | placeholder args going stale, so the method throws before fetching and the guard passes while covering nothing |
224+
| a URL containing `undefined` / `[object Object]` fails | a placeholder that is accepted but wrong masquerading as coverage |
225+
| `(unmatched)` is excluded from the pattern set | the `__api-endpoint` catch-all matching everything and making the suite vacuous |
226+
227+
**Two bounds, both explicit rather than papered over:**
228+
229+
- **The control plane.** `/api/v1/cloud/*` (23 `projects.*` methods) is served
230+
by the sibling `cloud` repo — this repo's dispatcher explicitly refuses those
231+
paths — so no in-repo ledger can vouch for them. Exempt by prefix and bounded
232+
from both ends: a non-`projects` method reaching `/cloud/` fails. Tracked as
233+
#3655.
234+
- **Dynamic families.** A `**` row claims a prefix, not a resolvable route.
235+
**60 of ~196 matched calls (~31%) rest on nothing stronger** — 54 of them on
236+
`* /auth/**`, where the routes come from a third-party dependency on its own
237+
release cadence. The guard counts and ratchets this, so it can only shrink.
238+
Tracked as #3656.
202239

203240
## Follow-up slicing (proposed)
204241

@@ -211,7 +248,9 @@ compares the URL a client method *builds* against the patterns any surface
211248
7. **Deprecate `DEFAULT_DISPATCHER_ROUTES`**; point at the ledger.
212249
8. **REST-surface tranche** (§8) with the same ledger+guard treatment — done in #3587.
213250
9. **Autonomous service mounts** (§9) — done in #3636.
214-
10. **Cross-surface URL conformance** (§9, the reverse direction) — #3642.
251+
10. **Cross-surface URL conformance** (§10, the reverse direction) — done in #3642.
252+
11. **Control-plane surface** (§10) — #3655, needs a ledger in the `cloud` repo.
253+
12. **Enumerate `/auth/**`** (§10) — #3656, lowers the wildcard ratchet.
215254

216255
Each gap closed must flip its ledger row to `sdk` and lower the ratchet bound
217256
in the conformance test — the guard enforces both directions from PR-1 onward.

0 commit comments

Comments
 (0)