Skip to content

cloud: take ownership of the control-plane contracts — local declarations for the six spec/cloud control-plane schemas, 12 import sites re-pointed (step 1 of 3 of the #16325 ruling) #16450

Description

@os-zhuang

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)

Acceptance

Refs #16325, #12036 (superseded), objectstack packages/spec/src/cloud/.

Activity

  1. self-assigned this
    on Sep 7, 2026
  2. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    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/main 9b85d761 — the card's readings were taken at daaac081. Re-measured:

    files symbols disposition
    service-tenant/src/{environment-provisioning,tenant-context,tenant-plugin,tenant-provisioning}.ts + {tenant-context,tenant-integration}.test.ts, service-cloud/src/billing.ts, service-cloud/test/billing-plan-vocabulary.test.ts Environment, EnvironmentCredential, EnvironmentDriver, ProvisionEnvironment*, ProvisionOrganization* (→ environment.zod.ts); TenantPlan(Schema), TenantRoutingConfig*, ProvisionTenant*, TenantDatabase (→ tenant.zod.ts) 8 files — MOVE, re-point at the new local declarations
    service-cloud/src/routes/package-publish.ts, service-cloud/test/package-id-declarations.test.ts PackageSchema → package.zod.ts ⛔ 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
    objectos-runtime/src/artifact-api-client.ts, service-cloud/src/registry-reader.ts EnvironmentArtifact ⭐ 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.
  3. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    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/main 9b85d761 (= 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".

    Generated by Claude Code

  4. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    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

  5. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    Review — REWORK (one item). PR objectstack-ai/cloud#2037

    Reviewer of record: dispatching PM, session session_f95e3874-e532-4748-a921-044aa2752a2b, 2026-09-07T07:1xZ.
    ⛔ Verified against GitHub and the pushed branch, not against the report's self-description.

    What checks out

    check reading
    PR form draft: true, base: main, first body 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
    Changeset present (.changeset/cloud-owns-control-plane-contracts.md)

    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 (objectstack origin/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:

    1. 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.
    2. 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.

  6. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    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

  7. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    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 objectstack origin/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:

    1. that reviewer's PASS;
    2. every check green on 98a997b3 — CI was in_progress at this reading;
    3. 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.
  8. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    Contract-review-tier review: PASS-pending-CI — clause-② obligation discharged. Patch round 2 (two one-line items) requested.

    Reviewer: contract-review-tier subagent at CONTRACT_REVIEW_TIER, dispatched by this PM seat because the seat
    does not hold that tier. Brief marked the round-0 and round-1 findings ⛔ non-inheritable; it re-derived
    independently. Target PR objectstack-ai/cloud#2037 at 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) same
    whole-block deletion, zero consumers EnvironmentType(Schema), EnvironmentRole(Schema), EnvironmentMember(Schema) PackageInstallationStatus(Schema), PackageInstallation(Schema)(Parsed)
    retained schema bodies byte-identical byte-identical

    Key sets, optionality, defaults, .min() / enum members and strip-vs-strict behaviour are unchanged on every
    retained schema. The zero-consumer assertion was re-verified: grepping all 11 deleted symbols across the head tree
    returns 0. 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.

    1. 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 also development, 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.
    2. A PR-body count about to go stale. main has moved two commits (feat(org-scoping): multi-tenant seed ownership handoff to default-org admin #2031, feat(dogfood): multi-user harness + RLS cross-owner proof runner (#1994) #2032) with no file intersection
      with this diff, but feat(dogfood): multi-user harness + RLS cross-owner proof runner (#1994) #2032 adds two more PackageSchema from '@objectstack/spec/cloud' importers. After merge
      the body's "exactly the 2 STAY files" becomes 4. The acceptance criterion is unaffected — zero control-plane
      importers still holds
      , and all four remaining are package-format, which step 3 re-points. Body to be reworded
      so it stays true post-merge.

    Outstanding before enqueue

    1. Patch round 2 landed (dispatched to the same dev, same claim — round 2 of at most 2).
    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.
    3. 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.
  9. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    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

  10. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    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.

  11. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    ⚠️ 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:

    $ gh api /repos/objectstack-ai/cloud/rulesets/20184398 --jq '.rules[].type'
    deletion / merge_queue / non_fast_forward / pull_request
    
    $ … --jq '.rules[]|select(.type=="merge_queue")|.parameters'
    {"merge_method":"SQUASH","grouping_strategy":"ALLGREEN","max_entries_to_build":5,
     "max_entries_to_merge":5,"check_response_timeout_minutes":60, …}
    

    Control that the endpoint I did read was answering: the same call returns
    required_status_checks: ["build-and-test"], a real value — so the endpoint was live and simply does not carry
    that field. The absence of a key is not evidence of the absence of a feature, and I should have reverse-checked
    the zero against a term known to exist before asserting it. That is the discipline I applied to the dev's greps
    all round and did not apply to my own.

    Consequence for the landing — none, and nothing was bypassed. gh pr merge --auto --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.

  12. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    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.

    1. pulls/2037 ⇒ merged: true, merged_at: 2026-09-07T08:19:35Z, merge commit 4e70d4b9, via the merge queue.
    2. 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#16450 did 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).
    • Zero control-plane-schema importers of @objectstack/spec/cloud remain in cloud. The importers that do remain
      are 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/spec and live in the cloud repo? (upstream of #12036) #16325, comment 5566901682). All four are step 3's to re-point.
    • 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:

    edge state
    Blocked-by: #16450 ✅ closed / merged
    Blocked-by: objectstack-ai/objectui#8225 ⏳ open, itself pm:blocked on objectui#7122 (hot-file serialisation, os-justin's in-flight chain)
    Blocked-by: #16526 ⏳ open, in the maintainer's decision inbox (whether cloud re-creates the four consumer-less files)

    The Blocked-by: #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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions