Controls
Each one names what enforces it. A control that cannot point at a migration, a grant or a test is a paragraph, and this list is short for that reason rather than long for effect.
Tenant isolation
A customer can only ever read its own data.
Row-level security on every table, FORCE enabled so it applies to the owner too, and the tenant bound per transaction from the credential — never from a path or a request body.
api.test.ts — a query with no tenant set returns nothing, and a cross-tenant insert is refused
The application cannot bypass its own isolation.
The service refuses to start on a superuser or BYPASSRLS login. Row-level security is void against either, so this is checked at boot rather than trusted.
Db.assertNotSuperuser(), asserted before every other API test runs
Exactly four reads cross a tenant boundary, and each returns a fixed shape.
SECURITY DEFINER functions whose RETURN TYPE is the whole of what may cross: resolving a certificate, authenticating a derived key, resolving an invitation, and checking one named domain against one certificate. Deliberate holes, documented in the migrations that add them.
migrations 0003, 0007, 0008, 0012
Evidence integrity
Evidence cannot be edited after the fact.
UPDATE and DELETE are granted nowhere by default. Corrections are new records, never edits. The handful of writable columns are granted one at a time and listed in their migration.
grants in 0001; api.test.ts asserts an order screening cannot be updated
A tampered audit record is detectable.
Every audit event carries the hash of the one before it. A removed or altered row breaks the chain, and a verifier reports the sequence number where it broke.
api.test.ts — a tampered row breaks the chain, and only that tenant’s
What was known on the day is recoverable.
Every screening record names which lists were checked, on what date, with the hash of each, and the dated policy version it was decided under. Records are immutable; a re-screen writes a new one.
order-screening.test.ts, api.test.ts
Two people’s names cannot be swapped without detection.
Each sealed value authenticates against the subject id AND the column it sits in. A ciphertext moved between rows or between columns fails to open rather than decrypting to somebody else.
subject-key.test.ts — a ciphertext lifted into another subject’s row is refused
Credentials
A copy of the database yields no usable credential.
Every secret is stored as a SHA-256 hash and returned exactly once, at issue. That covers API tokens, derived keys, enrolment grants, invitation tokens and session cookies.
keychain.test.ts, api.test.ts, enrol.test.ts
Revoking a certificate revokes everything derived from it.
A derived key is checked against its root on every request. Siblings stay independent — that is the point of deriving them — but a child cannot outlive its parent.
api.test.ts — revoking the root stops every key beneath it
Machine credentials name a human owner and expire.
Enforced in the application and again as database CHECK constraints. Ninety days by default, capped at a year, and the cap shortens rather than refuses so nobody routes around it.
keychain.test.ts; constraints member_has_mailbox, service_has_owner, service_expires
Data minimisation
No biometric data is processed.
Declarant identity is established by a qualified trust service provider under eIDAS. What reaches us is a signature and a certificate chain. No face, no document image, and therefore no Article 9 special-category processing.
attestations.test.ts — the identity port is a trust service, not a biometrics vendor
No name is sent to a screening vendor, because there is no screening vendor.
The sanctions and restricted-party lists are downloaded and matched in process. The subject of a check never leaves the system.
screening-public.ts; attestations.test.ts asserts the trade port carries no personal data
A person can be erased without breaking the record they appear in.
Every natural person’s identifying fields are sealed under a key belonging to that person alone. Erasure destroys the key, not the row — so the ownership structure, the percentages and the audit chain survive intact while the name becomes unrecoverable, including in every backup taken before the request. Deleting the row could not have achieved that.
erasure.test.ts — against a real database: the row survives, the key does not, the name is gone
A copy of the database yields no name.
AES-256-GCM in the application, never in Postgres: a key passed as a SQL argument lands in log_statement and in the memory of the process this is meant to survive. A stolen backup or a provider snapshot yields ciphertext and wrapped keys. This is containment against a database copy and NOT a defence against a compromise of the running service, which holds the root key.
subject-key.test.ts — a sealed value does not contain its plaintext, and repeats do not match
Documents are hashed, never served.
An extract carries a director’s home address and date of birth. The evidence manifest returns the adapter, the outcome, the dates and the hash — never the document and never the extracted field values.
api.test.ts — the evidence route returns a manifest, never a document
A grade is never disclosed to somebody who merely holds an identifier.
Free resolution returns existence, the registered name and validity. The band, the dossier and the evidence sit behind a grant from the holder, and every release is recorded in the holder’s own audit trail.
api.test.ts, enrol.test.ts — the free tier returns no band and no axis state
Change and review
Certificates are re-checked, not issued and forgotten.
A monitoring pass re-reads the registry and the lists. It records transitions rather than states, and it never revokes — a person decides, while the order path stops orders on its own.
monitoring.test.ts — an unreachable source produces no finding and cannot close one
The regulatory position we apply carries a date and its sources.
The destination policy names the instrument behind every entry, carries a version written into each decision, and warns on every order once it is past its review window.
order-screening.test.ts; src/order/destination-policy.ts
No third-party penetration test has been commissioned, and no SOC 2 audit period has begun. Both absences are on the documents page rather than left for you to notice.