diff --git a/research/2026-08-17-adr008-multi-tenancy-research-index.md b/research/2026-08-17-adr008-multi-tenancy-research-index.md index 81207f2..9f50e8a 100644 --- a/research/2026-08-17-adr008-multi-tenancy-research-index.md +++ b/research/2026-08-17-adr008-multi-tenancy-research-index.md @@ -101,7 +101,75 @@ digest: 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 +## 7. Adoption stance — structure, not tooling (operator decision, 2026-08-17) + +Research output is used as **design input**, not as a shopping list. The +operator's rule: + +> Use the structure that supports, extends or challenges our design. Do not +> adopt the tooling unless it is an established industry standard with broad +> application. Otherwise build ground up, so we can optimize and refactor as we +> see fit. + +Applied to this survey: + +| Finding | Adopt? | How | +|---|---|---| +| OWASP Multi-Tenant Security Cheat Sheet | **Yes — baseline** | A published security baseline, not tooling. Cite as the external reference our ladders answer to. | +| OWASP API Security Top 10 (BOLA / API1) | **Yes — vocabulary** | Names our gap in terms a security reviewer already holds. | +| OpenID AuthZEN Authorization API 1.0 | **Yes — standard** | Final Specification from a standards body with demonstrated multi-vendor interop. Qualifies as broad and established. Propose as the `A4` rung. | +| Azure tenancy models / AWS SaaS Lens / cell architecture | **Yes — structure** | Taxonomies and patterns. Corroborate the spectrum reframe and the E↔P coupling. | +| PostgreSQL RLS mechanics (`FORCE`, `BYPASSRLS`, `SET LOCAL`, leakproof) | **Yes — facts** | Engine behaviour, not a vendor's product. | +| **pgrls** | **No — structure only** | Take its rule *taxonomy* (54 security rules over RLS enablement, BYPASSRLS, LEAKPROOF, `SECURITY DEFINER`, cross-tenant leak; the SAFE/BREAKING/REQUIRES_REVIEW/DANGEROUS policy-diff classification) as a checklist for probes we write ourselves. 26 stars and beta was already a supply-chain caveat; the stance settles it. | +| **pgTAP** | **No** | Established in the Postgres world but still tooling, and we already have the ground-up equivalent: `scripts/isolation-test.sh`, 15 probes, bash + psql, no dependency tree. Extend that harness to the tenant boundary. | + +The practical consequence is small and good: `rapp-postgres` already owns a +probe harness that found four real defects in its own provisioning SQL. The E2 +and E3 evidence artifacts become new probes in an existing tool, informed by +pgrls's taxonomy of what to look for, with no new dependency. The +policy-diff idea — classifying a policy change as safe or breaking before it +lands — is the genuinely novel structural contribution worth reimplementing. + +## 8. Retention and erasure — designed, not just flagged + +Gap G1 (tenant offboarding and deletion) has been taken up rather than left as +an open question. See `rapp-postgres/docs/adr/ADR-0002-data-retention-and-erasure.md`. + +The shape, per operator direction: retention is declared per consumer as +`N` days defaulting to 30, `rapp-postgres` provides the abstract mechanism for +any dataset, and a consumer or its governance layer builds policy on top of +that interface. + +The finding that emerged while designing it: **per-consumer physical retention +is not implementable.** CNPG has one WAL stream and one window, so the instance +retention is derived as the maximum across consumers and every consumer's +erasure horizon is that maximum. A consumer declaring 7 days beside one +declaring 90 gets 90. This is the retention analogue of ADR-0001 §5 — state the +coupling rather than imply an isolation that is not there — and it produces two +things the estate could not previously say: + +- The **erasure horizon**: the interval between deleting data and it ceasing to + be recoverable from anything the platform holds. Deleted rows survive in + backups for the whole window; that is the difference between "deleted" and + "erased", and it had never been written down. +- **Retention as a placement trigger.** A consumer needing a shorter horizon + than the instance floor cannot have one at P1. It moves to a dedicated + cluster for a reason unrelated to performance — which is exactly why it needs + recording, since nobody looks for a retention argument when reviewing + placement. + +Deletion splits mechanism from policy: the platform sweeps whole datasets and +records an opaque `deletionPolicyRef` it never interprets, so every deletion +traces to the policy that authorised it. Rows are not a dataset. +`consumerDatabase` is deliberately not sweepable. + +**Open for draft-3:** whether this is a fifth plane (`R` — retention and +erasure, with its own ladder from "no policy" through "verified erasure within +a published horizon") or a lifecycle stage cutting across the existing four. +The interface is ladder-shaped, which argues for a plane — at the cost of the +framework's name. + +## 9. Method and limits Four search-and-read passes, one per plane, against vendor architecture guidance (AWS Well-Architected SaaS Lens, Azure Architecture Center), standards