Skip to main content
Security & data integrity

We let AI do the boring parts. We don't let it bypass the controls.

Most 'AI for accounting' tools either hand the model a service-role key and hope, or train on customer data without disclosing it. We do neither. The AI is bound by the same row-level security policies as a junior bookkeeper — not more. Your data is never used to train models. Posted entries are immutable at the database level, not just in application code.

A balanced double-entry journal: total debits equal total credits at 34,160.00, a balance the database trigger enforces at commit time before any row can post.

PROOF — the database enforces this, not the app

A posted entry the system cannot un-balance

DB-enforced
#AccountDebitCredit
1100Amazon clearing19,100.00
5210Referral & selling fees3,200.00
5220FBA fulfilment fees1,800.00
4001Sales returns & refunds700.00
4000Amazon sales — US24,800.00
Cost relief
5000Cost of goods sold (WAC)9,360.00
1330Inventory — FBA (WAC)9,360.00
Totals0.000.00

The shape of the trust model

Three independent layers. Bypassing one bypasses none of the others.

The hard accounting invariants live in the database — in Postgres triggers and SECURITY DEFINER functions — not in application code an AI bug or a malicious actor could route around. Tenancy is enforced at the row, not the request. And the model never holds more privilege than the human it acts for.

Database-enforced

The invariants live in Postgres

Double-entry balance, posted-entry immutability and the append-only audit trail are database triggers and constraints — they hold even against a service-role write. There is no application-layer override path.

  • DR = CR checked at commit, within ±0.0001 SAR
  • Posted entries blocked from edit by a guard trigger
  • Audit log: SELECT only, no INSERT/UPDATE/DELETE path

Tenant-isolated

Isolation at the row, not the request

Row-level security is enabled on every business table — zero exceptions in production. A user from one firm cannot read, write or delete another firm’s rows, enforced by Postgres rather than by a session check the web app could forget.

  • RLS on every ledger + audit table
  • Two levels: firm (org) and books (client)
  • Cross-tenant references rejected at the foreign-key layer

AI-bound

The model holds no extra keys

When the AI extracts a document or drafts an entry, it acts through the same SECURITY DEFINER RPCs a human would call — with strict tool-use schemas. There is no AI path that writes data it could not otherwise touch.

  • Extracted text treated as untrusted input, never instructions
  • Per-call + per-batch cost ceilings — no surprise bills
  • Low-confidence output flagged for human review
  • RLS on every business table
  • DR = CR enforced by trigger
  • Posted entries immutable
  • Append-only audit log
  • Cross-tenant FK guards
  • Signed-URL document access
  • Encrypted at rest + TLS 1.2+
  • EU-hosted · KSA-resident option
  • 11-year audit retention
  • Model output treated as untrusted

The control register

What the database guarantees, stated so your auditor can verify it.

These are not aspirations. Each one is a trigger, a constraint, or an RLS policy you can point a database introspection query at. The balance below is the same entry from the proof above — debits tie to credits, and the trigger is what makes that non-negotiable.

FIG — Trial balance that ties
AccountDebitCredit
1100Amazon clearing19,100.00
5210Referral & selling fees3,200.00
5220FBA fulfilment fees1,800.00
4001Sales returns & refunds700.00
4000Amazon sales — US24,800.00
5000Cost of goods sold (WAC)9,360.00
1330Inventory — FBA (WAC)9,360.00
Totals34,160.0034,160.00
Balanced · enforced by trigger0.00 SAR
FIG — Database-enforced guarantees
  • DR = CR tolerance, every posted entry±0.0001SAR
  • Posted-entry edit attempts permitted0
  • Same-day fat-finger un-post window24h
  • Audit-log mutation paths from a session0
  • Signed-URL time-to-live for source docs10min
  • Accounting-record retention design11yr
Default-deny posture0 overrides
FIG — Prepare → check → approve · nothing auto-posts
  • JE-018291Prepared

    Senior accountant prepares the draft

    34,160.00
  • JE-018291Checked

    Finance manager checks the entry

    34,160.00
  • JE-018291Posted

    CEO / admin approves — then it posts

    34,160.00

Every control, in depth

Six domains. One frame.

The same specific commitments a security questionnaire asks for — pick a domain and the panel reconciles in place.

The accounting invariants are enforced in Postgres, not in application code that an AI bug or a malicious actor could bypass.

DR = CR

Double-entry enforced by trigger

Every journal entry must satisfy SUM(debits) = SUM(credits) within ±0.0001 SAR. The check runs as a CONSTRAINT TRIGGER on the journal-entry-lines table — even a service-role write that violates the rule is rejected at COMMIT time. There is no application-layer override.

Immutable

Posted entries are immutable

Once an entry is posted, a guard trigger blocks any UPDATE to its accounts, amounts or narratives, and the RLS delete policy refuses to delete it. Corrections happen via reversal entries, not edits. One narrow, audit-logged exception: the same user who posted an entry can un-post it within 24 hours, and only while nothing downstream has consumed the posting.

Audit

Append-only audit log

Every workflow transition writes a row with actor, timestamp, from-state, to-state and a payload-diff. Application roles hold SELECT only; the table has no INSERT, UPDATE or DELETE path from any client session. Once written, a log row cannot be altered through the application.

Want our DPA, a security-questionnaire response, or a walkthrough of the controls?

We respond to security questionnaires within two business days. The DPA is signed before any pilot uploads its first document.

Security & data integrity · Nexus Ledger · Nexus Ledger