Skip to content

Four SDK metadata methods build compound-name URLs against routes mounted in one arity only — measured 404, and getHistory/deleteItem encode while getAudit/getReferences/diffItem/rollbackItem do not #12106

Description

@claude

Blocked-by: #12176

Measured while mounting the compound-name promotion door (#11932 / PR #12105). Filed rather than folded: #11932 is one route; this is four SDK methods aimed at an arity the server does not mount, and the split that produces it.

2026-08-25 maintainer ruling (recorded in the comments): the option-1/option-2 fork below is SUPERSEDED — metadata item names are ruled to not contain /, so the compound-name convention itself is being retired via #12176's staged plan. This card waits on that card; the defect dissolves at its re-addressing stage.

What was measured

Driven through the real registered server — new RestServer(new HonoHttpServer(0), …).registerRoutes() — and asked of the LIVE router which registration would answer each path (IHttpServer.resolveMountedRoute, the same observation the #7526 dogfood parity gate reads). views/all_leads is the compound name the SDK's own doc comments use.

GET /api/v1/meta/view/views/all_leads/references -> undefined client.meta.getReferences
GET /api/v1/meta/view/views/all_leads/audit -> undefined client.meta.getAudit
GET /api/v1/meta/view/views/all_leads/diff -> undefined client.meta.diffItem
POST /api/v1/meta/view/views/all_leads/rollback -> undefined client.meta.rollbackItem
GET /api/v1/meta/view/views%2Fall_leads/history -> GET /api/v1/meta/:type/:name/history client.meta.getHistory
DELETE /api/v1/meta/view/views%2Fall_leads -> DELETE /api/v1/meta/:type/:name client.meta.deleteItem
GET /api/v1/meta/view/views/all_leads/published -> GET /api/v1/meta/:type/:section/:name/published
POST /api/v1/meta/view/views/all_leads/publish -> POST /api/v1/meta/:type/:section/:name/publish

Live, through the real Hono app:

POST /api/v1/meta/view/views/all_leads/rollback -> status 404, body "404 Not Found"
GET /api/v1/meta/view/views/all_leads/audit -> status 404, body "404 Not Found"

That is Hono's notFound, byte-identical to an unmounted path — not a handler answer.

The root cause is a split inside the SDK, not four separate oversights

packages/client/src/index.ts builds these URLs two different ways, and nothing states which is the rule:

methodspellingresult for a compound name
getReferences${route}/${type}/${name}/referencesextra path segment, no route matches, 404
getAudit${route}/${type}/${name}/audit404
diffItem${route}/${type}/${name}/diff404
rollbackItem${route}/${type}/${name}/rollback404
getHistory${route}/${encodeURIComponent(type)}/${encodeURIComponent(name)}/historyone segment, resolves, works
deleteItem${route}/${encodeURIComponent(type)}/${encodeURIComponent(name)}resolves, works
getItem / saveItem / getPublished / publishItemunencodedworks — the server mounts the compound arity for these four

So the unencoded spelling is correct exactly where a compound arity is mounted, and silently wrong everywhere else. Two methods that reach the same family of doors (getHistory vs getAudit) sit on opposite sides of the split, one line apart in the same file.

meta.publishItem's own doc comment says "Compound names pass through unencoded, like getItem" — that sentence was true of the URL and false of the server until #11932 mounted the route. The same sentence is implied by the four methods above and is still false for them.

Why the existing guards cannot see this

#3642's reverse guard matches the URL a client method CONSTRUCTS against the ledger patterns. A compound name is interpolated into the same ${name} slot the single-arity pattern declares, so the guard sees /meta/:type/:name/audit, finds the row, and passes. The extra segment only exists at call time. #7526's live-mount parity gate probes ledger rows against the router and would catch an unmounted declared route — but no row declares the compound arity of these four, so there is nothing for it to probe. Both guards are green on this class by construction, which is the same shape #7526 was filed about.

Two dispositions, not one — the fork belongs to a maintainer

  1. Encode in the SDK (getHistory's spelling) — four one-line changes, no new public surface, and it makes these methods work for compound names today. But it makes the transport spelling inconsistent with getItem / saveItem / getPublished / publishItem, which deliberately pass through unencoded because the server mounts a compound arity for them.
  2. Mount the compound arity for /references, /audit, /diff and /rollback (and decide about /layers, which is server-only) — consistent with the direction Three ledgered /meta routes are never mounted and die in the /meta/:type catch-all — the route audit can't see this class because it treats the ledger as ground truth for what's mounted #7526 and A compound-name draft has no REST promotion door — POST /meta/:type/:name/publish is mounted in one arity while its read twin /published is mounted in both #11932 took, and it makes one spelling correct everywhere. But it is four new public routes, so it is a Clause-② surface expansion and wants the contract-review tier.

Not proposing which. What is not in doubt is that the current state is neither: an SDK method that constructs a URL nothing serves, answering 404 with no diagnostic.

Scope note

GET /api/v1/meta/:type/:name/layers is server-only in rest-route-ledger.ts and has no SDK method, so it is listed for completeness rather than as part of the defect.

Measured on origin/main at 1e79aa4f8 plus PR #12105 (efd007b73); the two mounted-compound controls in the first block are /published (#7526) and /publish (that PR).


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions