Launch offer: the first 1,000 users get Settl free for a year*Claim your spot
settlbuilding in public

"One number, one source": the balance-engine pattern that ended it

The short version

My app showed two people two different answers to the same question, and my own records had a third. The cause was not a hard bug. Every screen was calculating the balance separately, so every screen could drift.

The fix is a rule, not a patch: exactly one piece of code is allowed to do the arithmetic, and everything else asks it for the answer. If your app shows a number people trust, that number needs one home.

Copy these prompts

Paste them into Claude Code (or any AI that can see your project), in this order. Each one answers a different question, and the first is usually the one that matters.

1. Find every place your app works out the same number

Show me every place in my app that calculates the number my users actually trust: a balance, a total, a score. List each one separately and tell me how many different places compute it. If it is more than one, show me where two of them would disagree today.

2. Make exactly one of them the source

Move that calculation into a single function and show me how every screen would call it instead of doing its own version. Tell me which screens I would have to change, and what would break while I am changing them.

3. Check what happens when somebody joins halfway through

Check whether a person who joins my app or a group partway through gets counted in things that happened before they arrived. Show me where that decision is made, and write a test that proves a new member is not charged for history.

My QA fleet caught it: phone A said "they owe you $43.33", phone B said "you owe $57.27". Same pair of friends, $14 apart, and the server had a third number. No single bug caused it. The architecture did: every screen recomputed balances its own way, custom splits silently fell back to equal splits, and new members were getting charged for expenses from before they joined.

The pattern

One number, one source. Any number your users trust (a balance, a total, a score) is computed in exactly ONE engine. Nothing else in the codebase is allowed to do arithmetic on it.

The playbook

  1. Create a single pure engine. Mine is a BalanceEngine class: rows in → balances out. No I/O, no caching, no framework imports. Pure = trivially testable.
  2. Ban arithmetic everywhere else. Screens render balances; they never recompute, never cache, never sum rows themselves. Enforce it in review: any +/- on money outside the engine is a rejected PR.
  3. Derive, never store. Balances are computed from expense/settlement rows on read. A stored balance is a cache, and caches of money go stale (my reviewer agent later caught exactly that: a cached balance that survived a settle-up).
  4. Feed the engine from one repository. One source of rows → one engine → one provider that screens subscribe to. My invariant doc literally says: "I1: One number, one source."
  5. Make fallbacks loud. The killer sub-bug: custom splits silently degrading to equal splits. If your engine can't honor an input, throw or flag. Never "do something reasonable" with money.
  6. Time-scope membership. New members must not owe for pre-join expenses. Every share calculation filters participants by join date.
  7. Test the engine like it's your whole company. Cross-device scenarios, settle-then-check, join-mid-trip, multi-currency. My balance math now has hundreds of tests. It's the one module where that's not overkill.
  8. Reconcile in QA, not in prod. Run a job that computes balances two independent ways (engine vs. raw SQL) and alerts on any mismatch. That's how you find the third number before a user does.

Steal this for your app

Run this on your codebase

Paste this into Claude Code in your repo:

Audit this repo for "one number, one source" violations.
Identify the trust-critical numbers users see (balances, totals, scores) and find every place each is computed.
Grep for duplicate domain math: the same sum, split, or rounding logic in more than one file, screen, or query.
Flag stored or cached values that duplicate what could be derived from source rows on read.
Flag silent fallbacks where invalid input degrades to a default instead of throwing, like custom splits becoming equal splits.
Check that members or time-scoped records cannot be charged for events from before they existed.
Propose one pure engine per number, with screens only rendering its output.
Report findings as a checklist before changing anything.

CENTS: remove floating-point money before it removes trust

The single-source engine still fails if the source stores 10.10 as a binary float. Audit every money field and convert it to an integer minor unit, such as cents. Parse at the boundary, calculate with integers, and format only for display. For a three-way split, allocate the remainder with a stable rule so the shares always add back to the original amount. Then add boundary and property-based tests for zero, negative adjustments, very large values, and awkward divisions.

The migration sequence matters: add the integer column, backfill and reconcile it against the old value, switch all writers, switch all readers, then remove the float. Never run two independent money representations without a reconciliation check.

NORMAL: one fact, one canonical home

Normalization is the structural version of "one number, one source." Store each changing fact once and reference it by ID. Before copying a field into another table, classify the copy explicitly:

Test propagation, deletion, retry, and repair sequences. If a user changes a name, status, or price, every current view should agree without a manual cleanup query.

Download the runnable pack

Use this pack to dry-run the audit locally, trigger the same workflow in n8n, and keep the checks in GitHub Actions. The default mode is read-only and returns a deterministic report before any change.

Get the next one in your inbox