Platform

Built so the wrong state cannot be written.

Orchestrix serves many organisations from one deployment. Everything below exists to make that arrangement safe by construction rather than by discipline.

5layers
1authorisation model
2isolated databases
0shared rows

Request path

Five layers, one rule.

A request passes through each of these. The rule does not change between them: nothing is trusted because of where it came from.

01

The browser

React, client-rendered. Every query it makes carries the signed-in user's own token — it has no elevated credential to lose and no privileged path to abuse.

Permission checks in the interface are cosmetic. They hide controls; they do not protect data.

02

The policy layer

PostgreSQL row-level security. Every table carries its organisation, and every policy keys on the organisation the caller belongs to. Switched on by an event trigger when a table is created.

A table nobody wrote policies for is invisible to everyone, rather than open to anyone.

03

The functions

Anything that contends for a resource or grants an authority runs as a SECURITY DEFINER function: holding a unit, issuing a library copy, waiving a fine, releasing a child to an adult.

A check followed by a write, from a browser, is two statements with a gap between them.

04

The edge

Deno functions, used only where a service-role key is unavoidable — creating logins, signing uploads, receiving from devices. Each re-derives the caller from their token and re-checks their role before acting.

Never trusts anything in the request body about who is asking.

05

Storage

Cloudflare R2. A private bucket behind ten-minute signed URLs for records; a separate, physically distinct public bucket for anything intended to be seen by strangers.

A path-handling mistake in a shared bucket is how a private document becomes a public URL.

Isolation

One database per vertical. One organisation per row.

Schools and property firms do not merely have separate tables — they have separate Postgres projects. Nothing joins across them because there is nothing to join to.

WITHIN A DATABASE

Composite keys

A row that references two other rows must reference two rows belonging to the same organisation. The key carries the organisation, so the wrong combination is a constraint violation rather than a bug.

BETWEEN DATABASES

Separate projects

Each vertical has its own database, its own credentials and its own migration history. A mistake in one cannot reach the other, because there is no connection between them to misuse.

ACROSS THE CODEBASE

Enforced imports

Vertical code may import shared code. Shared code may never import a vertical, and one vertical may never import another — checked by a lint rule, not by convention.

One deployment, many organisations

Every table carries an organisation. Every policy keys on it.

No cross-tenant reference is possible

Composite keys make the wrong row a constraint violation.

Vendor access is recorded

Entering a customer's data is logged, and the session expires.

No biometric template ever leaves a device

A punch is an enrolment number and a time. A fingerprint cannot be reissued.

Pickup codes are unreadable

The code that collects a child has no select policy at all.

Deletion is archival first

Nothing is destroyed in one step, and the second step is narrow.

Support access

We can be held to it too.

Somebody at Orchestrix occasionally needs to look inside a customer’s data to fix something. That is a real requirement and pretending otherwise produces a worse system, not a safer one.

So it is explicit. Entering a customer’s organisation is a recorded action. The session carries a timestamp and expires on its own. Where a customer requires it, access needs their approval first, and the approval is per-request.

A support session that never ends is one forgotten browser tab away from an accidental write in somebody else’s business.

Technology

Every layer, and why.

LayerChoiceReason
ApplicationNext.js · React · TypeScriptClient-rendered throughout. There is no server tier to compromise because there is no server tier.
DataPostgreSQL via SupabasePolicies, triggers, constraints and functions carry the rules. One database per vertical — separate projects, no shared rows.
AuthorisationRow-level securityEnabled by an event trigger on table creation, so a table without policies is invisible rather than open.
Server workDeno edge functionsOnly where a service-role key is unavoidable: creating logins, signing uploads, ingesting from devices. Each re-derives the caller from their token before acting.
FilesCloudflare R2Private bucket behind short-lived signed URLs for records; a separate public bucket for anything meant to be seen.
PaymentsPluggable adaptersRazorpay, Cashfree, PayU, Stripe. Provider-agnostic on the application side; per-tenant secrets encrypted at rest.
MobilePWA · CapacitorInstallable on any phone, and a native Android build from the same static export.

Delivery

Tested where it counts.

The suite runs against a hand-written mock that reproduces the database’s own rules — the constraints, the refusals, the allocation order. A test that only proves the screen rendered is a test that passes while the money is wrong.

1,000+end-to-end tests
2browser profiles
0credentials needed
0network calls

Every request is intercepted, so the suite needs no database, no credentials and no internet. It runs the same on a laptop as it does in CI, which is the only way a test suite stays trusted.

The principles behind these choices.

Read the engineering notes