Your data reaches only the providers we name.
Every absolute URL in the server code is compared against the list of subprocessors on the privacy page. An undeclared host fails the test.
tests/test_trust_claims.mjs — “your data reaches only the providers we name”
This is how we found that our own typefaces were being fetched from Google, which told Google the IP address of every visitor. They are now served from our origin.
Nothing is ever written to your accounting system.
No write request to an accounting provider exists in the codebase. Any module that can reach an accounting API must use GET for every request it makes, with one exception stated in the test: fetching an access token is a POST at every provider, so a POST to a token path is allowed and a POST to any other path fails.
tests/test_trust_claims.mjs — “nothing is ever written to your accounting system”
Attest drafts entries for you to review. Posting them into your ledger is something you do, in your ledger. The check used to look for a write near the provider's hostname, which a write a few lines further down would have passed; it now reads the whole module, and that was verified by planting one.
The QuickBooks connection can read your books and cannot change them.
The client has exactly one function that makes a request and it hard-codes GET; no create, update, delete, sparse-update or batch method exists to call. The test fails on any non-GET request to the QuickBooks accounting API anywhere in the app, on a second request site appearing in the client, on a mutating statement at any query call site, and on a token written to the database in the clear.
tests/test_qbo_readonly.mjs — 9/9, and each check verified to go red when the mechanism is removed
Intuit publishes no read-only accounting scope, so we hold one that can write and put the guarantee where we can actually keep it: in the client, and in a test that fails if the client changes. Disconnecting from Settings deletes the stored tokens rather than marking them revoked. The connection is not open to accounts yet — it runs against an Intuit sandbox while their app review is under way — and files remain the way in until it is.
The re-derivation provably never reads your ledger except to compare against it.
The function that books the month takes bank rows and rules — the ledger has no way in, and its signature is asserted so it cannot gain one. The test runs it with the ledger removed and requires byte-identical output.
tests/test_rederive.py — “the ledger can be removed entirely and nothing changes”
This is the claim everything else rests on, and the one you are right to disbelieve, so it is not tested by inspection. The ledger is not emptied or blanked: it is never handed to the function at all. A second check reads the module's source and fails on any ledger concept appearing in it.
Nothing posts without a human, and the database enforces it.
A drafted entry is born unposted, and can only ever say otherwise once a posting record exists for it — the database checks that the record is there rather than trusting the code that claims it, including a credential that bypasses row-level security. It can never go back to unposted: a posting is a record, not a state. A failed review and an unbalanced entry are both rejected by database constraints.
tests/test_db_guarantees.py — run against the live database, 12/12
Verified as a real signed-in user, not with the service role, because the service role bypasses the very rules under test.
Trial uploads are deleted after 7 days unless you convert.
The deadline is stamped on the data itself when it is written, a scheduled sweep honours the stamp, and converted accounts are exempt. Your own date is shown on your data page.
tests/test_trust_claims.mjs — “the seven-day promise has a sweep behind it”
The sweep refuses to run without its secret rather than sitting open, because an unauthenticated endpoint that deletes things is worse than one that sends email.
You can delete everything, permanently, yourself.
One action removes your files, their stored originals, every transaction read out of them, everything drafted from them, and your rules. Immediately, not on a queue.
tests/test_trust_claims.mjs — “permanent delete is self-service and immediate”
Two things survive: your record of agreeing to the terms, and the record of the deletion request. Both are evidence about what was agreed and what you asked for; neither holds any financial data.
Your data is encrypted in transit.
Every outbound call in the codebase is https. The test fails on a plain-http host other than a local development engine.
tests/test_trust_claims.mjs — “every outbound call is https”
At rest, encryption is our hosting providers' (Supabase, Vercel, Railway). That is their attestation rather than something we can prove from here, which is why it is stated as theirs.
Your figures never reach an analytics vendor.
Analytics properties pass a whitelist by shape: numbers and booleans, and short strings with no digit runs, no date shapes and no @ signs. Amounts, account numbers, IBANs, vendor descriptors and emails are dropped.
tests/test_analytics_events.mjs — real leak candidates thrown at the filter, 9/9
All events are fired server-side. There is no browser SDK, no autocapture and no session recording, so nothing on screen has a path to a vendor.
We never train on your data.
The model is called at rule-authoring time only, and API inputs are not used for training under the provider's terms.
Not a test — a contractual term of the provider.
Stated here as what it is: somebody else's commitment, not a mechanism in our code. If that matters to your diligence, ask us and we will send you the terms.