Skip to content

Commit 5e55739

Browse files
os-zhuangclaude
andauthored
feat(client)!: delete projects.listTemplates() — nothing has ever mounted its route (#3702) (#3730)
`client.projects.listTemplates()` built `GET /api/v1/cloud/templates`. That path is mounted by nothing: none of the 17 registrars in cloud's `cloud-artifact-api-plugin.ts` (91 registrations, enumerated by driving them against a capturing mock `IHttpServer`), and nothing here — the string occurs exactly once in each repo, at the call itself. Every invocation was a 404 behind a type signature promising a resolved value. Templates are real as DATA — `sys_package_templates`, the `is_starter = true` view over `sys_package`, rendered as a console page — but there has never been an HTTP route that lists them, and an org-wide code search finds no caller in any repo. Mounting a route to satisfy a method nobody calls is the wrong order: the client's declared shape (`{ id, label, description, category? }`) does not match `sys_package`'s columns, so picking that mapping is a product decision. The method returns when a route exists to back it, with an `sdk` ledger row proving so — cloud's ledger already pins the absence ("DELIBERATELY ABSENT") and `UNMOUNTED_CLIENT_METHODS` shrinks to empty on the companion bump there. Sixth instance of the `the method exists != the method can be called` class, after analytics.explain/meta (#3584), meta.getView (#3611) and i18n.getTranslations/getFieldLabels (#3636) — and the first only a cross-repo guard could see: the framework capstone (#3642) exempts the `/api/v1/cloud/` prefix wholesale because this repo does not serve those routes, so the finding came from cloud's control-plane ledger (#3655). Audit doc and the capstone's prose updated to record the finding as resolved. Closes #3702 Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6877e9a commit 5e55739

4 files changed

Lines changed: 52 additions & 15 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
"@objectstack/client": minor
3+
---
4+
5+
feat(client)!: delete `projects.listTemplates()` — it targeted a route nothing
6+
has ever mounted (#3702, #3655 finding)
7+
8+
`client.projects.listTemplates()` built `GET /api/v1/cloud/templates`. That
9+
path is mounted by **nothing**: none of the 17 registrars in `cloud`'s
10+
`cloud-artifact-api-plugin.ts` (91 registrations, enumerated by driving them
11+
against a capturing mock `IHttpServer`), and nothing in this repo — the string
12+
occurred exactly once in each repo, at the call itself. Every invocation was a
13+
404 with a type signature promising a resolved value.
14+
15+
"Templates" are real as **data**`sys_package_templates`, the
16+
`is_starter = true` view over `sys_package`, rendered as a console page — but
17+
there has never been an HTTP route that lists them, and no caller in either
18+
repo (nor in `objectui`) used the method. Mounting a route to satisfy a method
19+
nobody calls is the wrong order: the client's declared shape
20+
(`{ id, label, description, category? }`) does not match `sys_package`'s
21+
columns, so picking that mapping is a product decision, not an implementation
22+
detail. The method returns when a route exists to back it.
23+
24+
Sixth instance of the `the method exists ≠ the method can be called` class this
25+
audit family keeps finding, after `analytics.explain` / `analytics.meta`
26+
(#3584), `meta.getView` (#3611) and `i18n.getTranslations` / `getFieldLabels`
27+
(#3636) — and the first one only a cross-repo guard could see. The framework
28+
capstone (#3642) exempts the `/api/v1/cloud/` prefix wholesale, because this
29+
repo does not serve those routes; `cloud`'s control-plane ledger (#3655) is
30+
where the mounted set and the SDK are both in scope, and it pins the absence.
31+
32+
Callers who somehow depended on it were already receiving a 404; read starter
33+
packages through the `sys_package` view (`is_starter = true`) instead.

docs/audits/2026-07-dispatcher-client-route-coverage.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,8 @@ tranche 3 — 90 routes, each with a reviewed disposition, enumerated for real b
320320
driving all 17 registrars against a capturing mock `IHttpServer`.
321321

322322
The client half is the part that actually closes the boundary. Without it a
323-
route could be renamed there, the ledger updated to match, and all 23 methods
324-
404 with the server-side guard still green — so
323+
route could be renamed there, the ledger updated to match, and every
324+
`projects.*` method 404 with the server-side guard still green — so
325325
`projects-namespace-coverage.test.ts` drives the real SDK with a recording
326326
`fetch` and matches each URL against the ledger. Mutation-checked against
327327
exactly that scenario.
@@ -335,11 +335,13 @@ exactly that scenario.
335335

336336
Two findings, both pinned by tests rather than prose:
337337

338-
- **`projects.listTemplates` is dead** — it builds `/api/v1/cloud/templates`,
338+
- **`projects.listTemplates` was dead** — it built `/api/v1/cloud/templates`,
339339
which the string search finds exactly once in each repo: the call itself.
340-
Templates exist as a filtered `sys_package` view, never as a route (#3702).
341-
The sixth instance of the `#3584 / #3611 / #3636` class, and the first one
342-
only a cross-repo guard could have seen.
340+
Templates exist as a filtered `sys_package` view, never as a route. The
341+
sixth instance of the `#3584 / #3611 / #3636` class, and the first one only
342+
a cross-repo guard could have seen. **Removed in #3702** — there was no
343+
route to reconcile the method against, and no caller in either repo; it
344+
returns when a route exists to back it, with an `sdk` ledger row proving so.
343345
- **A duplicate route registration**`POST /actions/sys_environment/:actionName`
344346
mounted twice with an identical path, the second commented "legacy alias"
345347
and aliasing nothing (cloud#887).

packages/client/src/client-url-conformance.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ function matches(verb: string, path: string): Pattern | undefined {
139139
* it is a statement about where the coverage lives, not that there is none.
140140
*
141141
* That guard immediately found `projects.listTemplates` building
142-
* `/api/v1/cloud/templates`, which no registrar in either repo mounts (#3702).
142+
* `/api/v1/cloud/templates`, which no registrar in either repo mounts — the
143+
* sixth instance of the class above, and the first one only a cross-repo guard
144+
* could see. The method has since been deleted (#3702): the route was never
145+
* mounted anywhere, so there was nothing to reconcile it against.
143146
*
144147
* Exempt by PREFIX, and bounded from both ends: the assertions below pin which
145148
* methods are allowed to use it, so the hole cannot quietly widen into a place

packages/client/src/index.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,14 +1274,13 @@ export class ObjectStackClient {
12741274
return this.unwrapResponse<{ drivers: Array<{ name: string; driverId: string }>; total: number }>(res);
12751275
},
12761276

1277-
/**
1278-
* List available project templates. Templates are seeded into the project
1279-
* database once at provisioning time when `template_id` is supplied.
1280-
*/
1281-
listTemplates: async () => {
1282-
const res = await this.fetch(`${this.baseUrl}/api/v1/cloud/templates`);
1283-
return this.unwrapResponse<{ templates: Array<{ id: string; label: string; description: string; category?: string }>; total: number }>(res);
1284-
},
1277+
// The former `listTemplates` was removed in #3702 — it built
1278+
// `GET /api/v1/cloud/templates`, which nothing mounts in this repo or in
1279+
// `cloud` (the string occurred exactly once in each: at the call itself),
1280+
// so every invocation was a 404. Templates are a DATA concept — the
1281+
// `sys_package_templates` view over `sys_package` (`is_starter = true`) —
1282+
// never a route; `cloud`'s ledger pins that absence deliberately. It comes
1283+
// back when a route exists to back it, with an `sdk` ledger row proving so.
12851284

12861285
/**
12871286
* Per-project package installation management (Power Apps "solution" model).

0 commit comments

Comments
 (0)