Skip to content

Commit c6f775c

Browse files
committed
fix(text): the delta review's text items — fresh installs restated, the bridge exports declared, the operator's first boot stated
Text, pins and changesets only (seat delta review of b5010d9, items 3.1-3.5). - cli.mdx says "Fresh installs are supported under every tenancy posture" again (os dev and the ceremony section), and the ceremony section states the sequence on an existing database: start this release once (refused, its schema sync creates the sibling tables), back up, plan, apply, start. The changeset's operator paragraph says the same. - This PR's changeset adds @objectstack/objectql and declares, in its adr-0087 marker and body, the removal of WALLED_ORGANIZATION_LESS_BRIDGE and isWalledOrganizationLessBridgeObject. Step 4's pending changesets (15212-8b-walled, 15212-8b-single, 15212-walled-posture-boots) and 24529 no longer announce the bridge, a later 18.x release or a refusing ceremony. - The two walled test docblocks (service-settings, service-datasource) and the apply refusal test's titles say what holds now. - check:closed-ceremony-text also reads "in a later 18.x release", a ceremony that still refuses, 18.0 refusing the command, and "until then the ceremony refuses". Ablation: 24529's base text reds it. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014UATXWqAXqkaZkbG92ox6V
1 parent 0f83a8d commit c6f775c

10 files changed

Lines changed: 62 additions & 37 deletions

‎.changeset/15212-8b-single-platform-insert-stamped.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ Clause-②: no (narrowing)
1212

1313
- **Before.** A system-context insert (`isSystem`, no `tenantId`) into a `sys_` / `cloud_` / `ai_` object that carries an organization column, but that the tenancy inventory had not admitted, skipped the organization rule entirely, under every posture. The row landed with `organization_id` NULL. Thirty-five platform objects in this repository were in that population, among them `sys_business_unit`, `sys_business_unit_member`, `sys_member`, `sys_team`, `sys_user_permission_set`, `sys_user_position`, `sys_sharing_rule`, `sys_share_link`, `sys_comment`, `sys_attachment`, `sys_activity`, `sys_email`, `sys_inbox_message`, `sys_secret`, `sys_setting`, `sys_setting_audit`, `sys_webhook` and `sys_flow_credential`.
1414
- **Now, under `single`.** That insert goes through the same decision every application object already gets. With exactly one organization (the Default Organization), the row is stamped with it. With none, or with several, the insert is refused with `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED` (status 500), and the message names the missing organization. Nothing is written.
15-
- **Unchanged.** Under a walled posture (`group` / `isolated`) the per-object exclusion stands as before; the walled half of this change ships in a later 18.x release. A write that carries an organization, on the execution context or on the record, is never re-stamped or refused. Objects with no organization column, objects declaring `tenancy: { enabled: false }`, and federated objects are outside the rule. Updates are untouched. The engine's diagnostics-only tenant-audit mute is unchanged.
15+
- **Unchanged.** Under a walled posture (`group` / `isolated`) the per-object exclusion stands as before; the walled half of this change is the companion `@objectstack/objectql` changeset on system inserts under a walled posture. A write that carries an organization, on the execution context or on the record, is never re-stamped or refused. Objects with no organization column, objects declaring `tenancy: { enabled: false }`, and federated objects are outside the rule. Updates are untouched. The engine's diagnostics-only tenant-audit mute is unchanged.
1616

1717
**The remedy.** If a refusal names your insert, carry the organization on the write: `ql.insert(object, { ...row, organization_id: orgId }, { context: { isSystem: true } })`, or `{ context: { isSystem: true, tenantId: orgId } }`. On a fresh `single` deployment the auth plugin creates the Default Organization at boot, so a refusal reading `no-organization` means the write ran before it, or in a composition that registers `sys_organization` but mounts no auth plugin to create the Default Organization.

‎.changeset/15212-8b-walled-platform-insert-refused.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ feat(objectql)!: under a walled posture (`group` / `isolated`), a system insert
66

