Skip to main content

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 to tenants is auto-scoped; you opt a specific FK out with a no-rls column comment. ⇒ adding a tenant_id FK is sufficient to protect a table.
  • false (rejected): a table is scoped only if its FK column carries an explicit rls comment. ⇒ a table can have a tenant_id column yet be silently unscoped if you forget the comment — exactly the dangerous, invisible failure we must avoid in a fintech. So true fails 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 to tenants → 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-rls DB comment.
  • Belt-and-suspenders: an automated test asserts each table we intend to scope actually has a pg_policies row, and that a cross-tenant read returns nothing.

← Decision log index