research: record the structure-not-tooling stance and the retention design

Operator decision: use research as design input, adopt tooling only where it is
an established standard with broad application, otherwise build ground up so we
can refactor freely.

Settles two open recommendations. AuthZEN qualifies (OpenID Final Spec,
multi-vendor interop) and stays as the proposed A4 rung; OWASP stays as a
baseline; PostgreSQL RLS mechanics are facts, not tooling. pgrls and pgTAP are
out as dependencies - we take pgrls's rule taxonomy as a checklist for probes
we write into the existing isolation-test.sh harness, which already found four
real defects and has no dependency tree. Its SAFE/BREAKING policy-diff idea is
the structural contribution worth reimplementing.

Also records that gap G1 was designed rather than deferred, and the finding
that came out of it: per-consumer physical retention is not implementable on
CNPG, so the erasure horizon is the instance maximum and retention becomes a
placement trigger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
codex 2026-08-17 14:20:33 +02:00
parent 7c69153da9
commit f09d3ef5d2

View file

@ -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