Skip to content

Commit ae6bd1a

Browse files
feat(cli)!: 18.0 guards — os migrate organization-ownership refuses, and os serve refuses a walled posture at boot (#24480)
Part of #15212 Clause-②: yes (narrowing) The **18.0 guards** stage (plan amendment 14 on #15212, from ruling records 6259608309 and 6259698357 on #15194). 18.0 ships for fresh installs under the `single` posture only. This PR makes the two things 18.0 does not support refuse loudly. It deletes no ceremony code, and it adds no re-enable flag or environment escape hatch. ## What changes 1. **`os migrate organization-ownership` refuses in 18.0, plan and apply alike.** - Exit `1` with the standard ADR-0112 code `NOT_IMPLEMENTED`. Under `--json` it prints `{ error: 'plan_refused' | 'apply_refused', reason: 'upgrade-unavailable', code: 'NOT_IMPLEMENTED', detail }`. - The message says 18.0 supports fresh installs only and that the v17 → v18 upgrade path ships in an 18.x release. - It reads and writes nothing. No posture, supplement, plan file, occupancy probe or boot is touched before it. - **Shape:** the ceremony class is kept whole as the named export `OrganizationOwnershipCeremony`. The registered default command is a subclass of it whose `run()` refuses: same flags, same help, plus a description prefix. The 18.x release that carries the upgrade path deletes the subclass. The ceremony's ~45 existing in-process tests now import the ceremony class, a one-line import change in each of 16 files, so the 18.x ceremony keeps its coverage. `organization-ownership-*.ts` utils are untouched. 2. **`os serve` refuses a walled posture (`group` / `isolated`) at boot.** - It refuses at its existing tenancy-posture gate, in the same FATAL plus `process.exit(1)` shape as an unrecognised posture: before a config is read, a plugin is mounted or anything is written. `OS_MULTI_ORG_ENABLED=true` resolves to `isolated` and is refused too. - The FATAL names `NOT_IMPLEMENTED`, the 18.x release and the fixes: `OS_TENANCY_POSTURE=single`, or stay on 17.x. `OS_ALLOW_DEGRADED_TENANCY` does not lift it. - `os dev` and `os start` spawn `serve`, so this is every operator boot. Under `single` nothing changes. ## Where the walled refusal sits (measured) | candidate | operator-facing? | walled fixtures it breaks | |:--|:--|:--| | `os serve`'s posture gate (chosen) | every CLI boot (`dev` and `start` spawn `serve`) | the two CLI e2e files that spawn `serve` under `isolated` — rewritten, see below | | `os dev` / `os start` only | misses production `os serve` | none | | runtime / kernel (`resolveTenancyPosture`, `createStandaloneStack`, `bootStack`) | also every embedded and test boot | every kernel-level walled suite: the dogfood `wall-*` files, verify posture tests, plugin-auth, core and driver tests | The kernel-level walled boots (`@objectstack/verify`'s `bootStack`, the dogfood wall suites, the unit tests that set `OS_TENANCY_POSTURE=isolated`) never pass through `serve`, and they keep working. The only walled boots this refusal breaks are the two nightly-tier CLI e2e files that spawn `os serve` under `isolated`. Those tested the organizations runtime's host resolution and its mount-stage classification, which no 18.0 walled boot reaches. They keep their fixtures and now pin, over the real process, that the 18.0 refusal comes first: whatever the package state (installed in the app, absent, reachable only through `NODE_PATH`, present-and-refusing) and with or without `OS_ALLOW_DEGRADED_TENANCY=1`, no organizations stage runs. The 18.x release restores their resolution cases from git history. ## The `os dev` automatic ceremony (A1) `os dev` runs the ceremony only when the boot guard refuses the database. That never happens on a fresh database: the guard passes a database with no retired column and no marker. When it does run, it spawns this same command, so it now gets the refusal. It prints the command's own refusal line, says nothing was changed, and keeps the copy it took. No `os dev` change was needed. ## Docs `content/docs/deployment/cli.mdx` (the command's section, its table row, and `os dev`'s in-place upgrade section), `tenancy-modes.mdx`, `environment-variables.mdx` (the `OS_TENANCY_POSTURE` row) and `self-hosting.mdx` (walled first boot) each gain the sentence "18.0 supports fresh `single` installs; upgrades and walled postures arrive in 18.x", only where the text would otherwise be false. Nothing under `content/docs/releases/`. ## Tests - New `packages/cli/src/commands/migrate/organization-ownership.release-refusal.test.ts` (5 cases): plan and apply refuse with `NOT_IMPLEMENTED` and exit 1; the plain output carries the code; nothing is touched (boot and occupancy stubs that throw, an unrecognised posture, a missing supplement and plan file, no db or out file created); the default class is a subclass of the ceremony with the same flags. - New `packages/cli/src/commands/serve-walled-posture-refusal.test.ts` (5 cases): `single` gives `null`; every walled posture in the spec vocabulary is refused; the `OS_MULTI_ORG_ENABLED` source is named; and the real `serve`, run in-process under `isolated` and under `OS_MULTI_ORG_ENABLED=true`, exits 1 with nothing announced (no config read). - `packages/qa/dogfood/test/organization-ownership-boot-refusal-order.dogfood.test.ts`: new step 2a, in which the real CLI binary refuses with `NOT_IMPLEMENTED` and the database columns are unchanged. Step 2's upgrade proof now runs `OrganizationOwnershipCeremony` from the CLI's built module in its own process (oclif auto-transpile off, as `bin/run.js` does), so the ceremony → boot-succeeds proof stays alive for 18.x. Readings on head `cca3540481` (the measurements below were taken on `9141352b`, before the merge of `main`, which touched only unrelated cli tests; the ratchet families were re-run on `cca3540481`): - cli `typecheck`: exit 0. - cli unit project: 312 files / 4615 tests passed. - The 16 changed ceremony test files plus the two new pins and `serve-tenancy-posture-gate`: 18 passed, 1 skipped (`remote-face`, which needs `sqld`), 199 tests. - The two nightly e2e files (`OS_TEST_TIERS=nightly`): 7/7. - The dogfood file: 5/5. Ablations, each through `scripts/ablation-replace.mjs`, mutation landed and restored with `git diff HEAD` empty: - the registered command's `run` replaced by the ceremony's → 4/5 refusal cases red, the ceremony answering `posture-unresolved`; - serve's `walledPostureRefusal(tenancyPosture)` call replaced by `null` → both in-process boot cases red; - the `single` early return deleted → the `single` case red. ## Acceptance notes - The boot guard (`organization-ownership-boot-guard.ts`), left as is per the dispatch, still names `os migrate organization-ownership` as the remedy for a v17-shaped database. In 18.0 that command refuses, and its refusal says to stay on 17.x. The guard's prescription is therefore incomplete for 18.0. Whether to reword it is the seat's call: the dispatch said to leave the guard alone. - `os serve`'s unrecognised-posture FATAL still lists `group` and `isolated` among its fixes (from `TENANCY_POSTURE_FIX_HINTS`). Following either now reaches the 18.0 refusal on the next boot, which is loud, so it is not touched here. - Embedded hosts that boot the kernel directly (adapters, `createStandaloneStack`) are not refused: they are not the operator's `os` boot path, and refusing there would break the kernel-level walled suites. - `os doctor` still reports a walled posture without saying 18.0 refuses it. --- _Generated by [Claude Code](https://claude.ai/code/session_01Q4U6vXvHNpcDZr24aXeJo2)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2ec6827 commit ae6bd1a

28 files changed

Lines changed: 565 additions & 234 deletions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/cli': minor
3+
---
4+
5+
feat(cli)!: 18.0 supports fresh `single` installs only — `os migrate organization-ownership` refuses, and `os serve` refuses a walled posture at boot
6+
7+
Clause-②: yes (narrowing)
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) Nothing an author writes, and no stored shape an author owns, is added, removed, renamed or converted: a CLI command and two deployment postures that used to run now refuse, with no spec key, export, config field or error code removed or renamed. The remedy is an operator's one, stated below: stay on 17.x, or run 18.0 under `single` on a fresh database. -->
10+
11+
**BREAKING** (a command and a boot posture that used to run now refuse), shipped as `minor` under the launch-window convention for breaking changes. 18.0 supports fresh installs under the `single` tenancy posture only; upgrading a v17 database and the walled postures arrive in an 18.x release.
12+
13+
- **FROM** `os migrate organization-ownership` (plan) and `os migrate organization-ownership --apply …` running the v18 ownership ceremony **TO** both refusing with exit `1` and the standard code `NOT_IMPLEMENTED` (under `--json`: `{ error: 'plan_refused' | 'apply_refused', reason: 'upgrade-unavailable', code: 'NOT_IMPLEMENTED', detail }`), reading and writing nothing. The 18.0 ceremony is knowingly incomplete, and an incomplete ceremony must never certify an old database. **Fix:** keep a v17 database on a 17.x release until the 18.x release that carries the upgrade path. `os dev`'s in-place upgrade of a v17 development database runs this command, so it now stops with the same refusal and changes nothing.
14+
- **FROM** `os serve` (and so `os dev` and `os start`) booting with `OS_TENANCY_POSTURE=group` or `isolated`, or with `OS_MULTI_ORG_ENABLED=true`, **TO** a FATAL refusal at the tenancy-posture gate, exit `1`, naming `NOT_IMPLEMENTED` and the 18.x release, before a config is read. `OS_ALLOW_DEGRADED_TENANCY` does not lift it, and no flag or variable does. **Fix:** set `OS_TENANCY_POSTURE=single` (or unset it and `OS_MULTI_ORG_ENABLED`), or keep a walled deployment on a 17.x release.
15+
16+
**What stays as it was.** A fresh install under `single` boots exactly as before. The boot guard that refuses a v17-shaped database is unchanged. Kernel-level boots (`@objectstack/verify`'s `bootStack`, embedded hosts) still accept every posture.

‎content/docs/deployment/cli.mdx‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ organization-ownership shape until the [ownership ceremony](#os-migrate-organiza
203203
has run on it. `os dev` runs that ceremony for you when the database is a local
204204
SQLite file:
205205

206+
<Callout type="warn">
207+
18.0 supports fresh `single` installs; upgrades and walled postures arrive in 18.x. In 18.0 the ceremony refuses, so `os dev` stops at step 2 with its
208+
refusal and changes nothing.
209+
</Callout>
210+
206211
1. It copies the database file and its `telemetry` sibling (each with its
207212
`-wal` / `-shm`) into `<data dir>/backups/organization-ownership-<timestamp>/`.
208213
2. It runs `os migrate organization-ownership --dev` (the plan), then applies
@@ -1197,7 +1202,7 @@ where the data lives.
11971202
| `os migrate value-shapes` | Scan stored reference and structured-JSON field values against the platform's value contract, and record the deployment's migration flag when clean |
11981203
| `os migrate summary-nulls` | Backfill roll-up `count` / `sum` columns still stored as `NULL` on parent rows created before the insert-time seed. Repairs values; no flag, nothing depends on it having run |
11991204
| `os migrate comment-reactions` | Carry the emoji reactions a 17.x deployment stored in the retired `sys_comment.reactions` column into `sys_comment_reaction` records, list every stored value that does not read, and record the verified run that lets `os migrate apply --allow-destructive` drop the column |
1200-
| `os migrate organization-ownership` | The v18 ownership ceremony (ADR-0131 D10): write a plan of what each table's rows will become, then `--apply` that reviewed plan. A v18 server refuses to start until it has run. See [below](#os-migrate-organization-ownership) |
1205+
| `os migrate organization-ownership` | The v18 ownership ceremony (ADR-0131 D10): write a plan of what each table's rows will become, then `--apply` that reviewed plan. A v18 server refuses to start until it has run. 18.0 supports fresh `single` installs; upgrades and walled postures arrive in 18.x. In 18.0 the command refuses. See [below](#os-migrate-organization-ownership) |
12011206
| `os migrate activity-changed-fields` | Stamp the two read-rule columns (`changed_fields`, `milestone_key`) on the `sys_activity` rows written before they existed. Until it runs, those rows are withheld from every reader but the system, so run it at once after upgrading. No flag; idempotent |
12021207
| `os migrate comment-parent-pair` | Stamp the parent pair (`object_name`, `record_id`) on the `sys_comment` rows written before it existed. Until it runs, a broad comment read past 2,000 candidate rows omits comments the reader may see, and its total agrees with the short list, so run it at once after upgrading. No flag; idempotent |
12031208
| `os migrate meta --stored` | Replay the metadata conversion chain over this deployment's `sys_metadata` rows and rewrite the ones still carrying a pre-protocol shape. Hygiene, not a gate — nothing depends on it having run |
@@ -1476,6 +1481,13 @@ to start on a database still in the v17 organization-ownership shape until this
14761481
has run. Under `os dev` it runs on its own for a local SQLite database (see
14771482
[`os dev`](#os-dev)).
14781483
1484+
<Callout type="warn">
1485+
18.0 supports fresh `single` installs; upgrades and walled postures arrive in 18.x. In 18.0 this command refuses, plan and apply alike: it exits `1` with
1486+
`NOT_IMPLEMENTED` (under `--json`, `code` beside `error`) and reads and
1487+
writes nothing. Keep a v17 database on a 17.x release until the 18.x release
1488+
that carries the upgrade path.
1489+
</Callout>
1490+
14791491
```bash
14801492
os migrate organization-ownership --out plan.json # the plan: reads only
14811493
os migrate organization-ownership --apply --plan-file plan.json --backup-taken

‎content/docs/deployment/environment-variables.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ Read by `@objectstack/service-print` when `PrintServicePlugin` is composed; see
9898
| `OS_AUTH_GOOGLE_ENABLED` | boolean | settings default | Settings env override for `auth.google_enabled`. Requires Google OAuth credentials from Settings or env. |
9999
| `GOOGLE_CLIENT_ID` | string | — | Deployment-level Google OAuth client id for the open-source Google login implementation. |
100100
| `GOOGLE_CLIENT_SECRET` | string | — | Deployment-level Google OAuth client secret for the open-source Google login implementation. |
101-
| `OS_TENANCY_POSTURE` | `single` \| `group` \| `isolated` | derived from `OS_MULTI_ORG_ENABLED` | Which organization wall the authorization kernel enforces (ADR-0105 D1). `single` = no wall. `group` = `organization_id IN accessible_org_ids` — organizations are membership boundaries over one shared dataset, with union read access across every organization the caller belongs to. `isolated` = `organization_id = <active organization>`, the hard legal-entity wall (formerly spelled `multi`). Unset derives from `OS_MULTI_ORG_ENABLED` (`true` ⇒ `isolated`, else `single`), so existing deployments are unchanged. An unrecognized value **refuses to boot** rather than silently falling back to a posture with no wall. Both walled postures require the enterprise `@objectstack/organizations` runtime — without it the request resolves to `single` and boot is refused unless `OS_ALLOW_DEGRADED_TENANCY=1`. |
101+
| `OS_TENANCY_POSTURE` | `single` \| `group` \| `isolated` | derived from `OS_MULTI_ORG_ENABLED` | Which organization wall the authorization kernel enforces (ADR-0105 D1). `single` = no wall. `group` = `organization_id IN accessible_org_ids` — organizations are membership boundaries over one shared dataset, with union read access across every organization the caller belongs to. `isolated` = `organization_id = <active organization>`, the hard legal-entity wall (formerly spelled `multi`). Unset derives from `OS_MULTI_ORG_ENABLED` (`true` ⇒ `isolated`, else `single`), so existing deployments are unchanged. An unrecognized value **refuses to boot** rather than silently falling back to a posture with no wall. Both walled postures require the enterprise `@objectstack/organizations` runtime — without it the request resolves to `single` and boot is refused unless `OS_ALLOW_DEGRADED_TENANCY=1`. 18.0 supports fresh `single` installs; upgrades and walled postures arrive in 18.x. In 18.0 `os serve` refuses `group` and `isolated` at boot. |
102102
| `OS_MULTI_ORG_ENABLED` | boolean | `false` | Superseded by `OS_TENANCY_POSTURE`, and still honoured: `true` selects the `isolated` posture. When `true`, organization creation/switching UI is exposed. |
103103
| `OS_AUTOMATION_SCHEDULED_WORK_ENABLED` | boolean | `false` | Whether this deployment runs **package-authored scheduled work**: time-triggered flows (a `type: 'schedule'` flow with a `config.schedule` cadence, and the `timeRelative` sweep) and packaged `defineJob` cron jobs. **OFF by default in every posture and every kernel** — a clock-driven workload's cost is a fact about the deployment, not about the flow, so an author cannot decide it and no metadata key asks them to. Accepts `true`/`1`/`on`/`yes` (case-insensitive); anything else, including unset, is off. While it is off nothing arms, and every such flow is listed by `getTriggerBindingAudit()` and the CLI startup summary as **disabled by deployment policy** — never as a binding failure, and nothing about the flow needs fixing. While it is on, what a time-triggered flow owes depends on the posture: under `isolated` it must declare `config.organization` on its start node or it is not armed; under `group` the declaration is optional and an undeclared sweep reads group-wide with each run acting as its own swept record's organization (a record-less cron there carries none, and its tenant-scoped writes are refused — declare one if it writes); under `single` it needs no declaration and its runs carry no organization. ⛔ Platform-internal scheduled work — approvals escalation, the lifecycle Reaper, the messaging dispatch loop, membership backfill — is **not** gated by this: the boundary is *authored by a package*, not *runs on the job service*. `os doctor` prints the effective value. |
104104
| `OS_PLATFORM_OWNER_EMAIL` | csv | — | The deployment's platform administrators: one email address, or a comma-separated list. A caller resolves `PLATFORM_ADMIN` when their own stored `sys_user` row carries a declared address **and** reads email-verified. Comparison is trimmed and case-insensitive; duplicates collapse and blank entries are dropped. One **unparseable** entry refuses the **whole** variable rather than just that entry — the deployment then has zero configured administrators, loudly — because a silently narrower administrator set is the worse failure. **Required under the walled postures** (`group` / `isolated`): unset or blank there **refuses to boot**, since the first self-registrant is not promoted and no grant row is written. Unset under `single` is normal — that posture still promotes the first human account. Read live per resolution, so revocation is a config change plus a process reload; there is no runtime endpoint that changes it. See [First boot: create the admin](/docs/deployment/self-hosting#first-boot-create-the-admin). |

‎content/docs/deployment/self-hosting.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,8 @@ lock sign-up down via `OS_AUTH_SIGNUP_ENABLED` /
466466
configuration.** First-registrant promotion is removed there: with
467467
self-registration reachable, whoever posts to the sign-up endpoint first would
468468
otherwise receive cross-tenant access. No grant row is written on those
469-
postures, ever. Declare the administrators **before first boot** instead:
469+
postures, ever. Declare the administrators **before first boot** instead
470+
(18.0 supports fresh `single` installs; upgrades and walled postures arrive in 18.x. In 18.0 `os serve` refuses a walled posture at boot):
470471

471472
```bash
472473
# one address, or a comma-separated list

‎content/docs/deployment/tenancy-modes.mdx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ it generalizes the two-valued *tenancy mode* of
1616
whose `tenancy` service, membership lifecycle and degraded-tenancy boot guard all
1717
still apply — widened to the three postures.
1818

19+
<Callout type="warn" title="18.0: single only">
20+
18.0 supports fresh `single` installs; upgrades and walled postures arrive in 18.x. In 18.0 `os serve` (and so `os dev` and `os start`) refuses `group` and
21+
`isolated` at boot with `NOT_IMPLEMENTED`, before it reads a config.
22+
</Callout>
23+
1924
<Callout type="info" title="Renamed, not re-configured">
2025
The posture formerly called `multi` is now `isolated`, and the authoritative knob
2126
is `OS_TENANCY_POSTURE`, not `OS_MULTI_ORG_ENABLED`. Existing deployments keep

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ vi.mock('../../utils/migrate-occupancy-gate.js', () => ({
2828
probeMigrationTarget: vi.fn(async () => ({ status: 'free' })),
2929
}));
3030

31-
type Command = (typeof import('./organization-ownership.js'))['default'];
31+
type Command = (typeof import('./organization-ownership.js'))['OrganizationOwnershipCeremony'];
3232
let MigrateOrganizationOwnership: Command;
3333

3434
// The command's module graph is large, and oclif reads the package's config on
3535
// the first run: both are paid outside the 5 s default (CASE_TIMEOUT_MS).
3636
beforeAll(async () => {
37-
({ default: MigrateOrganizationOwnership } = await import('./organization-ownership.js'));
37+
({ OrganizationOwnershipCeremony: MigrateOrganizationOwnership } = await import('./organization-ownership.js'));
3838
}, 60_000);
3939

4040
const CASE_TIMEOUT_MS = 60_000;

‎packages/cli/src/commands/migrate/organization-ownership.declared-rule-mirrors.integration.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { DECLARED_SHARING_RULE_SEEDER_RUNS } from '../../utils/organization-owne
5757
import '@objectstack/objectql';
5858
import '@objectstack/metadata-protocol';
5959

60-
type Command = (typeof import('./organization-ownership.js'))['default'];
60+
type Command = (typeof import('./organization-ownership.js'))['OrganizationOwnershipCeremony'];
6161
let MigrateOrganizationOwnership: Command;
6262

6363
const CLI_ROOT = fileURLToPath(new URL('../../..', import.meta.url));
@@ -158,7 +158,7 @@ describe('os migrate organization-ownership on a database whose packages declare
158158
savedEnv[key] = process.env[key];
159159
delete process.env[key];
160160
}
161-
({ default: MigrateOrganizationOwnership } = await import('./organization-ownership.js'));
161+
({ OrganizationOwnershipCeremony: MigrateOrganizationOwnership } = await import('./organization-ownership.js'));
162162
dir = mkdtempSync(join(tmpdir(), 'os-ceremony-declared-rules-'));
163163
file = join(dir, 'fixture.db');
164164
// The project the command runs in: the ceremony boots ITS composition, so

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ vi.mock('../../utils/organization-ownership-apply.js', async (importOriginal) =>
4444
applyOrganizationOwnership: apply,
4545
}));
4646

47-
type Command = (typeof import('./organization-ownership.js'))['default'];
47+
type Command = (typeof import('./organization-ownership.js'))['OrganizationOwnershipCeremony'];
4848
let MigrateOrganizationOwnership: Command;
4949

5050
// The command's module graph is large, and oclif reads the package's config on
5151
// the first run: both are paid outside the 5 s default (CASE_TIMEOUT_MS).
5252
beforeAll(async () => {
53-
({ default: MigrateOrganizationOwnership } = await import('./organization-ownership.js'));
53+
({ OrganizationOwnershipCeremony: MigrateOrganizationOwnership } = await import('./organization-ownership.js'));
5454
}, 60_000);
5555

5656
const CASE_TIMEOUT_MS = 60_000;

‎packages/cli/src/commands/migrate/organization-ownership.fate-supplement.integration.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ vi.mock('../../utils/organization-ownership-apply.js', async (importOriginal) =>
3434
applyOrganizationOwnership: apply,
3535
}));
3636

