The guarantees underneath every product, written down.
Most suites reach a shared model by acquisition, then spend years hiding the seams. These decisions were settled before the first product shipped, and several of them cannot be walked back once customer data exists.
The database enforces the boundary, not the code.
Every table carries the organisation and runs under FORCE ROW LEVEL SECURITY, so the policy applies to the table’s owner too. The application filter is still written — row-level security is the net under it, not permission to skip it.
The tenant is bound to the transaction. Setting it on the connection instead is the cross-tenant leak that pooling makes inevitable: the pool hands that same connection to somebody else’s next request.
-- every request opens a transaction, and binds itself
BEGIN;
SELECT set_config('app.organization_id', $1, true); -- true = this transaction only
SELECT id, number, total_minor, currency
FROM finance.invoices
WHERE organization_id = $1; -- filtered anyway, on purpose
COMMIT;NOTEThe third argument is what makes it transaction-local. Without it the setting outlives the request on a pooled connection, and the next caller inherits the wrong organisation.
A grant says what, and how far.
Every permission carries a scope. The same “read invoices” grant means four different things depending on how far it reaches, and the scope is injected into the query rather than checked after the rows come back.
A product’s administrator role is built from that product’s own catalogue of permissions — never from “all permissions”. Buying the service desk does not hand its administrator your finance ledger.
Authorisation is server-side. A hidden button is presentation; every endpoint carries its own negative-authorisation test.
- OWNOnly the records this person owns.LEVEL 1
- TEAMTheir team, down the reporting line.LEVEL 2
- LOCATIONEverything at a site or branch.LEVEL 3
- ORGThe whole organisation.WIDEST
OWN < TEAM < LOCATION < ORG — a wider scope contains every narrower one.
# the route declares its permission, the selector applies the scope
scope = ctx.scope_for("finance.invoices.read")
query = ctx.apply(select(Invoice)) # org filter + scope, before executionNOTEThe scope narrows the query before it runs. Nothing is fetched and then filtered in the application, so there is no window in which the wrong rows exist in memory.
Personal data is masked until someone proves they may see it.
Personal data is encrypted at rest and returned masked by default. Revealing an employee’s or a contact’s real number takes its own permission and writes an audit row every time — who, what, when.
A campaign can still reach that contact. Messaging-target resolution hands the channel a target without handing anybody the plaintext number, so marketing never needs the permission at all.
// 200 — the default shape, for everyone
{
"contact_id": "3c90f1a7-…",
"name": "R. Iyer",
"phone_masked": "+91 ••••• •7568",
"phone_revealable": true,
"messaging_target": "tgt_9f2b…"
}NOTERevealing the number is a separate call against a separate permission, and it writes an audit row whether or not anybody ever reads it.
Integers, and a ledger nobody edits.
Every amount is a whole number of minor units with an ISO currency beside it. No floats, and no two-decimal numeric standing in for one. ₹18,462.00 is stored, moved and totalled as 1846200.
Invoices, usage events and the credit ledger are append-only, and plan versions are immutable with a subscription pinned to a version. A correction is a new row — so the history a controller reads in March is the history an auditor reads in September.
What finance covers on top of this// 201 — a correction is a new row, never an edit
{
"credit_note_id": "c41b7e02-…",
"corrects_invoice_id": "8f21c4de-…",
"amount_minor": -246200,
"currency": "INR",
"original_unchanged": true
}NOTENothing was updated. The original invoice row is exactly as it was issued, and the correction stands beside it with its own identity.
The four in one paragraph each.
If you read nothing else on this page, read these.
The database enforces the boundary, not the code
Every table carries the organisation, with row-level security underneath as the net — so a forgotten filter returns nothing rather than someone else's data.
A grant says what, and how far
Own records, the team, a location, or the whole organisation. A product's administrator gets that product's permissions, never a blanket grant over everything else you own.
Personal data is masked until someone proves they may see it
Revealing an employee's or a contact's real number takes its own permission and writes an audit row every time. A campaign can reach that contact without ever decrypting it.
Integers, and a ledger nobody edits
Whole minor units with a currency. Invoices, usage and the credit ledger are append-only — a correction is a new row, so the history stays true through an audit.
Seven guarantees, and what each one prevents.
Each of these is enforced in the codebase rather than described in a policy document. The right-hand column is the failure it exists to stop.
SCROLL SIDEWAYS FOR THE REST OF EACH ROW
| GUARANTEE | WHAT IT MEANS | WHAT IT PREVENTS |
|---|---|---|
| Organisation on every table | Every table carries the organisation column, and every table has FORCE ROW LEVEL SECURITY on it — including for the table's owner. | A query that forgets the filter returns nothing, rather than another customer's rows. |
| Tenant bound per transaction | The organisation is set on the transaction, not on the connection, because the pool hands the same connection to the next request. | The classic connection-pool leak, where request two inherits request one's tenant. |
| Scoped permissions | A grant names what and how far: own records, the team, a location, or the organisation. | A product administrator quietly holding a blanket grant over every other product you own. |
| PII masked by default | Personal data is encrypted and returned masked. Unmasking needs its own permission and writes an audit row every single time. | A reveal nobody can account for afterwards, and a support tool that quietly decrypts a whole table. |
| Money as integer minor units | Whole minor units with an ISO currency. No floats, and no two-decimal numeric standing in for one. | The half-paisa that reconciles on screen and not in the ledger. |
| Append-only records | Invoices, usage events and the credit ledger are append-only. A correction is a new row. | A history that changed shape between the close and the audit. |
| One signing key | Exactly one service holds a private signing key. Every other service verifies through JWKS and holds nothing secret. | A key copied into nine services, where rotating it means nine deploys and one forgotten. |
An outage is never resolved into a refusal.
When a check cannot run, the answer is “we couldn’t check” with a retry — not a no, not an empty list, not a zero. The rule holds in every product, not only the storefront.
Background work is enqueued only after the transaction commits, via an outbox, so nothing is ever dispatched for a write that was rolled back.
- 200Delivery in 25 minutesWe deliver to 400072 · ₹29 delivery · free over ₹499
- 200We don't deliver to 422001 yetWe've recorded the request — we'll let you know when we get there.
- 503We couldn't check right nowThat's a connection problem, not a no. Try again in a moment.
Where it runs, and what we have not committed to yet.
The bracketed values below are deliberately unfilled. We would rather leave a blank on a public page than publish a commitment we have not signed.
DATA REGION
[DATA REGION]
Where customer data is stored and processed.
UPTIME COMMITMENT
[UPTIME COMMITMENT]
The number we are willing to be held to contractually.
CERTIFICATION STATUS
[CERTIFICATION STATUS]
Independent certification, audited rather than asserted.
Bring your security reviewer to the call.
We will walk the tenancy model, the permission catalogue and the audit trail at whatever depth they want, and tell them plainly which certifications we do not hold yet.