Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .changeset/client-url-conformance-capstone.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
"@objectstack/client": patch
---

test(client): close the route audit's reverse direction — every SDK URL must match a route some surface mounts (#3642)

The capstone of the #3563 route audit. The dispatcher (#3563), REST (#3587) and
service-mount (#3636) ledgers all run server → client: enumerate what a surface
mounts, demand a reviewed disposition, and for `sdk` rows demand the named
client method exists. None of them asked the reverse question — does the URL
the client *builds* match 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: `analytics.explain` and
`analytics.meta` (#3584), `meta.getView` (#3611), and `i18n.getTranslations` /
`getFieldLabels` (#3636) — the last pair having carried green `sdk` rows since
tranche 1.

`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 "method X targets route Y"
table, because such a table is an assertion *about* the code that the code can
drift away from — the exact failure being fixed. Mutation-checked: re-injecting
the #3636 dialect bug fails the suite.

The sweep's own completeness is asserted, since that is what rots silently — a
new method must be driven or declared `NON_HTTP` with a reason; a driven method
emitting zero requests fails (stale placeholder args are how a sweep quietly
stops covering anything); a URL containing `undefined` fails; and the
`__api-endpoint` `(unmatched)` catch-all is excluded from the pattern set 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/*` (23 `projects.*` methods) belongs to the sibling `cloud` repo
and is exempt by prefix, bounded so no other namespace can use it (#3655); and
60 of ~196 matched calls rest only on a `**` prefix claim rather than a
resolvable route — 54 of those on `* /auth/**` — a count the guard ratchets so
it can only shrink (#3656).

No runtime change: this is a guard plus the ledger-header and audit-doc notes
recording what it does and does not cover.
49 changes: 44 additions & 5 deletions docs/audits/2026-07-dispatcher-client-route-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,47 @@ decided by which plugin mounted the route.
Also filed, not fixed: `GET {base}/_local/file/:key` is built by three call
sites and mounted by none (#3641).

**The gap all three ledgers still share** is the reverse direction — no guard
compares the URL a client method *builds* against the patterns any surface
*mounts*. Four instances of that class have now been found one at a time
(#3584 ×2, #3611, #3636 ×2). Mechanizing it is the capstone, #3642.
**The gap all three ledgers shared** was the reverse direction — no guard
compared the URL a client method *builds* against the patterns any surface
*mounts*. Four instances of that class were found one at a time
(#3584 ×2, #3611, #3636 ×2). Mechanized in #3642, below.

## 10. The reverse direction, mechanized (#3642)

`packages/client/src/client-url-conformance.test.ts` drives **every** method on
a real `ObjectStackClient` with a recording `fetch` and matches each captured
URL against the **union** of all four ledgers (a union, not an intersection —
a route mounted by one surface is still reachable). A real drive, not a
declaration table: "method X targets route Y" written by hand is an assertion
*about* the code that the code can drift away from, which is the very failure
being fixed.

Result at landing: 196 of ~219 methods matched; the only unmatched family was
`projects.*`, which targets the control plane (below). Mutation-checked — the
#3636 dialect bug, re-injected, fails the suite.

**The sweep's own completeness is asserted**, because that is the part that
rots silently:

| Assertion | What it stops |
|---|---|
| every method is driven or declared `NON_HTTP` **with a reason** | a new SDK method escaping coverage |
| a driven method emitting **zero** requests fails | placeholder args going stale, so the method throws before fetching and the guard passes while covering nothing |
| a URL containing `undefined` / `[object Object]` fails | a placeholder that is accepted but wrong masquerading as coverage |
| `(unmatched)` is excluded from the pattern set | the `__api-endpoint` catch-all matching everything and making the suite vacuous |

**Two bounds, both explicit rather than papered over:**

- **The control plane.** `/api/v1/cloud/*` (23 `projects.*` methods) is served
by the sibling `cloud` repo — this repo's dispatcher explicitly refuses those
paths — so no in-repo ledger can vouch for them. Exempt by prefix and bounded
from both ends: a non-`projects` method reaching `/cloud/` fails. Tracked as
#3655.
- **Dynamic families.** A `**` row claims a prefix, not a resolvable route.
**60 of ~196 matched calls (~31%) rest on nothing stronger** — 54 of them on
`* /auth/**`, where the routes come from a third-party dependency on its own
release cadence. The guard counts and ratchets this, so it can only shrink.
Tracked as #3656.

## Follow-up slicing (proposed)

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

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