Skip to content

fix(cloud-connection): name the cloud-binding service in its contract, and read an empty controlPlaneUrl one way - #30956

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ms359b
Nov 6, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ms359b

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #30762

Clause-②: yes (widening)
Metadata-protocol: none

domain:server#5 card thread · session session_016WV4cwsvPqjJfJthsRiYpt · branch claude/project-thread-ms359b (named in the seat's re-posted claim on the card, with the widened file surface). Branched from main at 33043bd; origin/main merged at d1e3182. The two should-fixes of the contract review on PR #30749 (record 6673473934, ④ items 1 and 2). Item 2 takes the card's first route (A): the routes read the binding.

维护者速读

  • 改了什么:① cloud-binding 这个服务名现在只在 spec 契约里写一次(CLOUD_BINDING_SERVICE,和另外六个契约的做法一样),cloud-connection 注册、service-push 推送中继读取都用它;② cloud-connection 插件的路由不再自己读 controlPlaneUrl,改读它自己注册的 cloud-binding 服务,所以「显式传空字符串」对路由和推送中继都是「没配云端」。
  • 为什么改:PR feat(service-push)!: push sends through the hosted push relay, authenticated by the server's cloud binding #30749 的契约复核留下两条 should-fix:服务名两处各写一份、互不约束;同一个空选项,路由当「未配置」,绑定服务却当「用默认云地址」,同一插件里两种读法。
  • 风险与回滚:objectstack serve 和 showcase 行为不变(它们只用 resolveCloudUrl() 的非空结果组装插件,两种读法对它返回同一值);只有手工组装内核时才有差别,见下方 Acceptance notes 三条。spec 只多一个导出;service-push 原有的 CLOUD_BINDING_SERVICE 导出保留(改为转导出,值不变)。回滚即 revert,无迁移。
  • 先看:packages/cloud-connection/src/cloud-binding.ts 的 resolveBindingUrl;packages/cloud-connection/src/cloud-connection-plugin.ts 里 const cloudUrl = this.binding.controlPlaneUrl(); 那一行;packages/spec/src/contracts/cloud-binding-service.ts 末尾的新导出。
  • 两分钟验证:pnpm --filter @objectstack/cloud-connection exec vitest run src/cloud-connection-plugin.test.ts src/cloud-binding.test.ts,预期全绿,其中「the routes and the binding read the control-plane URL once」一组 6 条覆盖空值、空白、OS_CLOUD_URL=off、显式 URL、仅环境变量五种情况。
  • 需要你做的:无,席位落地(本 PR 触 packages/spec/src/** 且 Clause-②: yes,席位 ACCEPT 前需契约复核)。

What changes

1. One spelling of the service name. packages/spec/src/contracts/cloud-binding-service.ts exports CLOUD_BINDING_SERVICE = 'cloud-binding' beside ICloudBindingService, as EMBEDDER_SERVICE, KNOWLEDGE_SERVICE, OWNERSHIP_QUEUE_SERVICE, RLS_MEMBERSHIP_RESOLVER_SERVICE, SEED_SETTLEMENT_SERVICE and FILE_ATTACHMENT_SERVICE do. api-surface/contracts.json and export-origins/contracts.json regenerated (gen:api-surface, gen:export-origins): one row each, CLOUD_BINDING_SERVICE (const).

  • @objectstack/cloud-connection: the local constant in cloud-binding.ts is gone. It was not a public export of the package (src/index.ts never re-exported it), so nothing outside the package loses a name. CloudConnectionPlugin imports the contract's constant for providesServices and registerService.
  • @objectstack/service-push: transports/relay.ts imports the contract's constant. CLOUD_BINDING_SERVICE was a public export of the package (src/index.ts), so relay.ts keeps it as a re-export (export { CLOUD_BINDING_SERVICE }), same name, same value; index.ts is untouched.

2. One reading of an empty controlPlaneUrl (route A). At main, createCloudBinding answered resolveCloudUrl(option), which reads an explicit '' as unset and falls through to OS_CLOUD_URL and the production default, while the plugin's routes read (option ?? OS_CLOUD_URL ?? '') and treated '' as "cloud not configured".

  • createCloudBinding's controlPlaneUrl() is now resolveCloudUrl's answer except that an explicit empty or blank option answers '', the reading the routes always gave it.
  • The routes take cloudUrl from this.binding.controlPlaneUrl() (still read once at kernel:ready, where the env was read before), so the routes and the push relay sender read one answer.
  • The contract's docblock already says controlPlaneUrl() is '' when the deployment declared no control plane; a host composing the plugin with controlPlaneUrl: '' is that declaration, so the contract text is unchanged and the reading is written down on the option (CloudBindingOptions.controlPlaneUrl, CloudConnectionPluginConfig.controlPlaneUrl).

One changeset, .changeset/30762-cloud-binding-service-name.md: @objectstack/spec, @objectstack/cloud-connection, @objectstack/service-push, all patch.

Tests

  • cloud-connection/src/cloud-connection-plugin.test.ts, new top-level describe "the routes and the binding read the control-plane URL once": boots the plugin through init() and start(), drives GET /installed (multi-tenant, service key set) and reads the registered binding. An explicit '' and ' ' with OS_CLOUD_URL set: binding '', no control-plane call, the disconnected answer. A declined OS_CLOUD_URL=off under an absent option: binding '', no call. A configured URL and an env-only URL: the route calls exactly ${binding}/api/v1/cloud/environments/env-123/packages.
  • cloud-connection/src/cloud-binding.test.ts: controlPlaneUrl() is '' for an explicit '' or ' ', with and without OS_CLOUD_URL; CLOUD_BINDING_SERVICE is 'cloud-binding' (the wire name), and the registration test reads the contract's constant.
  • service-push/src/push-plugin.test.ts: the two tests that place a binding now register it under the contract's CLOUD_BINDING_SERVICE instead of the literal, so the registrar's test and the reader's test meet on one constant (the cross-package pin the review asked for).
Ablations (each red, each restored; scripts/ablation-replace.mjs WRAP mode, git diff HEAD empty after each)
# Mutation Test run Red
A1 routes read the option main's way (option ?? OS_CLOUD_URL ?? '') cloud-connection-plugin.test.ts 1 of 17: the declined OS_CLOUD_URL case (main's routes called off/api/...)
A2 binding answers resolveCloudUrl(option) (main's shape) cloud-binding.test.ts + cloud-connection-plugin.test.ts 4 of 26: both empty-option binding pins, both empty-option route pins
B1 relay.ts declares its own CLOUD_BINDING_SERVICE = 'cloud_binding' push-plugin.test.ts 2 of 19: the bound send and the bind-without-restart case
B2 the plugin registers under a local 'cloud_binding' cloud-binding.test.ts + cloud-connection-plugin.test.ts 6 of 26: the registration test and all five route/binding pins

cloud-connection tests import the plugin and the binding from src/ by relative path, and service-push aliases @objectstack/spec/contracts to source (below), so no dist rebuild sits between a mutation and its run.

Acceptance notes

  • Route A, and what it moves beyond ''. Reading the routes' URL through the binding also aligns two other options, both reachable only on a hand-composed kernel: (a) an absent option with a declined OS_CLOUD_URL (off, none, local, disabled): the routes used 'off' as a base URL and called off/api/v1/...; they now answer CLOUD_UNCONFIGURED, as the binding already did. (b) an absent option with OS_CLOUD_URL unset or empty: the routes answered CLOUD_UNCONFIGURED; they now use the ObjectStack-operated default, as the binding, MarketplaceProxyPlugin and MarketplaceInstallLocalPlugin (resolveCloudUrl(config.controlPlaneUrl)) already did, and as the config docblock now says.
  • objectstack serve is unchanged. planMarketplaceWiring composes the plugin only when resolveCloudUrl() is non-empty and passes that value (serve.ts, createCloudConnectionPlugin({ singleEnvironment: true, controlPlaneUrl: marketplaceUrl })); examples/app-showcase does the same. That value is already trimmed, slash-stripped and not a decline spelling, so main's route expression and resolveBindingUrl both return it byte-for-byte.
  • Not changed: MarketplaceProxyPlugin and MarketplaceInstallLocalPlugin still read an explicit '' through resolveCloudUrl (as the default). They are outside this card's surface and are never composed with '' by serve; noted, not filed.
  • Surface widened (claim re-posted by the seat): packages/services/service-push/vitest.config.ts gains one anchored alias, /^@objectstack\/spec\/contracts$/ → ../../spec/src/contracts/index.ts. Gate-forced: relay.ts now imports a VALUE from that subpath, and without the alias check:test-source-alias exits 1 on service-push ("tests import 1 workspace package(s) that resolve to dist/ with no source alias"); KNOWN_UNALIASED_TEST_IMPORTS is shrink-only. With it the gate exits 0. push-plugin.test.ts is the other widened path (above).
  • Metadata-protocol: none, re-derived from the diff: no metadata author writes a key, value or type this changes. Clause-②: yes (widening): one added export on @objectstack/spec/contracts.
  • Serial check before the first push: open PRs touching packages/cloud-connection/** or service-push/**: only feat(console): put Connect an AI Assistant where each person looks, and call it that #30935, which edits cloud-connection-ui.ts alone.

Verification

At head d1e31826 (the merge of origin/main 3479c33; the two incoming commits touch none of this diff's packages). Package tests and ablations ran at 9fdd39ad, the fix commit, which this head carries unchanged.

Builds, typecheck, package tests
  • pnpm --filter @objectstack/spec build (lock VERDICT command-exit 0), then pnpm --filter '@objectstack/cloud-connection^...' --filter '@objectstack/service-push^...' build (VERDICT command-exit 0).
  • pnpm --filter @objectstack/cloud-connection test: Test Files 72 passed (72), Tests 925 passed (925). typecheck exit 0.
  • pnpm --filter @objectstack/service-push test: Test Files 5 passed (5), Tests 74 passed (74). typecheck exit 0.
  • pnpm --filter @objectstack/spec check:generated before regeneration named exactly api-surface/ and export-origins/ (plus test-typecheck-debt.json, whose tsc exited without diagnostics against a stale dist; after a rebuild on the final head check:generated exits 0 with all 14 artifacts current).
Gates (dispatch-gates derivation, 88 families + 3 named)
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 88 families at d1e31826 (11 changed paths vs merge base 3479c33). All 88 plus pnpm lint, pnpm check:cli-command-ids and pnpm check:adr-symbol-anchors: 91 of 91 exit 0.
  • First pass: 84 exit 0; six packages/spec dist readers exited 3 or 1 on a dist built before the origin/main merge (declaration stamp mismatch: check:api-surface, check:browser-reachable-entries, check:dual-source-exports, check:entry-nameability, check:exported-any exit 3, check:generated exit 1 naming only api-surface/), and check:dual-build-cjs-loads exited 3 (no whole-tree dist). After pnpm --filter @objectstack/spec build the six exit 0; after turbo run build --concurrency=2 (82 of 82 tasks) check:dual-build-cjs-loads exits 0 (116 require entries across 71 packages load).
  • --ran reconciliation: 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN (exit 0).
  • pnpm check:test-source-alias exits 1 without the new service-push alias and 0 with it.
  • Exit-code file: gates-ran-d1e31826.txt (seat project folder).

Generated by Claude Code

…, read an empty controlPlaneUrl once

Export CLOUD_BINDING_SERVICE from @objectstack/spec/contracts beside
ICloudBindingService; cloud-connection registers under it and
service-push's relay sender reads it (its public export stays, as a
re-export). The plugin's routes take their control-plane URL from the
cloud-binding service, and the binding reads an explicit empty option
as "no control plane", the reading the routes always gave it.

Claude-Session: https://claude.ai/code/session_016WV4cwsvPqjJfJthsRiYpt
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 anchor(s) derived from 3 changed package(s); no hand-written page names any of them. ⚠️ 3 changed file(s) yielded no anchor (packages/services/service-push/vitest.config.ts, packages/spec/api-surface/contracts.json, packages/spec/export-origins/contracts.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/services/service-push/vitest.config.ts, packages/spec/api-surface/contracts.json, packages/spec/export-origins/contracts.json) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 63 of 222 client-bound route-ledger rows — the other 159 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 159: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 104 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 173 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6ddf827236902c0ccaee56b65431a4c8a9c7ac09 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d7e8b1d00d9648d1727767dc040cf672ff2bfbce — the merge of head 937d74e1af20fa66477c4da26d3747b492a8205a into base 6ddf827236902c0ccaee56b65431a4c8a9c7ac09, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d7e8b1d00d9648d1727767dc040cf672ff2bfbce && git checkout d7e8b1d00d9648d1727767dc040cf672ff2bfbce
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6ddf827236902c0ccaee56b65431a4c8a9c7ac09 937d74e1af20fa66477c4da26d3747b492a8205a && git checkout -B drift-repro 6ddf827236902c0ccaee56b65431a4c8a9c7ac09 && git merge --no-ff 937d74e1af20fa66477c4da26d3747b492a8205a

node scripts/docs-audit/affected-docs.mjs --json 6ddf827236902c0ccaee56b65431a4c8a9c7ac09

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d1e31826ad8bd98dc13621e02eaf6217c84f6237
Local-runs: none
Scope: packages/spec/src/contracts/cloud-binding-service.ts, packages/spec/api-surface/contracts.json, packages/spec/export-origins/contracts.json + packages/cloud-connection/src/cloud-binding.ts, packages/cloud-connection/src/cloud-connection-plugin.ts, packages/cloud-connection/src/cloud-url.ts, packages/cloud-connection/src/index.ts, packages/cloud-connection/package.json, packages/services/service-push/src/transports/relay.ts, packages/services/service-push/src/index.ts, packages/services/service-push/src/push-plugin.ts, packages/services/service-push/vitest.config.ts, packages/services/service-push/src/push-plugin.test.ts, packages/cloud-connection/src/cloud-binding.test.ts, packages/cloud-connection/src/cloud-connection-plugin.test.ts, packages/core/src/stack-auth.ts, packages/cli/src/commands/serve.ts, examples/app-showcase/objectstack.config.ts, scripts/check-test-source-alias.mjs, .changeset/30762-cloud-binding-service-name.md

Inputs: card #30762 (body; triage audit 6685166387; the seat's three claims 6685693761, 6685800461, 6685858377, the last carrying the widened surface; the dev's os-dev-report 6686963912), the source record 6673473934 on PR #30749 (④ items 1 and 2), PR #30956 body and file list (11 files, identical to the local net diff against merge-base 3479c33, +156/-18), and the check-runs on this head. Readings are against pr30956 at d1e31826 unless a base reading is named; objectstack serve is read at the merge-base, which the diff does not touch.

① Derived judgments

  1. @objectstack/spec/contracts gains CLOUD_BINDING_SERVICE = 'cloud-binding' (cloud-binding-service.ts, carried by the barrel's existing export * line). api-surface/contracts.json and export-origins/contracts.json each gain exactly that one row, CLOUD_BINDING_SERVICE (const) at src/contracts/cloud-binding-service.ts. Additive; the same shape as the six sibling name constants the source record named. Right: item 1 of 6673473934 asked for exactly this.
  2. @objectstack/cloud-connection drops its local CLOUD_BINDING_SERVICE (cloud-binding.ts). Verified not public: the package's exports map has one entry (. to dist/index), and src/index.ts never re-exported the constant at base or head (the diff does not touch it). The plugin's providesServices and registerService now read the contract's constant. No public surface moves. Right.
  3. @objectstack/service-push keeps CLOUD_BINDING_SERVICE on its public surface: src/index.ts exports it from ./transports/relay.js (untouched), and relay.ts now imports the contract's constant and re-exports it (export { CLOUD_BINDING_SERVICE }), same name, same value. push-plugin.ts still reads ctx.getService(CLOUD_BINDING_SERVICE) through relay.ts. A consumer importing the old export resolves the same string. Right.
  4. What controlPlaneUrl() answers (cloud-connection's implementation, not the contract text): unchanged for a URL, an absent option and a declined env; changed for an explicit empty or blank option, from the production default (base resolveCloudUrl('') fell through) to ''. The routes' cloudUrl moves from a local expression to this.binding.controlPlaneUrl(). Right by item 2 of 6673473934, route A as the card phrased it. The two further moves this implies for the routes are judged in ③.
  5. Metadata-protocol: none. Right: no *.zod.ts moves, no key, value, type or default an author writes changes.

② Semver level

  • Changeset .changeset/30762-cloud-binding-service-name.md: @objectstack/spec, @objectstack/cloud-connection, @objectstack/service-push, all patch; both declaration lines line-initial; the PR body carries the same two. The three packages are exactly the packages the diff moves (the alias and test edits move nothing publishable). Check Changeset is success on this head.
  • Clause-②: yes (widening): right. One export is added to a published spec subpath; nothing is removed from any package's public surface (① items 2 and 3), so no narrowing arm, no BREAKING banner, no ADR-0087 marker is owed.
  • patch: right. AGENTS.md Post-Task step 3 is explicit that the level follows the Metadata-protocol kind alone: a kind takes at least minor, none takes patch, and a non-protocol widening ships patch. Minor is not owed for an added export in this repo's rules.

③ Boundary flags

The widened surface (claim 6685858377). packages/services/service-push/vitest.config.ts gains one anchored alias, /^@objectstack\/spec\/contracts$/ to ../../spec/src/contracts/index.ts. Verified forced: relay.ts now imports a VALUE from that subpath (before, a type only, which the gate erases), KNOWN_UNALIASED_TEST_IMPORTS at head carries no @objectstack/service-push row and is marked SHRINK-ONLY, so the alias is the only green route; the array form with an anchored pattern is the shape the gate's own header prescribes. cloud-connection's tests also now value-import from @objectstack/spec/contracts; its ledger row already lists @objectstack/spec, so no alias is owed there. push-plugin.test.ts registers the binding under the contract constant instead of the literal, which is the cross-package pin item 1 asked for (the registrar's test and the reader's test now meet on one symbol). Both widenings are inside the card's own ask. Accept. The mechanical verdict is Lint · repo & tooling gates, in progress at review time.

Route A for item 2. The card offered two routes; the dev took the first (routes read the binding; the binding reads an explicit empty option the routes' way). Verified in the cited code: resolveBindingUrl answers '' for a string whose trim is empty and resolveCloudUrl(explicit) otherwise; the routes take cloudUrl from this.binding.controlPlaneUrl() once at kernel:ready, where the env was read before, so a host setting the env between config load and boot keeps the same window. Accept.

The two further behaviour changes the dev declares, hand-composed kernels only.

  • (a) absent option, declined env (off, none, local, disabled): at base the routes used the literal 'off' as a base URL and fetched off/api/v1/...; at head they answer CLOUD_UNCONFIGURED, as the binding and the README's contract (OS_CLOUD_URL=off disables every remote call) already said. A defect repaired, not a widening. The same move applies to an explicit decline spelling in the option (controlPlaneUrl: 'off'), which the PR does not list; same direction, same reason. Accept.
  • (b) absent option, env unset or empty: at base the routes answered CLOUD_UNCONFIGURED; at head they use DEFAULT_CLOUD_URL (https://cloud.objectos.ai). Judged on three questions. Is it a widening of where an unconfigured hand-composed kernel sends requests? Yes: a kernel that composed new CloudConnectionPlugin({}) with no env before refused every control-plane call and now dials the production control plane (device-code start, installed, org-packages, install, status probe). Is it inside the card's Expected line? Yes. Route A as the card phrased it is "the routes derive their URL from binding.controlPlaneUrl()", and the binding's reading of an absent option is the one the published contract on main already promises ("the configured value, else OS_CLOUD_URL, else the ObjectStack-operated default"), which MarketplaceProxyPlugin and MarketplaceInstallLocalPlugin also give it; the routes were the one reader in the package that lacked the default, and "one reading" moves them to the contract rather than moving the contract to them. Making the binding read an absent option the routes' way instead would have changed the push relay's and the contract's published answer, which the card did not ask. Must-fix, should-fix or acceptable as declared? Acceptable as declared: the changeset's third bullet, the PR's Acceptance notes and the config docblock (CloudConnectionPluginConfig.controlPlaneUrl, now naming the default) all state it; the existing base test installed degrades to an empty disconnected list without cloud credentials composes with an explicit '', not an absent option, and still holds; and every composer in this repo passes a resolved URL (serve.ts L4058, the showcase config L161, the deployment doc's hand-composition example composes the plugin only when cloudUrl is non-empty). Who is reached: a host that composed the plugin with no option and no env, which on main already got the default from the sibling marketplace plugins on the same kernel. No security edge: every route that calls out still requires a resolved session, and the install, installation, installed and org-packages doors still require a credential as well. The dev's constraint, objectstack serve unchanged, holds: read at the merge-base, planMarketplaceWiring returns the cloud arm only when marketplaceUrl is truthy (L2061), marketplaceUrl is resolveCloudUrl() (L4033) and the plugin receives that value (L4058); the value is trimmed, slash-stripped, non-empty and not a decline spelling, so the base route expression and resolveBindingUrl both return it byte for byte, and the diff touches no path under packages/cli, examples or content.

Noted by the dev, not filed: MarketplaceProxyPlugin and MarketplaceInstallLocalPlugin still read an explicit '' through resolveCloudUrl as the default, and RuntimeConfigPlugin reads the same '' as "stay on this origin" (its constructor special-cases it before the resolver). The package now carries three readings of an explicit empty controlPlaneUrl across four plugins. Outside this card's surface (the card named the binding and the routes), never composed with '' by serve or the showcase; the seat decides whether it earns a card. Not a finding on the hit.

Serial constraints: the only open PR on packages/cloud-connection/** named in the claim, #30935, edits cloud-connection-ui.ts alone, which this diff does not touch.

④ Design judgment

packages/spec/src/contracts/cloud-binding-service.ts — holds for its consumers. Break attempts: (1) the string: the contract declares 'cloud-binding', cloud-connection registers under the imported constant and service-push reads under the same imported constant; no literal spelling of the name remains in either package's source, and cloud-binding.test.ts pins the wire value so a rename of the constant's value is loud. (2) The old service-push import: import { CLOUD_BINDING_SERVICE } from '@objectstack/service-push' resolves through src/index.ts to relay.ts's re-export of the contract's constant; same name, same value, and export { X } of an imported value binding is a value re-export, so it survives isolatedModules and the dual build. (3) controlPlaneUrl() answering '' only when no control plane is declared, read across the inputs: explicit '' and ' ' answer '' with and without OS_CLOUD_URL (pinned in both cloud-connection suites); an absent option with OS_CLOUD_URL=off answers '' (pinned); an absent option with the env set answers the env value without a trailing slash (pinned); an absent option with no env answers the default (pinned at base, unchanged); an explicit URL answers it trimmed and slash-stripped (pinned); an explicit decline spelling in the option answers '' through resolveCloudUrl. Every '' is a declaration of no control plane; no input answers '' by accident, and no declaration answers a URL. (4) objectstack serve composes the plugin only when resolveCloudUrl() is non-empty and passes that value: confirmed at the merge-base (③ above), untouched by the diff. (5) providesServices = [CLOUD_BINDING_SERVICE] keeps its literal type without as const (a const string declaration infers the literal), so ADR-0116's declared set is unchanged.

One should-fix on the hit:

  1. should-fix — the controlPlaneUrl() docblock enumerates the sources ("the configured value, else OS_CLOUD_URL, else the ObjectStack-operated default") and the declarations that answer '' (OS_CLOUD_URL=off, none, local, disabled), and this PR adds a third door to '', an explicit empty or blank configured value, that the contract's text does not name. The dev's position (an empty option IS the declaration, so the text stands) is defensible, but the contract is the one place the reader in service-push learns what '' means, and the card's own Expected line offered "the docblock says what '' answers" as a route; the reading now lives only in cloud-connection's option docblocks. One clause on the parenthetical ("or an explicitly empty configured value") closes it. Not blocking: the behaviour is pinned in the registrar's package and the contract's promise ('' only when no control plane is declared) is true at head.

packages/spec/api-surface/contracts.json, packages/spec/export-origins/contracts.json — hold: one row each, the row names the file and the kind (const), alphabetical position correct, nothing else moves. Spec property liveness is success on this head; check:generated and check:api-surface run in Lint · repo & tooling gates, in progress.

No must-fix found. Check-runs on d1e31826 at review time: no conclusion is red. Completed success: Auto Label, Build UI Deps, Check Changeset, Check PR Size, Dogfood Regression Gate (gate job), Flag docs affected by code changes, Governed Surface Queue Guard, PR-body guards, Plan Test Core shards, Spec property liveness, filter, Lint · ESLint, Lint · CI release and engine gates, Lint · PM dispatch-gates, Lint · docs and code guards, Lint · scoped (query-options erasure; slot lookup and corpus; tooling self-tests), Type Check · source gates. Completed skipped by paths filter: 18.x upgrade harness, Build Docs, Dogfood Regression Gate shards, Dogfood Verify CLI, Lint UI, Packed-tarball smoke, Schedule gate, Temporal Conformance, UI Published Artifacts. Still in_progress: Test Core 1/8 to 8/8, Test UI 1/16 to 16/16, Build Core, Build and E2E UI, Live E2E UI, Lint · repo and tooling gates (the one that carries check:test-source-alias, check:generated and check:api-surface), Type Check UI, Type Check · workspace, Type Check · consumer gates, Type Check · debt ledger. The required contexts therefore have no verdict yet; this record does not substitute for them, and the seat reads their conclusions before any landing step.

Implemented-by: claude/project-thread-ms359b
Reviewed-by: session_016WV4cyvF7SP65frVFAYLv7

VERDICT: PASS

…alue as a third way to answer ''

Claude-Session: https://claude.ai/code/session_016WV4cwsvPqjJfJthsRiYpt
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 937d74e1af20fa66477c4da26d3747b492a8205a
Local-runs: none
Scope: packages/spec/src/contracts/cloud-binding-service.ts, packages/spec/api-surface/contracts.json, packages/spec/export-origins/contracts.json + packages/cloud-connection/src/cloud-binding.ts, packages/cloud-connection/src/cloud-connection-plugin.ts, packages/services/service-push/src/transports/relay.ts

① Derived judgments — delta review on top of PASS 6687101841 (head d1e3182)

This record judges the one commit 937d74e1 on top of d1e31826, the head the PASS record 6687101841 reviewed; that record's ①, ②, ③ and ④ findings carry forward unchanged and are not restated. The delta is one file, +2/-1: packages/spec/src/contracts/cloud-binding-service.ts, the controlPlaneUrl() docblock. Read from git diff d1e31826..pr30956d, with the PR's reported head confirmed as 937d74e1.

  1. The delta adopts the one should-fix of 6687101841 ④ item 1 exactly: the parenthetical that enumerated the declarations answering '' (OS_CLOUD_URL=off, none, local, disabled) now ends ", or an explicitly empty or blank configured value)". That is the third door resolveBindingUrl opens in cloud-binding.ts (a string whose trim is empty answers ''), read in the cited code at both heads; the contract's text and the registrar's behaviour now name the same three ways to ''. The promise itself ("'' when the deployment declared that it has no control plane") is unchanged, which is right: the finding was an omission in the enumeration, not a wrong promise. Right, and closed.
  2. Nothing else moves. The source line, the two preceding sentences, the CLOUD_BINDING_SERVICE export and the other two members are byte-identical to d1e31826; no other path is in the delta.
  3. The regenerated surfaces do not move, and cannot: api-surface/contracts.json and export-origins/contracts.json record names, kinds and origin files, not docblock text, and the delta changes no name, kind or file. No other generated artifact mirrors this parenthetical (a tree-wide read of the old sentence at 937d74e1 finds it only in push-plugin.ts, push-config.zod.ts, stack.zod.ts, the service-push README, ADR-0012 and the push-config reference page, each describing the env decline spellings in its own context, none quoting this docblock). The dev reports check:generated, check:api-surface, check:docs and check:spec-docblock-symbol-anchors at exit 0 locally; the CI verdict is Lint · repo & tooling gates, in progress on this head.

② Semver level

Unchanged from 6687101841: patch on the three packages, Clause-②: yes (widening), Metadata-protocol: none. A docblock sentence on an interface member changes no accept set, no export and no metadata key, so no re-derivation is owed and none is made. The changeset is not in the delta and needs no edit.

③ Boundary flags

  • The delta stays inside the seat's claimed surface (claim 6685858377 names packages/spec/src/contracts/cloud-binding-service.ts as the cross-seat line) and inside the card's Expected line, which offered "the docblock says what '' answers" as a route for item 2; together with route A at d1e31826 the PR now takes both halves of that line, which is more than the card asked and not less.
  • Prior head's check-runs, read as data: when 937d74e1 was pushed, the Lint & Type Check workflow run on d1e31826 (42232502547) was cancelled by concurrency; its Type Check · workspace leg records cancelled, and its summary job TypeScript Type Check records failure with no title, no summary and a five-second span that started after the cancellation. That is the superseded run's shape, not a diagnostic against the tree the PASS record judged, and the CI run on d1e31826 (42232502549) has one Test Core shard cancelled for the same reason. The verdicts that count are the runs on this head.

④ Design judgment

packages/spec/src/contracts/cloud-binding-service.ts — holds, and the one open finding on it is closed. Re-read against its two consumers at 937d74e1: the registrar (createCloudBinding, resolveBindingUrl) answers '' for exactly the inputs the parenthetical now lists (the four env decline spellings through resolveCloudUrl, a decline spelling in the option through the same, an explicitly empty or blank option directly) and a URL for every other input; the reader (RelayPushSender in service-push) treats '' as no relay address and the contract text it reads now matches what it will receive. No new finding, no must-fix, no should-fix.

packages/spec/api-surface/contracts.json, packages/spec/export-origins/contracts.json — not in the delta, hold as at d1e31826 (one row each for CLOUD_BINDING_SERVICE (const)).

Check-runs on 937d74e1 at review time: no conclusion is red. Completed success: Check PR Size, Governed Surface Queue Guard, PR-body guards. Completed skipped: 18.x upgrade harness, Packed-tarball smoke, Schedule gate. Still in_progress: Auto Label, Check Changeset, Flag docs affected by code changes, Spec property liveness, every Lint · job (ESLint; repo and tooling gates; docs and code guards; CI, release and engine gates; PM dispatch-gates; the three scoped lanes) and every Type Check · job (workspace; source gates; consumer gates; debt ledger). The CI workflow run on this head (42234725315) is still pending, so no Build Core, Test Core or UI check-run exists yet. The required contexts therefore have no verdict yet; this record does not substitute for them, and the seat reads their conclusions on 937d74e1 before any landing step.

Implemented-by: claude/project-thread-ms359b
Reviewed-by: session_016WV4cyvF7SP65frVFAYLv7

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

席位核对 · ACCEPT

PR #30956 · head 937d74e1 · 2026-11-06T14:34Z · card #30762 (should-fix items 1 and 2 of PR #30749's contract review, record 6673473934; triage audit 6685166387) · claim re-posted with Branch claude/project-thread-ms359b and the widened surface · os-dev report 6686963912 · contract review PASS 6687101841 (d1e31826) and delta PASS 6687302075 (937d74e1)

维护者速读(席位校正)

The body's six lines stand. They read the diff at d1e31826, and the one commit after it (937d74e1) is the adopted should-fix: one docblock clause in the spec contract, with no code. The Verification section's head is therefore d1e31826 for the code, and the delta's gates are in the dev's gates-ran-937d74e1 record.

席位核对

  • Card fit: item 1: CLOUD_BINDING_SERVICE is exported from @objectstack/spec/contracts. cloud-connection registers under it, and service-push reads it and keeps its public export as a re-export, so no literal spelling is left. Item 2, route A: the routes read binding.controlPlaneUrl(), and the binding answers '' for an explicitly empty or blank option. The contract's docblock now names that third way to '' (the adopted should-fix, so both routes the card offered are taken).
  • Declared behaviour changes, hand-composed kernels only, accepted by the review: (a) an absent option with OS_CLOUD_URL=off no longer fetches off/api/... (a repaired defect). (b) An absent option with no env now uses the default control plane, as the contract, the binding and the two marketplace plugins already did. objectstack serve is unchanged: it always passes resolveCloudUrl()'s non-empty answer.
  • Declarations: Fixes #30762, Clause-②: yes (widening), Metadata-protocol: none, and one changeset with patch on @objectstack/spec, @objectstack/cloud-connection and @objectstack/service-push. The level follows Metadata-protocol alone, so patch is right; Check Changeset reads it.
  • Widened surface: service-push/vitest.config.ts gets one anchored alias (forced by check:test-source-alias, whose ledger is shrink-only), and push-plugin.test.ts registers under the constant. Both are in the re-posted claim.
  • Carried, no card: MarketplaceProxyPlugin and MarketplaceInstallLocalPlugin read an explicit '' as the default, and RuntimeConfigPlugin reads it as same-origin. These are outside this card's surface, and neither serve nor the showcase ever composes with ''. Fix them on the next touch of those plugins.
  • Gates (report): 91/91 exit 0 on d1e31826, --ran 88/88, 0 NOT-MEASURED. On 937d74e1, spec check:generated, check:api-surface, check:docs, check:spec-docblock-symbol-anchors and lint all exit 0. Suites: 925/925 and 74/74. Four ablations went red and were restored. The red TypeScript Type Check and Test UI on d1e31826 are the superseded run's cancellation, not a verdict.
  • Landing: check-governed-merges --pr 30956 says NOT governed, +156/-18 before the delta. The seat lands with pr_ready + automerge_enable once every check on 937d74e1 is green or expected-skip.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 6, 2026 15:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 42239869046 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (2/8) — 失败步骤: Run this shard's tests

    @objectstack/platform-objects:test:  FAIL  src/apps/translations/app-nav-translation-parity.test.ts > every nav id an app presents is translated in every locale > account — zh-Hant
      ↳ 失败原因: @objectstack/platform-objects:test: AssertionError: untranslated nav ids in apps.account.navigation: expected [ 'grp_account_ai_assistant' ] to deeply equal []
    @objectstack/spec:test:      ✓ inverts the card's measured control: the three bogus keys now FAIL parse instead of adding zero warnings  391ms
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 22 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

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

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cloud-binding: name the service in its spec contract, and give controlPlaneUrl one reading of an empty option (follow-up to #29920)

1 participant