37-
type Command = (typeof import('./organization-ownership.js'))['default'];
37+
type Command = (typeof import('./organization-ownership.js'))['OrganizationOwnershipCeremony'];
3838
let MigrateOrganizationOwnership: Command;
3939

4040
// The command's module graph is large, and oclif reads the package's config on
4141
// the first run: both are paid outside the 5 s default (CASE_TIMEOUT_MS).
4242
beforeAll(async () => {
43-
({ default: MigrateOrganizationOwnership } = await import('./organization-ownership.js'));
43+
({ OrganizationOwnershipCeremony: MigrateOrganizationOwnership } = await import('./organization-ownership.js'));
4444
}, 60_000);
4545

4646
const CASE_TIMEOUT_MS = 60_000;

‎packages/cli/src/commands/migrate/organization-ownership.journal-mode.integration.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ import { JOURNAL_MODE_SWITCH_NOTE } from '../../utils/sqlite-occupancy.js';
3232
import '@objectstack/runtime';
3333
import '@objectstack/metadata-protocol';
3434

35-
type Command = (typeof import('./organization-ownership.js'))['default'];
35+
type Command = (typeof import('./organization-ownership.js'))['OrganizationOwnershipCeremony'];
3636
let MigrateOrganizationOwnership: Command;
3737

3838
// The command's module graph is large, and oclif reads the package's config on
3939
// the first run: both are paid outside the case timeout.
4040
beforeAll(async () => {
41-
({ default: MigrateOrganizationOwnership } = await import('./organization-ownership.js'));
41+
({ OrganizationOwnershipCeremony: MigrateOrganizationOwnership } = await import('./organization-ownership.js'));
4242
}, 60_000);
4343

4444
const CASE_TIMEOUT_MS = 60_000;

0 commit comments

Comments
 (0)