You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 9dcf97c
Browse filesBrowse the repository at this point in the historyBrowse files
fix(objectql)!: under `strictReadonlyWrites`, a write whose value a stored field's `expression` replaces (`stored_expression`) is refused, and nothing is written
7
+
8
+
Clause-②: no (narrowing: under `strictReadonlyWrites`, a write whose value a stored expression replaces is refused instead of reported; it ships now under the amended 「甲」, #6015`6303865025`)
9
+
10
+
<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling, object definition, stored shape or exported declaration is added, removed, renamed or narrowed, and no stored row is read, rewritten or converted, so there is nothing for `objectstack migrate meta` to rewrite. What changes is the answer the engine gives an in-process caller that opts into `strictReadonlyWrites` and sends a value a stored expression then replaces: that write is refused where it completed and was reported. The remedy is the caller's own payload (leave the field out, or send the computed value), which no ledger entry can derive. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
11
+
12
+
**BREAKING** (the strict mode's refusal set grows by one reason), shipped as `minor` under the amended ruling 「甲」: a narrowing that no conversion carries ships now, in 18.x. It applies only to a caller that opts into the in-process `strictReadonlyWrites` option. No REST or wire caller can set that option, and every write without it behaves exactly as before.
13
+
14
+
`strictReadonlyWrites` refuses every drop that `onFieldsDropped` reports. Since the `stored_expression` reason landed, one reported drop was left out: a caller's value for a stored (non-`formula`) field that declares an `expression`, which the engine replaces with the computed value. Under strict, such a write completed and was only reported. It is now refused like every other strip's drop.
15
+
16
+
-**FROM:** a write with `strictReadonlyWrites: true` that sent a value for a stored expression field, different from the value the engine computes, completed. The row held the computed value, and `onFieldsDropped` received a `stored_expression` event.
17
+
-**TO:** the same write throws `ReadonlyFieldRejectedError` (`code: 'ERR_READONLY_FIELD_REJECTED'`), whose `drops` carries the `stored_expression` event. Nothing is written, and `onFieldsDropped` does not fire. This holds on insert (a single row, a batch, and `insertMany`, which refuses the whole call as it does for the other strict drops), on update by id, and on a predicate update.
18
+
-**One refusal names everything.** When the same payload also carries another strip's drop (`readonly`, `readonly_when`, `primary_key`, `computed`), the one refusal names all of them together, `stored_expression` last. Before, such a write was refused for the other drops alone. The message's per-reason form gains a `stored_expression` remedy, spoken only when that reason is present, so every other refusal's message is unchanged.
19
+
-**Ordering is kept.** The strips' refusal still comes first: if the stored-expression pass itself throws, for example a predicate update whose patch computes different values on different matched rows (`VALIDATION_FAILED`), a strict write that also carries a strip's drop is still refused with `ERR_READONLY_FIELD_REJECTED` naming those drops, as before.
20
+
-**`@objectstack/spec` (patch):** the TSDoc on `WriteObservabilityOptions.strictReadonlyWrites` and `DroppedFieldsEvent.reason`, which ships in the published declarations, now says the `stored_expression` arm is refused under strict. No schema, `.describe()` text or export changes.
21
+
-**Unchanged:** a strict write that sends the computed value, or no value for the field, is not refused, because nothing it sent was replaced. A stored expression field also declared `readonly` is still refused once, as `readonly`. Without `strictReadonlyWrites`, the write completes with the computed value and the replacement is reported, as before. The error code is unchanged.
22
+
-**The fix for a strict caller:** leave the stored expression field out of the payload, or send the value its expression computes. To let the replacement happen and merely observe it, drop `strictReadonlyWrites` and pass `onFieldsDropped`.
23
+
24
+
This supersedes the `strictReadonlyWrites` bullet of the `stored_expression` reason's entry, which deferred this refusal to a later major.
25
+
26
+
Measured before the change: no non-test caller in this repository passes `strictReadonlyWrites` (the examples, dogfood suites and runtime doors pass none), so no in-repo producer moves.
fix(cloud-connection)!: a runtime holding the environment service key is platform-managed, so it registers no Cloud Connection Setup page and refuses to disconnect; everywhere else the Cloud Connection entry moves to Setup's Integrations group
6
+
7
+
A runtime whose control-plane credential is the environment service key (`controlPlaneApiKey`, or `OS_CLOUD_API_KEY`, which the cloud sets on the environments it hosts) is connected by the platform, not by anyone on the runtime. It still registered the Cloud Connection page in Setup's Apps group, which read "Connected" and offered Disconnect. Disconnecting there cleared the runtime's stored device credential while the service key stayed in use, so the page went on reading "Connected", and the control plane was asked to revoke a connection the platform had made.
8
+
9
+
Such a runtime now registers neither the page nor its navigation entry, and says so once at boot. A request to disconnect it is refused with `409` and `RESOURCE_CONFLICT`, after the sign-in and Setup Access checks and before anything is revoked or cleared. The message says the platform manages the connection.
10
+
11
+
A self-hosted or local runtime, connected through the device-code flow, keeps the page and its disconnect unchanged. Its Cloud Connection entry moves from the Apps group to the Integrations group, after Webhooks, Datasources and Connect an Agent. That group requires `manage_platform_settings`, so a user who holds Setup Access without it, such as an organization administrator, no longer sees the entry. The connect and disconnect requests still take Setup Access alone.
12
+
13
+
Clause-②: no (narrowing)
14
+
15
+
<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling, object definition or stored shape is added, removed, renamed or re-shaped, and no exported declaration is removed or narrowed, so there is nothing for `objectstack migrate meta` to rewrite. What changes is what a runtime holding the environment service key serves: it no longer registers the Cloud Connection page or its navigation entry, and it refuses a disconnect it used to carry out. The navigation entry's new group is a value inside an exported constant whose type is unchanged. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
16
+
17
+
**BREAKING** (on a runtime holding the environment service key, a disconnect request it carried out is now refused, and the Cloud Connection page and its navigation entry are no longer served), shipped as `minor` under the launch-window convention for breaking changes.
18
+
19
+
-**Unchanged:** a runtime without the service key, apart from the entry's group. The connection status answer is unchanged on every runtime.
20
+
-**Nothing to rewrite:** a cloud-hosted environment's connection is the platform's to manage. A self-hosted server leaves `OS_CLOUD_API_KEY` unset, as the deployment guide says; one that sets it is platform-managed by this rule.
Copy file name to clipboardExpand all lines: content/docs/deployment/cloud-connection.mdx
+20-10Lines changed: 20 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,9 +5,9 @@ description: Connect your own ObjectStack server to an ObjectStack Cloud account
5
5
---
6
6
7
7
A self-hosted ObjectStack server can be connected to an ObjectStack Cloud
8
-
account. You start the connection on your own server, in **Setup → Apps →
9
-
Cloud Connection**, and approve it in the cloud account with a one-time code.
10
-
Nothing is typed into the server: no id, no key, no password. Under the hood
8
+
account. You start the connection on your own server, in **Setup →
9
+
Integrations → Cloud Connection**, and approve it in the cloud account with a
10
+
one-time code. Nothing is typed into the server: no id, no key, no password. Under the hood
11
11
this is the OAuth 2.0 device authorization grant
12
12
([RFC 8628](https://www.rfc-editor.org/rfc/rfc8628)), run by the
13
13
`@objectstack/cloud-connection` package on your server.
@@ -56,6 +56,13 @@ const cloudUrl = resolveCloudUrl(); // OS_CLOUD_URL; '' when declared off
56
56
To check a running server, request `GET /api/v1/cloud-connection/status`. It
57
57
needs no sign-in, and a `200` means the plugin is mounted.
58
58
59
+
**The server does not hold the environment service key.** A server that holds
60
+
`OS_CLOUD_API_KEY` (the cloud sets it on every environment it hosts) is
61
+
connected by the platform, not from Setup. It registers no Cloud Connection page
62
+
or Setup entry, says so once in its boot log, and refuses a disconnect with
63
+
`409` (`RESOURCE_CONFLICT`): nothing is revoked and nothing is cleared. A
64
+
self-hosted server leaves `OS_CLOUD_API_KEY` unset.
65
+
59
66
**The server can reach the cloud.** Every call to the cloud is made by the
60
67
server, not by your browser: the code request, the polling, the connection
61
68
itself and the disconnect. The server needs outbound HTTPS to the control-plane
@@ -77,7 +84,9 @@ the server reads as not connected until you connect it again.
77
84
organization-administrator permission sets carry it. The connect and
78
85
disconnect requests take the same permission: a signed-in user without
79
86
Setup Access is refused with `403` (`PERMISSION_DENIED`) before the server
80
-
calls the cloud. A session
87
+
calls the cloud. The page's Setup entry sits in the **Integrations** group,
88
+
which Setup shows only to users who also hold `manage_platform_settings`; the
89
+
default platform-administrator permission set carries both. A session
81
90
that the authentication policy is holding (an expired password, or
82
91
multi-factor sign-in required but not yet set up) is refused with `403`
83
92
until it is fixed.
@@ -88,9 +97,9 @@ the server reads as not connected until you connect it again.
88
97
89
98
## Connect [#connect]
90
99
91
-
1.**Open the page.** Sign in to your server and go to **Setup → Apps →
92
-
Cloud Connection**. The header reads *Cloud Connection — Connect this
93
-
runtime to an ObjectStack control plane to browse your organization's
100
+
1.**Open the page.** Sign in to your server and go to **Setup →
101
+
Integrations → Cloud Connection**. The header reads *Cloud Connection —
102
+
Connect this runtime to an ObjectStack control plane to browse your organization's
94
103
private packages and install them here.* The panel shows *Checking
95
104
connection…*, then **Not connected** and a **Connect** button.
96
105
2.**Click Connect.** The server asks the cloud for a device code and hands
@@ -214,15 +223,16 @@ The routes the Cloud Connection page uses, all on the server's own origin:
214
223
|`GET /api/v1/cloud-connection/status`| not required | Every caller gets `bound`, `runtimeId` and `provider`. A signed-in user with Setup Access (`setup.access`) also gets `environmentId` and `connection`, the connection as the cloud reports it (the approving account and the organization). `bound` is `true` whenever the server holds a credential; `connection` is `null` when the cloud cannot be reached. |
215
224
|`POST /api/v1/cloud-connection/bind/start`| required | Asks the cloud for a device code; answers with `user_code`, `verification_uri`, `verification_uri_complete`, `interval`, `expires_in`. |
216
225
|`POST /api/v1/cloud-connection/bind/poll`| required | Body `{ "device_code": "…" }`. Answers `{ "pending": true }` while the cloud waits for approval, and the connection once it is approved. |
|`POST /api/v1/cloud-connection/unbind`| required | Disconnects; answers `{ "revoked", "cleared" }`. On a server holding `OS_CLOUD_API_KEY`, refused with `409` (`RESOURCE_CONFLICT`) and nothing changes. |
|**Integrations** (`group_integrations`) |`plugin-webhooks`; [Cloud Connection](/docs/deployment/cloud-connection) — `cloud-connection`, except on a runtime holding the environment service key, which registers no entry|
0 commit comments