Skip to content

Commit 2d4bd6b

Browse files
authored
docs(adr): true up ADR-0093 to as-built (descope console banner, ratify migration scope) (#2919)
Docs-only follow-up after ADR-0093 shipped. D5: descope the Setup-dashboard degraded banner — branding is the terminal boot warning + features.degradedTenancy in /auth/config (no objectui UI for this opt-in-past-a-refusal case); the API flag stays so a console can add a banner later. D4: record actual consumer-migration status and ratify the two by-design non-migrations (SQL driver's dev-warning gate holds no service handle; serve.ts boot guard runs before the service is registered). Mirrored in tenancy-modes.mdx.
1 parent 5540ced commit 2d4bd6b

2 files changed

Lines changed: 48 additions & 19 deletions

File tree

content/docs/deployment/tenancy-modes.mdx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,12 @@ Resolve it one of three ways:
116116
- **set `OS_ALLOW_DEGRADED_TENANCY=1`** to boot anyway in an explicitly degraded
117117
single-org state.
118118

119-
When you opt into the degraded state, it is branded everywhere an operator
120-
looks — a boot warning, `features.degradedTenancy: true` in `/auth/config`, and
121-
the Setup system-overview surface — so degraded operation is always a visible,
122-
chosen state, never a silent one.
119+
When you opt into the degraded state, it is surfaced where an operator looks — a
120+
loud red **terminal boot warning** and `features.degradedTenancy: true` in
121+
`/auth/config` — so degraded operation is always a visible, chosen state, never a
122+
silent one. (The `/auth/config` flag is there for any tooling that wants to
123+
render its own warning; the framework itself does not ship a console banner for
124+
this extreme, opt-in-past-a-refusal case.)
123125

124126
> **Upgrading?** A deployment that was *silently* degraded before this guard
125127
> existed will now fail to boot after upgrade. That is intentional — it was not

docs/adr/0093-tenancy-mode-and-membership-lifecycle.md

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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
261279
tenancy 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

Comments
 (0)