Found while implementing #12866 (ADR-0006 D2, SDK half). Filed unassigned. Request-side sibling: #3739 (projects.create() declares 13 body fields, the control plane reads 4) — this card is the RESPONSE side of the same method.
The gap
client.environments.create() (packages/client/src/index.ts; client.projects.create() before #12866) declares its unwrap shape as the single key environment. Measured 2026-08-28 against objectstack-ai/cloudorigin/main, the handler — packages/service-cloud/src/routes/environment-lifecycle.ts, POST ${prefix}/cloud/environments — answers 201 with:
ok({
environment: result.environment,
warnings: result.warnings,
durationMs: result.durationMs,
...(result.hostnameAssignment ? { hostnameAssignment: result.hostnameAssignment } : {}),
})
So three keys the route really sends are undeclared. warnings in particular is the channel a partially-degraded provision uses to tell the caller what it could not do, and no SDK caller can reach it without an as any.
hostnameAssignment is conditional by design — the handler's own comment says it is forwarded only when the control plane renamed a colliding hostname, "so absence stays absence" — so it would have to be declared optional, and declaring it at all means declaring the shape ProvisionEnvironmentResponseSchema (spec cloud/environment) gives it.
Why this is its own card and not part of #12866
#12866 corrected two falsehoods in this declaration (the key was project, which this route has never sent; and a non-optional database block the route does not send at all) because both were the card's own defect class — a declared key contradicting the wire. ADDING keys is different: it widens published surface, it needs the optionality of hostnameAssignment decided, and it needs a decision on whether to bind the spec schema or keep the erased any. That is a decision, not a rename.
Note the neighbouring constraint recorded on the namespace's docblock (#11925 / #12036): @objectstack/spec/cloud's camelCase row contracts are deliberately NOT bound here, because the /api/v1/cloud/* control plane speaks snake_case. Whatever is decided for these three keys has to answer that first.
Generated by Claude Code
Found while implementing #12866 (ADR-0006 D2, SDK half). Filed unassigned. Request-side sibling: #3739 (
projects.create()declares 13 body fields, the control plane reads 4) — this card is the RESPONSE side of the same method.The gap
client.environments.create()(packages/client/src/index.ts;client.projects.create()before #12866) declares its unwrap shape as the single keyenvironment. Measured 2026-08-28 againstobjectstack-ai/cloudorigin/main, the handler —packages/service-cloud/src/routes/environment-lifecycle.ts,POST ${prefix}/cloud/environments— answers201with:So three keys the route really sends are undeclared.
warningsin particular is the channel a partially-degraded provision uses to tell the caller what it could not do, and no SDK caller can reach it without anas any.hostnameAssignmentis conditional by design — the handler's own comment says it is forwarded only when the control plane renamed a colliding hostname, "so absence stays absence" — so it would have to be declared optional, and declaring it at all means declaring the shapeProvisionEnvironmentResponseSchema(speccloud/environment) gives it.Why this is its own card and not part of #12866
#12866 corrected two falsehoods in this declaration (the key was
project, which this route has never sent; and a non-optionaldatabaseblock the route does not send at all) because both were the card's own defect class — a declared key contradicting the wire. ADDING keys is different: it widens published surface, it needs the optionality ofhostnameAssignmentdecided, and it needs a decision on whether to bind the spec schema or keep the erasedany. That is a decision, not a rename.Note the neighbouring constraint recorded on the namespace's docblock (#11925 / #12036):
@objectstack/spec/cloud's camelCase row contracts are deliberately NOT bound here, because the/api/v1/cloud/*control plane speaks snake_case. Whatever is decided for these three keys has to answer that first.Generated by Claude Code