# Billing & notification source-of-truth map **Status:** Phase 2 convergence guide **Date:** 2026-08-12 ## Billing | Generation | Models | Rule | |------------|--------|------| | **Canonical** | `BillingAccount`, `BillingInvoice`, line items, intents, attempts, credits, tax, refunds | Write path for new money movement | | **Legacy compat** | `SubscriptionInvoice`, older payment-attempt shapes linked via `billingInvoiceId` | Read/migrate only; do not create new orphans | **Invariant:** One money-moving command → one canonical `BillingInvoice` (or intent) → provider idempotency key → webhook reconciliation → auditable outcome. Until legacy rows are archived: 1. Prefer canonical reads in admin/finance UI 2. Keep dual-read adapters only where needed for historical invoices 3. Block new writes that create legacy-only invoice rows ## Notifications | Generation | Models | Rule | |------------|--------|------| | **Canonical** | `NotificationEvent` → `NotificationRecipient` → `NotificationDelivery` + `NotificationOutbox` | All new product notifications | | **Legacy** | Flat `Notification` | Migrate reads; no new writes after GA | **Dispatch:** Outbox worker (Phase 1) is the only delivery executor. Direct `sendTransactionalEmail` remains allowed for auth/security messages that must not wait on the outbox. ## Exit evidence for convergence - [ ] Inventory counts of legacy vs canonical rows in staging/prod - [ ] No new legacy-only writes in code paths covered by tests - [ ] Retention policy applied consistently across both generations during migration