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
Bring single-database multi-organization isolation back to the open core — the wall is already open, only the org-scoping switch is closed (~1350 of 1660 lines move; the licence gate stays) #16130
Chartered by the maintainer, 2026-09-06, live chat, verbatim: 「感觉 单库多组织隔离是开源基本需求,如果迁移回开源项目成本有多大」 then 「直接立专题卡派发处理吧」. ⇒ The cost question is answered below by measurement; this card is the work.
The finding that makes this cheap
The wall is already open. What is closed is the switch.
Layer 0 (plugin-security/src/tenant-layer.ts, computeTenantLayer0Filter), the three postures in packages/spec, the API-key and session membership guards, ADR-0105's group posture — all open source. Three cross-organization defects were found and fixed in the open tree this week (#15256, #15409, cloud#1987's framework half). What an open-source deployment cannot do is turn the wall on, because the org-scoping service has exactly one registrar anywhere: the cloud-private @objectstack/organizations.
⭐ And it went the other way once, deliberately: organizations-plugin.ts's own docblock says it was "migrated verbatim from the open plugin-org-scoping" (ADR-0081 D2). The open core still consumes it unchanged — getService('org-scoping') and requiresService: 'org-scoping' nav gates — and the service name was kept on purpose so those anchors would not break. ⇒ No consumer-side change is needed.
Measured on cloud origin/main, 2026-09-06
8 non-test source files, 1660 lines, 8 test files. The licence headers already draw the boundary:
file
lines
header
organizations-plugin.ts
508
proprietary
membership-policy-gate.ts
416
proprietary
multi-org-license-gate.ts
306
proprietary
index.ts
107
proprietary
manifest.ts
28
proprietary
claim-orphan-org-rows.ts
135
Apache-2.0
claim-org-seed-ownership.ts
116
Apache-2.0
ensure-default-organization.ts
44
Apache-2.0
Coupling to cloud is exactly two points, both entitlement: multi-org-license-gate.ts imports security-enterprise's license/entitlement.ts; index.ts:41 carries a bare import '@objectstack/security-enterprise' to mount the multi-node authorization gate as a side effect (cloud#1827). Everything else imports framework packages only — 4 of the package's 5 dependencies are link: into this repo.
⇒ ~1350 lines move; 306 stay.
ADR-0081 already split this once: D1's open member-management basics landed here (plugin-auth's 415-line ensureDefaultOrganization), leaving cloud a 44-line multi-org wrapper.
⛔ Hard boundaries — these do NOT move
multi-org-license-gate.ts stays in cloud. It is the entitlement, and it exists because the gate must be answered by the package that implements multi-org, at construction — cloud#1020 records the previous shape (an env rewrite in a host config) failing exactly when ADR-0105 D1 made the posture authoritative. ⛔ Do not move it, do not reimplement it here, and ⛔ do not leave a hook that a host could use to re-add one.
⛔ The open package must not gain a licence check of any kind. If enforcement is wanted, it is cloud's, on cloud's side of the split.
⚠️ This reverses a recorded decision — the record moves with the code
ADR-0081 D2 put the multi-org machinery in enterprise on purpose. Landing this without amending it leaves the tree contradicting a live ADR. The open mirror of that record is #14508 (pm:queue, unstarted). ⇒ The ADR amendment is part of this work, not a follow-up: docs/adr/** is a governed surface — draft PR, human merge.
Phase 1 — classify before moving. ⛔ Do not open a code PR first.
Read all 1660 lines and produce, on this card, a line-level disposition for every one: move as-is / move with changes (say which) / stays in cloud / deleted. Two files decide whether the estimate above survives:
organizations-plugin.ts (508) — what it registers beyond the service, and what of that is cloud-shaped.
membership-policy-gate.ts (416) — a boot-time membership-policy assertion, and the one file whose depth this seat did not measure. ⚠️ If it encodes enterprise-only assumptions, the estimate is wrong and this card's shape changes.
⛔ Stop and report rather than proceeding if: the classification finds a third coupling point; membership-policy-gate.ts cannot be separated from entitlement; or the open package would need any part of security-enterprise.
Phase 2 — the move, once phase 1 is accepted
New open package (name it after the classification — plugin-org-scoping is the historical one and the anchors expect the service name org-scoping, not a package name). Cloud keeps the gate and imports the open package. Both sides' tests move with their code. ⛔ The service name org-scoping does not change — the open core's nav gates and getService calls are anchored on it.
Acceptance: an open-source-only install with OS_TENANCY_POSTURE=isolated boots with the wall ACTIVE and enforces it — the same matrix the three cross-organization repairs pinned (an ex-member's API key and session are refused; a member reads only its own organization). ⚠️ That matrix is the point of the whole card: today an open install cannot run it at all.
Refs: cloud packages/organizations · cloud ADR-0081 D1/D2 · #14508 (the open mirror) · cloud#1020 (why the gate lives where it lives) · #14114 / cloud#1827 (multi-node carrier) · ADR-0105 D1 · #15256 / #15409 (the wall's open-source repairs).
Chartered by the maintainer, 2026-09-06, live chat, verbatim: 「感觉 单库多组织隔离是开源基本需求,如果迁移回开源项目成本有多大」 then 「直接立专题卡派发处理吧」. ⇒ The cost question is answered below by measurement; this card is the work.
The finding that makes this cheap
The wall is already open. What is closed is the switch.
Layer 0 (
plugin-security/src/tenant-layer.ts,computeTenantLayer0Filter), the three postures inpackages/spec, the API-key and session membership guards, ADR-0105's group posture — all open source. Three cross-organization defects were found and fixed in the open tree this week (#15256, #15409, cloud#1987's framework half). What an open-source deployment cannot do is turn the wall on, because theorg-scopingservice has exactly one registrar anywhere: the cloud-private@objectstack/organizations.⭐ And it went the other way once, deliberately:
organizations-plugin.ts's own docblock says it was "migrated verbatim from the openplugin-org-scoping" (ADR-0081 D2). The open core still consumes it unchanged —getService('org-scoping')andrequiresService: 'org-scoping'nav gates — and the service name was kept on purpose so those anchors would not break. ⇒ No consumer-side change is needed.Measured on cloud
origin/main, 2026-09-068 non-test source files, 1660 lines, 8 test files. The licence headers already draw the boundary:
organizations-plugin.tsmembership-policy-gate.tsmulti-org-license-gate.tsindex.tsmanifest.tsclaim-orphan-org-rows.tsclaim-org-seed-ownership.tsensure-default-organization.tsCoupling to cloud is exactly two points, both entitlement:
multi-org-license-gate.tsimportssecurity-enterprise'slicense/entitlement.ts;index.ts:41carries a bareimport '@objectstack/security-enterprise'to mount the multi-node authorization gate as a side effect (cloud#1827). Everything else imports framework packages only — 4 of the package's 5 dependencies arelink:into this repo.⇒ ~1350 lines move; 306 stay.
ADR-0081 already split this once: D1's open member-management basics landed here (
plugin-auth's 415-lineensureDefaultOrganization), leaving cloud a 44-line multi-org wrapper.⛔ Hard boundaries — these do NOT move
multi-org-license-gate.tsstays in cloud. It is the entitlement, and it exists because the gate must be answered by the package that implements multi-org, at construction — cloud#1020 records the previous shape (an env rewrite in a host config) failing exactly when ADR-0105 D1 made the posture authoritative. ⛔ Do not move it, do not reimplement it here, and ⛔ do not leave a hook that a host could use to re-add one.MULTI_NODE_GATE_CARRIER_PACKAGES(fix(service-cluster,cli): multi-node gate fails closed when unregistered, and mounts on every boot route #14114) names both packages precisely so a deployment declares whichever it needs. Whatever remains in cloud must keep carrying it; the open package must not acquire it.ADR-0081 D2 put the multi-org machinery in enterprise on purpose. Landing this without amending it leaves the tree contradicting a live ADR. The open mirror of that record is #14508 (
pm:queue, unstarted). ⇒ The ADR amendment is part of this work, not a follow-up:docs/adr/**is a governed surface — draft PR, human merge.Phase 1 — classify before moving. ⛔ Do not open a code PR first.
Read all 1660 lines and produce, on this card, a line-level disposition for every one: move as-is / move with changes (say which) / stays in cloud / deleted. Two files decide whether the estimate above survives:
organizations-plugin.ts(508) — what it registers beyond the service, and what of that is cloud-shaped.membership-policy-gate.ts(416) — a boot-time membership-policy assertion, and the one file whose depth this seat did not measure.⛔ Stop and report rather than proceeding if: the classification finds a third coupling point;
membership-policy-gate.tscannot be separated from entitlement; or the open package would need any part ofsecurity-enterprise.Phase 2 — the move, once phase 1 is accepted
New open package (name it after the classification —
plugin-org-scopingis the historical one and the anchors expect the service nameorg-scoping, not a package name). Cloud keeps the gate and imports the open package. Both sides' tests move with their code. ⛔ The service nameorg-scopingdoes not change — the open core's nav gates andgetServicecalls are anchored on it.Acceptance: an open-source-only install with⚠️ That matrix is the point of the whole card: today an open install cannot run it at all.
OS_TENANCY_POSTURE=isolatedboots with the wall ACTIVE and enforces it — the same matrix the three cross-organization repairs pinned (an ex-member's API key and session are refused; a member reads only its own organization).Refs: cloud
packages/organizations· cloud ADR-0081 D1/D2 · #14508 (the open mirror) · cloud#1020 (why the gate lives where it lives) · #14114 / cloud#1827 (multi-node carrier) · ADR-0105 D1 · #15256 / #15409 (the wall's open-source repairs).