Skip to content

Commit aaee264

Browse files
hotlongclaude
andcommitted
docs(organizations): stop calling the open multi-org runtime enterprise/closed-source (#16718)
ADR-0132 moved the `org-scoping` registrar into open core: `packages/plugins/organizations` is Apache-2.0, carries no licence check of any kind, and declares `['group','isolated']` as its own constant. #16137's acceptance proved an open-only composition raises the `isolated` wall with no licence check reachable. The prose did not follow — 65 occurrences across 45 files still described that package as enterprise / closed-source / cloud-private. Prose only. Every non-comment hunk is a string literal (an operator message, a flag description, a conformance-matrix note, a test expectation); no control flow, no identifier and no behaviour changes anywhere. Corrected (population 1), led by the two sites the card names: - `plugin-auth/src/tenancy-service.ts` — open core's own authority for resolving the posture. Its header said both walled postures require the *enterprise* package and that multi-org operation is *a commercial capability*; `isolationActive` argued "ENABLING a multi-organization posture is an entitlement". Rewritten around ADR-0132 D2/D3/D4: the mechanism (both postures probe the same registrar) is unchanged, the answer to "can an open install run isolated?" is now yes, and the `probeEntitledPostures` seam is explained as existing for the commercial subclass rather than as a tier. - `cli/src/commands/serve.ts` — the mount block no longer says the runtime "lives in the closed-source package", and the operator-facing install remedy no longer ends "this runtime is closed-source and is NOT on the public npm registry ... Without one this bullet is not followable". That sentence told an open-source operator the remedy they had just been given was unusable. - `cli/src/utils/tenancy-posture-hints.ts` — the `isolated` hint both `os serve` and `os doctor` render. Not in the card's count: its regex cannot see `the enterprise ${ORGANIZATIONS_RUNTIME_PKG}`. - `plugin-dev/src/dev-plugin.ts` — two more operator-facing strings (the degraded-tenancy warning and the stage-2 mount refusal). Because one name now denotes two packages (ADR-0132 D3), every rewritten site names which one it means; where a site was load-bearing on ADR-0105 D12 the citation is repointed at D12-as-amended rather than deleted. Pin tests updated deliberately, with the reason recorded in each file: `serve-organizations-message-spelling.test.ts` (5 hits) and `doctor-organizations-message-spelling.test.ts`. The pins were not relaxed — they still compare whole rendered lines built from `Serve.ORGANIZATIONS_RUNTIME_PKG`, and both CONTROL blocks still differ from their expectation only in spacing. The doctor roster assertion still pins `edition: 'enterprise'`, which stays true: the roster records the ENTITLED variant. Left alone on purpose, and why, in the PR body: 2 hits that describe the genuinely commercial same-named package, and 15 that state the package is absent/not installable in this workspace as the reason a fixture fakes or skips a proof — the live capability question the card dispatches as report-only. Refs ADR-0132, ADR-0105 D12 (as amended), #16130, #16137, PR #16215. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent e669e5e commit aaee264

39 files changed

Lines changed: 188 additions & 116 deletions

‎packages/cli/src/commands/doctor-organizations-message-spelling.test.ts‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ describe('doctor — the posture description an operator reads names the declara
167167
// what is pinned.
168168
it('leg (i) — the `isolated` fix-list bullet names it, with the spacing intact', () => {
169169
expect(renderIsolatedBullet()).toBe(
170-
` • OS_TENANCY_POSTURE=isolated — organization wall + the enterprise ${PKG} runtime `
170+
` • OS_TENANCY_POSTURE=isolated — organization wall + the ${PKG} runtime `
171171
+ "(the legacy spelling 'multi' is accepted and normalizes to this)",
172172
);
173173
});
@@ -183,8 +183,16 @@ describe('doctor — the posture description an operator reads names the declara
183183
+ 'where it ships from (#10921); a command that prints a package name at operators must name a row in it.',
184184
).toContain(PKG);
185185

186-
// Provenance, read through doctor's own spelling rather than a literal: the
187-
// row this advice describes is the enterprise one.
186+
// Provenance, read through doctor's own spelling rather than a literal.
187+
//
188+
// ⚠️ The row stays `edition: 'enterprise'` after ADR-0132 and that is NOT a
189+
// contradiction of the reworded hint above. One name now denotes two
190+
// packages (ADR-0132 D3): the framework publishes an Apache-2.0 build of it
191+
// and a commercial deployment resolves the same name to a private,
192+
// licence-gated subclass. The roster row records the ENTITLED variant — its
193+
// own note says so — while the operator hint names the PACKAGE, which is
194+
// followable on any install. So this assertion still measures roster drift,
195+
// and it is deliberately not relaxed to match the prose reword.
188196
const row = PLATFORM_PLUGIN_WIRED_RUNTIMES[PKG];
189197
expect(row.edition, `edition drift for the runtime doctor names ('${PKG}')`).toBe('enterprise');
190198
});
@@ -233,14 +241,14 @@ describe('#12464 CONTROL — these pins can say no', () => {
233241
// If this instrument could not tell the two apart, leg (i) would be
234242
// decorative. Anchored on the exact regression interpolation invites.
235243
const expected =
236-
` • OS_TENANCY_POSTURE=isolated — organization wall + the enterprise ${PKG} runtime `
244+
` • OS_TENANCY_POSTURE=isolated — organization wall + the ${PKG} runtime `
237245
+ "(the legacy spelling 'multi' is accepted and normalizes to this)";
238246
expect(
239-
` • OS_TENANCY_POSTURE=isolated — organization wall + the enterprise ${PKG} runtime`
247+
` • OS_TENANCY_POSTURE=isolated — organization wall + the ${PKG} runtime`
240248
+ "(the legacy spelling 'multi' is accepted and normalizes to this)",
241249
).not.toBe(expected);
242250
expect(
243-
` • OS_TENANCY_POSTURE=isolated — organization wall + the enterprise ${PKG} runtime `
251+
` • OS_TENANCY_POSTURE=isolated — organization wall + the ${PKG} runtime `
244252
+ "(the legacy spelling 'multi' is accepted and normalizes to this)",
245253
).not.toBe(expected);
246254
// …and says yes to the real thing, so the two `not.toBe`s above are a

‎packages/cli/src/commands/serve-cluster-host-resolution.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
* binding. A load placed above it is not a compile error — the author writes
4747
* a bare `import()`, which resolves from the CLI and is green in any dev
4848
* checkout where everything is hoisted into one `node_modules`. The first
49-
* time it cost the enterprise organizations load (cloud#1013); the second
49+
* time it cost the organizations load (cloud#1013); the second
5050
* time it cost EE multi-node boot outright (#10645).
5151
*
5252
* #10769 closed the class rather than hoisting a third time: the helper is

‎packages/cli/src/commands/serve-organizations-message-spelling.test.ts‎

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,28 @@
6666
* via `test/serve-capability-vocabulary.test.ts` and the shared const's via
6767
* doctor's leg (ii).
6868
*
69+
* ── The 2026-09-08 reword, and why it was made HERE rather than absorbed ──
70+
*
71+
* These pins previously expected the word "enterprise" in two operator-facing
72+
* sentences: the install remedy ("add … (the enterprise multi-org runtime)")
73+
* and the `isolated` posture hint. ADR-0132 moved the multi-org runtime into
74+
* open core — `packages/plugins/organizations` is Apache-2.0 and carries no
75+
* licence check — so both sentences told an open-source operator that a remedy
76+
* they can in fact follow required a subscription.
77+
*
78+
* ⛔ The pins were NOT relaxed or deleted to accommodate the reword: they still
79+
* compare the whole rendered line, whitespace included, against text built from
80+
* `Serve.ORGANIZATIONS_RUNTIME_PKG`, and the #12151 CONTROL block below still
81+
* proves they can say no. Only the expected PROSE moved, in the same diff as
82+
* the prose itself, which is the shape this file is for — a wording change that
83+
* does not redden a pin here would mean the pin had stopped reading.
84+
*
85+
* ⚠️ ONE NAME, TWO PACKAGES (ADR-0132 D3) is why the new wording names no
86+
* edition at all: a commercial deployment resolves the same package name to a
87+
* private licence-gated subclass, so any adjective this message picks is wrong
88+
* for one of the two installs reading it. The roster keeps the edition fact,
89+
* and `doctor-organizations-message-spelling.test.ts` leg (ii) still pins it.
90+
*
6991
* ⚠️ This paragraph used to say the literal HAD to stay in `serve.ts`, because
7092
* `serve-cluster-host-resolution.test.ts` resolved the organizations `import()`
7193
* through that static and needed the literal in that file or the load dropped
@@ -152,7 +174,7 @@ describe('serve — the multi-org runtime name an operator READS comes from the
152174
it('site 2 — the "add it to THIS APP" remedy names it, with the spacing intact', () => {
153175
// The other instruction an operator acts on: the app never declared it.
154176
expect(lines(remedyUndeclared())[0]).toBe(
155-
` • add ${PKG} (the enterprise multi-org runtime) to THIS APP`,
177+
` • add ${PKG} (the multi-org runtime) to THIS APP`,
156178
);
157179
});
158180

@@ -164,7 +186,7 @@ describe('serve — the multi-org runtime name an operator READS comes from the
164186
// package's own `exports` names no runtime entry Node can load — and it
165187
// fell into the else leg, rendering the UNDECLARED remedy verbatim:
166188
//
167-
// • add @objectstack/organizations (the enterprise multi-org runtime) to THIS APP
189+
// • add @objectstack/organizations (the multi-org runtime) to THIS APP
168190
// — declare it in the app's package.json and install; the CLI resolves it from the
169191
//
170192
// i.e. "declare it and install it" to an operator who has already done
@@ -264,7 +286,7 @@ describe('serve — the posture description an operator reads names the declarat
264286
expect(verdict.ok, 'the gate accepted a value that is not a posture').toBe(false);
265287
if (verdict.ok) return;
266288
expect(lines(verdict.fatal)).toContain(
267-
` • set OS_TENANCY_POSTURE=isolated — organization wall + the enterprise ${PKG} runtime `
289+
` • set OS_TENANCY_POSTURE=isolated — organization wall + the ${PKG} runtime `
268290
+ "(the legacy spelling 'multi' is accepted and normalizes to this)",
269291
);
270292
});
@@ -301,9 +323,9 @@ describe('#12151 CONTROL — these pins can say no', () => {
301323
// The exact regression the card names: interpolating into a template is
302324
// where a stray space or a lost backtick hides. If this instrument could
303325
// not tell the two apart, every assertion above would be decorative.
304-
const expected = ` • add ${PKG} (the enterprise multi-org runtime) to THIS APP`;
305-
expect(` • add ${PKG}(the enterprise multi-org runtime) to THIS APP`).not.toBe(expected);
306-
expect(` • add ${PKG} (the enterprise multi-org runtime) to THIS APP`).not.toBe(expected);
326+
const expected = ` • add ${PKG} (the multi-org runtime) to THIS APP`;
327+
expect(` • add ${PKG}(the multi-org runtime) to THIS APP`).not.toBe(expected);
328+
expect(` • add ${PKG} (the multi-org runtime) to THIS APP`).not.toBe(expected);
307329
expect(lines(remedyUndeclared())[0]).toBe(expected);
308330
});
309331

‎packages/cli/src/commands/serve.ts‎

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3677,16 +3677,23 @@ export default class Serve extends Command {
36773677
}
36783678
}
36793679

3680-
// Pair: OrganizationsPlugin (multi-org, ENTERPRISE) — must register
3681-
// BEFORE SecurityPlugin. The multi-org runtime (`organization_id`
3680+
// Pair: OrganizationsPlugin (multi-org) — must register BEFORE
3681+
// SecurityPlugin. The multi-org runtime (`organization_id`
36823682
// auto-stamp, per-org seed replay, multi-org default-org bootstrap)
3683-
// lives in the closed-source `@objectstack/organizations` package
3684-
// (ADR-0105 D12; it registers the historical `org-scoping` service
3685-
// SecurityPlugin probes at start() to keep vs strip the wildcard
3686-
// `tenant_isolation` RLS — so registration order matters). Without
3687-
// it, deployments are single-org: the open member-management
3683+
// lives in the `@objectstack/organizations` package (ADR-0105 D12
3684+
// as amended by ADR-0132; it registers the historical `org-scoping`
3685+
// service SecurityPlugin probes at start() to keep vs strip the
3686+
// wildcard `tenant_isolation` RLS — so registration order matters).
3687+
// Without it, deployments are single-org: the open member-management
36883688
// basics (plugin-auth's default-org bootstrap + better-auth
36893689
// invitations) still work.
3690+
// ⚠️ ONE NAME, TWO PACKAGES (ADR-0132 D3): the framework publishes
3691+
// an Apache-2.0 package of that name (`packages/plugins/organizations`),
3692+
// and a commercial deployment resolves the same name to a private,
3693+
// licence-gated subclass of it through its own `workspace:*`
3694+
// declaration. `serve` does not choose between them and must not
3695+
// try — the host app's own manifest decides, which is exactly what
3696+
// the host-anchored importer below reads (#4719).
36903697
// [ADR-0105 D1] Key off the resolved POSTURE, not the legacy boolean.
36913698
// Both walled postures (`group` and `isolated`) need this package:
36923699
// gating on `OS_MULTI_ORG_ENABLED` alone would let
@@ -4076,7 +4083,7 @@ export default class Serve extends Command {
40764083
//
40774084
// #4719 — this used to be a local re-implementation of that read. It was
40784085
// right, and it was the ONLY place in the boot path that asked the question
4079-
// the right way: the enterprise organizations load two blocks up asked
4086+
// the right way: the organizations load two blocks up asked
40804087
// "does it resolve", which a hoisted store answered yes to regardless. Both
40814088
// now go through the one owner in `@objectstack/types/node`, so "declared"
40824089
// cannot mean two different things in one file (Prime Directive #12).
@@ -5441,14 +5448,15 @@ export function formatOrganizationsInstallRemedy(
54415448
' the remedy is in the package, and the cause below is the authority on what it\n' +
54425449
' has to publish — or\n';
54435450
}
5444-
return ` • add ${pkg} (the enterprise multi-org runtime) to THIS APP\n` +
5451+
return ` • add ${pkg} (the multi-org runtime) to THIS APP\n` +
54455452
" — declare it in the app's package.json and install; the CLI resolves it from the\n" +
54465453
' app, not from the framework it is linked out of. Being merely reachable\n' +
54475454
' through NODE_PATH / a hoisted workspace store is deliberately not enough\n' +
54485455
' (#4719) — that made this wall depend on how the process was launched.\n' +
5449-
' NOTE: this runtime is closed-source and is NOT on the public npm registry —\n' +
5450-
' it is distributed with an enterprise / cloud subscription. Without one this\n' +
5451-
' bullet is not followable, and one of the two below is your path — or\n';
5456+
' NOTE: this runtime is Apache-2.0 and published on the public npm registry\n' +
5457+
' (ADR-0132), so this bullet is followable on any install — no subscription.\n' +
5458+
' A commercial deployment resolves the same package name to its own private,\n' +
5459+
' licence-gated build; the manifest that declares it decides which — or\n';
54525460
}
54535461

54545462
/**

‎packages/cli/src/commands/verify.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export default class Verify extends Command {
8787
default: false,
8888
}),
8989
'multi-tenant': Flags.boolean({
90-
description: 'Boot org-scoped (register the enterprise @objectstack/organizations plugin) so tenant-isolation RLS policies apply (also honors a walled $OS_TENANCY_POSTURE, and the legacy $OS_MULTI_ORG_ENABLED it falls back to)',
90+
description: 'Boot org-scoped (register the @objectstack/organizations plugin) so tenant-isolation RLS policies apply (also honors a walled $OS_TENANCY_POSTURE, and the legacy $OS_MULTI_ORG_ENABLED it falls back to)',
9191
default: false,
9292
}),
9393
json: Flags.boolean({ description: 'Emit the structured report as JSON', default: false }),

‎packages/cli/src/utils/tenancy-posture-hints.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,6 @@ export const TENANCY_POSTURE_FIX_HINTS: Readonly<Record<string, string>> = {
136136
single: 'one organization, no organization wall — the default',
137137
group: 'organization wall enforced by the open engine, one shared database',
138138
isolated:
139-
`organization wall + the enterprise ${ORGANIZATIONS_RUNTIME_PKG} runtime `
139+
`organization wall + the ${ORGANIZATIONS_RUNTIME_PKG} runtime `
140140
+ "(the legacy spelling 'multi' is accepted and normalizes to this)",
141141
};

‎packages/cli/test/serve-organizations-mount-failure.e2e.test.ts‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/**
44
* #4818 — `os serve` must tell an operator WHICH of two different things went
5-
* wrong with the enterprise multi-org runtime, over the REAL CLI process.
5+
* wrong with the multi-org runtime, over the REAL CLI process.
66
*
77
* The defect: `importFromHost('@objectstack/organizations')` and
88
* `kernel.use(new mod.OrganizationsPlugin())` shared a single `try`, so an
@@ -27,10 +27,13 @@
2727
* `serve-organizations-host-resolution.e2e.test.ts`): every other test of the
2828
* walled postures hands the plugin in as `extraPlugins` or mocks the module,
2929
* which bypasses the CLI's own load/mount sequence — the only thing under test
30-
* here. The fixtures stand in for the closed-source enterprise package: one app
31-
* simply does not ship it, another ships a version whose plugin throws on
32-
* construction (the shape cloud#1020 gave its license gate). What is asserted
33-
* is the CLI's CLASSIFICATION and its message, not any enterprise semantics.
30+
* here. The fixtures stand in for a mounted multi-org runtime: one app simply
31+
* does not ship it, another ships a version whose plugin throws on construction
32+
* — which is specifically the COMMERCIAL package's shape, since ADR-0132 D2
33+
* leaves construction-time licence refusal to the private subclass and the open
34+
* `packages/plugins/organizations` carries no licence check of any kind (the
35+
* shape cloud#1020 gave its license gate). What is asserted is the CLI's
36+
* CLASSIFICATION and its message, not any runtime's semantics.
3437
*/
3538

3639
import { describe, it, expect, beforeAll, afterAll } from 'vitest';

‎packages/core/src/security/api-key.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,8 @@ describe('resolveApiKeyAdmission — organization (#8287)', () => {
195195

196196
/**
197197
* The posture must be the ENFORCED one, not the requested one. ADR-0093 D4/D5:
198-
* a deployment that asks for `isolated` without the enterprise organizations
199-
* runtime runs with NO wall, and `tenancy.isolationActive` is how the service
198+
* a deployment that asks for `isolated` without any organizations runtime
199+
* runs with NO wall, and `tenancy.isolationActive` is how the service
200200
* says so. Reading `OS_TENANCY_POSTURE` instead would refuse org-less keys on
201201
* a deployment that has no wall at all.
202202
*/

‎packages/core/src/security/api-key.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export interface TenancyPostureSource {
217217
* ⚠️ Deliberately NOT `resolveTenancyPosture()` from `@objectstack/types`, which
218218
* reads `OS_TENANCY_POSTURE` directly. That answers what the operator ASKED
219219
* for, not what is ENFORCED: under ADR-0093 D4/D5 a deployment that requests
220-
* `isolated` without the enterprise `@objectstack/organizations` runtime
220+
* `isolated` without the `@objectstack/organizations` runtime
221221
* resolves to `single` and runs with NO organization wall. Reading the env
222222
* there would refuse org-less API keys on a deployment whose wall is not even
223223
* active — breaking working automation to enforce a boundary that does not

‎packages/mcp/src/stdio-tenancy-posture-api-key-matrix.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ describe('[#15348] §5 — the posture and the membership are both re-read per c
543543
const { bridge } = await h.start();
544544
expect((await readAll(bridge)).total).toBe(2);
545545

546-
// The enterprise multi-org runtime registers and the wall goes live.
546+
// The multi-org runtime registers and the wall goes live.
547547
service.posture = 'isolated';
548548

549549
await expect(bridge.query(OBJECT, {})).rejects.toThrow(/no longer valid/);

0 commit comments

Comments
 (0)