DATA ARCHITECTURE
Account State
Account state is fragmented across six stores, mutated by three areas of the billing platform, with no unified view. Maps the current data flow, identifies consistency failures, and describes the target Gather→Compute→Apply architecture.
Account state is scattered across six stores. Three areas mutate overlapping subsets. No single component reads all six — the recompute engine is the first to attempt a unified view by reading all five subs-api tables.
| Store | What It Holds | Who Mutates | Consistency Risk |
|---|---|---|---|
| cf_prod | Account records, entitlements, product config | A1 (accounts), Entitlements API | Schema coupled to product models |
| billing_prod | Invoices, charges, dunning state, schedules | A2 (invoices), A3 (charges) | Financial ledger — audit-critical |
| subs_account | Account flags, billing profile | A1 (profiles), A3 (bad_debt) | Two areas write different fields |
| subs_customer | Stripe ↔ CF customer mapping | A1 (setup), A2 (checkout) | Race condition on concurrent writes |
| subs_dunning | Dunning state per account | A3 only | Clean — single writer |
| Stripe | Payment status, mandates, invoice state | External (Stripe owns) | Eventual consistency via webhooks |
- 6
- stores
- 3
- areas mutating state
- 691
- disagreeing accounts
When handlers write subsets of state in isolation, accounts drift into inconsistent states. 691 accounts were found in disagreement — dunning state says one thing, subscription state says another, Stripe says a third. No single component reads all six stores, so no component can detect the drift.
Each handler writes the subset it knows about. A dunning handler updates subs_dunning but doesn't check subs_account flags. An account handler updates profiles but doesn't check payment state.
No component reads all six stores. Drift accumulates silently until a customer reports a problem or an audit catches it.
Shared tables (subs_account, subs_customer) are written by multiple areas. Changes require cross-team coordination that doesn't exist in the current architecture.
The target architecture centralizes state resolution. One component gathers all signals, computes what the account state should be, applies the minimal diff, and emits domain events. The recompute engine is the first implementation of this pattern — reading all five subs-api tables to compute correct dunning state.
A single component reads all relevant state. No partial views, no assumptions about what other components have done.
The computed state is the correct state. If it differs from what's stored, the stored state is wrong — apply the diff.
Minimal writes. Only change what's actually wrong. Emit events for downstream consumers to react to the corrected state.
Account state fragmentation is the root cause of billing inconsistencies. The Gather→Compute→Apply pattern eliminates drift by design — one reader, one truth, one diff.