Measured while implementing #11712 (threading ?mode=draft on the compound-name PUT /meta/:type/:section/:name). Separate surface, filed rather than folded: #11712 is about a write door dropping a query parameter, this is about a route that is never registered.
What was measured
Driven through the real registered server — new RestServer(...).registerRoutes(), then every route whose path contains publish, read off getRoutes():
POST /api/v1/meta/:type/:name/publish
GET /api/v1/meta/:type/:name/published
GET /api/v1/meta/:type/:section/:name/published
Three routes, and the arities do not line up:
| Verb |
single-segment |
compound |
PUT (save / stage) |
mounted |
mounted |
GET .../published (read the published snapshot) |
mounted |
mounted |
POST .../publish (promote the draft) |
mounted |
not mounted |
POST /api/v1/meta/object/crm/task/publish is four segments after /meta. /:type/:name/publish is three plus a literal, and /:type/:section/:name/published requires the literal published, not publish. No registered route matches it.
Why this is a real gap and not a shape the platform declined
The compound arity is not an accident on this surface — registerMetadataEndpoints mounts the published READ in both arities deliberately, and says why in its own comment:
Both arities, mirroring the getItem / saveItem twins: the SDK documents getPublished('lead', 'views/all_leads'), and a compound name is how every other read on this surface addresses a sub-resource.
And nothing below the route refuses a compound name. publishMetaItem takes { type, name } and keys the draft on type/name/organization/package; the name's spelling is never read as a shape. Driven directly against a seeded draft it promotes normally:
PROMOTE -> {"success":true,"version":"sha256:8d86f0c6…","seq":1,
"message":"Published draft — type=object, name=crm_task [seq=1]"}
The consequence, once #11712 lands
Before #11712 the gap was unreachable: the compound PUT ignored ?mode=draft, so a compound-named draft could not be created through REST at all. After it, a caller can stage one — and then has no REST door to promote it. The draft is writable and readable and not publishable, over REST, by the same caller.
Workarounds that exist today, neither of which is the per-item door: POST /packages/:id/publish-drafts promotes a whole package's drafts at once, and the runtime dispatcher's own meta.publish verb is reachable without the REST route.
Where it would land
packages/rest/src/rest-server.ts — the POST ${metaPath}/:type/:name/publish registration, which would become a two-arity for loop over both path spellings exactly as the published read above it already is, with section folded into a compound name the same way. Registration ORDER matters here the way it does for /history, /audit and /published: Hono is first-match-wins and the three-segment form collides with GET /:type/:section/:name.
Related: #11712 (the write half of the same arity gap), #7526 (mounted the published read in both arities), #7019 (the twin-parity ruling this pair keeps being closed on).
Measured on origin/main at 1f6d04703.
Generated by Claude Code
Measured while implementing #11712 (threading
?mode=drafton the compound-namePUT /meta/:type/:section/:name). Separate surface, filed rather than folded: #11712 is about a write door dropping a query parameter, this is about a route that is never registered.What was measured
Driven through the real registered server —
new RestServer(...).registerRoutes(), then every route whose path containspublish, read offgetRoutes():Three routes, and the arities do not line up:
PUT(save / stage)GET .../published(read the published snapshot)POST .../publish(promote the draft)POST /api/v1/meta/object/crm/task/publishis four segments after/meta./:type/:name/publishis three plus a literal, and/:type/:section/:name/publishedrequires the literalpublished, notpublish. No registered route matches it.Why this is a real gap and not a shape the platform declined
The compound arity is not an accident on this surface —
registerMetadataEndpointsmounts thepublishedREAD in both arities deliberately, and says why in its own comment:And nothing below the route refuses a compound name.
publishMetaItemtakes{ type, name }and keys the draft on type/name/organization/package; the name's spelling is never read as a shape. Driven directly against a seeded draft it promotes normally:The consequence, once #11712 lands
Before #11712 the gap was unreachable: the compound
PUTignored?mode=draft, so a compound-named draft could not be created through REST at all. After it, a caller can stage one — and then has no REST door to promote it. The draft is writable and readable and not publishable, over REST, by the same caller.Workarounds that exist today, neither of which is the per-item door:
POST /packages/:id/publish-draftspromotes a whole package's drafts at once, and the runtime dispatcher's ownmeta.publishverb is reachable without the REST route.Where it would land
packages/rest/src/rest-server.ts— thePOST ${metaPath}/:type/:name/publishregistration, which would become a two-arityforloop over both path spellings exactly as thepublishedread above it already is, withsectionfolded into a compound name the same way. Registration ORDER matters here the way it does for/history,/auditand/published: Hono is first-match-wins and the three-segment form collides withGET /:type/:section/:name.Related: #11712 (the write half of the same arity gap), #7526 (mounted the
publishedread in both arities), #7019 (the twin-parity ruling this pair keeps being closed on).Measured on
origin/mainat1f6d04703.Generated by Claude Code