Bare Metal

Where the enforcement in a community association ledger ended up living, why it moved from the application to the database, and what the database still cannot see.

By Scott Vuilleumier · September 08, 2026 · 9 min read

An association's journal entry has to survive its authors. The treasurer who approved it serves a term. The management contract runs a few years. The software vendor is the vendor until it is not. Thirty years on, when the roof that entry was reserving for is replaced, someone who has met none of them has to open the record and answer three questions: what happened, who decided it, and against what rule.

I built the ledger at CommunityPay around that obligation. This is where the enforcement ended up living, and why it moved.

Where Controls Usually Live

When a rule lives in the application, it binds only the callers that pass through it. A request arrives, a function validates it, the row is written. Anything holding a database connection can go around: a management command written for a migration, a console session during an incident, a vendor integration that writes directly because the API was slow, a maintainer in 2031 who did not know the rule was there. None of these are attacks. Each produces a row the validation never saw and the record cannot distinguish from a row it did.

The failure is not that the rule was broken. It is that afterward, nothing says whether it was.

Two Choke Points

Every posting to the ledger goes through one interface, JournalEngine.post_transaction(), and is evaluated by one dispatcher before it persists. Eighteen guards are declared in a manifest the test suite checks; the ones that apply to a posting's flow run in a fixed order, at least three of them required. Twelve cannot be overridden. Six can, and an override is not the guard stepping aside — it is a fourth kind of decision, recorded with a reference to the authorization that permitted it. The dispatcher returns ALLOW, BLOCK, OVERRIDE, or ERROR. Each answer is written to an immutable record before the entry persists and, when it does persist, again after, so the two halves can be compared.

This is a choke point in code. It is also a convention. The test suite can show that nothing in the repository constructs a journal entry outside the engine today. It cannot show that about tomorrow, and it cannot see a connection that never loaded the repository at all.

Pushing It Down

trg_require_enforcement_decision is a SQL constraint trigger on the journal-entry table: AFTER INSERT, FOR EACH ROW, DEFERRABLE INITIALLY DEFERRED. It fires at COMMIT rather than at the write, so the entry and its decision can be created in either order inside the transaction — but the transaction cannot close unless a PRE_PERSIST decision exists for that entry's correlation id and association. If none does, the commit is refused with an integrity-constraint violation, and the message states the design in one line: an entry with no decision is indistinguishable from a bypass.

The difference from application validation is who the rule binds. The trigger does not know what wrote the row. A migration, a console, a process that has never heard of the engine — each is held to the same condition, because the condition is enforced where the row lands and not where it was made.

What It Cannot See

A constraint trigger on INSERT is blind to UPDATE.

The ledger had a button. On a draft entry, "Post Entry" set the status to POSTED and saved. It did not call the engine. No dispatcher ran, no decision was written, and account balances never moved, because the code path that moves them was never invoked. The insert trigger did not cover it: the row already existed, the trigger had already fired, and this was an UPDATE. The button was removed on August 21. Production held 4,184 journal entries, every one POSTED, and zero drafts. Nothing was waiting on it.

Other rules hold the UPDATE surface. trg_protect_closed_entries refuses changes to entries in a closed period. trg_protect_reversal_pair refuses a status change on either leg of a posted reversal — under append-only correction, an original and its reversal only net while both stay POSTED, and one UPDATE to either would restore, without tripping anything, the double count the design exists to remove. trg_validate_journal_balance checks the balance on the UPDATE that posts an entry, and trg_posted_entry_balance_at_commit checks it again at COMMIT.

That second rule exists because of the first. The balance trigger was BEFORE UPDATE — the only shape available when it was written, since on INSERT the entry has no lines yet and there is nothing to sum. An audit on August 10 found the consequence: a single INSERT carrying status POSTED never fired it. The admin's add form did exactly that — 100.00 of debit against 5.00 of credit, posted, and the row landed. The deferred trigger closes it by waiting until the lines exist.

Eight triggers now hold the ledger, on three tables. Each has a stated blind side, and the blind sides are where the next one gets written.

Rules That Verify They Are Installed

Every other check on the books reads what the rules produced: decisions, snapshots, and the bypass log. None of those can tell a book with nothing wrong from a book whose rules were switched off.

ALTER TABLE ... DISABLE TRIGGER is one statement. It writes no row and trips no constraint. Unlike the transaction-scoped escapes, it is not recorded, because the code that would record it is the code it turns off. Reading the system catalog directly is the only way to see it.

