@@ -42,13 +42,17 @@ Decision:
4242 heuristic from PR #2882 is retired.
4343- ** D4** — A ** ` tenancy ` kernel service** becomes the single source of truth:
4444 ` { mode, isolationActive, requested, degraded, defaultOrgId() } ` . plugin-auth
45- registers the baseline; ` @objectstack/organizations ` upgrades it. Every current
46- consumer (SecurityPlugin RLS stripping, SQL-driver tenant audit gate,
47- ` /auth/config ` features, CLI wiring) migrates to it.
45+ registers the baseline; ` @objectstack/organizations ` upgrades it. The
46+ service-layer consumers migrate to it (SecurityPlugin RLS stripping,
47+ ` /auth/config ` features, the reconciler); the two sites that sit * beneath* or
48+ * before* the service — the SQL driver's dev-warning gate and the ` serve.ts `
49+ boot guard — keep reading the env flag by design (see D4).
4850- ** D5** — The degraded middle state ** fails fast** : ` OS_MULTI_ORG_ENABLED=true `
4951 without a working ` @objectstack/organizations ` refuses to boot. The only escape
5052 hatch is an explicit ` OS_ALLOW_DEGRADED_TENANCY=1 ` , which brands the deployment
51- ` degraded: true ` end-to-end (boot banner, ` /auth/config ` , Setup dashboard).
53+ ` degraded: true ` on the surfaces that don't require shipping console UI: a loud
54+ ** terminal boot warning** and ` features.degradedTenancy ` in ` /auth/config ` .
55+ (A Setup-dashboard banner was considered and ** descoped** — see D5.)
5256- ** D6** — A bounded, idempotent ** backfill** binds pre-existing member-less users
5357 to the default org on ` kernel:ready ` — single-org mode only.
5458- ** D7** — Edge-case ledger: existing memberships always win; platform-admin
@@ -248,14 +252,28 @@ interface TenancyService {
248252 registers before SecurityPlugin per the existing ordering contract), setting
249253 ` isolationActive: true ` . Presence-probing of ` org-scoping ` remains for one
250254 deprecation cycle, then SecurityPlugin consumes ` tenancy.isolationActive ` .
251- - ** Migration of consumers** (each currently re-derives the fact):
252- 1 . SecurityPlugin's RLS strip gate → ` tenancy.isolationActive ` ;
253- 2 . SQL driver's tenant-audit gate → ` tenancy.isolationActive ` ;
254- 3 . auth-manager ` /auth/config ` ` features.multiOrgEnabled ` → ` tenancy.mode ` ;
255- 4 . ` serve.ts ` / dev / runtime wiring → ` tenancy.requested ` ;
256- 5 . the D2 reconciler → ` tenancy.mode ` + ` defaultOrgId() ` .
255+ - ** Migration of consumers** — status as implemented (PRs #2884 / #2887 ):
256+ 1 . SecurityPlugin's RLS strip gate → ` tenancy.isolationActive ` — ** done**
257+ (keeps a direct ` org-scoping ` probe as fallback for lean embeddings);
258+ 2 . auth-manager ` /auth/config ` ` features.multiOrgEnabled ` → ` tenancy.mode `
259+ (+ new ` features.degradedTenancy ` ← ` tenancy.degraded ` ) — ** done** ;
260+ 3 . the D2 reconciler → ` tenancy.mode ` + ` defaultOrgId() ` — ** done** .
261+ - ** Deliberately NOT migrated** (ratified after implementation, not oversights):
262+ - * SQL driver's tenant-audit gate* keeps reading the env ` requested ` flag
263+ (` resolveMultiOrgEnabled() ` ). The driver is a low-level component that holds
264+ ** no ` PluginContext ` / service-registry handle** , and the gate governs only a
265+ * dev-time* "you passed no tenantId" warning — never the enforcement path
266+ (` applyTenantScope ` , which keys off ` options.tenantId ` ). Threading a service
267+ reference (or an ` isolationActive ` boolean) down into the driver purely to
268+ fire a warning is the wrong coupling for the stakes; if ever migrated it
269+ should be * pushed in* at wiring time, not * pulled* by the driver reaching up.
270+ - * ` serve.ts ` boot guard* reads the env flag directly by necessity — it runs
271+ ** before** plugin-auth registers the ` tenancy ` service (it is the code that
272+ decides whether to even load the org runtime). ` requested ` at boot IS the env
273+ flag; there is nothing to consume yet.
257274- ` resolveMultiOrgEnabled() ` remains the * input parser* for the env flag but
258- stops being a decision point anywhere outside the tenancy implementation.
275+ stops being a decision point anywhere outside the tenancy implementation and
276+ the two boot/driver sites above (which are upstream of, or beneath, the service).
259277
260278** Rejected alternative:** a kernel-built-in tenancy object. The kernel has no
261279tenancy concept today and should not grow one for what is an auth/organizations
@@ -272,10 +290,19 @@ to load (missing, or its `init()` throws):
272290 must not serve traffic pretending otherwise — this is ADR-0049 applied to
273291 deployment configuration.
274292- ** Escape hatch:** ` OS_ALLOW_DEGRADED_TENANCY=1 ` boots anyway, with
275- ` tenancy.degraded = true ` propagated everywhere an operator looks: a red
276- boot banner, ` /auth/config ` (` degradedTenancy: true ` ), and the Setup
277- system-overview dashboard. Degraded operation becomes a visible, chosen
278- state instead of a log line.
293+ ` tenancy.degraded = true ` surfaced where operators actually look ** without
294+ shipping console UI** : a loud red ** terminal boot warning** and
295+ ` /auth/config ` (` degradedTenancy: true ` ) for any tooling that reads it.
296+ Degraded operation becomes a visible, chosen state instead of one buried
297+ log line.
298+ - ** Descoped: a Setup-dashboard banner.** The earlier draft also promised a
299+ console banner. That was cut deliberately: it lives in objectui (a separate
300+ repo/build), and this is an extreme misconfiguration a CE self-host reaches
301+ only by explicitly opting past a boot-time refusal — the terminal warning
302+ plus the API flag reach the operator who set the flag. The ` degradedTenancy `
303+ field stays in ` /auth/config ` (zero-cost, API-visible) so a console * can*
304+ render a banner later without further framework work, but shipping that UI
305+ is not part of this ADR.
279306- ** Rollout honesty:** some existing deployments are unknowingly degraded
280307 today; fail-fast will stop them on upgrade. That is the point — but the
281308 release notes must say so loudly, and the error message must make recovery
0 commit comments