Four search-and-read passes against AWS Well-Architected SaaS Lens, Azure Architecture Center, OWASP, the OpenID Foundation, and PostgreSQL practitioner literature. Each digest holds one of our ladders against outside practice and lists its sources. Headline: the draft-1 -> draft-2 reframe is corroborated. Microsoft states "isolation is a spectrum, not a discrete property" and derives our E/P coupling independently. The shape is sound; the details are not. Five corrections draft-2 needs: - E3 overclaims. Any session can re-SET the GUC, so RLS is strong against a forgotten predicate and useless against SQL injection or a compromised process. The ADR says "the application cannot trivially route around" - the exact overclaim it prohibits elsewhere. - The pooling claim in section 16 is backwards. Transaction-scoped SET LOCAL is what makes E3 safe under pooling; statement-level pooling is what serves other tenants' rows, only under production concurrency. - E3 layers on E2 rather than superseding it. - Placement cannot be expressed per tenant, but the commercial section requires exactly that - a tier bought by some tenants puts a service at two P levels at once, which the posture vector cannot represent. - FORCE ROW LEVEL SECURITY is mandatory: ADR-0001 already established that our migration role owns the tables it creates, so it would silently bypass every policy. Also: pgTAP and pgrls make the E3 evidence artifact far cheaper than assumed; OWASP publishes a Multi-Tenant Security Cheat Sheet we reference nowhere; our tenant-boundary gap is BOLA, OWASP API1, top of that list since it launched; AuthZEN 1.0 went Final in January and flex-auth is pre-standard; and no plane covers tenant offboarding, which OWASP makes a MUST. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.1 KiB
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.md2026-08-17-adr008-plane-a-authorization.md2026-08-17-adr008-plane-e-enforcement.md2026-08-17-adr008-plane-p-placement.mdPurpose: 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
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:<grouping>:<name> 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 SECURITYon every tenant-owned table. Without it the table owner bypasses policies silently — and ADR-0001 already established that objects created by<consumer>_migrateare owned by_migrate, so our migration role would bypass every policy on tables it created.- No
BYPASSRLSattribute on any leased role. SECURITY INVOKERfor ordinary logic;SECURITY DEFINERreserved for deliberate, reviewed cross-tenant access.- Context set with
SET LOCALinside an explicit transaction, never session-scoped. - An
EXPLAINcomparison before and after enabling RLS: functional indexes using non-leakproof functions (lower,enum_eq) stop being used under RLS. Relevant totenant-engine, which sits on the synchronous authorization path and just requested a 5s statement timeout.
6. Recommended shape of draft-3
- 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.
- 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.
- 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.
- Add G1 (offboarding/deletion) and G3 (identity placement) as open questions; add G2 as a scope note.
- Cite the external baselines — OWASP Multi-Tenant Security Cheat Sheet, Azure tenancy models, AWS SaaS Lens — in §15, and BOLA/API1 in §13.2.
- Reframe §18.8 from "evaluate cells" to "declare
platform-pg's maximum size and its overflow target". - Route G7 to
tenant-engineseparately. 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.