Security
Access is enforced in the query, not in the screen.
Most products check permissions where the page is drawn, which means every new endpoint is a new chance to forget. Here the check happens underneath, in the database, and the reporting layer runs through the same rule the screens do.
None of it is a paid upgrade. Role-based access, the audit trail, encryption, backups, consent and export are on every plan, including the smallest.
Every request
Five gates, server-side, in this order
The first three refuse and say why. The last two narrow silently — telling somebody there are forty records they may not see leaks the count, which is itself information.
- 1Is this workspace active?A suspended tenant gets a screen, not an error.
423 - 2Is this module in the plan?A billing answer, so it is asked before permissions.
402 - 3Does this role hold the verb?Read, create, update, delete, approve, export, grant.
403 - 4Is the row this tenant's?Enforced by Postgres, not by the query that forgot.
narrows - 5Is the row in your scope?Global, function, team, assigned, self — or one client.
narrows
Gate 2 sitting before gate 3 is deliberate. A module outside your plan is a billing answer, not a permissions answer, and it comes back with the upgrade path — so an administrator goes to the invoice rather than hunting through the role matrix for a permission that was never the problem.
Isolation
Rules enforced by the database, not by good intentions
Two database roles, neither of which can bypass a policy
One reads sessions and tenants only — the bootstrap surface, looked up by unguessable token hash. Everything else runs as a role that owns nothing and cannot bypass row-level security.
Every business table carries FORCE row-level security
With both USING and WITH CHECK, so a policy governs what can be read AND what can be written. FORCE means the table owner is subject to it too — provisioning gets no exemption, it simply knows the answer.
The tenant is bound to the transaction, not the connection
A transaction-local setting, so a pooler in transaction mode cannot carry one customer's identity into another customer's request.
A missing tenant raises rather than returning nothing
The policy uses the one-argument form of the setting lookup, so an unbound session fails loudly instead of quietly matching zero rows — the failure mode that looks like an empty screen.
The application cannot rewrite its own audit trail
No UPDATE and no DELETE on the audit log, granted at the database level rather than promised in a code review.
The build fails on an unscoped query
One sanctioned function may query a tenant-scoped table. A linter in CI fails on any query written outside it, so the rule is enforced by the build rather than by whoever is reviewing that day.
The rules are tested against the live catalogue
Tests assert against the database's own catalogue — every scan index leads with the tenant, every table has FORCE, every policy carries both clauses. A rule that only lives in a review lasts until the reviewer is busy.
Support access
The riskiest path in any SaaS, made structural
An engineer looking at your screen to help you is the highest-risk thing a vendor can do. The controls are not policy — they are constraints the code cannot get around.
- ✓Your workspace has to opt in, and it is off by default.
- ✓A reason is required and cannot be blank — the column refuses it.
- ✓The session expires after 60 minutes.
- ✓It is read-only: every verb but read is stripped, so nobody can act as you.
- ✓It lands in your own audit trail, not only in ours.

Twenty-six roles across sixteen modules. Each cell carries the verbs (read, create, update, delete, approve, export, grant) and a scope: everything, one department, your reporting line, your assigned records, your own — or one external client.
Delegation
Handing out access has stricter rules than holding it
You cannot give away what you do not hold
A module you have nothing on is a module you cannot delegate.
Scope can never be widened beyond your own
On either path, however senior the person delegating.
Administering is separate from reading
Which is what lets somebody run Payroll without being able to read a salary.
Nobody edits their own role
Or changes their own status, or writes their own exception. Four eyes on the one table where it matters.
Every change is audited and takes effect on the holder's very next request — grants are read on every call, so there is no cache to wait out when you revoke something.