Repository navigation
[Decision] Should the cloud control-plane contracts leave @objectstack/spec and live in the cloud repo? (upstream of #12036) #16325
Description
Activity
Ruling recorded — option B, cut by owner (director seat, decision batch #62, 2026-09-07)
Maintainer reply, verbatim: 「同意」 (all five batch #62 recommendations adopted). Direction, verbatim from the maintainer's own filing: 「我一直觉得 cloud 的协议应该放在云端,没必要开源」.
Ruling. The control-plane half of
packages/spec/src/cloud/(six files, 2087 lines:environment,environment-package,tenant,developer-portal,marketplace-admin,app-store) leaves@objectstack/specand becomes the cloud repo's own declarations. The package / marketplace format half (five files, 1400 lines:package,package-version,marketplace,package-l10n,template-manifest) stays in the open-source spec, relocated out ofsrc/cloud/under a name that says what it is. The./cloudsubpath export is deleted. No alias, no deprecation window (maintainer 2026-08-27: 「项目在创业阶段,用户也很少,短期不考虑渐进」). Not urgent: customers are not broken today.Three cards, in order.
- cloud: take ownership of the control-plane contracts — local declarations for the six
spec/cloudcontrol-plane schemas, 12 import sites re-pointed (step 1 of 3 of the #16325 ruling) #16450 — cloud takes ownership (local declarations, 12 import sites, casing becomes cloud's internal choice matched to the wire). - objectui#8225 — drop
export type * as Cloud from '@objectstack/spec/cloud'. - This card (objectstack half): relocate the package-format half out of
cloud/, re-declareEnvironmentTypeinspec/api, re-pointpackages/metadataat@objectstack/spec/system, delete the./cloudsubpath; major version, release note (written centrally at release time — the PR's input is its changeset).pm:blockedon 1 and 2. Clause-② yes.
#12036 (camelCase vs snake_case) is closed as superseded: once the declarations live beside their producer, the casing is one repo's internal style.
Labels:
needs-user-decision→pm:blocked(Blocked-by #16450, objectui#8225). Ledger on #12708 (batch #62).
Generated by Claude Code
- cloud: take ownership of the control-plane contracts — local declarations for the six
- addedenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 7, 2026 Half-state repair —
pm:blockedcarried noBlocked-by:index lineCard author's session (the direct maintainer session that filed this card), 2026-09-07T05:59Z readings.
⛔ Not a claim, not a dispatch, not a re-adjudication — see the last section.The defect
Ruling comment
5563914257states this card is "pm:blockedon 1 and 2", and the label was set. But that
sentence is prose; the unlock scan reads the machine-grepableBlocked-by:line in the body, and this
body had none:$ gh api /repos/objectstack-ai/objectstack/issues/16325 --jq '.body' | grep -cE "^Blocked-by:" 0 # control — the same predicate on a card known to carry one, same hour: $ gh api /repos/objectstack-ai/objectstack/issues/12036 --jq '.body' | grep -cE "^Blocked-by:" 1⇒ The zero is a real absence, not a broken pattern. Consequence: when #16450 and objectstack-ai/objectui#8225
close, nothing reverse-indexes to this card and it stayspm:blockedindefinitely — a silent permanent
block, not a tracked wait.The repair
Two lines added to the body, in the ruling's own order, plus a pointer marking the analysis below as the
pre-ruling record rather than an open question:Blocked-by: #16450 Blocked-by: objectstack-ai/objectui#8225Read back after the write: both present at body lines 18-19. Labels untouched (
enhancement,
priority:p2,domain:spec,pm:blocked) — the label was already correct; only its index was missing.What this seat did NOT do, and why
The
domain:specseat post (#6017) shows an incumbent,session_01T6HeZvT9wdSJD1ZxJb5Eno, with seat
activity at 2026-09-07T05:20Z — 39 minutes before this reading. Seat mutual exclusion therefore holds
against this session:- ⛔ no claim, no assignee, no dispatch on this card or on types: drop
export type * as Cloud from '@objectstack/spec/cloud'before the upstream subpath is removed (step 2 of 3 of objectstack#16325) objectui#8225 (pm:queue,
domain:spec, unassigned — it is the incumbent's to take, not this session's); - ⛔ cloud: take ownership of the control-plane contracts — local declarations for the six
spec/cloudcontrol-plane schemas, 12 import sites re-pointed (step 1 of 3 of the #16325 ruling) #16450 carriesrepo:cloudand belongs to that seat; - ⛔ nothing about the ruling was reopened. Option B stands exactly as recorded in
5563914257.
The repair above is a state-index fix on a card this session authored, of the kind the half-state patrol
reports; it changes no decision and no ownership.- ⛔ no claim, no assignee, no dispatch on this card or on types: drop
Step 1 of 3 (cloud stops importing) is OPEN as a draft, not merged: objectstack-ai/cloud#2037 for #16450.
What the receiving side measured, for this card's step 3 to read before deleting
./cloud:- On cloud
9b85d761, onlyenvironment.zodandtenant.zodof the six control-plane files have a consumer.developer-portal.zod,marketplace-admin.zod,app-store.zodandenvironment-package.zodexport 90 symbols and none of those names appears in the cloud repo outside the lockfile — cloud did not re-declare them (reported on cloud: take ownership of the control-plane contracts — local declarations for the sixspec/cloudcontrol-plane schemas, 12 import sites re-pointed (step 1 of 3 of the #16325 ruling) #16450 as a PM decision, not settled by the dev). If step 3 deletes them, nothing on the cloud side notices. EnvironmentType(Schema),EnvironmentRole/EnvironmentMember(sys_environment_memberwas dropped in cloud) andPackageInstallation(Status)Schemaalso have zero cloud consumers.environment-artifact.zod.ts's two cloud import sites were re-pointed at@objectstack/spec/system— no cloud-side dependence on the re-export remains.- After the PR, the only
@objectstack/spec/cloudimports left in cloud are the twoPackageSchemasites (routes/package-publish.ts,test/package-id-declarations.test.ts) — the package-format half this ruling keeps open-source; step 3 re-points them when it relocates that half.
"Landed" (merged) is the PM's call; this comment is the open-PR heads-up only.
Generated by Claude Code
- On cloud
Additive correction to comment
5566370040— a count in it goes stale at mergeDispatching PM seat, session
session_f95e3874-e532-4748-a921-044aa2752a2b, 2026-09-07T07:4xZ.
⛔ The earlier comment is left standing; this is the correction, not a rewrite of it.That comment (the step-1 heads-up from objectstack-ai/cloud#2037's dev) says:
the only
@objectstack/spec/cloudimports left in cloud are the twoPackageSchemasitesTrue on the PR branch, and false shortly after it merges.
objectstack-ai/cloudmainhas since taken
#2031 and #2032; #2032 adds two morePackageSchema from '@objectstack/spec/cloud'importers
(packages/service-cloud/src/routes/package-install.ts,packages/service-cloud/test/package-export.test.ts).
Post-merge the count is 4, not 2.Nothing about step 3 changes. All four are the package-format half — the half this ruling deliberately keeps
in the open-source spec — so what step 3 (#16325) must do is unchanged in kind, only in count: re-point four
PackageSchemaimporters, not two, at the relocated open-source path.The acceptance criterion the step-1 PR was actually held to is unaffected and still holds: zero control-plane
schema importers remain (symbol-level grep forEnvironment*/TenantPlan*/TenantRoutingConfig*/
Provision*against@objectstack/spec/cloud⇒ 0 on the branch).The dev spotted this while doing an unrelated patch round and correctly did not amend it — the instruction
for that round was "nothing else", and silently editing a landed statement is worse than appending the fix. The
correction is the dispatching seat's to make, which is this comment. Whoever picks up step 3 should count the
importers on the tree in front of them rather than trusting either number here.Blocked-by: #16526discharged — ruled A (director seat, decision batch #77, 2026-09-07, maintainer 「其他同意」): cloud hosts onlyenvironment.zodandtenant.zod; step 3 deletes the four consumer-less files with the./cloudsubpath. This card's deletion half is executable.pm:blocked→pm:queue.
Generated by Claude Code
⚠️ Step 3 landmines, mapped in advance — two numeric pins inobjectstack-ai/objectuiwill break the moment./cloudleaves the specDispatching PM seat, 2026-09-08T00:5xZ. Filed here, on step 3's own card, because this is what step 3 walks into —
⛔ not on the step-2 card where it was discovered and where nobody doing step 3 would look.Why this exists
Step 2 (objectui#8225, PR objectui#8360) was FAILed by contract review on a defect nobody predicted: removing the
repo's last'@objectstack/spec/cloud'string literal broke an anti-vacuity floor pin that counts distinct
@objectstack/specspecifiers across a tree — a guard that exists so the scan cannot silently go empty. Nothing in
the card mentioned it; it surfaced only as a red CI shard.⇒ This seat asked the dev to sweep for the whole class — who counts instances of these specifiers — precisely
because step 3 deletes the subpath outright and will hit every counter that exists. The sweep found two more, in the
same file, and they are NOT step 2's to touch.The two pins
objectstack-ai/objectui,scripts/__tests__/vite-objectstack-spec-dist.test.ts, measured on21b1bf51f:line assertion reads :152expect(declared.length).toBe(19)the installed @objectstack/spec/package.jsonexports-map entry count:608expect(injectedSpecKeys).toHaveLength(19)the same manifest's injected subpath keys Both are exact pins, not floors — so both go 19 → 18 the moment objectui installs a spec release whose
manifest no longer exports./cloud. The file's own comment at:146-151records the previous move (18 → 19 on the
17.2.0 refresh), so this pin has a history of tracking exactly this kind of change.Correctly untouched by step 2: installed spec is still 17.3.0, which still exports
./cloud(verified), and both
pins are green. Moving them now would break a true assertion to pre-empt a future one.⇒ They must be re-pinned in the same PR that bumps objectui's
@objectstack/specto the release that deletes the
subpath. Not before — the pins are honest until that bump — and not after, or objectui's CI goes red on a bump PR
for a reason that will look unrelated.Also checked, and clear
scripts/check-spec-range-floors.mjsreconciles subpaths mentioned in built artifacts against the spec manifest's
exports map (findingopaque-subpath). After PR objectui#8360 no artifact mentions/cloud, so deleting it
upstream produces no finding there.scripts/check-eager-closure-budget.mjs:109and its test:696mention/cloudin prose only — no counter.- Remaining
/cloudmentions in the scanned roots after step 2: 5, all comments (4 code comments plus
packages/auth/README.md:220); the collector reads code extensions only, so none is counted. Code literals: 0.
Scope note
⛔ This is not a new blocking edge on this card and no
Blocked-by:is being added — the pins live in objectui and
move with objectui's own spec-version bump, which is downstream of step 3's release, not a precondition for it. It is
recorded so the person doing that bump does not rediscover it from a red shard, the way step 2 did.The same class question is worth asking once inside
objectstack-ai/objectstackitself before step 3 deletes the
subpath: what in that repo counts@objectstack/specsubpaths or exports-map entries. This seat has not measured
that, and says so rather than implying the sweep was repo-wide — it covered objectui.3 remaining items
Claim: PM direct maintainer dispatch (no numbered round — direct-dispatch channel); step 3 of 3, all three
Blocked-by:edges now closed
Session:session_f95e3874-e532-4748-a921-044aa2752a2b
Branch:claude/issue-16325-spec-drops-cloud-subpath
Worktree:objectstack-issue-16325
Domain:domain:spec
File surface:packages/spec/package.json(the./cloudexports entry) ·packages/spec/src/cloud/**(delete four zod files + three companion.test.ts; relocate the package-format five out of this directory) ·packages/spec/src/api/{discovery,package-api}.zod.ts·packages/spec/src/system/index.ts·packages/metadata/src/plugin.ts·packages/metadata/vitest.config.ts·packages/runtime/vitest.config.ts·packages/runtime/src/spec-subpath-alias-coverage.pin.test.ts·packages/spec/scripts/export-origins.test.ts·packages/cli/test/package-publish-namespace.test.ts·packages/client/src/return-type-precision.test.ts·.changeset/(stop on breach; explain in the report)
Container & model:L,mode:subagent,model: claude-fable-5-1(=CONTRACT_REVIEW_TIER; clause ② — deleting a published subpath export from@objectstack/specis a public-surface change by definition)
Clause-②: yes
Serial constraints cleared:#16450closed/merged 2026-09-07T08:19Z,objectstack-ai/objectui#8225closed/merged 2026-09-07T17:15Z,#16526closed/completed 2026-09-10 — all three edges CLOSED, not merely declared discharged. No in-flight objectstack card declarespackages/spec/src/cloud/**; thedomain:specseat post (#6017) has an incumbent but this dispatch runs on the direct-dispatch channel, ⛔ no seat seized.
Maintainer authorisation, verbatim (⛔ quoted, not translated): 「你直接负责派发处理」, following 「同意 A」 on #16526.
Premise re-check at dispatch time (⛔ not inherited — the card's readings are 5 days old)
origin/main706ad0fcc(the card was written againstd5d8d50db). All five work items re-verified as still undone:# item reading 1 ./cloudstill exportedpackages/spec/package.json⇒ 1 hit2 package-format half still under src/cloud/22 matching entries 3 EnvironmentTypestill imported byapi/discovery.zod.ts1 4 ArtifactReferenceSchemastill imported byapi/package-api.zod.ts1 5 packages/metadata/src/plugin.tsstill imports@objectstack/spec/cloud1 ⚠️ The four files to delete carry three companion test files the card does not name:app-store.test.ts,
developer-portal.test.ts,marketplace-admin.test.ts(environment-package.zod.tshas none).⭐ Three counters inside THIS repo that name
cloudexplicitly — the class that already broke step 2Step 2 (
objectstack-ai/objectui#8360) was FAILed in review because removing the last@objectstack/spec/cloud
literal broke an anti-vacuity floor pin nobody had listed. That sweep covered objectui only, and this seat said so
rather than implying it was repo-wide. It is now done for objectstack, and it found three:packages/runtime/src/spec-subpath-alias-coverage.pin.test.ts:126—
expect(PUBLISHED_SUBPATHS).toContain('@objectstack/spec/cloud');— an assertion thatcloudis a published
subpath, derived at:85fromObject.keys(specPackageJson.exports). It goes red the moment the entry is
removed.:123is also a floor,toBeGreaterThanOrEqual(15)— check whether the post-deletion count still
clears it rather than assuming.packages/runtime/vitest.config.tsandpackages/metadata/vitest.config.ts— alias tables enumerating
published subpaths,cloudamong them. Their own comments record incident packages/runtime's vitest alias list omits@objectstack/spec/cloud, so any runtime test that loads a compiled artifact dies with ENOTDIR #9457, where a MISSING entry made
@objectstack/spec/cloudresolve to the garbage path…/spec/src/index.ts/cloudand die withENOTDIR. A
stale entry pointing at a deleted directory is the same failure with the sign flipped.packages/spec/scripts/export-origins.test.ts:53—['./cloud', () => import('../src/cloud/index')], a
table entry that dynamically imports the cloud barrel.
Cross-repo state, already handled — ⛔ do not re-do it
objectstack-ai/cloudno longer imports any control-plane schema (step 1, merged). Its remaining
@objectstack/spec/cloudimporters are 4 files, allPackageSchema— the package-format half, whose new
home this card decides. They are the cloud seat's to re-point after this lands, ⛔ not from here.objectstack-ai/objectuidropped itsexport type * as Cloudre-export (step 2, merged). Its two exports-map
pins (scripts/__tests__/vite-objectstack-spec-dist.test.ts:152and:608, both19) go 19 → 18 on the objectui
PR that bumps the spec dependency to the release carrying this deletion — that PR's job, ⛔ not this one's.
os-dev-report
{ "issue": 16325, "status": "done", "branch": "claude/issue-16325-spec-drops-cloud-subpath", "pr": "https://github.com/objectstack-ai/objectstack/pull/17372", "premise_still_valid": true, "summary": "Step 3 of the cloud-contracts chain, implemented as ruled (option B cut by owner; option A on the four consumer-less files). The ./cloud subpath is deleted (exports map, tsup entry, packages/spec/src/cloud/** whole); the six control-plane modules leave with 62 defs declared in RETIRED_DEFS_BY_MAJOR[18] plus one semantic entry cloud-subpath-retired; the package & marketplace format five relocate to packages/spec/src/marketplace/ and publish as @objectstack/spec/marketplace with 30 defs carried through RENAMED_DEFS (plus 0-key carries cloud/EnvironmentType to api/EnvironmentType and cloud/Sha256Digest to system/Sha256Digest); EnvironmentType(Schema) is re-declared in api/discovery.zod.ts; packages/metadata reads the artifact envelope from @objectstack/spec/system; cli/client/create-objectstack re-point. Premise re-check at 706ad0fcc: all five work items were still undone (each hit count matched the PM's table); the tree I got was the same commit. One gate defect was fixed in place under the bounded exemption: the #4666 authorable-defaults ratchet carried the baseline KEY set through RENAMED_DEFS but not its DEFAULTS, so the category move read as 22 false added flips - carryDefaultsThroughRenames now carries both halves, with four discrimination tests (negative control included). Naming choice: marketplace over package because src/package/ would emit a shard literally named package.json in six generated-artifact directories; flagged for the maintainer in the PR's 维护者速读 as the one reversible-before-merge choice. The objectui pin (.objectui-sha = 53ded82bf) predates step 2's merge and still carries the type-only export type * as Cloud line; build-console.sh was run against this tree and is GREEN (8817 modules, spec-injection assertion passed), so main does not go red on merge - the pin bump stays the objectui-side follow-up the card already records.", "tests": "All on the final head edfbc7f22 unless noted. Typecheck (pnpm --filter X typecheck, VERDICT command-exit 0): spec (tsc + scripts + test-typecheck OK), metadata, runtime, cli, client (check:test-typecheck 0 files / 0 errors - the ts-expect-error on the retired Environment import is consumed), create-objectstack. Tests: spec 'Test Files 467 passed (467) / Tests 13163 passed (13163)'; runtime '251 passed / 3532 passed'; client '41 passed / 492 passed'; metadata '53 passed / 788 passed'; create-objectstack '15 passed | 1 skipped / 195 passed | 8 skipped'; cli --project unit '190 passed / 2662 passed, 5 failed' - all 5 are /var/folders vs /private/var/folders tmpdir-realpath mismatches in published-subpath-console.pin.test.ts and published-subpath-hook-body.pin.test.ts (host-environmental, untouched by this diff); cli integration tier declared to CI. Reverse verification on the committed head: packages/metadata/src/plugin.ts import flipped back to @objectstack/spec/cloud (on-disk proof grep -c cloud 0 to 1, system 1 to 0), tsc --noEmit went RED with 'src/plugin.ts(895,60): error TS2307: Cannot find module @objectstack/spec/cloud', restored with git checkout HEAD -- FILE, blob hash identical before/after (0bdb1484...), git diff HEAD empty. Direction: red, as expected. Ablation of the gate fix: scripts/authorable-defaults.test.ts negative control reproduces the added flip without the carry (27/27 pass). Build: pnpm build (turbo, 73 tasks) green; spec build green ('34/34 declared declaration files present'). Console Pin Gate: scripts/build-console.sh at pin 53ded82bf against this tree - 'Console bundle carries THIS tree's @objectstack/spec, and only it', exit 0. Narrowing declared: turbo ls --affected = 77 packages (spec is the graph root); the packages above were run in full, the rest and pnpm lint are CI's. Verify-lock wrapper reported UNLOCKED (no usable flock on this host) on every run, declared in the PR body.", "gates": "Derived with node scripts/pm/dispatch-gates.mjs --commands on the actual changed set: 147 commands. --ran reconciliation: '147 derived, 147 run, 0 NOT-MEASURED, 0 UNRUN'. 141 exited 0 on first run; check:skill-examples exit 1 until @objectstack/client-react was built then '258 prose examples type-check' exit 0; check:dual-build-cjs-loads and check:i18n-coverage exit 3 PREREQUISITE NOT MET until the full pnpm build then both exit 0 ('check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new)'); check:bash32-floor and check:merge-driver self-tests exit 1 and reproduce identically on clean main cca1dc0bf (host shell) - NOT MEASURED locally, CI's. Ratchet families re-run on the final head edfbc7f22 (clean tree): 23/23 exit 0 - spec check:generated (15/15 artifacts current), check:api-surface, check:authorable-surface, check:export-origins, check:docs, check:llms-txt, check:browser-reachable-entries, check:dual-source-exports, check:entry-nameability, check:template-manifests, check:yaml-examples, check:skill-examples; root check:spec-parsed-alias, check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage, check:docs-redirects, check:quick-reference-counts, check-system-context-census, check-adr-0087-registration ('1 declared-breaking changeset(s), each carrying an ADR-0087 disposition'), check-changeset-no-major ('This diff introduces no major bump'), check-empty-changeset. Point-named by the dispatch: nul-bytes green; no engine double, no error code, no .claude/agents edit.", "counter_resweep": "PM's three confirmed and moved (runtime alias-coverage pin :126 names marketplace, floor 15 clears at 16; the two vitest alias tables needed NO edit - both are the anchored regex rule from #9457, cloud survives only in their incident prose; export-origins.test.ts table entry). Beyond them, this sweep found and moved: kernel/package-dependency-dual-source.test.ts (owner ./cloud + origin path), system/environment-artifact.test.ts (the ./cloud re-export half, replaced by holdersOf equals ['./system']), cloud/tenant.test.ts (the #4739 system/contracts half relocated to system/tenant-provisioning-family-retired.test.ts with holdersOf('TenantPlan') empty), type-alias-convention.pin.test.ts (the ADR-0122 registry check:spec-parsed-alias reads: 30 pins left, EnvironmentTypeSchema re-pinned under api/discovery, count 811 to 782), file-description.test.ts (PAGES.cloud fixture, cloud/package link fixture, openingOf, corpus floor 140 to 130 on a measured 138, exact 37 to 31 level-1 openers), root-meta.test.ts 14-category list, build-docs.ts section/blurb tables, lib/category-title.ts, build-schemas.ts and check-yaml-examples.ts namespace maps, lib/renamed-defs.ts, check-template-manifests.ts import, unemitted-schemas.baseline.json (Cloud.EnvironmentArtifactSchema duplicate row), browser-reachable-entries.json, tsup entries, llms.txt row + heading count (check:llms-txt), quick-reference.mdx section counts (check:quick-reference-counts), check-system-context-census NON_READ_ANCHORS + system-context.mdx 'four declarations', client return-type-precision.test.ts (Environment near-miss guard moved to an import-level ts-expect-error), apps/docs/redirects.mjs. Controlled zero after the sweep: git grep for spec/cloud and src/cloud across packages/ and docs/design returns only prose that names the retirement or the #9457 incident (control: the same grep on 706ad0fcc returned 40+ code hits). Not this PR's: objectui's two exports-map pins (19 to 18 on the bump PR) and cloud's 4 PackageSchema importers.", "deviations": [ "Changeset level minor with a BREAKING banner and adr-0087: registered cloud-subpath-retired, not major - scripts/check-changeset-no-major.mjs refuses major under the launch-window convention; the dispatch text said MAJOR. Gate output quoted in the PR.", "./marketplace publishes WITHOUT a browser condition (./cloud had one): measured dist/marketplace/index.mjs links pg-connection-string 0 times (dist/kernel/index.mjs 4, the control); the poisoned members were the deleted environment/tenant modules. check:browser-reachable-entries green.", "In-place gate fix (carryDefaultsThroughRenames in packages/spec/scripts/lib/authorable-defaults.ts + build-schemas.ts + tests) under the bounded exemption; file surface added to the claim in this report: packages/spec/scripts/lib/authorable-defaults.ts, packages/spec/scripts/authorable-defaults.test.ts, packages/spec/scripts/build-schemas.ts.", "Two 0-key defs carried by RENAMED_DEFS rather than retired (cloud/EnvironmentType to api/EnvironmentType, cloud/Sha256Digest to system/Sha256Digest) - retiring them would claim they left the published set.", "Docs redirects added for the retired references/cloud pages (the table's own convention); docs/adr/** and AGENTS.md untouched (governed) - AGENTS.md row filed as #17370.", "Local verification narrowed and declared (see tests): 77 affected packages, six run in full locally, cli integration tier and the rest to CI; pnpm lint is CI-owned and was not run.", "Verify-lock wrapper UNLOCKED on this host (no usable flock); nothing was serialized." ], "line_budget": "not applicable - no file under skills/** is touched (git grep on the diff: 0 hits under skills/ and .claude/)", "files_changed": 170, "mcp_calls": "0 - every GitHub read and write went through gh (REST): issue + comments read, search/issues x2 (directed, with a control term that returned 24), issues POST for #17370, pr create, labels POST, pulls GET read-backs, this comment", "open_questions": [ { "question": "The relocated half's name: marketplace was chosen over package (both offered by the dispatch as honest). Consequence of the choice: the public subpath @objectstack/spec/marketplace that cloud's 4 PackageSchema importers and objectui prose will re-point to. Reason package was not chosen: src/package/ would produce a shard literally named package.json inside api-surface/, export-origins/, declaration-map/, json-schema.manifest/, authorable-surface/ and authorable-defaults/.", "options": [ "A keep marketplace (as implemented)", "B rename to package-format or another word before merge (mechanical: one directory + the RENAMED_DEFS targets + regenerate)" ], "recommendation": "A, because it says what the half is (the marketplace protocol a package author publishes into) and avoids the package.json shard footgun; B is cheap while the PR is a draft if the maintainer prefers another word." }, { "question": ".objectui-sha (53ded82bf) predates step 2's merge cb847fd6d and still carries export type * as Cloud from @objectstack/spec/cloud. Measured: build-console.sh against this tree is green (type-only, erased), so AGENTS.md Post-Task #4's premise (main goes red) does not hold here. When should the pin move?", "options": [ "A land this PR without a pin bump; bump .objectui-sha (with pnpm sdui:manifest) on the objectui bump card that also moves the 19-to-18 pins", "B require the pin bump in this PR" ], "recommendation": "A, because the gate is measured green and a pin bump brings 76+ unrelated objectui commits and the Playwright sdui:manifest step into a spec contract PR; the card already records the bump as objectui-side follow-up." } ], "out_of_scope_findings": [ "filed as #17370: AGENTS.md Protocol Domains table still lists the Cloud | cloud/ row (governed surface; no gate reads the table; one-line docs-only fix after this lands)" ] }os-dev-report
{ "issue": 16325, "status": "done", "branch": "claude/issue-16325-spec-drops-cloud-subpath", "pr": "https://github.com/objectstack-ai/objectstack/pull/17372", "premise_still_valid": true, "summary": "Patch round 1 (contract review), same claim, same branch, no rebase: head edfbc7f22 -> ef1fda259, one docs-only commit (3 files, 7 lines). (1) content/docs/deployment/troubleshooting.mdx's 'Available subpaths' now lists the ./* entries of packages/spec/package.json's exports map in the map's own order (16 entries, meta-spelling included) and names that source; the controlled sweep over content/docs/** found two more hand-written enumerations of the same set that no gate reads and corrected them: content/docs/plugins/packages.mdx's 15-namespace list, and content/docs/getting-started/glossary.mdx (its 15-namespace list, the layers table incl. tenants->packages in the purpose column, the '### Cloud Protocol' section now '### Marketplace Protocol', and the Tenant entry that named the deleted cloud/tenant.zod.ts as the schema's home). (2) The PR body's wrong claim is corrected: the retirement is registered in RETIRED_DEFS_BY_MAJOR[18] and the cloud-subpath-retired semantic entry (src/migrations/registry.ts); docs/protocol-upgrade-guide.md and spec-changes.json do not project protocol 18 at all (the guide's last section is 'Protocol 16 -> 17'; pre-existing 18-bucket entries such as api/CrudEndpointPattern are absent from both, verified by grep), so neither carries it and neither is stale; a 'Patch round 1' section records both items and the sweep table. PR body edited via REST PATCH and read back byte-identical (first write stored no footer; a second write with a rule-less session-URL footer stored exactly one). Nothing else touched: the four consumer-less files' disposition, the naming, .objectui-sha and the checkRenameTable merge weakness the reviewer noted are untouched; docs/qa/platform-checklist/FOLLOW-UPS.md:269 untouched as history. concepts/architecture.mdx still carries a 'Cloud Protocol' card in its six-card conceptual layering; reported and filed (#17387), not edited, since it is a framing choice rather than a namespace list.", "tests": "Gates for this round: node scripts/pm/dispatch-gates.mjs --commands over the three docs files derives 39 families (the full changed-set derivation is 147, printed with a STALE TREE warning: 14 commits behind origin/main, not rebased per the dispatch). All 39 ran in the foreground on ef1fda259 with redirect-then-capture; all exit 0. Four first returned PREREQUISITE NOT MET (exit 3: check:doc-formula-expressions, check:doc-security-posture, check:docs-transcript-drift; exit 1: check:skill-examples, 'packages/client-react/dist holds no .d.ts') until 'pnpm exec turbo run build --filter=@objectstack/lint... --filter=@objectstack/client-react... --concurrency=2' (34 tasks, 1m35s) and 'pnpm --filter @objectstack/spec build' (39s) ran under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 both; 'UNLOCKED (declared)' — no usable flock on this host); then 0 with the gates' own lines: '✅ 258 prose examples type-check across 3 surface(s)', '✓ check-docs-transcript-drift: 4 declared transcript value(s) across 401 page(s) under content/docs/ equal what the registry derives today', '✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 438 files / 1374 TS blocks judged clean', '✅ 27 ObjectSchema.create example(s) in 227 marked block(s) across 239 prose file(s) ... carry an os validate-clean security posture'; check:docs, check:liveness, check:empty-state, check:yaml-examples, check:variant-docs, check:strictness-ledger, check:corpus-claim-drift, check:role-word, check:docs-redirects, check:doc-anchors, check:nul-bytes and the rest of the 39 all exit 0. --ran reconciliation with per-command exit codes: '39 derived, 39 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero)'. Declared narrowing: the 108 code-derived families of the full set derive from files unchanged since edfbc7f22, where they ran last round (CI on edfbc7f22: 35 success / 6 skipped / 0 failed); not re-run for a docs-only delta; pnpm lint and the cli integration tier remain CI's. Docs sweep on ef1fda259 (git grep -E over content/docs, each zero beside a same-shape control that fires): import-shaped @objectstack/spec/cloud = 0 (controls: spec/data 131, spec/marketplace 8; a first attempt with a backslash-s class had a 0 control on macOS git grep and was discarded as not a reading); namespace lists naming Cloud ('Kernel, Cloud' / 'Security, Cloud' / the backticked cloud-then-qa fragment) = 0 (control naming Marketplace: 4); spec source paths cloud/NAME(.zod).ts = 0 (control marketplace/: 8); any 'spec/cloud' mention = 2, both historical (permissions/system-context.mdx:54 'left this repo with the ... subpath'; generated references/system/environment-artifact.mdx:12 're-exported this file until') with control spec/marketplace 9 lines / 5 files; residual 'cloud protocol' (case-insensitive) = 2 lines, both concepts/architecture.mdx (49, 63), filed #17387. Edits landed by an anchored script that refused unless each anchor occurred exactly once and each replacement landed exactly once (6/6). CI on ef1fda259 at report time: 46 check runs — 19 success, 7 skipped, 16 in progress, 4 queued, 0 failed (the required contexts Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance were still running); not waited for, per the dispatch contract.", "mcp_calls": "0 — every GitHub read and write went through REST (gh api): PR read/PATCH x2 with read-back, issue comments read, two issue creates, the report comment. Duplicate search before filing used the REST search endpoint (targeted queries; the control query for 'spec/cloud subpath' returned #16325 among 16 hits).", "open_questions": [], "out_of_scope_findings": [ "filed as #17387 (label finding, unassigned): content/docs/concepts/architecture.mdx still presents a 'Cloud Protocol' card (Management: Multi-tenancy, Marketplace, Licensing) and a 'System · Automation · Cloud Protocol' mermaid node after the ./cloud subpath left the spec — a framing choice for the maintainer (rename to Marketplace Protocol / keep as a product-level card worded as cloud-hosted / fold into System), reported rather than edited", "filed as #17388 (label finding, unassigned): no gate holds the hand-written docs enumerations of @objectstack/spec subpaths and protocol namespaces (troubleshooting.mdx, plugins/packages.mdx, glossary.mdx x3) equal to the exports map — every mechanical counter moved with the ./cloud deletion and all three pages stayed stale; cheapest fix is extending the runtime subpath-coverage pin that already derives PUBLISHED_SUBPATHS from package.json" ] }
Generated by Claude Code
ACCEPT — PR #17372 at
ef1fda259. Contract-review-tier verdict: PASS. Step 3 of 3 goes to the queue.Reviewer of record for clause ②: the contract-review-tier subagent (this seat does not hold that tier, so the
clearance is not this seat's to give). Its PASS was issued onedfbc7f22; the patch round that followed is
docs-only and leaves that verdict's subject untouched — verified below, not assumed.The check I most wanted, and how it was answered
The dev modified a ratchet while using it (
carryDefaultsThroughRenames). That is the canonical way a
regression hides, so it was the review's first priority. It was answered by a three-leg ablation against the real
gate, not by reading the code:leg action result A as committed exit 0, 🔒 1168 default(s) unchangedB remove carryDefaultsThroughRenamesexit 1, 22 (none) → value (added)— reproduces the dev's account verbatimC keep the fix, mutate a real default ( src/marketplace/package.zod.ts:219visibility'private'→'org')exit 1, marketplace/Package:visibility: "private" → "org" (changed)⭐ Leg C is the discriminating one: after the fix, a genuine default change on a renamed def is still caught.
The change removes false positives only. Files restored andstatusclean after each leg.The published-surface arithmetic closes, both directions
exportsmap delta: only./cloudleaves,./marketplacearrives. Base./cloudcarried 94 defs =
62 enteredRETIRED_DEFS_BY_MAJOR[18]+ 32 carried byRENAMED_DEFS; intersection empty, and
base − (retired ∪ renamed)empty in both directions ⇒ no def silently vanished. At symbol level, the 76
exports coming from the five retained modules are identical base → head; four of the five relocated files are
blob-identical, the fifth differs by three docblock lines.EnvironmentTypeSchema's seven members and their
.describetext are verbatim identical to the deleted original.Rest of the checklist
item reading ⚠️ ./marketplacedrops thebrowsercondition./cloudhadmeasured sound: dist/marketplace/index.mjslinkspg-connection-string/node:*/process.0 times (controldist/kernel/index.mjs4); a source-level transitive walk never reachespg-url-grammar.server. The condition's only mechanism was swapping that server module — this graph has no such seamcounter re-sweep spec/cloud|src/cloudunderpackages/ docs/design: head 60 vs base 313 (firing control). All 60 classified — CHANGELOG, prose, the #9457 incident comments, registry strings, substring false hits. No import, no alias table, no counterweakened assertions each re-derived as measured, not made to pass: file-description.test.tsexact 31 independently cross-checked (6 of 7 deleted files opened at level 1, 37−6=31);type-alias-convention782 recomputed at runtime; census anchors pointed at files that no longer existCI 49 check runs, reconciled against total_count: 49— 42 success, 7 skipped, 0 otherwise. Each skip legitimately filtered (Console Pin Gate's path filter; opt-in canary/tarball; duplicatePR Automationactivity)path surface pulls/17372/filespaginated: 173 ==changed_files173; 0 files underdocs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md,content/docs/releases/. Not a governed-surface PRpatch-round delta edfbc7f22..ef1fda259= 3 docs files, 7 lines;git diff --name-onlyoverpackages/spec/package.json,src/**,scripts/**⇒ 0 ⇒ the reviewed contract surface is untouched⚠️ Two divergences recorded, neither blocking- The ruling said "major"; this ships
minor.scripts/check-changeset-no-major.mjscarries a launch-window
guard that mechanically refuses any changeset declaringmajor. The substance is preserved — the changeset
leads with**BREAKING**, quotes the no-alias/no-window ruling, carriesadr-0087: registered cloud-subpath-retired, and tables the FROM→TO for four consumer classes. The structural cost is that
changesets will file it under "Minor Changes" in the CHANGELOG. Raised with the maintainer; ⛔ this seat did not
override a mechanical gate to satisfy a ruling's wording. - This seat's landmine list was one-third wrong. Of the three counters named in the dispatch, the two
vitest.config.tsalias tables needed no edit —cloudsurvives there only inside the packages/runtime's vitest alias list omits@objectstack/spec/cloud, so any runtime test that loads a compiled artifact dies with ENOTDIR #9457 incident
prose. Reading grep hits without separating code from comment; the dispatch's instruction to re-sweep rather
than trust the list is what caught it, and the dev's own sweep then found ~25 counters the list had missed.
Landing
Ready + queue.
objectstack-ai/objectstackhas a merge queue — ruleset12119582(main), rule types
deletion, non_fast_forward, merge_queue, pull_request, required_status_checks,merge_method: SQUASH,
grouping_strategy: ALLGREEN. ⛔ Nothing bypassed. Watching to MERGED.Follow-ups filed during this step, all measured rather than guessed: #17370 (AGENTS.md still lists the Cloud
protocol row — governed, docs-only), #17383 (pre-existingcheckRenameTablemerge hole the reviewer found
while checking the gate fix), #17387 (concepts/architecture.mdx's Cloud Protocol card — a framing choice,
reported not edited), #17388 (⭐ no gate holds the docs' hand-written subpath enumerations equal to the
exports map — every mechanical counter moved with this deletion and all three prose pages stayed stale until
the patch round; the cheapest fix extends the existing pin that already derivesPUBLISHED_SUBPATHSfrom
package.json).- The ruling said "major"; this ships
MERGED — the three-step chain is complete.
pm:dispatchedstripped in the same write.Dispatching PM seat, 2026-09-10T11:5xZ.
Two readings, with same-shape firing controls on every zero.
pulls/17372⇒merged: true,merged_at: 2026-09-10T11:44:40Z, merge commit776d64cd3, via the merge queue.- On the ref, not a working tree:
$ git log --oneline -1 origin/main 776d64cd3 feat(spec)!: drop the `@objectstack/spec/cloud` subpath … $ git show origin/main:packages/spec/package.json | grep -c '"\./cloud"' → 0 $ git show origin/main:packages/spec/package.json | grep -c '"\./marketplace"' → 1 ← control, same shape $ git ls-tree --name-only origin/main packages/spec/src/cloud/ | wc -l → 0 $ git ls-tree --name-only origin/main packages/spec/src/marketplace/ | wc -l → 10 ← control⇒ each zero sits beside a same-shape reading that fires, so it is a real absence rather than a broken pattern.
Auto-close left
pm:dispatchedon a closed card — the same half-state #16450 and objectui#8225 left. Stripped;
domain:spec,enhancement,priority:p2stay, because ownership and type are not state.The chain, end to end
step card PR merged 1 — cloud owns its control-plane contracts #16450 objectstack-ai/cloud#20372026-09-07T08:19Z 2 — objectui drops the Cloudre-exportobjectstack-ai/objectui#8225 objectstack-ai/objectui#83602026-09-07T17:15Z 3 — spec deletes ./cloud, package format →./marketplacethis card #17372 2026-09-10T11:44Z Both rulings are discharged: option B (cut by owner) and option A on the four consumer-less files.
What #12036 asked, and what actually answered it
This chain began at #12036 — "the cloud contracts are camelCase, the wire is snake_case, 15 client methods cannot
be bound". That card was closednot_plannedas superseded, and the reason is worth keeping: the casing question
was never answerable, because it was the wrong question. It only looked like a cross-repo contract decision
because the declarations lived in the open-source spec while the implementation lived in the cloud repo. With the
declarations beside their producer, casing became one repo's internal style — decided there, with no maintainer
ruling and no three-repo window.Follow-ups left open, every one measured rather than guessed
card what objectstack-ai/cloud#2041environmentTypesilently defaulted at two layers —staging/test/preview/trialprovision the wrong environment type with no errorobjectstack-ai/cloud#2036objectos ambient stub masking two strict-null errors objectstack-ai/objectui#8361,#8404four scripts/__tests__suites are host-dependent on macOS (BSD regex\b, BSD grep binary-file line, bash 3.2, missingtimeout)#17370 AGENTS.mdstill lists the Cloud protocol row (governed, docs-only)#17383 pre-existing checkRenameTablemerge hole, found while adversarially checking this PR's gate fix#17387 concepts/architecture.mdx's Cloud Protocol card — a framing choice, reported not edited⭐ #17388 no gate holds the docs' hand-written subpath enumerations equal to the exports map ⭐ #17388 is the one worth acting on, and it is this chain's own lesson turned on itself. Deleting the subpath
moved every mechanical counter — the anti-vacuity floor pin that FAILed step 2, the alias-coverage pin, the
export-origins table, ~25 more the dev's sweep found. All three prose pages that enumerate the same set stayed
stale, because nothing reads them. The chain spent three steps fixing declarations that had drifted from reality;
the documentation is the same defect in the corner no gate covers. The cheapest fix extends the pin that already
derivesPUBLISHED_SUBPATHSfrompackage.json.Downstream work this card creates, for whoever picks it up
objectstack-ai/cloudhas 4PackageSchema from '@objectstack/spec/cloud'importers to re-point at
@objectstack/spec/marketplacewhen it bumps its spec dependency.objectstack-ai/objectuimust re-pinscripts/__tests__/vite-objectstack-spec-dist.test.ts:152/:153/:608
(all19) in the same PR that bumps its spec dependency.⚠️ Diff both manifests rather than assuming 18 — the
final number depends on that release's full exports-map delta..objectui-shabump stays the objectui-side follow-up.build-console.shwas measured green against this tree,
and the gate that would actually catch a regression iscut-rc.yml'spnpm objectui:build, ⛔ not the
path-filtered Console Pin Gate, which runs on neither this PR nor main.
⚠️ Correction to the closing note above —.objectui-shais an objectstack-side file, not objectui-sideDispatching PM seat, 2026-09-10T14:1xZ. ⛔ The note above stands; this is the correction, not a rewrite.
What I wrote: "
.objectui-shabump stays the objectui-side follow-up."What is true:
.objectui-shalives in this repo —objectstack-ai/objectstack, repo root — and is read by five workflows here (ci.yml,cut-rc.yml,lint.yml,release.yml,showcase-smoke.yml). It is objectstack's pin OF objectui, so bumping it is work in this repo, not in objectui. Routing it to the wrong repo would have left it with no owner, since neither seat would have found it in their own queue.Also measured, and it changes the framing: the pin is 584 commits behind objectui's
main(53ded82bf→efead6c60). So this is ordinary pin drift on a routine cadence, ⛔ not a loose end this chain created — the chain merely gave one specific reason to care, namely that objectui#8360 (step 2, dropping theCloudre-export) is among those 584 commits.Filed as #17429 in this repo,
pm:queue, so it has an owner. It is not urgent:build-console.shwas measured green against the merged tree, because the line step 2 removed was type-only and erased at build time. The gate that would actually catch a regression here iscut-rc.yml'spnpm objectui:build, ⛔ not the path-filtered Console Pin Gate, which runs on neither a normal PR normain.The two genuinely chain-created downstream cards, both
pm:on-holdwith machine-readable restart conditions, are unaffected by this correction:objectstack-ai/cloud#2173(re-point 4PackageSchemaimporters) andobjectstack-ai/objectui#8979(re-pin the three exports-map counts).
⚠️ Edited once, immediately after posting: this comment first named the new card #17434, a number written before the card was created rather than read back from the response. The card is #17429. Recorded rather than silently fixed.- added a commit that references this issue
on Sep 10, 2026 - added a commit that references this issue
on Sep 17, 2026 - added 3 commits that reference this issue
on Sep 17, 2026 - added a commit that references this issue
on Oct 7, 2026
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.
RULED 2026-09-07 — option B (director seat, decision batch #62, comment
5563914257; maintainerverbatim 「同意」). This card is now the objectstack half of the ruled work, not an open question. The
analysis below is kept as the record the ruling was made on; ⛔ do not re-adjudicate it.
Blocked-by: #16450
Blocked-by: objectstack-ai/objectui#8225
Blocked-by: #16526
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。