Found by the control-plane ledger built for #3655 — the first thing that has ever been able to see this, because it is the first guard that spans the framework↔cloud boundary.
The gap
listTemplates: async () => {
const res = await this.fetch(`${this.baseUrl}/api/v1/cloud/templates`);
return this.unwrapResponse<{ templates: Array<{ id; label; description; category? }>; total: number }>(res);
},
/api/v1/cloud/templates is mounted by nothing:
- cloud repo — all 17 registrars in
cloud-artifact-api-plugin.ts were enumerated by driving them against a capturing mock IHttpServer. 91 registrations, 90 distinct. None is /cloud/templates.
- framework — the string occurs exactly once in this repo: line 1282 of
packages/client/src/index.ts, the call itself. http-dispatcher.ts has a handleCloud doc block listing the /cloud/* routes, left behind when those routes were relocated to cloud; it does not list templates either, and the method it documents no longer exists.
"Templates" are real as a data concept — sys_package_templates, a filtered sys_package view where is_starter = true, rendered as a console page — but there has never been an HTTP route that lists them.
So the method 404s. It is the sixth instance of the the method exists ≠ the method can be called class this audit family has turned up, after analytics.explain / analytics.meta (#3584), meta.getView (#3611), and i18n.getTranslations / getFieldLabels (#3636).
Why nothing caught it
The capstone (#3642) drives every SDK method and matches its URL against the union of the in-repo ledgers, but exempts the /api/v1/cloud/ prefix wholesale — the framework does not serve those routes and cannot vouch for them (#3655). listTemplates sat inside that exemption. The cloud-side ledger is what sees it, because it is the only place the mounted route set and the SDK are both in scope.
Options
- Trim the method. Nothing in either repo calls it, and the console reads starter packages through the
sys_package view rather than an API. Smallest correct change; the blast radius is any external caller, who is 404-ing today regardless.
- Mount the route in cloud. Reasonable if templates are meant to be an API concept — but it needs a decision about what a "template" is on the wire. The client's declared shape (
{ id, label, description, category? }) does not match sys_package's columns, so it is not a straight passthrough, and picking the mapping is a product/spec call, not an implementation detail.
- Leave it and document the method as unimplemented — the weakest option, since the type signature promises a resolved value.
Recommendation: option 1. Option 2 is defensible if starter templates are on the roadmap as an API, but shipping a route to satisfy a method nobody calls is the wrong order — add the method back with the route when there is a consumer.
Guard already in place
Whichever way this goes, the cloud-side ledger already pins it: UNMOUNTED_CLIENT_METHODS asserts the method still matches no mounted route, so mounting the route fails the test and forces an sdk ledger row; and the list is asserted to equal exactly ['projects.listTemplates'], so a second dead method cannot hide behind this one.
Refs: objectstack-ai/cloud (control-plane ledger, #3655), #3642 (the capstone whose exemption hid this), #3563 (the audit family).
Found by the control-plane ledger built for #3655 — the first thing that has ever been able to see this, because it is the first guard that spans the framework↔cloud boundary.
The gap
/api/v1/cloud/templatesis mounted by nothing:cloud-artifact-api-plugin.tswere enumerated by driving them against a capturing mockIHttpServer. 91 registrations, 90 distinct. None is/cloud/templates.packages/client/src/index.ts, the call itself.http-dispatcher.tshas ahandleClouddoc block listing the/cloud/*routes, left behind when those routes were relocated to cloud; it does not list templates either, and the method it documents no longer exists."Templates" are real as a data concept —
sys_package_templates, a filteredsys_packageview whereis_starter = true, rendered as a console page — but there has never been an HTTP route that lists them.So the method 404s. It is the sixth instance of the
the method exists ≠ the method can be calledclass this audit family has turned up, afteranalytics.explain/analytics.meta(#3584),meta.getView(#3611), andi18n.getTranslations/getFieldLabels(#3636).Why nothing caught it
The capstone (#3642) drives every SDK method and matches its URL against the union of the in-repo ledgers, but exempts the
/api/v1/cloud/prefix wholesale — the framework does not serve those routes and cannot vouch for them (#3655).listTemplatessat inside that exemption. The cloud-side ledger is what sees it, because it is the only place the mounted route set and the SDK are both in scope.Options
sys_packageview rather than an API. Smallest correct change; the blast radius is any external caller, who is 404-ing today regardless.{ id, label, description, category? }) does not matchsys_package's columns, so it is not a straight passthrough, and picking the mapping is a product/spec call, not an implementation detail.Recommendation: option 1. Option 2 is defensible if starter templates are on the roadmap as an API, but shipping a route to satisfy a method nobody calls is the wrong order — add the method back with the route when there is a consumer.
Guard already in place
Whichever way this goes, the cloud-side ledger already pins it:
UNMOUNTED_CLIENT_METHODSasserts the method still matches no mounted route, so mounting the route fails the test and forces ansdkledger row; and the list is asserted to equal exactly['projects.listTemplates'], so a second dead method cannot hide behind this one.Refs: objectstack-ai/cloud (control-plane ledger, #3655), #3642 (the capstone whose exemption hid this), #3563 (the audit family).