77
Clause-②: yes (narrowing)
88

9-
<!-- adr-0087: not-required (no-migration-prescription) A validity narrowing at the engine's system-insert door: no key of any metadata schema is removed, renamed or re-shaped, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone. No export, type or error code is removed or renamed: `PLATFORM_OBJECT_TENANCY`, `isPlatformObjectOutOfTenantAuditScope` and `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED` keep their shapes; two exports are added, `WALLED_ORGANIZATION_LESS_BRIDGE` and `isWalledOrganizationLessBridgeObject`, the temporary bridge named below. The remedy is a run-time one, stated below. -->
9+
<!-- adr-0087: not-required (no-migration-prescription) A validity narrowing at the engine's system-insert door: no key of any metadata schema is removed, renamed or re-shaped, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone. No export, type or error code is removed or renamed: `PLATFORM_OBJECT_TENANCY`, `isPlatformObjectOutOfTenantAuditScope` and `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED` keep their shapes, and none is added. The remedy is a run-time one, stated below. -->
1010

11-
**BREAKING** accept-set narrowing, shipped as `minor` under the repo's launch-window convention for breaking changes (ADR-0131 D9, D11). This is the walled half of the change whose `single` half shipped in 18.0.
11+
**BREAKING** accept-set narrowing, shipped as `minor` under the repo's launch-window convention for breaking changes (ADR-0131 D9, D11). This is the walled half of the change whose `single` half is the companion `@objectstack/objectql` changeset on system inserts under `single`.
1212

1313
- **Before.** Under `group` or `isolated`, a system-context insert (`isSystem`, no `tenantId`) into a `sys_` / `cloud_` / `ai_` object that carries an organization column, but that the tenancy inventory had not admitted, skipped the organization rule. The row landed with `organization_id` NULL, a row no organization owns and the wall hides from every organization.
14-
- **Now.** That insert goes through the same decision every application object already gets under a wall: no organization is derivable, so it is refused with `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED` (status 500, reason `walled-posture`), and nothing is written. Apart from the two tables named below, the posture no longer changes which objects the rule covers.
15-
- **Unchanged.** A write that carries an organization, on the execution context or on the record, is never re-stamped or refused. Objects with no organization column (`systemFields: { tenant: false }`), objects declaring `tenancy: { enabled: false }`, and federated objects are outside the rule. `sys_secret` and `sys_flow_credential` keep their organization-less system inserts under a wall, as before: a global-scope encrypted setting, a datasource credential and a flow credential are deployment-level, and both tables drop their organization column in a later 18.x release, which retires this bridge (`WALLED_ORGANIZATION_LESS_BRIDGE`). Updates are untouched. The engine's diagnostics-only tenant-audit mute still reads the inventory.
14+
- **Now.** That insert goes through the same decision every application object already gets under a wall: no organization is derivable, so it is refused with `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED` (status 500, reason `walled-posture`), and nothing is written. The posture no longer changes which objects the rule covers.
15+
- **Unchanged.** A write that carries an organization, on the execution context or on the record, is never re-stamped or refused. Objects with no organization column (`systemFields: { tenant: false }`), objects declaring `tenancy: { enabled: false }`, and federated objects are outside the rule. `sys_secret` and `sys_flow_credential` are among them: a global-scope encrypted setting, a datasource credential and a flow credential are deployment-level, so both declare `systemFields: { tenant: false }` and their organization-less system inserts land under a wall. Updates are untouched. The engine's diagnostics-only tenant-audit mute still reads the inventory.
1616

1717
**The remedy.** If a refusal names your insert, carry the organization on the write: `ql.insert(object, { ...row, organization_id: orgId }, { context: { isSystem: true } })`, or `{ context: { isSystem: true, tenantId: orgId } }`. If the row is deployment-level by design, the object should declare no organization column: `systemFields: { tenant: false }`.

‎.changeset/15212-walled-posture-boots.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ feat(cli): `os serve` (and so `os dev` and `os start`) boots a walled tenancy po
66

