Repository navigation
cloud: take ownership of the control-plane contracts — local declarations for the six spec/cloud control-plane schemas, 12 import sites re-pointed (step 1 of 3 of the #16325 ruling) #16450
Description
Activity
- addedenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3
on Sep 7, 2026 Claim: PM direct maintainer dispatch (no numbered round — direct-dispatch channel)
Session:session_f95e3874-e532-4748-a921-044aa2752a2b
Branch:claude/issue-16450-cloud-owns-control-plane-contracts
Worktree:cloud-issue-16450
Domain:repo:cloud(single-lane repo — nodomain:*by design)
File surface: inobjectstack-ai/cloud— new local declaration files underpackages/service-tenant/src/·packages/service-cloud/src/billing.ts·packages/service-cloud/test/billing-plan-vocabulary.test.ts·packages/service-tenant/src/{environment-provisioning,tenant-context,tenant-plugin,tenant-provisioning}.ts·packages/service-tenant/src/{tenant-context,tenant-integration}.test.ts·packages/objectos-runtime/src/artifact-api-client.ts·packages/service-cloud/src/registry-reader.ts·.changeset/. ⛔ EXCLUDED and not to be touched:packages/service-cloud/src/routes/package-publish.tsandpackages/service-cloud/test/package-id-declarations.test.ts(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: claude-fable-5-1(=CONTRACT_REVIEW_TIER, read from this session'snode scripts/pm/dispatch-gates.mjs --tierrun, not recalled; mandated by clause ② below, not by a path glob)
Clause-②: yes
Serial constraints cleared: cloud#1932 + cloud#1949 (folded chain, shared branchclaude/issue-1932-package-id-protocol-alignment, assigneeos-justin) declarepackages/service-cloud/test/**, which GLOB-intersects this card — resolved to an EMPTY intersection by reading the chain's actual work onrefs/heads/claude/issue-1932-publish-id-declarations(18cff737):git diff --name-only origin/main...returns exactly.changeset/cloud-1932-publish-id-declarations.md,packages/service-cloud/src/routes/package-publish.ts,packages/service-cloud/test/package-id-declarations.test.ts— and those two source files are the STAY-half files this card excludes by design.claude/issue-1932-package-id-protocol-alignmentsits at9b85d761=origin/main, i.e. zero commits. cloud#1991 declaresapps/cloud/test/**only — disjoint. objectstack-ai/objectui#8225 (sibling step of the same ruling) was NOT dispatched — it yielded to objectui#7122 on a real hot-file collision; different repo, no intersection with this card either way. objectstack#16325 is the downstream consumer and stayspm:blocked.
Maintainer authorisation for this dispatch, verbatim (⛔ quoted, not translated): 「这个作为专题卡,你直接派发」.
⚠️ Dispatched under the direct-dispatch channel while thedomain:specseat post (objectstack#6017) has an
incumbent (session_01T6HeZvT9wdSJD1ZxJb5Eno, active 2026-09-07T05:20Z). This card isrepo:cloud, not
domain:spec, so it is outside that seat's lane; the authorisation above is what routes it here.Premise re-check taken at dispatch time (⛔ not inherited from the card)
cloud
origin/main9b85d761— the card's readings were taken atdaaac081. Re-measured:@objectstack/spec/cloudimport sites: 12 files / 15 import statements (the card's "12" is the file
count and still holds), but the file SET has changed since the@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 reading —routes/package-publish.ts
andtest/package-id-declarations.test.tsare new (commitsa3467c93,cefe6360).- ⭐ A split the card does not carry, and getting it wrong is the main way this card goes wrong. The 12
files do not all move. By imported symbol:
files symbols disposition service-tenant/src/{environment-provisioning,tenant-context,tenant-plugin,tenant-provisioning}.ts+{tenant-context,tenant-integration}.test.ts,service-cloud/src/billing.ts,service-cloud/test/billing-plan-vocabulary.test.tsEnvironment,EnvironmentCredential,EnvironmentDriver,ProvisionEnvironment*,ProvisionOrganization*(→environment.zod.ts);TenantPlan(Schema),TenantRoutingConfig*,ProvisionTenant*,TenantDatabase(→tenant.zod.ts)8 files — MOVE, re-point at the new local declarations service-cloud/src/routes/package-publish.ts,service-cloud/test/package-id-declarations.test.tsPackageSchema→package.zod.ts⛔ 2 files — STAY. package.zod.tsis the package-format half the ruling keeps in the open-source spec. Do not touch; they are also the in-flight cloud#1932 chain's only filesobjectos-runtime/src/artifact-api-client.ts,service-cloud/src/registry-reader.tsEnvironmentArtifact⭐ 2 files — NEITHER. Not a move at all: packages/spec/src/cloud/environment-artifact.zod.tsis a pure re-export whose whole body isexport { … } from '../system/environment-artifact.zod'. Re-point these at@objectstack/spec/systemand the dependency is gone with a one-word editSymbol→file mapping measured on objectstack
d5d8d50db;EnvironmentArtifactreturns NOT-FOUND as a
declaration anywhere inpackages/spec/src/cloud/, which is the reading behind row 3.- Runtime accept gate still present:
packages/service-tenant/src/environment-provisioning.ts:750
const parsed = ProvisionEnvironmentRequestSchema.parse(request);— this is why clause ② isyes.
Claim: dev seat for this card (dispatched by the PM claim above; this is the dev's own presence bit, not a second assignment)
Session:session_f95e3874-e532-4748-a921-044aa2752a2b
Branch:claude/issue-16450-cloud-owns-control-plane-contracts(repoobjectstack-ai/cloud, pushed empty as the write-route probe — exit 0)
Worktree:cloud-issue-16450(sibling level)
Base: cloudorigin/main9b85d761(= the PM's dispatch-time measurement; re-fetched at claim time)Premise re-check at claim time, on the tree I actually got:
@objectstack/spec/cloudimport statements on cloud9b85d761: 15 statements in 12 source files (+1 prose mention in.changeset/auto-default-env-plan-single-source.md, +1 comment inroutes/package-publish.ts:161). Control term@objectstack/spec/systemfires on 16 files. The PM's 8 MOVE / 2 STAY / 2 NEITHER split reproduces exactly.- objectstack
origin/mainhas advanced toc383352cb(PM measuredd5d8d50db); the six files are unchanged, 2087 lines total, andspec/src/index.tsdoes NOT re-export./cloud, so the./cloudsubpath is the only route into them. ⚠️ Receiving-cost reading the card does not carry: of the six files, onlyenvironment.zodandtenant.zodhave ANY consumer in cloud.developer-portal,marketplace-admin,app-store,environment-packageexport 90 symbols between them and zero of those symbol names appear anywhere in cloud outside the lockfile (the one name that does,SubscriptionStatus, is cloud's own Polar/Stripe type inbilling.ts). Within the two live files,EnvironmentMember/EnvironmentRole/EnvironmentType(Schema)andPackageInstallation(Status)Schemaalso have zero consumers. Plan: land local declarations for what cloud actually consumes, and report the rest as a decision for the PM rather than importing 1,266 lines of consumer-less schema as "cloud's own".
Generated by Claude Code
os-dev-report
{ "issue": 16450, "status": "done", "branch": "claude/issue-16450-cloud-owns-control-plane-contracts", "pr": "https://github.com/objectstack-ai/cloud/pull/2037", "premise_still_valid": true, "summary": "Cloud now owns the control-plane contracts it consumes: packages/service-tenant/src/spec/{tenant,environment}.zod.ts (+barrel) re-exported via @objectstack/service-tenant; 8 sites re-pointed at them, 2 EnvironmentArtifact sites re-pointed at @objectstack/spec/system, 2 PackageSchema sites untouched by the ruling. The runtime accept gate (ProvisionEnvironmentRequestSchema.parse at environment-provisioning.ts:750) now runs against the local declaration, which additionally declares environmentType (both callers already sent it; the service read it off the raw request). All PM mechanism assumptions (8/2/2 split, pure re-export, runtime gate) reproduced on cloud 9b85d761. Partial premise on the card body: of the six files, only environment.zod and tenant.zod have any consumer in cloud; the other four (90 exported symbols) and the members EnvironmentType/Role/Member and PackageInstallation(Status) have zero consumers and were not re-declared — decision left to the PM (open_questions). Casing measured route by route and recorded, not converged: POST create emits the camelCase DTO verbatim, the read routes emit the snake_case sys_environment row, GET packages emits packageId and package_id; local declarations agree with the one route that emits them.", "tests": "At final HEAD 38767d55 (cloud), built against the pinned framework e581457b: service-tenant build VERDICT command-exit 0; service-tenant typecheck (tsc + check:test-typecheck, 0 debt) exit 0; service-tenant full pnpm test: 19 files, 316 passed, 2 skipped (pre-existing); service-cloud typecheck exit 0; service-cloud targeted vitest (environment-create-local-contract, billing-plan-vocabulary, environment-create-hostname-assignment, environment-create-single-impl, error-envelope-conformance, environment-lifecycle-guards): 6 files, 77 passed; objectos-runtime typecheck exit 0; consumer sweep ...@objectstack/service-tenant (downstream): @objectstack/cloud typecheck exit 0 (after removing apps/cloud/types/service-tenant.d.ts; with it: TS2709 on billing.ts), @objectstack/objectos typecheck exit 0. Acceptance grep at 38767d55: from '@objectstack/spec/cloud' -> exactly 2 STAY files (PackageSchema); control from '@objectstack/spec/system' -> 12 files; symbol-level grep of control-plane names against spec/cloud -> 0. Ablation (restore proven by bytes: source hash-object == HEAD blob 2669d279, dist d.ts sha256 53c2b7cd identical before/after, git diff HEAD empty): leg 1 mutating TenantPlanSchema to z.number() turned service-tenant's OWN DTS build red (dist wiped by clean) — cross-package not reached, recorded as observed; leg 2 mutating only the alias TenantPlan to number: service-tenant build green, dist carries type TenantPlan = number, service-cloud tsc red at src/billing.ts(73,41) TS1360 — the consumer reads the rebuilt d.ts. Declared narrowing: service-cloud full suite (100+ files) not re-run locally; verification ran UNLOCKED (no flock on macOS), disclosed in the PR body.", "gates": { "derivation": "cloud repo's own .github/workflows/test.yml build-and-test job (objectstack's dispatch-gates.mjs not used; it does not describe cloud)", "run_locally_exit_0": ["check:control-bytes", "check:link-deps", "check:retired-framework-deps", "check:declared-twins", "check:route-envelope", "check:workspace-dist-preflight", "check:framework-anchor", "check:dts-gating", "check:worker-entry-typecheck", "audit-spec-changes.mjs --framework ../objectstack (nothing unacknowledged)", "check:adr-numbering", "check:ee-manifest", "check:image-git-hygiene", "check:workflow-credentials", "check:hotcrm-publish", "check:ee-boot-refusal-declared", "check:simulation-switches", "check:ee-plugin-wiring", "check:kernel-ready-registration", "check:migration-has-invoker", "check:walled-declares-owner", "check:turbo-pin-inputs", "check:acceptance-ports", "check:ee-feature-consumers", "check:ai-route-sweep", "check:ai-model-pin", "check:outbound-suggestion", "check:docs-error-codes", "typecheck: service-tenant, service-cloud, objectos-runtime, cloud, objectos", "test: service-tenant (full), service-cloud (6 targeted suites)"], "ci_owned_not_run": ["turbo run typecheck test (full farm)", "test:boot-smoke", "test:production-flow", "test:billing-flow", "test:cloud-app", "hotcrm acceptance", "service-cloud remaining suites"], "ci_status": "in_progress — PR opened, report delivered at draft-PR time per the dispatch contract" }, "line_budget": "n/a — no skills/** in the diff. Diffstat origin/main..38767d55: 20 files, +1324 / -25 (two commits: a68ac67c implementation, 38767d55 stub removal)", "deviations": [ "Worktree relocated: created at /Users/zhuangjianguo/Documents/GitHub/cloud-issue-16450 as instructed, then moved with git worktree move to /Users/zhuangjianguo/Documents/GitHub/rig-pin/cloud-issue-16450 (still sibling-level, same name) because the primary ../objectstack checkout's dist is stale (built Aug 29, HEAD d5d8d50db): packages/organizations DTS build red there on isDefaultOrganizationBootstrapTrigger (present in plugin-auth source at pin and HEAD, absent from that dist). rig-pin/objectstack is the framework at cloud's pin e581457b = the CI baseline. Worktree removed after the PR (no --force needed).", "File-surface increment 1: apps/cloud/types/service-tenant.d.ts DELETED (one-line shorthand ambient module from the April scaffold 91ee0c44). Required: it turns every named import from @objectstack/service-tenant into an any-typed value in apps/cloud's programs, so billing.ts (compiled inside apps/cloud's test project via a deep import) failed TS2709 the moment it took a type from that specifier. No non-test app code imports service-tenant, no Dockerfile runs tsc, tsconfig.cloudflare.json includes only cloudflare/** — no live purpose. apps/cloud typecheck green without it. The objectos twin was NOT removed (it masks two pre-existing strict-null errors in apps/objectos/test/provisioning.test.ts) — filed as objectstack-ai/cloud#2036.", "File-surface increment 2: new packages/service-cloud/test/environment-create-local-contract.test.ts — the Zone 2C code+status pin can only be asserted where the ADR-0112 envelope is minted (the route), not in service-tenant. Disjoint from the cloud#1932 chain's two files.", "File-surface increment 3 (mechanical): packages/service-tenant/package.json gains zod ^4.4.3 (schemas now live there; emitted d.ts references zod types); pnpm-lock.yaml +3 lines.", "Scope reading: 4 of the 6 ruled files (developer-portal, marketplace-admin, app-store, environment-package) and the members EnvironmentType(Schema), EnvironmentRole/Member, PackageInstallation(Status)Schema were NOT re-declared — zero consumers in cloud (measured by symbol name across the repo). Hosting them anyway is an open question for the PM, not decided here.", "Casing not converged: POST create (camelCase DTO) vs read routes (snake_case row) disagree today; converging is a wire change for @objectstack/client and the Console, outside this card and surface. Recorded in the PR body table and the declaration file header.", "needs:contract-review label does not exist in objectstack-ai/cloud (it exists only in objectstack); not created (repo config is not the dev's), request stated in the PR body's first paragraph. skip-changeset not applied (a changeset is included; cloud has no changeset gate — convention only).", "Claim: posted my own Claim comment (5566029573) as the dispatch instructed, same session id and branch as the PM's claim 5565868515, so the newest Claim still names this branch; objectstack CLAUDE.md's 'posts no second claim' wording conflicts with the dispatch — followed the dispatch, noting it here.", "'#16325 notified when landed': posted an open-PR heads-up on objectstack#16325 (comment 5566370040) with the receiving-side readings step 3 needs; the merge-time notification is the PM's.", "Verification ran UNLOCKED (os-verify-lock.sh: no usable flock on this host) — the script's disclosure block is pasted in the PR body.", "Ablation leg 1 measured a direction other than designed (service-tenant's own DTS build red); leg 2 (alias-only) measured the designed cross-package red. Both reported as observed." ], "files_changed": [ "A .changeset/cloud-owns-control-plane-contracts.md", "D apps/cloud/types/service-tenant.d.ts", "M packages/objectos-runtime/src/artifact-api-client.ts", "M packages/service-cloud/src/billing.ts", "M packages/service-cloud/src/registry-reader.ts", "M packages/service-cloud/test/billing-plan-vocabulary.test.ts", "A packages/service-cloud/test/environment-create-local-contract.test.ts", "M packages/service-tenant/package.json", "M packages/service-tenant/src/environment-provisioning.ts", "M packages/service-tenant/src/index.ts", "A packages/service-tenant/src/spec/control-plane-contracts.test.ts", "A packages/service-tenant/src/spec/environment.zod.ts", "A packages/service-tenant/src/spec/index.ts", "A packages/service-tenant/src/spec/tenant.zod.ts", "M packages/service-tenant/src/tenant-context.test.ts", "M packages/service-tenant/src/tenant-context.ts", "M packages/service-tenant/src/tenant-integration.test.ts", "M packages/service-tenant/src/tenant-plugin.ts", "M packages/service-tenant/src/tenant-provisioning.ts", "M pnpm-lock.yaml" ], "mcp_calls": "0 — every GitHub read and write went through gh api (REST); no MCP GitHub tool was called", "open_questions": [ { "question": "Should cloud host the four consumer-less control-plane files (developer-portal, marketplace-admin, app-store, environment-package — 1,266 lines, 90 exported symbols, zero references in cloud) and the zero-consumer members of the two live files, or does step 3 (#16325) simply delete them upstream with nothing landing in cloud?", "options": [ "A: do not host — step 3 deletes them; if a schema is ever needed it is recovered from objectstack history (git show d5d8d50db:packages/spec/src/cloud/...). Zero dead code lands in cloud.", "B: host verbatim in packages/service-tenant/src/spec/ (or a sibling module) with their upstream tests, as a literal reading of 'six files become cloud's own declarations' — 1,266 lines nobody parses, kept green by tests that pin nothing the runtime does.", "C: host only environment-package.zod (the one whose CONCEPT is live: GET environments/:id/packages) after making it agree with that route's packageId+package_id emission — a small follow-up card, not this PR." ], "recommendation": "A, because the maintainer's direction is that cloud's protocol should not be open-source, not that cloud must carry schemas it never implemented; a declaration with no parser is not a contract. If the PM wants B or C, a follow-up card on the same branch family is a copy, not a redesign — nothing in this PR forecloses it." } ], "out_of_scope_findings": [ "filed as objectstack-ai/cloud#2036: apps/objectos/types/service-tenant.d.ts (shorthand ambient stub) hides @objectstack/service-tenant's types from the objectos app and masks two pre-existing strict-null errors in apps/objectos/test/provisioning.test.ts (result.environment.databaseUrl is optional on the DTO; the test calls .startsWith/.replace unguarded). Label: finding.", "not filed (prose-only, noted in the PR body): routes/environment-lifecycle.ts comment still says spec cloud/environment; environment-provisioning.test.ts header still calls ProvisionEnvironmentRequest z.infer (stale since ADR-0122); .changeset/auto-default-env-plan-single-source.md mentions @objectstack/spec/cloud historically." ] }
Generated by Claude Code
Review — REWORK (one item). PR objectstack-ai/cloud#2037
Reviewer of record: dispatching PM, session
session_f95e3874-e532-4748-a921-044aa2752a2b, 2026-09-07T07:1xZ.
⛔ Verified against GitHub and the pushed branch, not against the report's self-description.What checks out
check reading PR form draft: true,base: main, first body lineFixes objectstack-ai/objectstack#16450, 2 commits, 20 files, +1324/-25Declared surface honoured changed-file list from pulls/2037/filesmatches the claim's surface plus the three declared increments; nothing undeclared⭐ Serial constraint held packages/service-cloud/src/routes/package-publish.tsandtest/package-id-declarations.test.tsare absent from the diff — the cloud#1932 chain's only two files were not touchedAcceptance grep from '@objectstack/spec/cloud'on the branch returns exactly the 2 STAY files (PackageSchema). The 4 extra hits my looser grep found are provenance comments in the new files, not imports. Control:@objectstack/spec/systemmatches 17 files ⇒ the grep firesControl-plane symbols symbol-level grep of Environment*/TenantPlan*/TenantRoutingConfig*/Provision*against@objectstack/spec/cloud⇒ 0apps/cloud/types/service-tenant.d.tsstatus=removed,-1— a one-line ambient stub, as declaredChangeset present ( .changeset/cloud-owns-control-plane-contracts.md)The 8/2/2 split and the
environment-artifact.zodpure-re-export finding both reproduced.premise_still_valid: trueis
supported by the branch.⛔ The REWORK item — a free-string key added to a runtime accept gate
packages/service-tenant/src/spec/environment.zod.ts:306adds toProvisionEnvironmentRequestSchema:environmentType: z.string().optional().describe( 'Environment type requested by the caller (production | development; folded by the provisioning service).'),
This key is new — the upstream schema (
objectstackorigin/main:packages/spec/src/cloud/environment.zod.ts)
does not declare it. It lands on the live accept gate atenvironment-provisioning.ts:750
(ProvisionEnvironmentRequestSchema.parse(request)), so this is a contract accept-surface change, not a relocation.Two things are wrong together, and the second is what makes it a defect rather than a choice:
EnvironmentTypeSchema— a 7-value enum upstream,
['production','sandbox','development','test','staging','preview','trial'](environment.zod.ts:69-73) — was
dropped as consumer-less, and then the field that needs exactly that vocabulary was declared as a bare string.- The fold at
environment-provisioning.ts:839-849honours five spellings and silently swallows everything else:
production | prod -> 'production' development | dev | sandbox -> 'development' anything else -> parsed.isDefault ? 'production' : 'development' // no error, no warningFailure scenario, concrete: a caller posts
POST /api/v1/cloud/environmentswith
environmentType: "staging"— a value the platform's own published enum calls legal. It parses clean, reaches the
fold, matches no arm, and becomesdevelopment(orproductionifisDefault). The environment is provisioned with
the wrong type and the wrong system host prefix (dev-vsos-,:859), and nothing anywhere reports a problem.
The same holds for"test","preview","trial", and for any typo such as"prodction".This is the shape the decision framework's anti-error axis names: declaring a wide acceptance set and having the
consumer quietly cope, rather than tightening the contract and refusing loudly. A declaration that accepts strings
the runtime cannot honour isdeclared ≠ enforcedon a gate that was clean before this PR.Either resolution is acceptable — your measurement decides which:
- (a) Declare what the fold actually honours and refuse the rest at the gate, with the ADR-0112 envelope's
code+statusasserted inenvironment-create-local-contract.test.ts. If the four unhandled enum members
ought to be honoured rather than refused, that is a wire-behaviour change and belongs on its own card — say so and
pick refusal here. - (b) Drop the
environmentTypeaddition entirely, keeping this PR a faithful relocation, and file the
declared-vs-honoured gap as its own card. The service keeps reading the raw request exactly as it did before.
⛔ Not acceptable: leaving
z.string()on the gate. Whichever you pick, the PR body's contract table must say which
values the gate accepts and what happens to the rest.Not blocking this PR, recorded so it is not lost
open_questions[0]— the four consumer-less files (developer-portal,marketplace-admin,app-store,
environment-package; 1,266 lines, 90 symbols) — is not the dev's to answer and not this seat's. Deleting a
published capability with no replacement is a maintainer floor item. It is going to the decision inbox against
[Decision] Should the cloud control-plane contracts leave@objectstack/specand live in the cloud repo? (upstream of #12036) #16325 (step 3), which is where the deletion would actually happen. ⛔ Do not act on it in this PR; the dev's
recommendation (A — do not host) is recorded there as input, not as a decision.needs:contract-reviewdoes not exist inobjectstack-ai/cloud— verified, the repo has noneeds:*labels
at all. Correctly not created by the dev (repo config is not the dev's). This is the PM's to resolve before the
card can enqueue; the clause-② obligation stands regardless of whether a label exists to carry it.- CI is
in_progress(build-and-test,Build and exercise EE image) at review time — an honest reading at
draft-PR time, not a gate result. Enqueue needs every check green, not a required subset. - Dev's
Claim:comment (5566029573) was posted because this dispatch order asked for it. That instruction
was wrong:CLAUDE.mdsays a PM-dispatched dev inherits the PM's claim and "posts no second claim". The dev
flagged the conflict rather than silently following one side — correct behaviour. The error is the dispatcher's,
not the dev's, and no rework follows from it.
Verdict
REWORK, one item, same claim, patch round 1 of at most 2. Everything else on this PR is accepted as read.
os-dev-report
{ "issue": 16450, "status": "done", "patch_round": "1 of at most 2 — the single REWORK item from review comment 5566464742", "branch": "claude/issue-16450-cloud-owns-control-plane-contracts", "pr": "https://github.com/objectstack-ai/cloud/pull/2037", "head": "98a997b3 (was 38767d55 at review; one commit added, nothing rebased or force-pushed)", "premise_still_valid": true, "resolution": "(b) — the environmentType addition is dropped entirely; ProvisionEnvironmentRequestSchema is relocated with upstream's key set verbatim, the service reads environmentType off the raw request exactly as before the move, and the declared-vs-honoured gap is filed as objectstack-ai/cloud#2041.", "why_b_not_a": "Measured on the branch: the route normalises environmentType BEFORE the service (plan-entitlements.ts normalizeEnvironmentType: production|prod, development|dev|sandbox, else undefined -> count-based default), so the service gate never receives a non-canonical value from POST /cloud/environments. A gate-level refusal for staging is therefore unreachable from environment-create-local-contract.test.ts without changing the route (out of surface), and the silent fold exists at two layers (route + service) — settling the vocabulary is one card covering both, not a patch on the relocation.", "gate_statement": "ProvisionEnvironmentRequestSchema accepts exactly the upstream key set (organizationId, displayName non-empty, createdBy, optional driver/plan/storageLimitMb/isDefault/metadata/hostname/templateId/visibility with default private); every other key, environmentType included, is STRIPPED by z.object. environmentType today (unchanged by this PR): route normalises five spellings and count-defaults the rest silently; service reads the handed value raw, folds the same five, isDefault-defaults the rest silently. Stated in the PR body's contract section and in the schema's inline note.", "summary": "Patch round 1 removes the environmentType: z.string().optional() key from the relocated accept gate, restores the raw read in environment-provisioning.ts with a comment naming why the key stays undeclared, and re-points both contract tests to pin that the gate STRIPS the key (service-level: parse drops it and the schema shape lacks it; route-level: the raw request the route hands the service carries environmentType: production while the local parse drops it). PR body: dropped the 'one declaration change' bullet, rewrote the request-body table row, added the explicit gate statement with the cloud#2041 reference. Changeset updated to match. Nothing else touched; the four consumer-less files and the label are left to the PM as instructed.", "tests": "At HEAD 98a997b3, against the pinned framework e581457b (worktree recreated under rig-pin/ on the same branch, pnpm install exit 0, dependency closure rebuilt: VERDICT command-exit 0, 20 build successes): service-tenant targeted vitest (spec/control-plane-contracts, environment-provisioning, environment-provisioning-audience, tenant-context, tenant-integration) 5 files, 111 passed, 2 skipped (pre-existing; one case fewer than round 0 because the non-string-environmentType refusal case was removed with the key); service-tenant typecheck (tsc + check:test-typecheck, 0 debt) exit 0; service-cloud typecheck exit 0; service-cloud targeted vitest (environment-create-local-contract, billing-plan-vocabulary, environment-create-hostname-assignment) 3 files, 15 passed; dist check: service-tenant dist/index.d.ts no longer declares environmentType on the request schema (its 3 remaining mentions are the header comment, buildEnvironmentRow's options type and systemHostPrefix's parameter). check:control-bytes exit 0 (1716 files). Verification ran UNLOCKED (no flock on this host) — disclosure already in the PR body.", "gates": { "run_locally_exit_0": ["check:control-bytes", "typecheck: service-tenant, service-cloud", "test: service-tenant (5 targeted suites), service-cloud (3 targeted suites)", "build: service-cloud dependency closure (service-tenant included)"], "not_rerun_this_round": ["apps/cloud and apps/objectos typecheck (the patch removes a schema key; billing.ts's TenantPlan import — the only type apps/cloud's program takes from service-tenant — is untouched)", "the 27 other static gates (no file they read changed: 5 TS/MD files inside packages/**, all already in the surface)", "service-cloud remaining suites (CI)"], "ci_status": "in_progress — report delivered at push time per the dispatch contract" }, "line_budget": "n/a — no skills/** in the diff. Patch commit 98a997b3: 5 files, +57 / -52. PR total origin/main..98a997b3: 20 files, +1329 / -25", "deviations": [ "Worktree recreated at /Users/zhuangjianguo/Documents/GitHub/rig-pin/cloud-issue-16450 (same branch, same rig placement as round 0, for the same stale-primary-dist reason) and removed again after the push.", "Resolution (b) chosen over (a) on measurement (see why_b_not_a); the reviewer's 'not acceptable: z.string() on the gate' is satisfied by removal, and the PR body now states what the gate accepts and what happens to the rest.", "One new card filed for the gap (objectstack-ai/cloud#2041, no assignee, no label — a concrete defect for PM triage); search-first: 'environmentType' and 'environment_type staging' returned 0 open hits, control 'environment_type' returned 6.", "Not acted on, per the review: open_questions[0] (the four consumer-less files) and the needs:contract-review label." ], "files_changed": [ "patch commit 98a997b3:", "M .changeset/cloud-owns-control-plane-contracts.md", "M packages/service-cloud/test/environment-create-local-contract.test.ts", "M packages/service-tenant/src/environment-provisioning.ts", "M packages/service-tenant/src/spec/control-plane-contracts.test.ts", "M packages/service-tenant/src/spec/environment.zod.ts", "PR total (origin/main..98a997b3) unchanged in membership: the same 20 files as the round-0 report" ], "mcp_calls": "0 this round (cumulative 0) — gh api (REST) for every GitHub read and write", "open_questions": [], "out_of_scope_findings": [ "filed as objectstack-ai/cloud#2041: environmentType on POST /cloud/environments is declared nowhere, honoured for five spellings, and silently defaulted at two layers (route count-default, service isDefault-default); staging/test/preview/trial provision the wrong type with no error. Two resolutions laid out (refuse at the gate vs honour the seven-member enum); either needs the route to stop normalising unknown values to undefined before a gate-level refusal is observable.", "unchanged from round 0: objectstack-ai/cloud#2036 (objectos ambient stub masking two strict-null errors in test/provisioning.test.ts)." ] }
Generated by Claude Code
Patch round 1 received and fact-checked — ⛔ clearance NOT given here; contract-review-tier review is running
Dispatching PM, session
session_f95e3874-e532-4748-a921-044aa2752a2b, 2026-09-07T07:3xZ.
PR objectstack-ai/cloud#2037 head98a997b3(was38767d55at round 0; one commit added, nothing rebased or force-pushed).⚠️ Correction — my round-0 review located the defect one layer offRound-0 review (
5566464742) said a caller postingenvironmentType: "staging"would reach
ProvisionEnvironmentRequestSchema.parseand fold todevelopmentat the service. That path is wrong.
Re-derived on the branch:packages/service-cloud/src/plan-entitlements.ts:683 export function normalizeEnvironmentType(value: unknown): EnvironmentType | undefined { const k = String(value ?? '').trim().toLowerCase(); if (k === 'production' || k === 'prod') return 'production'; if (k === 'development' || k === 'dev' || k === 'sandbox') return 'development'; return undefined; }The route normalises first and turns every non-canonical spelling into
undefinedbefore the service sees
it, so the service gate never receives"staging"and a gate-level refusal is not observable from
environment-create-local-contract.test.tswithout changing the route — which is outside this card's file
surface. ⇒ Resolution (a) as I wrote it was not implementable here. The dev measured that, said so, and took
(b). That is the correct call and the correction is the dev's, not mine.The underlying defect is real and unchanged in substance —
staging/test/preview/trialstill
provision the wrong environment type with no error — but it lives at two layers (route count-default,
service isDefault-default), not at the gate. Correctly filed asobjectstack-ai/cloud#2041rather than patched
into a relocation card.What I verified on the branch (facts only — not a clearance)
check reading at 98a997b3environmentTypeoff the accept gateabsent from ProvisionEnvironmentRequestSchema; an explicit ⛔ comment names why it stays undeclared⭐ key set vs upstream diffof the schema's top-level keys againstobjectstackorigin/main:packages/spec/src/cloud/environment.zod.ts⇒ identical, byte for bytePR form still draft, basemain, 3 commits, 20 files, +1328/-24,mergeable: truescope file membership unchanged from round 0; the cloud#1932 chain's two files remain absent cloud#2041exists, open, no assignee, no labels — correct shape for that repo's own triage ⛔ Why this comment is not an ACCEPT
This card is Clause-② (the diff lands on a runtime accept gate). Seat-internal review of a clause-② card must
run atCONTRACT_REVIEW_TIER, and this PM seat does not hold that tier — so the enqueue clearance is not
mine to give, and the fact-check above is deliberately not dressed up as one. A contract-review-tier reviewer
has been dispatched to re-derive independently, with the round-0 and round-1 findings marked as ⛔ not
inheritable. Its brief includes the one thing round 0 did not do: a key-by-key diff of the relocated
tenant.zodagainst upstream.Outstanding before this card can enqueue, independent of that verdict:
- that reviewer's PASS;
- every check green on
98a997b3— CI wasin_progressat this reading; needs:contract-reviewdoes not exist as a label inobjectstack-ai/cloud(verified — that repo has no
needs:*labels at all). The clause-② review obligation is being met by the dispatched reviewer regardless;
the missing label is a repo-config gap raised with the maintainer, ⛔ not the dev's and not silently created.
Contract-review-tier review: PASS-pending-CI — clause-② obligation discharged. Patch round 2 (two one-line items) requested.
Reviewer: contract-review-tier subagent at
CONTRACT_REVIEW_TIER, dispatched by this PM seat because the seat
does not hold that tier. Brief marked the round-0 and round-1 findings ⛔ non-inheritable; it re-derived
independently. Target PR objectstack-ai/cloud#2037 at head98a997b3.Verdict and what backs it
Clause-② core — the relocation is semantically faithful. Reviewer took a comment-stripped code diff of
both relocated files against upstreampackages/spec/src/cloud/{environment,tenant}.zod.ts(baseline: objectstack
origin/mainand cloud's pine581457bare identical there, so one baseline suffices). Every difference falls
into exactly three classes:class environment.zod.tstenant.zod.tsimport source lazySchemafrom@objectstack/spec/shared(same implementation on pin and main)same whole-block deletion, zero consumers EnvironmentType(Schema),EnvironmentRole(Schema),EnvironmentMember(Schema)PackageInstallationStatus(Schema),PackageInstallation(Schema)(Parsed)retained schema bodies byte-identical byte-identical Key sets, optionality, defaults,
.min()/ enum members and strip-vs-strict behaviour are unchanged on every
retained schema. The zero-consumer assertion was re-verified: grepping all 11 deleted symbols across the head tree
returns 0. DeletingEnvironmentTypeSchemais consistent with the ruling, which names re-declaring
EnvironmentTypeinspec/apias step 3's job (#16325 comment5563914257). Barrel export-name collision scan
acrossspec/index.tsand the other 15 modules ⇒ 0.The runtime gate — unchanged and genuinely pinned. Beyond reading the code, the reviewer executed the
replicated constraints against cloud's installed zod 4.4.3: five refusal cases match the tests' asserted
code+pathexactly (invalid_typefor missingorganizationId/createdByand non-objectmetadata;
too_smallfor emptydisplayNameandstorageLimitMb: 0),environmentTypeis stripped and absent from
.shape,visibilitydefaults toprivate,TenantRoutingConfigSchema.parse({})matches key-for-key, and
TenantPlanSchemaaccepts''and rejects42. It also confirmed the route-level 400 in
environment-create-local-contract.test.tsreally originates in the service'sparse, not a route pre-check
(environment-lifecycle.ts:789-940has nometadatapre-check; the catch at:1066-1074maps it).Scope held.
package-publish.tsandpackage-id-declarations.test.tsare absent from the diff AND their
blobs are identical between head and merge-base. The deletedapps/cloud/types/service-tenant.d.tswas a one-line
shorthand ambient module; removing it narrows@objectstack/service-tenantimports fromanyto real types —
it cannot silently widen anything, and any breakage would surface as a tsc error that CI's typecheck catches.Changeset consistent with the diff (
service-tenantminor,service-cloud/objectos-runtimepatch;
@objectstack/cloudis private and needs none).⛔ Patch round 2 requested — two claims wider than what they establish
Neither is a code defect; both are statements in the repo that are not true as written.
- A test comment claims proof it cannot deliver.
control-plane-contracts.test.ts's case titled
"…still reads environmentType off the RAW request" passesenvironmentType: 'Development'withisDefault
omitted. Per the fold atenvironment-provisioning.ts:844-850, deleting the raw read entirely falls back to
parsed.isDefault ? 'production' : 'development'— which for that input is alsodevelopment, also the
dev-prefix, alsoisDefault === false. Every observable it asserts is identical whether the raw read exists
or not, so it cannot fail for the reason its comment gives. (The behaviour itself IS pinned, by the untouched
environment-provisioning.test.ts:217-225— this is about making THIS test's claim true, not coverage.)
Requested fix:environmentType: 'production'withisDefaultomitted, asserting theos-prefix. - A PR-body count about to go stale.
mainhas moved two commits (feat(org-scoping): multi-tenant seed ownership handoff to default-org admin #2031, feat(dogfood): multi-user harness + RLS cross-owner proof runner (#1994) #2032) with no file intersection
with this diff, but feat(dogfood): multi-user harness + RLS cross-owner proof runner (#1994) #2032 adds two morePackageSchema from '@objectstack/spec/cloud'importers. After merge
the body's "exactly the 2 STAY files" becomes 4. The acceptance criterion is unaffected — zero control-plane
importers still holds, and all four remaining are package-format, which step 3 re-points. Body to be reworded
so it stays true post-merge.
Outstanding before enqueue
- Patch round 2 landed (dispatched to the same dev, same claim — round 2 of at most 2).
- Every check green. At review time
build-and-testandBuild and exercise EE image (no publish)were both
in_progresson98a997b3. The reviewer confirmed the expected check set is exactly those two:actionlint,
half-state-patrolandpin-smokecarrypaths:filters this PR does not touch, so their absence is correct,
not missing coverage. No commit statuses (no Vercel on this repo). The patch push restarts both. needs:contract-reviewdoes not exist inobjectstack-ai/cloud. The clause-② obligation is discharged by
the review recorded here, not by a label; the missing label is a repo-config question raised with the
maintainer and ⛔ deliberately not resolved by creating one unilaterally.
- A test comment claims proof it cannot deliver.
os-dev-report
{ "issue": 16450, "status": "done", "patch_round": "2 of 2 — the two one-line items from the coordinator's PASS-pending-CI review of 98a997b3", "branch": "claude/issue-16450-cloud-owns-control-plane-contracts", "pr": "https://github.com/objectstack-ai/cloud/pull/2037", "head": "678f133e (was 98a997b3; one commit added — nothing rebased, force-pushed, or merged in; main's #2031/#2032 still intersect zero files of this diff)", "premise_still_valid": true, "item_1": "control-plane-contracts.test.ts: the raw-read case now sends environmentType: 'production' with isDefault OMITTED and asserts isDefault === true plus the os- host prefix — the fold's fallback for a missing type is development, so only the raw read can produce that result. Title and comment rewritten to say what the case pins and that environment-provisioning.test.ts pins the raw-read behaviour independently (this is about the claim, not coverage). Proven by mutation after committing: blanking the raw read (String('')) turned the case red — 'expected false to be true' on isDefault and the os- toMatch — 1 failed / 19 passed; restore by git checkout HEAD -- proven by bytes (hash-object 1ee7bed0 == HEAD blob, git diff HEAD empty), restored tree 20/20 green.", "item_2": "PR body: the acceptance bullet no longer says 'exactly the 2 STAY files'. It now states: zero control-plane-schema importers of @objectstack/spec/cloud remain (symbol-level grep 0 on the branch; origin/main pre-merge shows 6 for contrast), and the importers that remain are ALL package-format PackageSchema — 2 on this branch, 4 after merge because #2032 adds routes/package-install.ts and test/package-export.test.ts — the half the ruling keeps open-source and step 3 re-points. The 12-site bullet is annotated as counted at base 9b85d761. Read back: new wording present, old wording gone, one session-URL footer.", "summary": "Two claims made true, no behaviour changed: one test now fails when the behaviour it names goes away, and one PR-body number no longer goes stale at merge. Nothing else touched — the four consumer-less files (objectstack#16526, with the maintainer) and the needs:contract-review label were not acted on.", "tests": "At HEAD 678f133e against the pinned framework e581457b (worktree recreated under rig-pin/, pnpm install exit 0, service-tenant's workspace deps rebuilt VERDICT command-exit 0): service-tenant vitest spec/control-plane-contracts + environment-provisioning: 2 files, 90 passed; service-tenant typecheck (tsc + check:test-typecheck, 0 debt) exit 0; check:control-bytes exit 0 (1716 files); mutation proof and restored-tree rerun as in item_1. Not rerun (no file they read changed — the patch is one test file): service-cloud, objectos-runtime, apps typechecks and the other static gates; CI reruns build-and-test and the EE image on the push, as expected.", "gates": { "run_locally_exit_0": ["check:control-bytes", "typecheck: service-tenant", "test: service-tenant (spec/control-plane-contracts, environment-provisioning)", "build: service-tenant workspace deps"], "not_rerun_this_round": ["service-cloud / objectos-runtime / apps typechecks and tests (untouched)", "the 27 other static gates (no file they read changed)"], "ci_status": "in_progress — restarted by the push, as the coordinator anticipated" }, "line_budget": "n/a — no skills/** in the diff. Patch commit 678f133e: 1 file, +14 / -9. PR total origin/main..678f133e: 20 files, +1334 / -25", "deviations": [ "Worktree recreated at /Users/zhuangjianguo/Documents/GitHub/rig-pin/cloud-issue-16450 (same branch, same rig placement, same stale-primary-dist reason) and removed after the push.", "The round-1 heads-up I posted on objectstack#16325 (comment 5566370040) also says 'the only @objectstack/spec/cloud imports left in cloud are the two PackageSchema sites' — true on the branch, 4 after #2032. Not amended, per '⛔ Nothing else'; flagged here for the PM: same disposition (package-format half, step 3 re-points), only the count moved.", "Not acted on, per the review: the four consumer-less files (objectstack#16526) and the needs:contract-review label." ], "files_changed": [ "patch commit 678f133e:", "M packages/service-tenant/src/spec/control-plane-contracts.test.ts", "PR body edited (not a file): acceptance bullet + 12-site bullet reworded", "PR total (origin/main..678f133e) unchanged in membership: the same 20 files" ], "mcp_calls": "0 this round (cumulative 0) — gh api (REST) for every GitHub read and write", "open_questions": [], "out_of_scope_findings": [ "unchanged: objectstack-ai/cloud#2041 (environmentType silently defaulted at two layers) and objectstack-ai/cloud#2036 (objectos ambient stub)." ] }
Generated by Claude Code
ACCEPT — objectstack-ai/cloud#2037 at
678f133e. Ready + auto-merge enabled.Reviewer of record: dispatching PM seat, session
session_f95e3874-e532-4748-a921-044aa2752a2b, 2026-09-07T08:0xZ.
Clause-② clearance rests on the contract-review-tier review recorded at comment5566829522(updated to this head),
⛔ not on this seat, which does not hold that tier.Checklist conclusions
item reading, taken now — ⛔ not from any report every check green commits/678f133e/check-runs⇒build-and-test: success,Build and exercise EE image (no publish): success. Non-success count: 0 — the whole set, not the required subset (build-and-testalone is what protection requires)⚠️ the earliercancelledon the superseded head 98a997b3the EE check readcancelled— that wasverify-objectos-ee-image.yml'scancel-in-progressconcurrency group being pre-empted by the new push, ⛔ neither a red nor a green. Only678f133e's runs count, and both aresuccesspath surface pulls/2037/files⇒ 0 files underdocs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md. Distribution:packages/service-tenant12,packages/service-cloud4,apps/cloud1,packages/objectos-runtime1,.changeset/1,pnpm-lock.yaml1 ⇒ not a governed-surface PR; the ordinary ready → land path applies, not the four-part maintainer-merge pathPR form draft(flipping now), basemain, first body lineFixes objectstack-ai/objectstack#16450,mergeable: true, 20 filesscope the cloud#1932 chain's two files still absent from the diff changeset present and consistent with the diff Spot-checks this seat ran itself
- Patch-2 delta
98a997b3..678f133e: exactly one file,control-plane-contracts.test.ts(+14/−9);git diff --name-onlyoverpackages/service-tenant/src/spec/*.zod.ts⇒ 0 ⇒ the contract-review verdict's subject is untouched by the patch. - The re-worked case now sends
environmentType: 'production'withisDefaultomitted and assertsisDefault === trueplus theos-host prefix. Deleting the raw read makes the fold fall back todevelopment, flipping both assertions — the dev proved it by mutation and restored by byte comparison. The test now fails for the reason its title gives, which is what round 2 was for.
Deviations accepted, each declared before the fact
Worktree relocated under
rig-pin/(stale primary dist);apps/cloud/types/service-tenant.d.tsdeleted (one-line
shorthand ambient module — removing it narrows to real types and cannot silently widen); one new route-level
contract test;zodadded toservice-tenant's manifest. Two out-of-scope cards filed with search-first controls:
objectstack-ai/cloud#2041(theenvironmentTypetwo-layer silent default) and#2036(the objectos ambient stub).Landing
Ready + auto-merge (squash).
objectstack-ai/cloudhas no merge queue configured
(required_merge_queue: false,allow_auto_merge: true), so auto-merge is that repo's sanctioned path — ⛔ nothing
is being bypassed. Watching to MERGED, at which point #16325'sBlocked-by: #16450clears.⚠️ needs:contract-reviewstill does not exist as a label inobjectstack-ai/cloud. The clause-② obligation is met
by tier and by record — the card was dispatched atCONTRACT_REVIEW_TIERand reviewed at it, both documented on this
issue — ⛔ not by a label, and no label was created unilaterally. The repo-config gap stays open with the maintainer.- Patch-2 delta
⚠️ Correction to the ACCEPT above —objectstack-ai/cloudDOES have a merge queue. The ACCEPT stands; one sentence in it was wrong.Same seat, 2026-09-07T08:1xZ. ⛔ The ACCEPT comment is left standing; this is the correction, not a rewrite.
What I wrote: "
objectstack-ai/cloudhas no merge queue configured (required_merge_queue: false,
allow_auto_merge: true), so auto-merge is that repo's sanctioned path."What is true: that repo has a merge queue, and this PR went into it.
How I got it wrong — a zero I did not control. I read the classic branch-protection endpoint and evaluated
.required_merge_queue != null. That key does not exist on that endpoint at all, so the expression was
null != null⇒ false, and I read a missing key as a configured-off feature. The queue is defined in a
ruleset, not in classic protection:$ gh api /repos/objectstack-ai/cloud/rulesets/20184398 --jq '.rules[].type' deletion / merge_queue / non_fast_forward / pull_request $ … --jq '.rules[]|select(.type=="merge_queue")|.parameters' {"merge_method":"SQUASH","grouping_strategy":"ALLGREEN","max_entries_to_build":5, "max_entries_to_merge":5,"check_response_timeout_minutes":60, …}Control that the endpoint I did read was answering: the same call returns
required_status_checks: ["build-and-test"], a real value — so the endpoint was live and simply does not carry
that field. The absence of a key is not evidence of the absence of a feature, and I should have reverse-checked
the zero against a term known to exist before asserting it. That is the discipline I applied to the dev's greps
all round and did not apply to my own.Consequence for the landing — none, and nothing was bypassed.
gh pr merge --auto --squashwas refused
precisely because the queue owns the strategy ("The merge strategy for main is set by the merge queue"), which
is how I found the error. Re-issued without a strategy flag, the answer is "already queued to merge": the PR is
in the queue, which is the sanctioned landing path. The wrong sentence would have been dangerous had the queue
been absent and I claimed it present; in the direction it actually ran, the platform refused the wrong mechanism
before it could do anything.The ACCEPT itself is unaffected — every check green (non-success count 0), path surface carrying zero governed
files, scope held, changeset present. The contract-review-tier reviewer has since returned its final PASS on
678f133ewith both checkssuccess(runs 34095863072 and 34095863009), superseding its earlier
PASS-pending-CI.Now watching to MERGED, at which point #16325's
Blocked-by: #16450clears.MERGED — step 1 of 3 of the #16325 ruling has landed.
pm:dispatchedstripped in the same write.Dispatching PM seat, 2026-09-07T08:2xZ.
Two readings, ⛔ not one.
pulls/2037⇒merged: true,merged_at: 2026-09-07T08:19:35Z, merge commit4e70d4b9, via the merge queue.- The commit is actually on the branch, checked on the ref rather than any working tree:
git log --oneline -3 origin/maincarries4e70d4b9, and
git cat-file -e origin/main:packages/service-tenant/src/spec/environment.zod.tssucceeds — the relocated
declarations exist onmain, not just in a merged PR record.
Cross-repo
Fixes objectstack-ai/objectstack#16450did auto-close this card (closed/completed). What it
did not do is clean the state label —pm:dispatchedwas still on a closed card, which is a half-state. Removed
now;enhancement,priority:p2andrepo:cloudstay, because ownership and type are not state.What is true on cloud
mainas of4e70d4b9packages/service-tenant/src/spec/{environment,tenant}.zod.tsare cloud's own declarations; the retained schema
bodies are byte-identical to the upstream originals (contract-review-tier verdict, comment5566829522, final
PASS).- Zero control-plane-schema importers of
@objectstack/spec/cloudremain in cloud. The importers that do remain
are all package-formatPackageSchema— 4 files after this merge, not the 2 the PR body originally claimed
(see the correction on [Decision] Should the cloud control-plane contracts leave@objectstack/specand live in the cloud repo? (upstream of #12036) #16325, comment5566901682). All four are step 3's to re-point. - The runtime accept gate at
environment-provisioning.ts:750runs against the local declaration with the upstream
key set unchanged;environmentTypeis still undeclared and still read raw, pinned by a test that now fails when
that raw read is removed.
Downstream
#16325step 3 has threeBlocked-by:edges. One is now satisfied:edge state Blocked-by: #16450✅ closed / merged Blocked-by: objectstack-ai/objectui#8225⏳ open, itself pm:blockedon objectui#7122 (hot-file serialisation,os-justin's in-flight chain)Blocked-by: #16526⏳ open, in the maintainer's decision inbox (whether cloud re-creates the four consumer-less files) The
Blocked-by: #16450line stays in #16325's body as the record — a closed target reads as satisfied to the
unlock scan; ⛔ deleting satisfied edges would erase why the card was ever blocked.Two follow-ups filed by this card's dev remain open in
objectstack-ai/cloudfor that repo's own triage:
#2041 (environmentTypesilently defaulted at two layers —staging/test/preview/trialprovision the
wrong type with no error) and #2036 (objectos ambient stub masking two strict-null errors).
Step 1 of 3 of the #16325 ruling (director seat, decision batch #62, 2026-09-07, option B — cut by owner). Maintainer direction, verbatim: 「我一直觉得 cloud 的协议应该放在云端,没必要开源」.
Ordering is a hard constraint: cloud stops importing (this card) and objectui drops its re-export (objectui card) before objectstack deletes the
./cloudsubpath (#16325 itself,pm:blockedon both).Scope (in
objectstack-ai/cloud)environment.zod,environment-package.zod,tenant.zod,developer-portal.zod,marketplace-admin.zod,app-store.zod(2087 lines at objectstackd5d8d50db). Intra-directory edge to cut:app-storeanddeveloper-portalimportmarketplace.zod, which stays in the open-source spec as package format — import it from the relocated open-source path when [Decision] Should the cloud control-plane contracts leave@objectstack/specand live in the cloud repo? (upstream of #12036) #16325 lands, from@objectstack/spec/clouduntil then.@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 comment 5520459478, re-confirmed at clouddaaac081), including the runtimeProvisionEnvironmentRequestSchema.parse(request)atpackages/service-tenant/src/environment-provisioning.ts:750.@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 becomes cloud's internal style: pick one (the wire is snake_case today) and make the declarations match the wire — that is the whole point of the move.Acceptance
@objectstack/spec/cloudremain in cloud (with a firing control on the same grep)Environmentliterals compile against the local declarations@objectstack/specand live in the cloud repo? (upstream of #12036) #16325 notified when landedRefs #16325, #12036 (superseded), objectstack
packages/spec/src/cloud/.