fix(rest,runtime): mount six ledgered-but-dead routes and gate the class that hid them (#7526) - #7584
Conversation
…lass that hid them (#7526) Three routes were in the ledgers, implemented in the dispatcher, and mounted by nobody — two of them answering a plausible 200 instead of a 404: * GET /meta/types fell into the /meta/:type catch-all and answered {"type":"types","items":[]}, shape-identical to /meta/zzz_not_a_type; * GET /meta/:type/:name/published fell into the compound-name route and answered a stub identical before publish AND for a bogus name — a route that structurally could not 404; * GET /meta/objects/:name/state/:field needs four path segments and REST's /meta registrations topped out at three, so it answered Hono's notFound. All three mount now, ahead of the catch-alls that were swallowing them, with the compound-name /published arity the SDK documents. The routes were the symptom. The ledgers are a DECLARATION and every guard built on them (#3563/#3587/#3636/#3642) reads their union as an OBSERVATION of what is mounted, so the audit chain was green on this class by construction. This adds the missing observation: a route-ledger <-> live-mount parity gate that boots a real server, reads the mount table off it, and asserts both directions. It consults no second hand-written list of what is mounted, and it PROBES reachability through the live router rather than checking presence in a table — a literal registered after a catch-all sibling is mounted and unreachable. IHttpServer grows two optional feature-detected members for it — getMountedRoutes() and resolveMountedRoute() — implemented by the Hono adapter. On its first run the gate found three more instances of the same class: /automation/actions, /automation/connectors and /automation/_status were ordered ahead of the /:name catch-all inside dispatch(), with a comment calling the order load-bearing, while the bridge that mounts /automation registered /:name and never those three. It also found the unledgered live mounts — the four /api/settings routes get a ledger of their own, and GET /.well-known/objectstack plus the object-less POST /actions//:action get rows in the dispatcher ledger. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnUXMJxLDLoQ9MTRthtAGd
…7526) The parity gate proves they are MOUNTED. This proves they ANSWER — a different question, and the one `/published` failed most visibly: it was reachable in the sense that requests to it got a 200, from the compound-name route, with a body identical before publish AND for a name that does not exist. Booted with a MetadataPlugin because `getPublished` is a metadata- SERVICE capability and `objectstack serve` composes one for every deployment; without it the route answers a typed 501, which is the honest degradation but not the behaviour under test. Also conforms the new error bodies to the envelope `BaseResponseSchema` declares (`pnpm check:route-envelope` ratchets the two non-conforming dialects DOWN only), and retires a stale assertion in rest-openapi-route.test.ts: `/api/v1/meta/types` was listed as a path that 'exists nowhere in the repo', which was true of the ROUTE and is no longer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnUXMJxLDLoQ9MTRthtAGd
…te-ledger-live-mount-parity
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 6 package(s): 116 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #7526
The deliverable is the gate
The three dead
/metaroutes are the symptom. The five route ledgers are a declaration, and every guard built on them (#3563 / #3587 / #3636 / #3642) reads their union as an observation of what is mounted — soGET /meta/objects/:name/state/:fieldcounted as mounted because it was ledgered, the SDK URL guard passed the method calling it, and it 404'd at runtime. The audit chain was green on this class by construction.This adds the missing observation.
The gate —
packages/qa/dogfood/test/route-ledger-live-mount-parity.dogfood.test.tsBoots a real showcase stack, reads the mount table off the running server, asserts both directions:
/meta/types, which is the whole point.The mounted side is never hand-maintained — a second hand-written list of "what we mount" would drift exactly the way the ledgers did.
IHttpServergrows two optional, feature-detected members for it, implemented by the Hono adapter:getMountedRoutes()— the live mount table, in registration order;resolveMountedRoute(method, path)— which registration answers a concrete request, per the router itself.The gate fails rather than skips when either is absent: a parity gate that quietly passes because it could not look is the failure it exists to end.
Registration order is measured, not assumed.
plugin-hono-server/src/mounted-route-introspection.test.tsregisters a literal and a:paramsibling in both orders and pins that the later one never runs.Proof the gate works — reverted, red; reapplied, green
With
rest-server.tsanddispatcher-plugin.tsreverted toorigin/mainand rebuilt:Reapplied and rebuilt:
Test Files 3 passed (3) · Tests 20 passed (20).The routes
GET /meta/types/meta/:typecatch-all →{"type":"types","items":[]}, shape-identical to/meta/zzz_not_a_type/meta/:type; same handler asGET /metaGET /meta/:type/:name/published/:type/:section/:name; 404s for a bogus name. Compound arity/:type/:section/:name/publishedmounted too — the SDK documentsgetPublished('lead','views/all_leads')GET /meta/objects/:name/state/:fieldnotFound; REST's/metaroutes topped out at 3 segments, this needs 4/meta/object/...spelling the dispatcher branch acceptedThree more instances, found by the gate on its first run
GET /automation/actions,/automation/connectors,/automation/_statuswere ordered ahead of the/:namecatch-all insidedispatch()— with a module comment saying the order was load-bearing — while the bridge that actually mounts/automationregistered/:nameand never those three. Same class, same fix. This is the card's "fixing the routes without the gate buys the next three", measured.Unledgered live mounts (PENDING-GAPS §E)
Read off the booted server, not off a list:
/api/settings×4 → newpackages/services/service-settings/src/settings-route-ledger.ts(allserver-only; the Setup UI consumes them over plain HTTP, the SDK expresses none).GET /.well-known/objectstack→ a dispatcher-ledger row +/.well-known/objectstackinNON_DISPATCH_MOUNT_PREFIXES. Needed a newabsolute?: booleanfield: a well-known URI under/api/v1is not a well-known URI, andclient-url-conformance.test.tshonours it too rather than compiling a pattern nothing serves.POST /actions//:action(the object-less shape, deliberate since [17.0.0-rc.0] Global actions unreachable: registered under key 'global', REST fallback probes '*' — and handler failures return HTTP 200 {success:true,data:{success:false}} #3913) → a dispatcher-ledger row.POST /actions/global/:actionuses a newservedByfield: it is a calling convention served by/actions/:object/:action, not a registration. The gate does not take the field's word for it — it probes and asserts the live router answers with exactly that pattern.Measured, not closed:
/api/v1/datasourcesand/api/v1/datasources/driversare 2 of 9 unledgered admin routes inservice-datasource, and that plugin is not in this gate's boot. Ledgering 9 routes needs 9 reviewed dispositions I have not verified consumers for — see the report on the issue for the recommended follow-up.The drifted pin
meta-types-create-seed.dogfood.test.tssaid/meta/typesin its header and called/meta. It calls/meta/typesnow, and pins the alias (/meta/types≡/meta) plus the disguise (/meta/zzz_not_a_type→{type,items:[]},/meta/types→entries[]). A systematic comment-vs-call scan of all 89 dogfood files found no other pin with the same gap — details in the issue report.Checks
pnpm lint✅ ·pnpm typecheck(rest, runtime, client, plugin-hono-server, dogfood, spec) ✅ ·pnpm check:route-envelope✅ (new bodies conform; both ratchets unchanged) · rest 1348 ✅ · runtime 1990 ✅ · client 279 ✅ · plugin-hono-server 198 ✅ · dogfood 562 ✅ (89 files).origin/mainmerged and rebuilt before the final run.Serial constraints honoured
Region touched in
rest-server.tsis the/metaroute registration only. The reports-delete handler (#7523) andmapDataError/ the error-mapping region (#7525) are untouched.🤖 Generated with Claude Code
https://claude.ai/code/session_01DnUXMJxLDLoQ9MTRthtAGd
Generated by Claude Code