Back to blog

Accounting rules the database enforces

A posting contract checked by a boundary lint, GL invariants that run continuously against production data, a characterization harness replayed on every change, and a migration chain that builds from zero again.

PlatformLedgerEngineering

In March we said Zeno holds the books itself. That claim is only worth something if the ledger can’t be talked out of it. Last week went into making the accounting rules structural: enforced by the database and the test suite, not by whoever wrote the most recent posting path.

A posting contract, checked at the boundary

Every posting path writes through one contract. The columns a report reads are the denormalized scope, party, and cost-center fields that make aggregation fast, and they’re derived from the entry rather than set by hand at each call site. A lint rule fails the build if a module reaches around the contract and writes them itself. The fast columns are a function of the slow ones, so a report can’t disagree with the entries beneath it.

Invariants that run continuously

Debits equal credits is the easy one. The harder invariants run continuously against production data instead of waiting for a quarterly audit: every leg carries a scope, no entry references a closed period, no attribution is left unassigned.

We paired them with a characterization harness, a set of known-good postings captured from production that the test suite replays after every change. If a refactor changes what a bill posts, the harness catches it before the deploy does.

Journal entries file each leg under its own scope

Journal entries used to be exempt from attribution. A JE could sit in the ledger belonging to nothing in particular, and property- or project-level reports would miss it without saying so. Each row of a JE now files under its own leg’s scope, and the exemption is gone. Older entries were backfilled.

Migrations that build from zero

The migration chain had accumulated years of ordering assumptions and could no longer run against an empty database, which meant nobody could stand up an environment matching production. We captured a genesis baseline from production and rebuilt the chain on top of it. A fresh database builds to the current schema in one pass, and migration safety is part of the same checked contract.


None of this shows up in the interface. Live for every organization: [email protected].