SYSTEM ARCHITECTURE
Shared State
Five database tables sit at the gravitational center. Every area reaches inward to read and mutate this shared state — coupling is in the data, not the code paths.
| Table | Area | Access Type | Data / Effect | Coupling |
|---|---|---|---|---|
| subs_account | A1, A3 | direct DB | Account flags, billing profile. A1 writes profiles, A3 writes bad_debt. | Data coupling |
| subs_customer | A1, A2 | direct DB | Stripe ↔ CF customer mapping. A1 writes during setup, A2 during checkout. | Data coupling |
| subs_dunning | A1 (read), A3 | direct DB | Dunning state per account. A3 writes, A1 reads for account status. | Data coupling |
| subs_mandate | A2 (read), A3 | direct DB | Payment mandates. A3 reads and writes, A2 checks before operations. | Data coupling |
| subs_stuck_events | A3 | direct DB | Unprocessed webhook events. A3 writes, ops reads for investigation. | Data coupling |
| Stripe | All areas | webhook → DB | Payment status, mandates, invoice state. External source of truth. | External |
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.
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 — it reads all five subs-api tables and determines the correct dunning state in a single pass. Disagreement is structurally impossible when one reader sees everything.
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. The recompute engine proves the pattern works.