77
Clause-②: yes (widening)
88

9-
18.0 refused a walled posture at boot, because the walled half of the platform-object organization rule had not shipped. It ships in this release (see the `@objectstack/objectql` changeset on walled system inserts), so the refusal is removed.
9+
The 18.0 pre-releases refused a walled posture at boot, because the walled half of the platform-object organization rule had not shipped. It ships in this release (see the `@objectstack/objectql` changeset on walled system inserts), so the refusal is removed.
1010

1111
- **Before.** With `OS_TENANCY_POSTURE=group` or `isolated`, or `OS_MULTI_ORG_ENABLED=true`, `os serve` exited `1` with a FATAL `NOT_IMPLEMENTED` before it read a config. `os doctor` printed a `Tenancy posture` warning saying so, and the unrecognised-posture fix list marked both walled values as refused in 18.0.
12-
- **Now.** A walled posture boots like `single`, under the same prerequisites as before 18.0: the enterprise `@objectstack/organizations` runtime, and `OS_PLATFORM_OWNER_EMAIL`. `os doctor` reports no row for a valid posture, and the fix list offers `group` and `isolated` without the 18.0 note.
13-
- **Unchanged.** An unrecognised posture is still refused at boot. Upgrading an existing database still waits for the upgrade ceremony, which still refuses in this release.
12+
- **Now.** A walled posture boots like `single`, under the same prerequisites as before the 18.0 pre-releases: the enterprise `@objectstack/organizations` runtime, and `OS_PLATFORM_OWNER_EMAIL`. `os doctor` reports no row for a valid posture, and the fix list offers `group` and `isolated` without the refusal note.
13+
- **Unchanged.** An unrecognised posture is still refused at boot. Upgrading an existing database is the ownership ceremony's: `os migrate organization-ownership --out PLAN`, then `--apply --plan-file PLAN --backup-taken`.

