# ADR-008 multi-tenancy — external research index > Research artefact — 2026-08-17. Four deep-research digests, one per plane of > `canon/architecture/adr-008-multi-tenancy-model.md` (draft-2), surveying prior > knowledge and best practice and holding our ladders against it. > > - `2026-08-17-adr008-plane-i-identity.md` > - `2026-08-17-adr008-plane-a-authorization.md` > - `2026-08-17-adr008-plane-e-enforcement.md` > - `2026-08-17-adr008-plane-p-placement.md` > > Purpose: feed draft-3. Nothing here amends the ADR by itself. ## 1. Headline: the reframe is externally corroborated The operator's draft-1 → draft-2 instruction — stop specifying one model, build a framework of graduated levels — matches the most rigorous public guidance almost verbatim: > "Instead of viewing isolation as a discrete property, consider it a spectrum. > You can deploy components of your architecture that are more isolated or less > isolated than other components in the same architecture." > — [Azure Architecture Center, Tenancy models](https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/considerations/tenancy-models) Microsoft also derives our E↔P coupling independently: shared deployment ⇒ enforcement lives in application code; dedicated deployment ⇒ enforcement is structural. Draft-2's shape is sound. What follows is about its details. ## 2. Corrections to draft-2 — things it currently states wrongly Ranked by how badly they mislead. | # | Correction | Plane | Where | |---|---|---|---| | C1 | **E3 does not prevent an application from routing around it.** Any session can re-`SET` the GUC; SQL injection or a compromised process defeats it entirely. E3 is strong against *accident*, weak against *compromise*. The ADR's phrase "the application cannot trivially route around" overclaims — the exact fault §6 prohibits. | E | §4.3 | | C2 | **The pooling claim in §16 is backwards.** Transaction-scoped context (`SET LOCAL`) is what makes E3 *safe* under pooling; **statement**-level pooling is what breaks it — returning other tenants' rows, only under production concurrency. The ADR says E3 "forecloses aggressive transaction-level pooling". | E | §16 | | C3 | **"Set at pool checkout" is the wrong scope.** Should be set transaction-locally inside an explicit transaction. | E | §4.3 | | C4 | **E3 does not supersede E2 — it layers on it.** Practice treats them as coexisting; a service dropping E2 on reaching E3 is worse off, because E3 fails open under injection. Claiming E3 should require the E2 artifact too. | E | §4.3, §13 | | C5 | **P cannot express per-tenant placement, which §11 requires.** A tier requiring `E3 P2` bought by only some tenants puts the service at two P levels at once. The posture vector cannot represent it, forcing an over- or under-claim. | P | §5, §11 | ## 3. Gaps — real concerns no plane covers | # | Gap | Suggested home | |---|---|---| | G1 | **Tenant offboarding and deletion.** OWASP lists complete data deletion on offboarding as a MUST. `tenant-engine` has reversible retirement and explicitly no hard-delete; `platform-pg` retains backups 30 days. Falls between two repos' declared scopes. | New open question; a lifecycle stage across all four planes, not a fifth plane. | | G2 | **Non-database stores.** Caches, search indices and background jobs are named leak surfaces. Our ladders are datastore-shaped throughout. | Scope note on the E ladder, or widened evidence artifacts. | | G3 | **Identity-provider placement.** Realm-per-tenant vs Keycloak Organizations is the same silo/pool decision, live in our estate, undecided. Realm-per-tenant has a stated ceiling of 5–20 tenants. | Note in §3 that P scopes to data substrate; identity placement is parallel and unowned. | | G4 | **Noisy-neighbour evidence.** Azure requires testing both that data does not leak *and* that noisy-neighbour outcomes are acceptable. §13 covers only leakage. | Add a P1/P2 evidence artifact. | | G5 | **Tenant→deployment mapping record.** A first-class component in the reference guidance. Our placement facts in §1 were assembled by inspection. | Follows from G3/C5. | | G6 | **Cell sizing.** `platform-pg` is an uncapped cell with a computed ~6-consumer ceiling and no overflow target. | Reframes §18.8: ask what its declared maximum size is. | | G7 | **Tenant identifier encodes mutable headcount bands.** `tenant::` embeds `small`/`medium`/`large`, which change. Contradicts identifier-design consensus. | Not ADR-008's; raise to `tenant-engine` / NetKingdom canon re ADR-0013. | | G8 | **Logging.** OWASP MUST: include tenant context in all log entries. Unmodelled. | Minor; fold into evidence artifacts. | ## 4. Opportunities — cheaper or better than we assumed | # | Finding | Effect | |---|---|---| | O1 | **pgTAP + pgrls** (67 RLS lint rules, semantic policy-diff for CI gating, pytest isolation plugin) exist off the shelf. `rapp-postgres` already runs a 15-probe harness of the same shape. | The E3 half of open question §18.3 is far cheaper than assumed. Splits the question into a tractable mechanical half and a human half. | | O2 | **OWASP Multi-Tenant Security Cheat Sheet** is the closest external baseline to our framework and is referenced nowhere in the estate. | Adopt as the named external baseline in ADR §15. | | O3 | **OpenID AuthZEN Authorization API 1.0** reached Final Specification January 2026; Keycloak shipped experimental support May 2026. Demonstrated PDP interoperability across 5+ engines. | `flex-auth`'s bespoke `CheckRequest` is pre-standard. Propose an `A4` rung for standard-interface PDP delegation. | | O4 | **Token versioning** as a middle rung between cached claim and live re-query. | Cheap global invalidation without a hot-path lookup. | | O5 | **BOLA / OWASP API1.** Our tenant-boundary gap is the industry's #1 API vulnerability, top of the list since it launched. | Cite by name. Converts an internal judgement into an external consensus for reviewers with a security background. | ## 5. Specific technical risks for the E3 implementation contract (§18.6) Concrete requirements the contract must carry, each sourced in the enforcement digest: - `FORCE ROW LEVEL SECURITY` on every tenant-owned table. Without it the table owner bypasses policies **silently** — and ADR-0001 already established that objects created by `_migrate` are owned by `_migrate`, so our migration role would bypass every policy on tables it created. - No `BYPASSRLS` attribute on any leased role. - `SECURITY INVOKER` for ordinary logic; `SECURITY DEFINER` reserved for deliberate, reviewed cross-tenant access. - Context set with `SET LOCAL` inside an explicit transaction, never session-scoped. - An `EXPLAIN` comparison before and after enabling RLS: functional indexes using non-leakproof functions (`lower`, `enum_eq`) stop being used under RLS. Relevant to `tenant-engine`, which sits on the synchronous authorization path and just requested a 5s statement timeout. ## 6. Recommended shape of draft-3 1. Apply corrections C1–C4 to §4.3, §13 and §16. These are the ones a reviewer will otherwise catch, and C1 is the ADR overclaiming in exactly the way it forbids. 2. Resolve C5 by making placement expressible per tenant class — a default plus exceptions in the posture vector — or by declaring per-tenant placement out of scope and accepting that isolation tiers become unmodellable. 3. Split open question §18.3 into a mechanical half (E1/E3, tooling exists) and a semantic half (E2, adversarial review on a recorded cadence). Cross-tenant findings "require a human tester who can set up separate tenant contexts and compare the responses" — a green CI run is not E2 evidence. 4. Add G1 (offboarding/deletion) and G3 (identity placement) as open questions; add G2 as a scope note. 5. Cite the external baselines — OWASP Multi-Tenant Security Cheat Sheet, Azure tenancy models, AWS SaaS Lens — in §15, and BOLA/API1 in §13.2. 6. Reframe §18.8 from "evaluate cells" to "declare `platform-pg`'s maximum size and its overflow target". 7. Route G7 to `tenant-engine` separately. It is a critique of ratified canon, not of this ADR, and folding it in would overreach. ## 7. Method and limits Four search-and-read passes, one per plane, against vendor architecture guidance (AWS Well-Architected SaaS Lens, Azure Architecture Center), standards bodies (OpenID Foundation), security baselines (OWASP), PostgreSQL practitioner literature, and IdP vendor documentation. Full source lists are in each digest. Limits worth stating: - No primary academic sources beyond the Zanzibar lineage; this is an engineering-practice survey, not a literature review. - Vendor guidance is not neutral — AWS and Azure both describe patterns their platforms sell. The Azure tenancy-models page is the most rigorous taxonomy found and is used heavily in the P digest; readers should discount its Azure-service-specific recommendations accordingly. - Nothing here was tested against our own systems. Every claim about our estate is drawn from repo documents, not from probing running infrastructure. - The E digest's corrections are the highest-confidence findings, being PostgreSQL mechanics rather than architectural opinion.