Measured at c804f0ca5 while implementing #11925, which expected the cloud families to be bindable
("InstalledPackage … 20 of the 38 plausibly land on a type that exists today") and found they are not.
Filed rather than fixed: choosing a casing is a contract decision for domain:spec / the cloud control
plane, and packages/spec is read-only from a domain:cli seat.
The measurement
packages/spec/src/cloud/environment.zod.ts declares EnvironmentSchema entirely in camelCase:
displayName, organizationId, isDefault, isSystem, databaseUrl, databaseDriver, createdBy.
$ grep -rcoE "^\s+[a-z]+_[a-z_]+:" packages/spec/src/cloud/tenant.zod.ts \
packages/spec/src/cloud/environment.zod.ts packages/spec/src/cloud/environment-package.zod.ts
packages/spec/src/cloud/tenant.zod.ts:0
packages/spec/src/cloud/environment.zod.ts:0
packages/spec/src/cloud/environment-package.zod.ts:0
# reverse-check — a term independently present, not a substring of the term under test
$ grep -cE "^\s+[a-zA-Z]+:" packages/spec/src/cloud/environment.zod.ts
63
Zero snake_case keys across the three cloud row schemas, against 63 key lines in the same file — so the
zero is a real absence, not a broken pattern.
The in-repo consumers of those same routes read and write snake_case, on both request and response:
| file | reads / writes |
|---|
packages/cli/src/commands/environments/show.ts:52-64 | p.display_name, p.organization_id, p.is_default, p.is_system, res.database.database_url, res.membership.role |
packages/cli/src/commands/environments/list.ts:53-72 | p.display_name, p.is_default, p.is_system |
packages/cli/src/commands/environments/create.ts:84-91 | sends organization_id, display_name, clone_from_environment_id |
These call client.projects.get/list/create/activate/update, i.e. /api/v1/cloud/environments/*. They were
written against the live control plane, so the wire is snake_case and Environment is not its type.
Consequence
Binding the 15 unannotated cloud client methods — projects.list/get/create/update/activate/ rotateCredential/updateHostname/updateVisibility/retryProvisioning (9) and projects.packages.list/install/ get/enable/disable/upgrade (6) — to Environment / EnvironmentCredential /
EnvironmentPackageInstallationtypechecks and is false, and would break the CLI call sites above at
compile time while telling them they are wrong when they are right. This is the same near-miss class #8140
recorded for SearchResult, at family scale.
It also makes one spec docblock's claim untrue as written: ListEnvironmentPackagesResponseSchema
(environment-package.zod.ts:185) names GET /cloud/environments/:environmentId/packages as its route and
declares camelCase EnvironmentPackageInstallation rows.
The fork
- The spec is right and the control plane should emit camelCase — one wire change in the cloud repo,
then all 15 methods bind and the CLI's reads move with it. - The wire is right and the spec should declare snake_case — the cloud row schemas are the ones that move.
- Both are right at different layers and the control plane is doing a documented case transform, in which
case the transform needs to be stated somewhere a consumer can read.
Nothing here is guessable from this repo; the control-plane implementation is not in it (grep -rn "sessionUpdated" packages/ apps/ finds only the client's own declaration).
Related: #11925, #11924.
Generated by Claude Code
Measured at
c804f0ca5while implementing #11925, which expected the cloud families to be bindable("
InstalledPackage… 20 of the 38 plausibly land on a type that exists today") and found they are not.Filed rather than fixed: choosing a casing is a contract decision for
domain:spec/ the cloud controlplane, and
packages/specis read-only from adomain:cliseat.The measurement
packages/spec/src/cloud/environment.zod.tsdeclaresEnvironmentSchemaentirely in camelCase:displayName,organizationId,isDefault,isSystem,databaseUrl,databaseDriver,createdBy.Zero snake_case keys across the three cloud row schemas, against 63 key lines in the same file — so the
zero is a real absence, not a broken pattern.
The in-repo consumers of those same routes read and write snake_case, on both request and response:
packages/cli/src/commands/environments/show.ts:52-64p.display_name,p.organization_id,p.is_default,p.is_system,res.database.database_url,res.membership.rolepackages/cli/src/commands/environments/list.ts:53-72p.display_name,p.is_default,p.is_systempackages/cli/src/commands/environments/create.ts:84-91organization_id,display_name,clone_from_environment_idThese call
client.projects.get/list/create/activate/update, i.e./api/v1/cloud/environments/*. They werewritten against the live control plane, so the wire is snake_case and
Environmentis not its type.Consequence
Binding the 15 unannotated cloud client methods —
projects.list/get/create/update/activate/ rotateCredential/updateHostname/updateVisibility/retryProvisioning(9) andprojects.packages.list/install/ get/enable/disable/upgrade(6) — toEnvironment/EnvironmentCredential/EnvironmentPackageInstallationtypechecks and is false, and would break the CLI call sites above atcompile time while telling them they are wrong when they are right. This is the same near-miss class #8140
recorded for
SearchResult, at family scale.It also makes one spec docblock's claim untrue as written:
ListEnvironmentPackagesResponseSchema(
environment-package.zod.ts:185) namesGET /cloud/environments/:environmentId/packagesas its route anddeclares camelCase
EnvironmentPackageInstallationrows.The fork
then all 15 methods bind and the CLI's reads move with it.
case the transform needs to be stated somewhere a consumer can read.
Nothing here is guessable from this repo; the control-plane implementation is not in it (
grep -rn "sessionUpdated" packages/ apps/finds only the client's own declaration).Related: #11925, #11924.
Generated by Claude Code