‎.changeset/24529-declared-webhooks-from-registry.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ On a database an 18.0 install created (expand first — nothing is moved or dele
2727
- A seeded row whose declaration was removed keeps delivering, as it did on 18.0.
2828
- Redelivering an old delivery of a pristine seeded row is judged by the declaration that now answers for it, so a webhook switched off through the ledger is not re-sent.
2929

30-
Deleting the seeded rows, and re-pointing their `sys_http_delivery` history at the declaration, is a step of the ownership migration in a later 18.x release.
30+
Deleting the seeded rows, and re-pointing their `sys_http_delivery` history at the declaration, is the ownership ceremony's `declared-webhooks` step (`os migrate organization-ownership`).

‎.changeset/24581-organization-ownership-ceremony-open.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
'@objectstack/spec': minor
55
'@objectstack/driver-sql': minor
66
'@objectstack/driver-turso': minor
7+
'@objectstack/objectql': minor
78
'@objectstack/plugin-webhooks': minor
89
'@objectstack/platform-objects': minor
910
'@objectstack/service-automation': minor
@@ -14,7 +15,7 @@ feat(cli,runtime)!: `os migrate organization-ownership` upgrades a database 17.x
1415

1516
Clause-②: yes (narrowing)
1617

17-
<!-- adr-0087: not-required (no-migration-prescription) No key of any metadata schema is removed, renamed or re-shaped, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone. The data migration this change prescribes is the operator's ceremony, `os migrate organization-ownership`, stated below; the boot refusal names it. -->
18+
<!-- adr-0087: not-required (no-migration-prescription) No key of any metadata schema is removed, renamed or re-shaped, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone. The data migration this change prescribes is the operator's ceremony, `os migrate organization-ownership`, stated below; the boot refusal names it. Two `@objectstack/objectql` exports are removed, `WALLED_ORGANIZATION_LESS_BRIDGE` and `isWalledOrganizationLessBridgeObject`: they named the two tables whose organization-less inserts were let through under a wall while those tables still carried an organization column, and both tables now declare none. Neither is a metadata key and no stored row or stack names them, so a caller has nothing to migrate: drop the import, since a deployment-level table is recognised by `systemFields: { tenant: false }`. -->
1819

1920
**BREAKING** for a database with no birth marker — one 17.x or an 18.0 pre-release (`18.0.0-next.*`) created — shipped as `minor` under the repo's launch-window convention for breaking changes (ADR-0131 D10).
2021

@@ -24,6 +25,6 @@ Clause-②: yes (narrowing)
2425
- `declared-rule-copies`: each stored sharing rule under a declared rule's name becomes a clone under a new name, its grants kept, and the declaration is switched off.
2526
- `not-null`: after the column drops, every table the post-step plan clears takes `organization_id` NOT NULL. The ceremony owns that nullability, so the schema differ no longer reports it as `relax_not_null` drift.
2627
- **The boot guard decides on a marker.** The first boot that creates a datastore from empty writes a birth marker (`sys_migration` row `adr-0131-organization-ownership`, `details.birth.shapeGeneration`). Every CLI data boot writes it too, so a fresh install whose first command is `os migrate apply` or `os seed` still boots. On the Turso remote face (`libsql://`), `getSchemaSyncStats` now answers what its schema doors created and found (it answered `{ created: 0, existing: 0 }` before), so a fresh remote database is observed as one and gets its marker. The ceremony's verified marker carries the same `shapeGeneration` (now `1`, `ORGANIZATION_OWNERSHIP_SHAPE_GENERATION` in `@objectstack/spec/system`). A server refuses a database with no marker (one 17.x or an 18.0 pre-release created), one certified at an older generation, or an uncertified one. The refusal names the command and no longer says to stay on 17.x. The v17-shape probe also reads `tenancy: { enabled: false }` and `systemFields: false`.
27-
- **`sys_secret` and `sys_flow_credential` are deployment-level** (`systemFields: { tenant: false }`). Their `organization_id` is dropped by the ceremony's column-drops step. The settings service no longer writes one, and `SettingsSecretStore.insert` no longer takes it.
28+
- **`sys_secret` and `sys_flow_credential` are deployment-level** (`systemFields: { tenant: false }`). Their `organization_id` is dropped by the ceremony's column-drops step. The settings service no longer writes one, and `SettingsSecretStore.insert` no longer takes it. With no organization column, their organization-less system inserts land under a walled posture on the engine's own rule, so `@objectstack/objectql` no longer exports `WALLED_ORGANIZATION_LESS_BRIDGE` or `isWalledOrganizationLessBridgeObject`, and the system-write refusal's remedy names `systemFields: { tenant: false }`.
2829

29-
**What an operator does.** Before serving a database 17.x or an 18.0 pre-release created with this release: take a backup, run `os migrate organization-ownership --out plan.json`, review the plan, then run `os migrate organization-ownership --apply --plan-file plan.json --backup-taken`, and start the server. `os dev` does this for a local SQLite development database. On a walled database, a customized copy of a declared webhook or sharing rule blocks its step, naming the row: rename it to a name no package declares, or delete it, then re-run.
30+
**What an operator does.** Fresh installs are supported under every tenancy posture and need nothing. Before serving a database 17.x or an 18.0 pre-release created with this release: start this release against it once (the boot is refused, and its schema sync has already created the tables this release adds, which the ceremony moves rows into), take a backup, run `os migrate organization-ownership --out plan.json`, review the plan, then run `os migrate organization-ownership --apply --plan-file plan.json --backup-taken`, and start the server. `os dev` does this for a local SQLite development database. On a walled database, a customized copy of a declared webhook or sharing rule blocks its step, naming the row: rename it to a name no package declares, or delete it, then re-run.

‎content/docs/deployment/cli.mdx‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,9 @@ os dev --database file:./data/test.db --auth-secret $(openssl rand -hex 32)
198198

199199
##### A 17.x or 18.0 pre-release development database is upgraded in place
200200

201-
A v18 server refuses to start on a database with no birth marker, which 17.x
202-
and the 18.0 pre-releases (`18.0.0-next.*`) created, until the [ownership ceremony](#os-migrate-organization-ownership)
201+
Fresh installs are supported under every tenancy posture. A v18 server
202+
refuses to start on a database with no birth marker, which 17.x and the 18.0
203+
pre-releases (`18.0.0-next.*`) created, until the [ownership ceremony](#os-migrate-organization-ownership)
203204
has run on it. `os dev` runs that ceremony for you when the database is a local
204205
SQLite file:
205206

@@ -1507,19 +1508,31 @@ upgrading rather than wait for that.
15071508
#### `os migrate organization-ownership`
15081509
15091510
The v18 ownership ceremony (ADR-0131 D10), and the upgrade path of a database
1510-
17.x or an 18.0 pre-release (`18.0.0-next.*`) created. A v18 `os serve` / `os start` serves a database this release
1511-
created itself, or one this ceremony certified; it refuses any other until the
1512-
ceremony has run. Under `os dev` it runs on its own for a local SQLite
1513-
database (see [`os dev`](#os-dev)).
1511+
17.x or an 18.0 pre-release (`18.0.0-next.*`) created.
1512+
Fresh installs are supported under every tenancy posture and never need it.
1513+
A v18 `os serve` / `os start` serves a database this release created itself, or one this ceremony
1514+
certified; it refuses any other until the ceremony has run. Under `os dev` it
1515+
runs on its own for a local SQLite database (see [`os dev`](#os-dev)).
15141516
15151517
How a database is recognised: the first boot that creates a datastore from
15161518
empty writes a **birth marker** into `sys_migration` (row
15171519
`adr-0131-organization-ownership`) naming the ownership shape generation it was
15181520
born in. The ceremony writes the same row, verified, when its post-check
15191521
passes. A server decides on that generation alone: a database with no birth
15201522
marker, which 17.x and the 18.0 pre-releases created, or one a shorter ceremony
1521-
certified at an older
1522-
generation, is refused, and the refusal names this command.
1523+
certified at an older generation, is refused, and the refusal names this
1524+
command.
1525+
1526+
The sequence on an existing database:
1527+
1528+
1. Start this release against it once (`os serve`). The boot is refused, but
1529+
its schema sync has already created the tables this release adds, the
1530+
sibling logs the ceremony moves rows into among them. A plan drawn before
1531+
that boot blocks its `sibling-moves` step, naming the missing table and this
1532+
remedy.
1533+
2. Take a backup.
1534+
3. Write the plan, review it, then apply that reviewed plan (below).
1535+
4. Start the server again.
15231536
15241537
```bash
15251538
os migrate organization-ownership --out plan.json # the plan: reads only

‎packages/cli/src/commands/migrate/apply.organization-ownership-refusal.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ const ORGANIZATION_DROP = {
6767
description: 'sys_platform_setting.organization_id: column exists in the database but not in metadata (orphaned)',
6868
};
6969

70-
/** A step of `os migrate organization-ownership`, which 18.0 refuses on every database. */
7170
/** The ceremony's two steps, as the refusal prescribes them. */
7271
const CEREMONY_STEPS = [
7372
'os migrate organization-ownership --out PLAN',
@@ -143,7 +142,7 @@ async function runApply(argv: string[]) {
143142

144143
const ARGV = ['--database-url', 'file:v17.db', '--allow-destructive', '--yes'];
145144

146-
describe('os migrate apply --allow-destructive: the organization-ownership refusal names no command 18.0 refuses', () => {
145+
describe('os migrate apply --allow-destructive: the organization-ownership refusal names the ceremony\'s steps', () => {
147146
for (const [state, flag] of [['no ceremony run recorded', null], ['an unverified run recorded', UNVERIFIED]] as const) {
148147
it(`${state}: the human face refuses, exit 1, naming the ceremony's steps, and nothing is written`, async () => {
149148
bootWithOrganizationDrop();

0 commit comments

Comments
 (0)