Found while trimming projects.listTemplates (#3702 / #3730). That method's doc comment was the thread: "Templates are seeded into the project database once at provisioning time when template_id is supplied." Neither half of that sentence is true anymore, and the listing route was only the half that 404'd loudly.
The gap
The framework still ships the writing half of the template concept:
packages/client/src/index.ts:1081 — projects.create() declares template_id?: string on its body and POSTs it to /api/v1/cloud/environments.
packages/cli/src/commands/environments/create.ts:34 — --template is advertised as "Built-in template id (e.g. crm, todo, blank)", and line 83 forwards it as template_id.
Nothing consumes it:
- In
cloud (origin/main), template_id / templateId appears in zero non-test .ts files.
sys_environment.object.ts has no such column — the value is not even stored.
sys_package.object.ts:246 states the position outright: there "is no separate 'template' concept"; CI promotes examples/app-* packages by setting is_starter.
apps/cloud/objectstack.config.ts: "No built-in environment templates: every environment is provisioned empty. Apps/content come from the App Marketplace (sys_package)."
The blank / crm / todo registry the flag names was the apps/server createTemplatesRoutePlugin snapshot (see CHANGELOG ~L1143), which went away when the control plane moved to the cloud repo. The flag outlived the registry.
So os env create --name X --template crm is accepted, sent, and has no effect — no seeding, no error, no stored trace. This is the CLI advertising a capability the runtime does not deliver (AGENTS.md Prime Directive #10 corollary), and it is worse than the listTemplates 404 it is a sibling of: a 404 tells the caller something is wrong; a silently ignored flag reports success.
Options
- Trim both — drop
--template from the CLI and template_id from the projects.create body type. Smallest correct change; the only behaviour lost is a no-op. A script passing --template blank starts erroring instead of silently doing nothing, which is the point.
- Implement it — resolve
template_id to a marketplace package and install it at provisioning. Defensible: the is_starter = true view already exists and projects.packages.install already does the install post-provisioning, so this is mostly a "do it during provisioning" wiring job. But it needs the product decision listTemplates also needed — which packages are starters on the wire, and by what id — plus a route to list them, or the flag is unusable without out-of-band knowledge of the ids.
- Leave it and document the flag as inert — weakest; the flag's own help text is the false claim.
Recommendation: option 1, for the same reason #3702 took it: implement the concept when there is a consumer and a decided wire shape, not to justify an existing flag. Option 2 is a reasonable roadmap item if starter templates are meant to be a product surface — in which case it should land with a route and an sdk ledger row, and re-add both halves together.
Guard note
Nothing catches this class today. projects.create targets a route that is mounted, so both the framework capstone (#3642) and cloud's control-plane ledger (#3655) are green: they compare URLs, not request bodies. A body field the server never reads is invisible to a route-level guard — which is worth recording as the next edge of the audit family (#3563), not just as this one bug.
Found while trimming
projects.listTemplates(#3702 / #3730). That method's doc comment was the thread: "Templates are seeded into the project database once at provisioning time whentemplate_idis supplied." Neither half of that sentence is true anymore, and the listing route was only the half that 404'd loudly.The gap
The framework still ships the writing half of the template concept:
packages/client/src/index.ts:1081—projects.create()declarestemplate_id?: stringon its body and POSTs it to/api/v1/cloud/environments.packages/cli/src/commands/environments/create.ts:34—--templateis advertised as "Built-in template id (e.g. crm, todo, blank)", and line 83 forwards it astemplate_id.Nothing consumes it:
cloud(origin/main),template_id/templateIdappears in zero non-test.tsfiles.sys_environment.object.tshas no such column — the value is not even stored.sys_package.object.ts:246states the position outright: there "is no separate 'template' concept"; CI promotesexamples/app-*packages by settingis_starter.apps/cloud/objectstack.config.ts: "No built-in environment templates: every environment is provisioned empty. Apps/content come from the App Marketplace (sys_package)."The
blank/crm/todoregistry the flag names was theapps/servercreateTemplatesRoutePluginsnapshot (see CHANGELOG ~L1143), which went away when the control plane moved to thecloudrepo. The flag outlived the registry.So
os env create --name X --template crmis accepted, sent, and has no effect — no seeding, no error, no stored trace. This is the CLI advertising a capability the runtime does not deliver (AGENTS.md Prime Directive #10 corollary), and it is worse than thelistTemplates404 it is a sibling of: a 404 tells the caller something is wrong; a silently ignored flag reports success.Options
--templatefrom the CLI andtemplate_idfrom theprojects.createbody type. Smallest correct change; the only behaviour lost is a no-op. A script passing--template blankstarts erroring instead of silently doing nothing, which is the point.template_idto a marketplace package and install it at provisioning. Defensible: theis_starter = trueview already exists andprojects.packages.installalready does the install post-provisioning, so this is mostly a "do it during provisioning" wiring job. But it needs the product decisionlistTemplatesalso needed — which packages are starters on the wire, and by what id — plus a route to list them, or the flag is unusable without out-of-band knowledge of the ids.Recommendation: option 1, for the same reason #3702 took it: implement the concept when there is a consumer and a decided wire shape, not to justify an existing flag. Option 2 is a reasonable roadmap item if starter templates are meant to be a product surface — in which case it should land with a route and an
sdkledger row, and re-add both halves together.Guard note
Nothing catches this class today.
projects.createtargets a route that is mounted, so both the framework capstone (#3642) and cloud's control-plane ledger (#3655) are green: they compare URLs, not request bodies. A body field the server never reads is invisible to a route-level guard — which is worth recording as the next edge of the audit family (#3563), not just as this one bug.