Split from #3563. The route audit found four routes where a client method exists but speaks a different dialect than the dispatcher — ledgered as mismatch in packages/runtime/src/route-ledger.ts (the only non-sdk/non-deliberate dispositions left after PR-1…PR-5 drove the gap ratchet 27→0).
The four
| Dispatcher serves | Client calls | Ledger row |
|---|
GET /analytics/meta (domains/analytics.ts:49) | analytics.meta(cube) → GET /analytics/meta/:cube | extra path segment only the REST server understands |
POST /analytics/sql (analytics.ts:55) | analytics.explain() → POST /analytics/explain | different route name entirely |
POST /storage/upload (domains/storage.ts:47) | storage.upload() → presigned/chunked protocol (/upload/presigned, /upload/complete, /upload/chunked/*) | client can't upload through a bare dispatcher |
GET /storage/file/:id (storage.ts:56) | storage.getDownloadUrl() → GET /storage/files/:id/url | ditto for download |
Consequence: these client methods work only where @objectstack/rest fronts the dispatcher. Against a bare-dispatcher runtime they 404 — the method exists, the call fails, and nothing in types or tests says why.
The decision to make (API-shape call, needs an owner)
- Option A — server-side aliases (compatible): the dispatcher additionally accepts the REST shapes (
/analytics/meta/:cube, /analytics/explain, the presigned protocol). No client change, no breakage; the dispatcher grows a few routes whose ledger rows become sdk. - Option B — client aligns to the dispatcher (breaking): rename/reshape the four client methods. Clean long-term, but a breaking change for every SDK consumer and likely needs the REST server to serve the dispatcher shapes too.
Recommendation from the audit: A, with the REST shapes documented as canonical and the dispatcher aliases marked as such in the ledger.
Acceptance
- A decision recorded (ADR note or PR description) on which side moves.
- The four ledger rows reclassified from
mismatch to sdk (or a documented deliberate disposition). - The conformance guard keeps passing throughout —
mismatch rows already require notes, so the reclassification is mechanical once shapes agree.
Refs: audit doc docs/audits/2026-07-dispatcher-client-route-coverage.md §4; ledger rows at route-ledger.ts (analytics/storage sections).
Split from #3563. The route audit found four routes where a client method exists but speaks a different dialect than the dispatcher — ledgered as
mismatchinpackages/runtime/src/route-ledger.ts(the only non-sdk/non-deliberate dispositions left after PR-1…PR-5 drove the gap ratchet 27→0).The four
GET /analytics/meta(domains/analytics.ts:49)analytics.meta(cube)→GET /analytics/meta/:cubePOST /analytics/sql(analytics.ts:55)analytics.explain()→POST /analytics/explainPOST /storage/upload(domains/storage.ts:47)storage.upload()→ presigned/chunked protocol (/upload/presigned,/upload/complete,/upload/chunked/*)GET /storage/file/:id(storage.ts:56)storage.getDownloadUrl()→GET /storage/files/:id/urlConsequence: these client methods work only where
@objectstack/restfronts the dispatcher. Against a bare-dispatcher runtime they 404 — the method exists, the call fails, and nothing in types or tests says why.The decision to make (API-shape call, needs an owner)
/analytics/meta/:cube,/analytics/explain, the presigned protocol). No client change, no breakage; the dispatcher grows a few routes whose ledger rows becomesdk.Recommendation from the audit: A, with the REST shapes documented as canonical and the dispatcher aliases marked as such in the ledger.
Acceptance
mismatchtosdk(or a documented deliberate disposition).mismatchrows already require notes, so the reclassification is mechanical once shapes agree.Refs: audit doc
docs/audits/2026-07-dispatcher-client-route-coverage.md§4; ledger rows atroute-ledger.ts(analytics/storage sections).