382 migrations. Most of the rules that matter aren't in my app code at all — they're triggers, constraints, RLS policies and SQL functions, tested with pgTAP.
The reasoning is uncomfortable but simple: anything enforced in the client is a suggestion. My product sells tamper-evident approval records. If a sign-off's immutability lives in a React component, it isn't immutable, it's polite.
So the update and delete triggers on a completed sign-off refuse for everyone, table owner included. Deposit gating withholds deliverable files at the point where the signed download URL is minted, not in the UI, and it fails closed on error. Seat limits are a trigger on the members table, not a check in a hook.
That pushes testing down with it. Vitest covers 531 files' worth of app logic. The rules live in pgTAP suites that run against a real Postgres in CI, because a mocked trigger proves nothing.
Two things I learned the hard way.
A test suite can be dark. I used mnemonic UUIDs in fixtures for readability. A UUID is hex, and letters like n, y, i and p aren't. Postgres rejected the insert — but the insert was in the fixtures, so the suite aborted before its first assertion and reported no failure and no result. 24 suites were in that state. Not randomly distributed: milestone billing, currency consolidation, the dunning ladder, money webhook events. The features people adopt the product for were the ones with no executing coverage. 125 literals repaired, and the gate now also asserts every suite declares a plan and calls finish(), so a suite can't ship unable to fail.
Global coverage numbers lie in a specific direction. My repo-wide floor is 22% lines, which is honest for a codebase dominated by page components and useless for lateFee.ts. An average lets a 100%-covered tax module and a 0%-covered page swap places without moving the number. So the arithmetic that lands on an invoice has per-module floors — invoice composition, tax, late fees, deposits, retainers, receivables — and the check also fails when a new money-shaped module appears in src/lib with no floor, because a hand-maintained list of important files rots by addition.
Yes, and you've named the distinction I'd defend: coverage is a percentage, executability is a per-suite fact, and the second can regress while the first improves.
Two layers now. The 22% is a deliberately dumb global ratchet dominated by page components and generated types, and its only job is to stop the whole repo sliding. I don't treat it as information about any individual module. The real gates are per-module floors on the money path, generated from a single manifest that both the Vitest config and a separate post-hoc check read, so the runner and the auditor can't disagree about what's gated or at what level. That second check also fails when a new money-shaped module shows up with no floor at all, which is the case a threshold can't express.
Executability is enforced separately and earlier. Before the database even starts, CI verifies a named allowlist of 13 pgTAP suites is present: sign-off flow, Stripe webhooks, org_id immutability, dunning ladder, invoice idempotency, version-hash canonicalisation. A missing file fails the build on its own error. The suite is auto-discovered rather than registered, which kills the commonest dark vector: there's no list anyone can forget to add to. 281 SQL suites, and zero skipped or
.onlytests in the TS suite.The edge I haven't closed: presence isn't assertion. A hollowed-out suite with a dropped
plan()count would still pass that check. Pinning expected assertion counts per gating suite is the obvious next move.