Maintainer direction given in a direct PM session on 2026-09-06, verbatim (⛔ quoted, not translated):
我一直觉得 cloud 的协议应该放在云端,没必要开源
Filed as a decision card on the maintainer's instruction in the same session ("开决策卡"). This card is
UPSTREAM of #12036: if the control-plane schemas leave the open-source contract package, #12036's
camelCase-vs-snake_case fork stops being a cross-repo contract decision and becomes one repo's internal
coding style. #12036 is therefore moved out of the decision inbox and blocked on this card.
⛔ Nothing was changed. No branch, no edit. All readings below are read-only.
Readings
Taken 2026-09-06T04:42Z. objectstack-ai/objectstack origin/main d5d8d50db;
objectstack-ai/cloud origin/main daaac081; objectstack-ai/objectui origin/main 0558e0f78.
1. The boundary already exists — it was drawn half-way
@objectstack/spec's main entry does not re-export the cloud namespace. ./cloud is a standalone
published subpath in the package's exports map (packages/spec/package.json), with its own
browser/import/require conditions.
$ grep -cE "^export" packages/spec/src/index.ts # control — the file does export things
59
$ grep -cE "^export.*cloud" packages/spec/src/index.ts # term under test
0
⇒ 59 export lines and zero of them name cloud: the absence is real, not a broken pattern. Reaching these
schemas already requires the explicit @objectstack/spec/cloud subpath.
2. The open-source repo's real dependency on the control-plane half is ONE symbol
Every other in-repo occurrence of @objectstack/spec/cloud outside packages/spec/src/cloud/ is a
comment, a test, or a re-export of something that lives elsewhere:
| site |
what |
real dependency? |
packages/spec/src/api/discovery.zod.ts:8 |
import type { EnvironmentType } from '../cloud/environment.zod' |
⭐ YES — the only one. A string-union type |
packages/spec/src/api/package-api.zod.ts:10 |
import { ArtifactReferenceSchema } from '../cloud/marketplace.zod' |
YES, but on the PACKAGE-FORMAT half (see §3) |
packages/metadata/src/plugin.ts:895 |
dynamic import('@objectstack/spec/cloud') for EnvironmentArtifactSchema |
No — packages/spec/src/cloud/environment-artifact.zod.ts is a pure re-export whose body is export { … } from '../system/environment-artifact.zod'. Re-point the import at @objectstack/spec/system and the dependency is gone |
packages/client/src/index.ts:2233,2350 |
comments only |
No — see §4 |
packages/cli/test/package-publish-namespace.test.ts |
CreatePackageRequestSchema |
test, package-format half |
packages/client/src/return-type-precision.test.ts |
Environment type |
test |
3. spec/cloud holds two families with different owners
| half |
files |
lines |
what it actually is |
| control-plane private |
environment.zod, environment-package.zod, tenant.zod, developer-portal.zod, marketplace-admin.zod, app-store.zod |
2087 |
the cloud service's own row shapes and back-office. Open-source code touches one type from it |
| package & marketplace format |
package.zod, package-version.zod, marketplace.zod, package-l10n, template-manifest.zod |
1400 |
⚠️ not a cloud protocol. A package author needs it; the open-source CLI's os package publish path uses CreatePackageRequestSchema; spec/api/package-api.zod.ts depends on its ArtifactReferenceSchema |
Intra-directory dependency roots: package.zod ← marketplace.zod ← {developer-portal, app-store};
tenant.zod ← environment.zod. So the two halves are separable, with one edge to cut
(app-store and developer-portal sit on marketplace.zod).
4. The SDK already refuses to bind these — with a maintainer ruling on record
packages/client/src/index.ts:2228-2248 is a 20-line docblock stating that the 15 environments.* methods
keep their erased any deliberately, naming #11925/#12036 and the casing conflict. At :2340-2372 a
prior ruling is recorded verbatim — maintainer, 2026-08-29: 「同意」— option 甲 — choosing the INLINE wire
shape and ⛔ NOT binding @objectstack/spec/cloud's ProvisionEnvironmentResponseSchema.
⇒ The direction on this card has effectively been ruled once already, per-method. This card asks whether it
becomes the rule for the whole subpath.
5. Producer and consumers of the control-plane half both live in the cloud repo
From the repo:cloud reading already on #12036 (comment 5520459478, cloud 9b6abe0), re-confirmed at
daaac081: 12 import sites, including a runtime parse at
packages/service-tenant/src/environment-provisioning.ts:750
(ProvisionEnvironmentRequestSchema.parse(request)) and const environment: Environment = { … } at :1094
and :1429. The schemas' only real consumer is the repo they would move to.
6. objectui's dependency is one blanket re-export line
$ git grep -n "@objectstack/spec/cloud" origin/main # objectstack-ai/objectui @ 0558e0f78
packages/types/src/index.ts:1106:export type * as Cloud from '@objectstack/spec/cloud';
Every other hit in that repo is a comment, and each of those comments says the type was deliberately
re-declared inline instead of imported (app-shell/src/console/marketplace/marketplaceApi.ts:85,
usePackageL10n.ts:6, app-shell/src/views/metadata-admin/PackagesPage.tsx:75).
The options
A — move the whole ./cloud subpath to the cloud repo. All 3487 lines leave @objectstack/spec; the
subpath export is deleted.
⚠️ Cost: the package/marketplace format leaves with it, so os package publish and
spec/api/package-api.zod.ts lose their declarations — a package author would need a cloud-repo package to
author a package for the open-source CLI.
B — cut by owner (recommended). The control-plane half (2087 lines, 6 files) moves to the cloud repo.
The package/marketplace half (1400 lines, 5 files) STAYS in the open-source spec, relocated out of
src/cloud/ under a name that says what it is (package format, not cloud protocol). EnvironmentType is
re-declared in spec/api. packages/metadata re-points at @objectstack/spec/system. The ./cloud
subpath export is deleted.
C — no move. The boundary stays where it is, and the decision reverts to #12036's three-way casing fork
(spec moves to snake_case / the wire moves to camelCase / a transform is documented).
What B costs, stated plainly
- Deleting a published public subpath export is a breaking change to
@objectstack/spec — major
version plus a release note. Precedent exists on this exact API family: content/docs/releases/v17.mdx:218
shipped "client.projects.* becomes client.environments.*, with no aliases".
objectstack-ai/objectui packages/types/src/index.ts:1106 must drop or re-point its
export type * as Cloud line before the subpath is removed.
- Ordering is a real constraint: cloud and objectui must stop importing BEFORE objectstack removes the
export, because cloud consumes this through its .objectstack-sha pin. Three cards, not one PR.
- Whether a deprecation window is left is NOT reopened here: the maintainer's 2026-08-27 ruling (verbatim:
「项目在创业阶段,用户也很少,短期不考虑渐进。」) makes immediate removal with no alias the default.
Say so explicitly in the answer if a window is wanted instead.
Not a duplicate, and not in conflict with the open-core work
#16215 and #16130 move multi-organization RUNTIME capability INTO the open core. This card moves the
commercial CONTROL PLANE's contracts out of it. Same boundary, opposite sides — the framework grows, the
service's private shapes leave. Searched repo:objectstack-ai/objectstack state:open for spec/cloud
in:title (2 hits: #12036, #16084 — neither is this), cloud contracts boundary (7), and
open source boundary spec (81); no existing card asks this question.
推荐:B。 ①领起(把错放的搬走,让契约包里每样都真是契约);③让误绑在结构上不可能,而不是靠一段 docblock;④净收窄。②不反对,但要如实说它不紧急 —— 这不是 P0,客户今天不坏。
回退:C(不动)。 若你认为三仓窗口此刻不值得,C 是诚实的选择,代价是回到 #12036 的三选一,且窗口随消费者增加而变贵。
⛔ 不荐 A。 它会把包/市场格式一起搬走,而 os package publish 是开源 CLI 的能力、包作者需要那个格式;A 会为了搬走 2087 行而误伤 1400 行。
置信缺口(本分析看不见什么): 未量 cloud 侧接收这 6 个文件的施工成本(它今天 import 的是已发布包,改成本地声明要动 12 个站点、含一处运行时 .parse())。⚠️ 这是执行排序的输入,不改变方向判断;若你选 B,该由 repo:cloud 席先出一份接收面读数再排卡。
Governing text: AGENTS.md Prime Directive #12 — "Contract-first — fix the metadata, not the runtime.
packages/spec is the one contract between metadata…"; and the maintainer's 2026-08-27 ruling on staged
retirement (verbatim: 「项目在创业阶段,用户也很少,短期不考虑渐进。」).
Downstream
维护者速读
这是什么事 —— 我们的开源包 @objectstack/spec 里,放着一批只有云端在用的类型:环境、租户、后台管理的数据结构,2087 行。它们的生产者和消费者都在云端仓库,只有声明留在开源包里。你说的「cloud 的协议应该放在云端」,实测站得住 —— 开源那边对这批东西的真实依赖只有一个类型,其余全是注释和测试。
为什么值得现在处理 —— 不是因为坏了。客户今天不受影响,SDK 那 15 个方法本来就没类型、而且是故意没类型(代码里有你 2026-08-29 的裁决记录)。值得处理是因为:只要这批类型还挂在开源包上,就会一直有人想把它们和方法接起来,而那样做编译通过、运行时读到空值。搬走之后,这个类型在开源包里根本不存在,误接在结构上就不可能了。同时它还顺手解决了 #12036 那个字段命名之争 —— 那个争议之所以要你拍板,唯一原因就是声明和实现分在两个仓。
选项 —— A:整个 cloud 子路径全搬走(我不推荐:会连"包格式"一起搬走,而那是包作者发包要用的,属于开源侧的东西)。B:按归属切开 —— 云端私有的 2087 行搬去云端,包格式那 1400 行留在开源包里但换个不叫 cloud 的名字。C:不动 —— 回到 #12036 的字段命名三选一。
我的意见 —— 推荐 B。但要如实说两点:一,这不是急事,客户今天不坏,你完全可以先放着;二,B 要花一次跨三个仓的协调(云端、objectui、开源仓各一张卡,还有先后顺序),不是一次改动能完成的。另外,删掉一个已发布的公开子路径算破坏性变更,要升 major 写发行说明 —— 这个我们 v17 对同一批 API 做过一次,当时就是不留别名直接换。
你要做的 —— 选一个:A / B / C。
Maintainer direction given in a direct PM session on 2026-09-06, verbatim (⛔ quoted, not translated):
Filed as a decision card on the maintainer's instruction in the same session ("开决策卡"). This card is
UPSTREAM of #12036: if the control-plane schemas leave the open-source contract package, #12036's
camelCase-vs-snake_case fork stops being a cross-repo contract decision and becomes one repo's internal
coding style. #12036 is therefore moved out of the decision inbox and blocked on this card.
⛔ Nothing was changed. No branch, no edit. All readings below are read-only.
Readings
Taken 2026-09-06T04:42Z.
objectstack-ai/objectstackorigin/maind5d8d50db;objectstack-ai/cloudorigin/maindaaac081;objectstack-ai/objectuiorigin/main0558e0f78.1. The boundary already exists — it was drawn half-way
@objectstack/spec's main entry does not re-export the cloud namespace../cloudis a standalonepublished subpath in the package's
exportsmap (packages/spec/package.json), with its ownbrowser/import/require conditions.
⇒ 59 export lines and zero of them name cloud: the absence is real, not a broken pattern. Reaching these
schemas already requires the explicit
@objectstack/spec/cloudsubpath.2. The open-source repo's real dependency on the control-plane half is ONE symbol
Every other in-repo occurrence of
@objectstack/spec/cloudoutsidepackages/spec/src/cloud/is acomment, a test, or a re-export of something that lives elsewhere:
packages/spec/src/api/discovery.zod.ts:8import type { EnvironmentType } from '../cloud/environment.zod'packages/spec/src/api/package-api.zod.ts:10import { ArtifactReferenceSchema } from '../cloud/marketplace.zod'packages/metadata/src/plugin.ts:895import('@objectstack/spec/cloud')forEnvironmentArtifactSchemapackages/spec/src/cloud/environment-artifact.zod.tsis a pure re-export whose body isexport { … } from '../system/environment-artifact.zod'. Re-point the import at@objectstack/spec/systemand the dependency is gonepackages/client/src/index.ts:2233,2350packages/cli/test/package-publish-namespace.test.tsCreatePackageRequestSchemapackages/client/src/return-type-precision.test.tsEnvironmenttype3.
spec/cloudholds two families with different ownersenvironment.zod,environment-package.zod,tenant.zod,developer-portal.zod,marketplace-admin.zod,app-store.zodpackage.zod,package-version.zod,marketplace.zod,package-l10n,template-manifest.zodos package publishpath usesCreatePackageRequestSchema;spec/api/package-api.zod.tsdepends on itsArtifactReferenceSchemaIntra-directory dependency roots:
package.zod←marketplace.zod← {developer-portal,app-store};tenant.zod←environment.zod. So the two halves are separable, with one edge to cut(
app-storeanddeveloper-portalsit onmarketplace.zod).4. The SDK already refuses to bind these — with a maintainer ruling on record
packages/client/src/index.ts:2228-2248is a 20-line docblock stating that the 15environments.*methodskeep their erased
anydeliberately, naming #11925/#12036 and the casing conflict. At:2340-2372aprior ruling is recorded verbatim — maintainer, 2026-08-29: 「同意」— option 甲 — choosing the INLINE wire
shape and ⛔ NOT binding
@objectstack/spec/cloud'sProvisionEnvironmentResponseSchema.⇒ The direction on this card has effectively been ruled once already, per-method. This card asks whether it
becomes the rule for the whole subpath.
5. Producer and consumers of the control-plane half both live in the cloud repo
From the
repo:cloudreading already on #12036 (comment5520459478, cloud9b6abe0), re-confirmed atdaaac081: 12 import sites, including a runtime parse atpackages/service-tenant/src/environment-provisioning.ts:750(
ProvisionEnvironmentRequestSchema.parse(request)) andconst environment: Environment = { … }at:1094and
:1429. The schemas' only real consumer is the repo they would move to.6. objectui's dependency is one blanket re-export line
Every other hit in that repo is a comment, and each of those comments says the type was deliberately
re-declared inline instead of imported (
app-shell/src/console/marketplace/marketplaceApi.ts:85,usePackageL10n.ts:6,app-shell/src/views/metadata-admin/PackagesPage.tsx:75).The options
A — move the whole
⚠️ Cost: the package/marketplace format leaves with it, so
./cloudsubpath to the cloud repo. All 3487 lines leave@objectstack/spec; thesubpath export is deleted.
os package publishandspec/api/package-api.zod.tslose their declarations — a package author would need a cloud-repo package toauthor a package for the open-source CLI.
B — cut by owner (recommended). The control-plane half (2087 lines, 6 files) moves to the cloud repo.
The package/marketplace half (1400 lines, 5 files) STAYS in the open-source spec, relocated out of
src/cloud/under a name that says what it is (package format, not cloud protocol).EnvironmentTypeisre-declared in
spec/api.packages/metadatare-points at@objectstack/spec/system. The./cloudsubpath export is deleted.
C — no move. The boundary stays where it is, and the decision reverts to #12036's three-way casing fork
(spec moves to snake_case / the wire moves to camelCase / a transform is documented).
What B costs, stated plainly
@objectstack/spec— majorversion plus a release note. Precedent exists on this exact API family:
content/docs/releases/v17.mdx:218shipped "
client.projects.*becomesclient.environments.*, with no aliases".objectstack-ai/objectuipackages/types/src/index.ts:1106must drop or re-point itsexport type * as Cloudline before the subpath is removed.export, because cloud consumes this through its
.objectstack-shapin. Three cards, not one PR.「项目在创业阶段,用户也很少,短期不考虑渐进。」) makes immediate removal with no alias the default.
Say so explicitly in the answer if a window is wanted instead.
Not a duplicate, and not in conflict with the open-core work
#16215 and #16130 move multi-organization RUNTIME capability INTO the open core. This card moves the
commercial CONTROL PLANE's contracts out of it. Same boundary, opposite sides — the framework grows, the
service's private shapes leave. Searched
repo:objectstack-ai/objectstack state:openforspec/cloudin:title (2 hits: #12036, #16084 — neither is this),
cloud contracts boundary(7), andopen source boundary spec(81); no existing card asks this question../cloud是独立子路径(59 条 export 行、0 条提 cloud)。今天的形状是「声明住在开源包、生产者和消费者都在云端」,这不是契约,是错放。Prime Directive Add comprehensive test suite for Zod schema validation #12 说packages/spec是元数据与运行时之间的那一条契约 —— 云控制面的行结构不在那条线上,它是一个商业服务的内部形状。①指向 B:把不属于契约的搬走,让packages/spec里剩下的每一样都真的是契约。⛔ 不指向 A,因为包格式是开源侧的契约(包作者要用它发包)。@objectstack/spec/clouddeclares camelCase rows but the/api/v1/cloud/*control plane speaks snake_case — the cloud contracts are not the wire types, so 15 client methods cannot be bound to them #12036 的所谓拉动是 A fifth client-SDK erasure spelling no grep in #8140's census counted: 38 methods with NO return annotation, typed fromunwrapResponse< …any… >#11925 想绑类型,那是内部代码质量,不是客户拉动。实测:15 个方法今天返回any且故意如此(client/src/index.ts:2228-2248有 docblock,并记着 2026-08-29 的裁决原话),线上与三个消费者(CLI/console/SDK)一致,客户今天不坏。⇒ ②既不推 B 也不反对 B,它只是把这件事从"救火"降级成"该做的整理"。any的公开方法,一边给出一个叫Environment的公开类型;把两者接起来是最自然的动作,编译通过、运行时读到undefined。这正是"声明即强制"要防的形状 —— 一个 AI 写元数据 app 时看到@objectstack/spec/cloud导出Environment,没有任何结构性的东西告诉它这不是那条线的类型(只有一段人类要去读的 docblock)。搬走之后,那个类型在开源包里不存在,误绑在结构上不可能。这是响亮拒绝,不是消费端宽容。@objectstack/spec少一个已发布子路径。@objectstack/spec/clouddeclares camelCase rows but the/api/v1/cloud/*control plane speaks snake_case — the cloud contracts are not the wire types, so 15 client methods cannot be bound to them #12036 消失,还有一条以后不用再反复问的边界。而 C 的代价是把这个问题留着 —— 双拼写兼容代码已经从 cloud(24 处)漏到 objectui(2 处),这正是「不留双拼写宽限」那条纪律所防的终局。推荐:B。 ①领起(把错放的搬走,让契约包里每样都真是契约);③让误绑在结构上不可能,而不是靠一段 docblock;④净收窄。②不反对,但要如实说它不紧急 —— 这不是 P0,客户今天不坏。⚠️ 这是执行排序的输入,不改变方向判断;若你选 B,该由
回退:C(不动)。 若你认为三仓窗口此刻不值得,C 是诚实的选择,代价是回到 #12036 的三选一,且窗口随消费者增加而变贵。
⛔ 不荐 A。 它会把包/市场格式一起搬走,而
os package publish是开源 CLI 的能力、包作者需要那个格式;A 会为了搬走 2087 行而误伤 1400 行。置信缺口(本分析看不见什么): 未量 cloud 侧接收这 6 个文件的施工成本(它今天 import 的是已发布包,改成本地声明要动 12 个站点、含一处运行时
.parse())。repo:cloud席先出一份接收面读数再排卡。Governing text:
AGENTS.mdPrime Directive #12 — "Contract-first — fix the metadata, not the runtime.packages/specis the one contract between metadata…"; and the maintainer's 2026-08-27 ruling on stagedretirement (verbatim: 「项目在创业阶段,用户也很少,短期不考虑渐进。」).
Downstream
@objectstack/spec/clouddeclares camelCase rows but the/api/v1/cloud/*control plane speaks snake_case — the cloud contracts are not the wire types, so 15 client methods cannot be bound to them #12036 — its three-way casing fork is dissolved by A or B, and restored by C. Moved out of the decisioninbox to
pm:blockedon this card so exactly one question is in front of the maintainer.维护者速读
这是什么事 —— 我们的开源包
@objectstack/spec里,放着一批只有云端在用的类型:环境、租户、后台管理的数据结构,2087 行。它们的生产者和消费者都在云端仓库,只有声明留在开源包里。你说的「cloud 的协议应该放在云端」,实测站得住 —— 开源那边对这批东西的真实依赖只有一个类型,其余全是注释和测试。为什么值得现在处理 —— 不是因为坏了。客户今天不受影响,SDK 那 15 个方法本来就没类型、而且是故意没类型(代码里有你 2026-08-29 的裁决记录)。值得处理是因为:只要这批类型还挂在开源包上,就会一直有人想把它们和方法接起来,而那样做编译通过、运行时读到空值。搬走之后,这个类型在开源包里根本不存在,误接在结构上就不可能了。同时它还顺手解决了 #12036 那个字段命名之争 —— 那个争议之所以要你拍板,唯一原因就是声明和实现分在两个仓。
选项 —— A:整个
cloud子路径全搬走(我不推荐:会连"包格式"一起搬走,而那是包作者发包要用的,属于开源侧的东西)。B:按归属切开 —— 云端私有的 2087 行搬去云端,包格式那 1400 行留在开源包里但换个不叫 cloud 的名字。C:不动 —— 回到 #12036 的字段命名三选一。我的意见 —— 推荐 B。但要如实说两点:一,这不是急事,客户今天不坏,你完全可以先放着;二,B 要花一次跨三个仓的协调(云端、objectui、开源仓各一张卡,还有先后顺序),不是一次改动能完成的。另外,删掉一个已发布的公开子路径算破坏性变更,要升 major 写发行说明 —— 这个我们 v17 对同一批 API 做过一次,当时就是不留别名直接换。
你要做的 —— 选一个:A / B / C。