D7 — Table scoping: TableRLSManager::$scopeByDefault = true (RESOLVED, code-verified)
Architecture decision record, relocated verbatim from the retired single-file
docs/tenancy/decision log. Status, thematic clusters, and how to record a new ADR: the decision log index.
Reversed from an earlier lean toward false, after reading the dev-master
TableRLSManager source:
true(chosen): every table with a FK path totenantsis auto-scoped; you opt a specific FK out with ano-rlscolumn comment. ⇒ adding atenant_idFK is sufficient to protect a table.false(rejected): a table is scoped only if its FK column carries an explicitrlscomment. ⇒ a table can have atenant_idcolumn yet be silently unscoped if you forget the comment — exactly the dangerous, invisible failure we must avoid in a fintech. Sotruefails safe. Convention:- Tenant-scoped table → give it
tenant_id(foreignId('tenant_id')->constrained('tenants')->cascadeOnDelete(), indexed). Auto-policied. - Central/reference table → no
tenant_id, no FK path totenants→ no policy (e.g.countries,currencies,fee_schedules,tenants, framework tables). - Has a FK toward tenants but must stay central → mark that column with a
no-rlsDB comment. - Belt-and-suspenders: an automated test asserts each table we intend to scope actually has a
pg_policiesrow, and that a cross-tenant read returns nothing.