So a nightly probe reads the database's own system catalog. It looks for the eight triggers by name and checks that each is enabled for ordinary writes — a trigger disabled or set to replica-only sits in the catalog looking installed while firing on nothing. It looks for the recorder function. And since August it looks for ten declared constraints, because of what happened to one of them.

unique_payment_per_bill had been declared on the bill-payment model since a migration applied on January 20, 2026. The guard that checks for duplicate vendor payments named it as the canonical check. On August 22, reading the database catalog directly, it was not in the production database. The table carried its primary key, three foreign keys, and one other constraint from the same declaration list — and nothing else. The development database had it. Every migration reported applied. Seven other declared unique constraints were missing from production the same way.

A declared control and an installed control are different objects, and only one of them refuses anything. The constraint has since been installed in production as a partial unique index, so a reversed payment releases the bill for retry, and the probe now reads constraints as well as triggers.

What an Override Actually Is

An override is a recorded decision, OVERRIDE, carrying a reference to the authorization that permitted it. It is distinguishable from ALLOW in the record forever. The ten non-overridable guards have no such path; a BLOCK from any of them ends the posting.

Below the guards, two escapes exist at the database layer. SET LOCAL communitypay.allow_undecided_je = 'on' tells the decision trigger to stand down for the current transaction. allow_unbalanced_je does the same for the balance-at-commit rule. Both exist because test fixtures and demo seeding build ledgers whose contents are incidental. SET LOCAL scopes each to its transaction; neither can leak into another request.

Until August they left no trace. The rule did not fire, and nothing recorded that it had been asked not to. Now each escape, when honored for an association that is not a demo tenant, writes a row: which rule, which entry, which database user, when. A release gate reads that table. It should be empty forever, and its being empty is a fact the system can state rather than assume.

The reversal-pair trigger has no escape at all. Nothing legitimately needs to move a posted reversal pair out of POSTED, in a fixture or anywhere else. A rule with no exception needs no recorder.

The accurate claim is not that the ledger cannot be bypassed. A person with the database password can disable a rule, write, and re-enable it, and the catalog probe sees only what is on at the moment it runs. What that leaves behind — an entry with no decision behind it — is what the nightly orphan scan reads from the data itself, whatever the catalog said. The bypass is possible. Leaving no trace of it is the part that is not.

What Is Not Covered

Every enforcement decision hashes the stored hash of the one before it, and with each integrity scan the head of that chain is written into an immutable snapshot, so a deleted decision breaks the chain and a deleted-then-recomputed chain fails to match its anchor.

The chain starts on August 10, 2026. There are 7,593 decision rows before it, and they are not hashed. I declined to backfill them: hashing a row today attests only that it has not changed since today, and says nothing about the year and a half before. A backfilled chain would need a marker separating native links from retroactive ones, or it would assert more than it knows. The rows are there. They are readable. They are not chained, and the system says so.

Why Associations

Homes inside community associations are worth $13.1 trillion in the United States. Boards turn over. Management contracts change. Nobody is assigned to check whether the rule was followed at the moment the row was written. The reserve balance is money set aside against a roof that a future owner, not the current one, will stand under.

A procedural control is a control nobody is coming to verify. The rule has to hold when the person who wrote it is gone, the person who approved the entry is gone, and the person reading it has no one to ask. The only layer that meets that is the one the row cannot land without passing through.

The Entry, Again

Take the entry from the first paragraph and trace it through. It was posted through the engine, so a decision preceded it. The dispatcher ran its guards and wrote ALLOW before the row existed and again after. The commit could not have closed without that decision; the trigger would have refused it. If the trigger had been asked to stand down, a row would say so. If the trigger had not been there to ask, the nightly probe would have said that instead. The decision hashes the one before it, and the chain's head is anchored outside itself.

Thirty years from now, the person who opens it can answer the three questions. Not because anyone remembers. Because nothing could have been written that would not have answered them.


Mandatory Enforcement Choke-Point Architecture — non-provisional application filed with the USPTO, April 2026.

Scott Vuilleumier · Founder of CommunityPay, Inc.

Data science, investment banking, and financial systems engineering and ledger design.

Governance Tools

Free tools for reserve planning and board compliance.

Governance Tools
Subscribe
RSS feed
Financial risk governance infrastructure for community associations.
Payments and accounting systems that govern how money moves, record every decision, evaluate counterparty risk, and enforce statutory compliance.
Request Access →
Login