|
1 | 1 | # ADR-0105: Group Tenancy Posture — Organization Scope as a First-Class Authorization Dimension |
2 | 2 |
|
3 | | -**Status**: Proposed (2026-07-25, draft) |
| 3 | +**Status**: Accepted (2026-07-27; proposed 2026-07-25) — Phase 0/1 implemented (#3559). Amended 2026-07-27: **D12 correction** — `group` posture activation is entitled, not open (#3570; see the D12 Amendment) |
4 | 4 | **Deciders**: ObjectStack Protocol Architects |
5 | 5 | **Builds on**: [ADR-0049](./0049-no-unenforced-security-properties.md) (enforce-or-remove), [ADR-0057](./0057-erp-authorization-core-business-units-and-scope-depth.md) (business units + scope depth), [ADR-0066](./0066-unified-authorization-model.md) (unified authz, superuser bypass), [ADR-0086](./0086-authz-metadata-config-boundary-and-cross-package-composition.md), [ADR-0090](./0090-permission-model-v2-concept-convergence.md) (permission set / position / business unit), [ADR-0091](./0091-grant-lifecycle-and-recertification.md) (validity windows), [ADR-0092](./0092-sys-user-profile-field-delegation.md) (identity write guard + field whitelist), [ADR-0093](./0093-tenancy-mode-and-membership-lifecycle.md) (tenancy service), [ADR-0095](./0095-authz-kernel-tenant-layer-and-posture-ladder.md) (tenant Layer 0, posture ladder), [ADR-0103](./0103-managedby-write-policy-and-engine-write-guard.md); cloud ADR-0016 (open/paid boundary: 强制免费、治理收费), cloud ADR-0081 (`@objectstack/organizations`) |
6 | | -**Tracking**: issue to be filed on acceptance; P0 findings F1/F2 below warrant their own issues immediately |
| 6 | +**Tracking**: #3541 (P0 findings F1/F2 became #3539/#3540, closed by #3559); cloud-side tracking cloud #874 |
7 | 7 | **Consumers**: `@objectstack/plugin-security`, `@objectstack/core` (`resolve-authz-context`), `@objectstack/plugin-auth` (tenancy service), `@objectstack/plugin-sharing`, `@objectstack/plugin-approvals`, `@objectstack/lint`, dogfood conformance suite, `@objectstack/organizations` (cloud) |
8 | 8 |
|
9 | 9 | --- |
@@ -280,16 +280,41 @@ reserves the concept and its place in Phase 2. |
280 | 280 | with its own ADR. |
281 | 281 |
|
282 | 282 | **D12 — Edition split, per the cloud ADR-0016 iron rule (强制免费、治理收费).** |
283 | | -Enforcement primitives ship open: D3/D4 correctness, the `group` wall + D5 |
284 | | -stamping/validation, `accessible_org_ids` resolution, the D6 red-line lints. |
285 | | -An open deployment configured into the group shape must be safe by default — |
286 | | -the wall's correctness is never paid. Commercial (`@objectstack/organizations` |
287 | | -and successors): org lifecycle management, grouping/registry UI, scoped |
288 | | -invitations UX, cross-org approval templates, master-data distribution |
289 | | -management, per-org seed/config replay, org analytics, and the D13 promotion |
290 | | -tooling. (This re-draws the current line, under which the wall itself lives in |
291 | | -the commercial package; the correctness floor moves into the open edition, the |
292 | | -management surface stays commercial.) |
| 283 | +*(As amended 2026-07-27, #3570 — see the Amendment below for the original |
| 284 | +text and why it was wrong.)* The split is **code vs. activation**, not code |
| 285 | +vs. code. The wall's *implementation* ships open — D3/D4 correctness, the |
| 286 | +Layer 0 predicates, D5 stamping/validation, `accessible_org_ids` resolution, |
| 287 | +the D6 red-line lints — exactly as `isolated`'s wall has always lived in |
| 288 | +`plugin-security`. Posture *activation* is entitled: `group` probes the |
| 289 | +enterprise `org-scoping` runtime (`@objectstack/organizations`) exactly like |
| 290 | +`isolated`; without it the tenancy service resolves the posture to `single` + |
| 291 | +`degraded`, and an `os serve` boot configured for `group` **refuses to |
| 292 | +start** (the ADR-0093 D5 guard, keyed off the resolved posture) rather than |
| 293 | +silently running unwalled. The iron rule guarantees a deployment *running* a |
| 294 | +multi-org shape is safe by default; that is satisfied by refusing to run one |
| 295 | +unwalled — **open code is not free activation**. ADR-0081 D2's commercial |
| 296 | +line stands: both multi-org postures are `@objectstack/organizations` |
| 297 | +capability. Commercial surface (unchanged): org lifecycle management, |
| 298 | +grouping/registry UI, scoped invitations UX, cross-org approval templates, |
| 299 | +master-data distribution management, per-org seed/config replay, org |
| 300 | +analytics, and the D13 promotion tooling. |
| 301 | + |
| 302 | +> **Amendment (2026-07-27, #3570).** As proposed, this section read "the |
| 303 | +> `group` wall ships open — an open deployment configured into the group |
| 304 | +> shape must be safe by default; the wall's correctness is never paid", and |
| 305 | +> #3559 implemented that reading: a self-activating `group` posture with no |
| 306 | +> entitlement probe. That was the founder decision flagged in #3559, and the |
| 307 | +> founder ruled it wrong on review. Two defects in the original reading: |
| 308 | +> it made the *stronger* multi-org posture (union wall) free while the |
| 309 | +> *weaker* one (`isolated`) stayed entitled — inverting ADR-0081 D2 and |
| 310 | +> handing out a free path around the `org-scoping` gate — and it opened a |
| 311 | +> silent-degradation hole (`os serve` gated the enterprise package load on |
| 312 | +> `OS_MULTI_ORG_ENABLED`, so `OS_TENANCY_POSTURE=group` skipped both the |
| 313 | +> load and the ADR-0093 D5 fail-fast, booting single-org without saying so). |
| 314 | +> The iron rule's obligation is a *security* property (never run a multi-org |
| 315 | +> shape unwalled), not a *packaging* one (give the posture away). #3570 |
| 316 | +> restored the entitled model above; safety stays free because an unwalled |
| 317 | +> `group` boot is refused, not run. |
293 | 318 |
|
294 | 319 | **D13 — Migration along the spectrum; BU-subtree → organization promotion.** |
295 | 320 | Deployments move `single → group → isolated` without schema rework because |
@@ -371,8 +396,10 @@ boundary honest per cloud ADR-0016. |
371 | 396 | **Negative / costs**: a third posture multiplies the authz test matrix (every |
372 | 397 | conformance row × posture); `accessible_org_ids` adds a resolver read path |
373 | 398 | that must respect ADR-0091 validity windows and the existing 60s cache |
374 | | -staleness envelope; the D12 line re-draw moves wall enforcement into the open |
375 | | -edition — a deliberate commercial concession justified by the iron rule; D13 |
| 399 | +staleness envelope; the D12 split is code-vs-activation — the wall's |
| 400 | +implementation is open while both multi-org postures stay entitled (the |
| 401 | +original open-activation concession shipped in #3559 and was reverted by |
| 402 | +#3570, see the D12 Amendment); D13 |
376 | 403 | promotion is a data migration with real blast radius and needs its own |
377 | 404 | verification harness. |
378 | 405 |
|
|
0 commit comments