Skip to content

Route audit tranche 2: the @objectstack/rest surface (search / forms / reports / sharing / …) — #3563 follow-up #3587

Description

@os-zhuang

Split from #3563. The dispatcher tranche is complete (gap ratchet 27→0, #3569 #3571 #3575 #3577 #3579), but the dispatcher is not the only server surface: @objectstack/rest mounts a second, larger route set the client also reaches — and it has never been audited with the same rigor.

Known zero-expression routes (from the PR-1 sweep, rest-server.ts line refs in the audit doc)

Route family Routes
GET /search?q&objects&limit&perObject 1
POST /email/send 1
forms/:slug (get / submit / lookup) 3
Record shares GET/POST/DELETE /data/:object/:id/shares[/:shareId] 3
POST /data/:object/:id/clone 1
POST /analytics/dataset/query 1
sharing/rules (incl. /evaluate) 5
reports (incl. /run, /schedule, /schedules) 8
external-datasource/* (external-datasource-routes.ts) family

Plus the reverse direction that tranche 1 deliberately left out of scope: client surfaces that target REST-only routes (views CRUD, permissions, workflow, approvals, realtime, notification devices/preferences, presigned storage, import jobs) — currently expressed but with no ledger row anywhere asserting their server side exists.

Proposed method — same as tranche 1, one new problem to solve

  1. Enumerate the REST mounts (rest-server.ts is large; the mounts are server.<verb>() calls — scriptable).
  2. Extend the ledger (or add a sibling rest-route-ledger.ts in packages/rest) with per-route dispositions.
  3. Guard on both sides, same split as feat(runtime): route ledger + conformance guard for the dispatcher↔client surface (#3563) #3569: enumeration-side conformance next to where routes are declared; client-method existence next to the SDK. The tranche-1 lesson applies verbatim: no runtime→client package edge (build cycle) — each package verifies its own half.
  4. New problem tranche 1 didn't have: REST route registration is method calls, not a registry with .list(). Either add an introspection seam to the REST server (preferred — mirrors DomainHandlerRegistry.list()), or pin the mount list the way LEGACY_CHAIN_PREFIXES pins the if-chain.

Acceptance

  • Every REST-mounted route has a reviewed disposition.
  • A ratchet equivalent to tranche 1's, wired into CI.
  • The reverse direction closed: every client method's target route provably exists on some server surface.

Refs: audit doc docs/audits/2026-07-dispatcher-client-route-coverage.md §8; ledger scope note at packages/runtime/src/route-ledger.ts header.

Activity

  1. os-zhuang commented on Jul 27, 2026

    @os-zhuang
    ContributorAuthor

    PR-1 merged: #3609 — every route @objectstack/rest mounts (89) now carries a reviewed disposition in packages/rest/src/rest-route-ledger.ts (38 sdk / 43 gap, ratcheted / 3 server-only / 3 public / 2 mismatch), with real enumeration on both sources (RouteManager via getRoutes(); the two bypassing registrars via captured mock-server registrations) and the client-method direction guarded package-side. All five guard directions were negative-tested.

    The issue's anticipated "no introspection seam" problem mostly didn't materialize — RouteManager/getRoutes() already existed; only the 9 directly-mounted routes needed their own enumeration path.

    Findings split out per convention:

    Remaining under this issue: gap-closure batches (43 → 0), the third surface (service-storage / service-i18n autonomous mounts), and the reverse-direction guard.


    Generated by Claude Code

  2. os-zhuang commented on Jul 27, 2026

    @os-zhuang
    ContributorAuthor

    The REST-surface tranche is complete — the ledger now carries zero mismatches and zero gaps, both bounds CI-pinned.

    The arc, PR by PR:

    Every closed gap = a client method speaking the handler's exact wire shape, a URL-pinning unit test, and a flipped ledger row the two-sided guard verifies (@objectstack/client 163 tests, @objectstack/rest 394). The gap ratchet now rests at 0 and stays there — a new gap row demands an explicit, reviewed decision.

    Still open under this issue (from the ledger's NOT-COVERED note): the third surface — services that autonomously mount routes on the host IHttpServer (service-storage's storage-routes.ts, i.e. the SDK's whole storage surface, and service-i18n). Those live outside @objectstack/rest and need their own enumeration seam before they can be ledgered. Leaving the issue open for that tranche.


    Generated by Claude Code

  3. self-assigned this
    on Jul 27, 2026
  4. os-zhuang commented on Jul 27, 2026

    @os-zhuang
    ContributorAuthor

    Closing as completed — all three acceptance criteria are met and CI-pinned:

    1. Every REST-mounted route has a reviewed disposition — 109 routes across both registrar families in rest-route-ledger.ts.
    2. Ratchet wired into CI — mismatch bound 0 (fix(rest,client)!: reconcile the two REST↔client mismatches the audit ledgered (#3610, #3611) #3621), gap bound 0 (feat(client): close the 9 metadata-family REST gaps (#3587 batch 1/5) #3625 → feat(client): close the final 17 REST gaps — ratchet 17→0 (#3587 batches 4+5/5) #3633), both enforced by the two-sided conformance guard.
    3. Reverse direction closed — the 21 no-serving-surface client methods are deleted (21 client SDK methods target routes no server surface mounts — delete on the open major train, or build the servers (#3587 finding) #3612/feat(client,spec)!: delete 21 dead SDK methods + the four ghost route tables under them (#3612) #3616), the two wrong-shape ones reconciled (fix(rest,client)!: reconcile the two REST↔client mismatches the audit ledgered (#3610, #3611) #3621), and every ledger row naming a client method is verified to resolve.

    The third surface noted in the ledger header (autonomously-mounted service-storage / service-i18n routes — 13 mounts, currently expressed by the SDK but backed by no ledger) is split to #3636, following the same close-and-split convention this issue got from #3563.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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