Skip to content

Commit 73d96a8

Browse files
committed
docs(adr): anchor the multi-org entitlement to ADR-0105 D12 (#8531 claim D)
"Multi-organization operation is a commercial ENTITLEMENT" was cited in 15 places as "ADR-0081 D2". ADR-0081 in this repo is the trusted `kind:'react'` page tier; its Decision section is numbered 1-4, so "D2" names nothing in it. The label is inherited from a pre-repo decision record whose number now collides -- the same collision ADR-0093 D9 named for the "ADR-0081 D1" half. ADR-0105 D12 (Edition split, as amended 2026-07-27 by the founder ruling in #3570) already IS this decision: posture activation is entitled, both walled postures probe `@objectstack/organizations` to activate, and without it the tenancy service resolves to single + degraded. No anchor is invented here. The repo had already converged on that anchor everywhere else -- auth-plugin.ts, harness.posture-only.test.ts, harness.host-resolution.test.ts, the serve e2e test and content/docs/deployment/tenancy-modes.mdx all cite ADR-0105 D12 for this exact claim. These 15 were the stragglers. Re-points 13 sites to ADR-0105 D12. The 2 sites inside ADR-0105 itself cannot cite ADR-0105 D12 -- they ARE D12 -- and both sentences refer to the inherited prior line D12 affirms, so they are de-numbered descriptively instead, matching the language ADR-0093 already uses for the same pre-repo record. D12 gains a citation note (hygiene, explicitly not a decision) warning that the anchor is D12, not this ADR's own D2 (`accessible_org_ids`). Claims B and C of #8531 (`ensureDefaultOrganization` bootstrap, the org capability gate) are untouched -- all 22 `ADR-0081 D1` sites remain as they are, including those in files edited here. Both need a new owning record, which is a maintainer act. Behaviour unchanged: 10 comments, 3 documentation strings, no logic. Part of #8531 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011RB4waLuNbdruCo6X9oobm
1 parent c0525b2 commit 73d96a8

10 files changed

Lines changed: 41 additions & 21 deletions

File tree

docs/adr/0105-group-tenancy-posture-and-first-class-org-scope.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,31 @@ enterprise `org-scoping` runtime (`@objectstack/organizations`) exactly like
331331
start** (the ADR-0093 D5 guard, keyed off the resolved posture) rather than
332332
silently running unwalled. The iron rule guarantees a deployment *running* a
333333
multi-org shape is safe by default; that is satisfied by refusing to run one
334-
unwalled — **open code is not free activation**. ADR-0081 D2's commercial
335-
line stands: both multi-org postures are `@objectstack/organizations`
336-
capability. Commercial surface (unchanged): org lifecycle management,
337-
grouping/registry UI, scoped invitations UX, cross-org approval templates,
338-
master-data distribution management, per-org seed/config replay, org
339-
analytics, and the D13 promotion tooling.
334+
unwalled — **open code is not free activation**. The inherited commercial
335+
line stands, and **this decision is now its owner**: both multi-org postures
336+
are `@objectstack/organizations` capability. Commercial surface (unchanged):
337+
org lifecycle management, grouping/registry UI, scoped invitations UX,
338+
cross-org approval templates, master-data distribution management, per-org
339+
seed/config replay, org analytics, and the D13 promotion tooling.
340+
341+
> **Citation note (2026-08-16) — hygiene, not a decision.** Code and tests
342+
> carried this entitlement as **"ADR-0081 D2"**, a label inherited from a
343+
> decision record that predates this repo's ADR series — the same pre-repo
344+
> record whose "ADR-0081 D1" label [ADR-0093](./0093-tenancy-mode-and-membership-lifecycle.md)
345+
> D9 names. That number now collides with this repo's
346+
> [ADR-0081](./0081-trusted-react-page-tier.md), the trusted `kind:'react'`
347+
> page tier, whose Decision section is numbered 1–4 and has no D-numbered
348+
> decisions at all — so a reader following the citation landed in a document
349+
> about React pages with no signal they were in the wrong record. **D12 is the
350+
> anchor** for "enabling multi-organization operation is an entitlement": it
351+
> is the accepted, repo-local decision that both walled postures probe
352+
> `@objectstack/organizations` to activate, and the #3570 amendment below is
353+
> the founder ruling that settled it. Nothing about the decision changes here;
354+
> it simply stops being cited by a number that resolves elsewhere.
355+
>
356+
> ⚠️ The anchor is **D12**, not `D2` — this ADR's own **D2** is
357+
> `accessible_org_ids`, an unrelated kernel decision. The one-character slip
358+
> reproduces the exact defect this note closes.
340359
341360
> **Amendment (2026-07-27, #3570).** As proposed, this section read "the
342361
> `group` wall ships open — an open deployment configured into the group
@@ -345,8 +364,9 @@ analytics, and the D13 promotion tooling.
345364
> entitlement probe. That was the founder decision flagged in #3559, and the
346365
> founder ruled it wrong on review. Two defects in the original reading:
347366
> it made the *stronger* multi-org posture (union wall) free while the
348-
> *weaker* one (`isolated`) stayed entitled — inverting ADR-0081 D2 and
349-
> handing out a free path around the `org-scoping` gate — and it opened a
367+
> *weaker* one (`isolated`) stayed entitled — inverting the inherited
368+
> commercial line and handing out a free path around the `org-scoping` gate
369+
> — and it opened a
350370
> silent-degradation hole (`os serve` gated the enterprise package load on
351371
> `OS_MULTI_ORG_ENABLED`, so `OS_TENANCY_POSTURE=group` skipped both the
352372
> load and the ADR-0093 D5 fail-fast, booting single-org without saying so).

packages/cli/src/commands/serve.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2403,7 +2403,7 @@ export default class Serve extends Command {
24032403
// BEFORE SecurityPlugin. The multi-org runtime (`organization_id`
24042404
// auto-stamp, per-org seed replay, multi-org default-org bootstrap)
24052405
// lives in the closed-source `@objectstack/organizations` package
2406-
// (ADR-0081 D2; it registers the historical `org-scoping` service
2406+
// (ADR-0105 D12; it registers the historical `org-scoping` service
24072407
// SecurityPlugin probes at start() to keep vs strip the wildcard
24082408
// `tenant_isolation` RLS — so registration order matters). Without
24092409
// it, deployments are single-org: the open member-management

packages/plugins/plugin-auth/src/tenancy-service.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('createTenancyService', () => {
6161
expect(t.degraded).toBe(false);
6262
});
6363

64-
// [ADR-0105 D1 / ADR-0081 D2] Multi-organization operation is an ENTITLEMENT.
64+
// [ADR-0105 D1 / ADR-0105 D12] Multi-organization operation is an ENTITLEMENT.
6565
// The wall's code is open, but activating either walled posture requires the
6666
// enterprise org-scoping runtime — otherwise `group` would be a free multi-org
6767
// back door around the `isolated` gate. The iron rule (cloud ADR-0016) is

packages/plugins/plugin-auth/src/tenancy-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* DEGRADED without it. The wall's implementation is open — the Layer 0
3434
* compiler, `accessible_org_ids` resolution and the D5 write
3535
* stamping/validation all ship in open packages — but multi-organization
36-
* operation is a commercial capability (ADR-0081 D2).
36+
* operation is a commercial capability (ADR-0105 D12).
3737
*
3838
* That is not in tension with cloud ADR-0016's 铁律 (强制免费、治理收费): the rule
3939
* guarantees a deployment RUNNING a multi-org shape is safe, which is satisfied
@@ -186,7 +186,7 @@ export function createTenancyService(deps: TenancyServiceDeps): TenancyService {
186186
* BOTH walled postures probe. The wall's CODE is open (the Layer 0 compiler,
187187
* `accessible_org_ids` resolution and D5 stamping all live in open packages),
188188
* but ENABLING a multi-organization posture is an entitlement — exactly the
189-
* shape `isolated` has had since ADR-0081 D2. Open code, entitled activation:
189+
* shape `isolated` has always had (ADR-0105 D12). Open code, entitled activation:
190190
* the two are separate questions, and conflating them is what briefly made
191191
* `group` a free multi-org back door around the `isolated` gate.
192192
*

packages/plugins/plugin-dev/src/dev-plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ export class DevPlugin implements Plugin {
645645

646646
// 5. Security Plugin (RBAC, RLS, field-level masking)
647647
// OrganizationsPlugin (when multi-org; ENTERPRISE `@objectstack/organizations`,
648-
// ADR-0081 D2) MUST register BEFORE SecurityPlugin because
648+
// ADR-0105 D12) MUST register BEFORE SecurityPlugin because
649649
// SecurityPlugin.start() probes the `org-scoping` service (the historical
650650
// name the enterprise plugin keeps registering) and caches the result for
651651
// the lifetime of the plugin.

packages/qa/dogfood/test/authz-conformance.matrix.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export const AUTHZ_CONFORMANCE: AuthzPrimitive[] = [
9797
// unit-proven at the plugin-security layer, like the #2937 row above. ──
9898
{ id: 'tenancy-posture-spectrum', summary: 'three tenancy postures — single | group | isolated (ADR-0105 D1/D2)', state: 'enforced',
9999
enforcement: 'plugin-auth/tenancy-service.ts resolves the posture in force; plugin-security/tenant-layer.ts computeTenantLayer0Filter switches the Layer 0 predicate on it — inert (single), `organization_id IN accessible_org_ids` (group, MOAC union), `organization_id = activeOrganizationId` (isolated); empty/absent scope → RLS_DENY_FILTER',
100-
note: 'Only the PREDICATE widens; composition is untouched — Layer 0 is still computed independently of the RLS compiler, AND-composed outermost, and crossable only by a true PLATFORM_ADMIN on a posture-permitting object, so ADR-0095 W1/W2 hold in every posture. BOTH walled postures require the enterprise @objectstack/organizations runtime to ACTIVATE and can resolve degraded without it (ADR-0081 D2): the wall\'s IMPLEMENTATION is open, but enabling multi-organization operation is an entitlement, and the runtime may narrow it further via `supportedPostures`. Cloud ADR-0016\'s 铁律 is satisfied by REFUSING to run an unwalled multi-org deployment (ADR-0093 D5), not by giving the posture away. Unit-proven in plugin-security/tenant-layer.test.ts and plugin-auth/tenancy-service.test.ts.' },
100+
note: 'Only the PREDICATE widens; composition is untouched — Layer 0 is still computed independently of the RLS compiler, AND-composed outermost, and crossable only by a true PLATFORM_ADMIN on a posture-permitting object, so ADR-0095 W1/W2 hold in every posture. BOTH walled postures require the enterprise @objectstack/organizations runtime to ACTIVATE and can resolve degraded without it (ADR-0105 D12): the wall\'s IMPLEMENTATION is open, but enabling multi-organization operation is an entitlement, and the runtime may narrow it further via `supportedPostures`. Cloud ADR-0016\'s 铁律 is satisfied by REFUSING to run an unwalled multi-org deployment (ADR-0093 D5), not by giving the posture away. Unit-proven in plugin-security/tenant-layer.test.ts and plugin-auth/tenancy-service.test.ts.' },
101101
{ id: 'accessible-org-ids', summary: 'accessible_org_ids — core-resolved org access set (ADR-0105 D2)', state: 'enforced',
102102
enforcement: 'core/security/resolve-authz-context.ts resolveUserAuthzGrants reads every sys_member row for the user under ADR-0091 validity windows; carried on ExecutionContext by every transport (rest-server, runtime resolve-execution-context, mcp, hono) and read directly by the Layer 0 group wall',
103103
note: 'ONE read serves both the active-org position projection and the full membership set, so the two facts cannot disagree. A transport that fails to carry the set denies under `group` rather than falling back to the active org — the wall must not depend on which surface the request arrived through. Delegated (on-behalf-of) reads resolve the DELEGATOR\'s own set (explain-engine buildContextForUser), never inherit the live principal\'s.' },

packages/qa/dogfood/test/enterprise-organizations.ts

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

33
/**
44
* #4700 — availability of the enterprise `@objectstack/organizations` package
5-
* (ADR-0081 D2), for the dogfood gates that can only run multi-org.
5+
* (ADR-0105 D12), for the dogfood gates that can only run multi-org.
66
*
77
* ── The defect this replaces ─────────────────────────────────────────────────
88
*
@@ -68,7 +68,7 @@
6868

6969
import { createHostImporter, hostImportFailureKind } from '@objectstack/types/node';
7070

71-
/** The cloud-private enterprise package (ADR-0081 D2). */
71+
/** The cloud-private enterprise package (ADR-0105 D12). */
7272
export const ORGANIZATIONS_PKG = '@objectstack/organizations';
7373

7474
/**
@@ -114,7 +114,7 @@ export async function probeOrganizations(
114114
'reachable as somebody else\'s transitive dependency is not enough (#4719)';
115115
if (declared) {
116116
throw new Error(
117-
`${MULTI_ORG_ENV}=1 declares that ${ORGANIZATIONS_PKG} (enterprise, ADR-0081 D2) is ` +
117+
`${MULTI_ORG_ENV}=1 declares that ${ORGANIZATIONS_PKG} (enterprise, ADR-0105 D12) is ` +
118118
`installed for this run, but it could not be resolved from ${root}. ` +
119119
'Refusing to skip the multi-org dogfood gates silently: a run that believes it is ' +
120120
'exercising cross-tenant isolation and is not would report green over gates that ' +

packages/qa/dogfood/test/rls-multitenant.dogfood.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import { bootStack, type VerifyStack } from '@objectstack/verify';
5454
import { runRlsProofs, formatRlsReport, type RlsReport } from '@objectstack/verify';
5555

5656
// The multi-org runtime moved to the ENTERPRISE `@objectstack/organizations`
57-
// package (ADR-0081 D2) — not part of this open workspace. Skip (loudly) when
57+
// package (ADR-0105 D12) — not part of this open workspace. Skip (loudly) when
5858
// it isn't linked in; enterprise/cloud CI, which ships the package, runs this.
5959
//
6060
// #4700: the probe used to be a bare `import()`, which Node ESM resolves against

packages/spec/src/security/tenancy-posture.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
* The wall's IMPLEMENTATION is open — the Layer 0 compiler, `accessible_org_ids`
2626
* resolution and the D5 write stamping all ship in open packages — but ENABLING
2727
* a multi-organization posture (`group` or `isolated`) requires the enterprise
28-
* `@objectstack/organizations` runtime (ADR-0081 D2). The two are separate
28+
* `@objectstack/organizations` runtime (ADR-0105 D12). The two are separate
2929
* questions and must not be conflated: open code does not mean free activation.
3030
*
3131
* Cloud ADR-0016's 铁律 (强制免费、治理收费) is satisfied by the first half alone —

packages/verify/src/harness.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ export async function bootStack(
445445
}
446446

447447
// Multi-org: the enterprise OrganizationsPlugin (`@objectstack/organizations`,
448-
// ADR-0081 D2) MUST register BEFORE SecurityPlugin — the latter probes the
448+
// ADR-0105 D12) MUST register BEFORE SecurityPlugin — the latter probes the
449449
// `org-scoping` service (the historical name the enterprise plugin keeps
450450
// registering) exactly once at start and caches it, then keeps (vs strips)
451451
// the wildcard `organization_id` RLS policies accordingly. Mirrors the CLI's
@@ -493,7 +493,7 @@ export async function bootStack(
493493
'package.json, which is what is actually checked: a package merely reachable through ' +
494494
'NODE_PATH or a hoisted workspace store is not accepted (#4719) — to run multi-org fixtures.';
495495
throw new Error(
496-
'verify: multiTenant=true requires the enterprise @objectstack/organizations package (migrated from plugin-org-scoping, ADR-0081 D2). ' +
496+
'verify: multiTenant=true requires the enterprise @objectstack/organizations package (migrated from plugin-org-scoping, ADR-0105 D12). ' +
497497
`${remedy} (${(e as Error).message})`,
498498
);
499499
}

0 commit comments

Comments
 (0)