Repository navigation
Commit 000a4c0
fix(cloud-connection): name the cloud-binding service in its contract, and read an empty controlPlaneUrl one way (#30956)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DD5m3UuAxayNiHrDSFDaU94)_
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 #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).
<details><summary>Ablations (each red, each restored;
<code>scripts/ablation-replace.mjs</code> WRAP mode, <code>git diff
HEAD</code> empty after each)</summary>
| # | 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.
</details>
## 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 #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.
<details><summary>Builds, typecheck, package tests</summary>
- `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).
</details>
<details><summary>Gates (dispatch-gates derivation, 88 families + 3
named)</summary>
- `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).
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_016WV4cwsvPqjJfJthsRiYpt)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9d313f3 commit 000a4c0
11 files changed
Lines changed: 158 additions & 19 deletions
File tree
- .changeset
- packages
- cloud-connection/src
- services/service-push
- src
- transports
- spec
- api-surface
- export-origins
- src/contracts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
15 | | - | |
| 14 | + | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
73 | 82 | | |
74 | 83 | | |
75 | 84 | | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
76 | 89 | | |
77 | 90 | | |
78 | 91 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | 22 | | |
26 | 23 | | |
27 | 24 | | |
28 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
| |||
40 | 41 | | |
41 | 42 | | |
42 | 43 | | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
43 | 51 | | |
44 | 52 | | |
45 | 53 | | |
46 | 54 | | |
47 | 55 | | |
48 | 56 | | |
49 | 57 | | |
50 | | - | |
| 58 | + | |
51 | 59 | | |
52 | 60 | | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
268 | 269 | | |
269 | 270 | | |
270 | 271 | | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
149 | 149 | | |
150 | 150 | | |
151 | 151 | | |
152 | | - | |
153 | | - | |
| 152 | + | |
| 153 | + | |
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
| |||
266 | 266 | | |
267 | 267 | | |
268 | 268 | | |
269 | | - | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
270 | 276 | | |
271 | 277 | | |
272 | 278 | | |
| |||
333 | 339 | | |
334 | 340 | | |
335 | 341 | | |
336 | | - | |
337 | | - | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
338 | 348 | | |
339 | 349 | | |
340 | 350 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| |||
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
125 | | - | |
| 125 | + | |
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
26 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
27 | 32 | | |
28 | 33 | | |
29 | 34 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
27 | 31 | | |
28 | 32 | | |
29 | 33 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| 61 | + | |
61 | 62 | | |
62 | 63 | | |
63 | 64 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| 61 | + | |
61 | 62 | | |
62 | 63 | | |
63 | 64 | | |
| |||
0 commit comments