13 — Full design: RevRec Next (enterprise ASC 606 subledger)
Status: design lock candidate (2026-07-30)
Audience: engineers building or reviewing the system
Companions: 14 domain · 15 ASC 606 eng spec · 16 gap matrix · 17 AC boundary · 18 architecture from model · 19 exhaustiveness · ADRs
This is the wholesome design package: what we have, what differs, what we need, domain, architecture, tech choices, pipelines, NFRs, tradeoffs. Implementation sequencing is secondary; the model and architecture are not.
0. Executive picture
Zuora-class product outcomes
(ASC 606 subledger · close · journals · reports · audit)
│
Chargebee (primary) ──► SourceAdapter ──► other adapters (extendable)
│
Extract (Temporal) → Raw lake
│
Normalize → domain events
│
┌─────────────────┴─────────────────┐
▼ ▼
REVREC APPLY AR / facts
Contract · PO · Schedule Billing · Cash
recognize(period) (same event log)
│ │
└──────────► amounts ───────────────┤
▼
ACCOUNTING CENTRAL
journals · COA · close · export
Stance (locked): ASC 606 Contract/PO model first; architecture from domain properties (18); external UX shapes bend to the model (ADR 0001–0007).
1. Problem statement
Finance must recognize revenue under ASC 606 / IFRS 15 while billing continuously changes — upgrades, credits, usage, custom terms — and the GL must stay clean, closable, and auditable.
Product: an enterprise revenue subledger with Zuora-class outcomes: explicit contracts and performance obligations, recognition schedules, period close, journals, waterfalls/disclosures, and audit — Chargebee-native first, extendable sources, AR/AC as sibling contexts (00-product.md).
Correctness bar: ASC 606 + audit + close + journal integrity (ADR 0001). External report shapes adapt to the domain model — not the reverse.
2. What we have (inputs to this design)
| Asset | Role |
|---|---|
Existing docs/00–12 |
Product aims, domain drafts, arch, events, scenarios, Zuora tiers |
| ADRs 0001–0002 | Stance + contracts≠invoices |
| ADRs 0003–0007 | Domain/apply, stores/tech, policies, AC boundary, enterprise scope |
Smoke scenario library (revanu/docs/revrec-scenarios) |
317 behavior contracts — quarry for policies & accounting equality |
| AC / RevRec Premium PRD | AC journals; does not calculate revenue |
| ASC 606 codification PDF | Engineering mapped in 15 |
temporal_poc |
Extract-plane experiments |
3. Have / diff / need
3.1 Inputs we already collected
| Asset | Use |
|---|---|
| Product aims, ASC 606 drafts, use cases, Zuora research | Scope & domain |
| Smoke scenario library (317 cases) | Policy / accounting-equality quarry |
| AC Premium PRD | Journaling boundary |
| ASC 606 PDF | Engineering paragraph map (doc 15) |
3.2 Conceptual shift (domain)
| From (common industry trap) | To (this design) |
|---|---|
| Reconstruct promises from invoice time series | Promises = Contract/PO from commercial arrangement |
| Spreadsheet/job output as truth | Ops SoR + audit events as truth |
| Org-global batch as scale model | Parallel apply by contract |
| Report shape defines the engine | Engine defines amounts; reports project |
3.3 Need (design must deliver)
- Explicit ASC 606 domain + apply API (14)
- Model-derived architecture (18)
- Owned extract + normalize to canonical events
- Close, journals interface, audit, RPO/waterfall
- AC boundary (17)
- Full capability coverage map (16)
- Migration via adapters + shadow accounting equality (when moving existing tenants)
4. Scope
4.1 In scope (product)
- ASC 606 five-step engine + modifications + VC hooks
- Chargebee-primary ingest; extendable adapters; legacy migration adapter
- AR sibling projector; AC as separate bounded context
- Period close, journal export, waterfalls, RPO, audit drill-down
- Multi-entity / multi-currency as model capabilities
- Multi-PO + SSP allocation as model capabilities (not omitted from design)
4.2 Out of scope (product)
- Catalyst / Billing→ERP as this system’s core
- Cloning Zuora RevPro staging / RC template engines
- Hotglue CSV byte-parity
- AC calculating recognition amounts
4.3 Maturity labels (not a sprint plan)
| Label | Meaning |
|---|---|
| Foundation | Single/multi contract ratable, bill/pay/credit, recognize, close, export, audit |
| Complete | Multi-PO SSP, PIT/delivery, usage basic, ramps, strong disclosures |
| Enterprise-deep | Multi-book, LT/ST, VC analyzer depth, cost amortization packs, advanced SOX UX |
| Out | ERP-as-RevRec, sheet parity, Catalyst |
Full matrix: 16.
5. End-user use cases (must solve)
From 10, treated as requirements:
| ID | User outcome |
|---|---|
| UC1 | Onboard + backfill without spreadsheets |
| UC2 | New subscription becomes an explainable contract/PO |
| UC3 | Invoice/payment update AR/deferred without inventing revenue plans |
| UC4 | Mid-term upgrade/downgrade with explicit mod treatment |
| UC5 | Credit note with named policy |
| UC6 | Cancellation with named terminate policy |
| UC7–9 | Recognize, post journals, lock period |
| UC10 | Audit: every dollar → source event + policy |
| UC11–12 | Waterfall, SaaS metrics, RPO |
| UC13 | Fix policy/late fact via replay/PPA — not archaeology |
| UC14 | AR/cash views (Premium) |
Smoke themes (modification, deferred-revenue, recognition-plan, credit-note, contract-term, multi-currency, service-delivery, ssp, ppa, subledger-ac, …) inform policies and golden tests, they do not define schema.
6. Architecture (technical depth)
Normative write-up: 18-architecture-from-model.md — model → properties → roles → tech.
Do not start from incumbent stacks; start from ASC 606 invariants.
6.1 Method (short)
Domain needs (single SoR, audit, per-contract parallelism, replay, rebuildable analytics, AC published facts) define component roles. Technologies only fill roles (ADR 0004).
6.2 Logical layers
| Layer | Responsibility | Failure mode if wrong |
|---|---|---|
| SourceAdapter | Pull/authenticate/page sources | Lost/duplicate facts |
| Extract | Schedule, checkpoint, write lake | Unreplayable history |
| Normalize | Map to canonical events | Policy bugs look like source bugs |
| Apply | Idempotent domain mutations | Corrupt subledger |
| Close | recognize / post / lock | Unclosable books |
| Serve | API/UI/read models | Finance can’t operate |
| Analytics | OLAP projections | Slow/untrusted waterfalls |
| AC consumer | Journals from published amounts | Unbalanced GL |
6.3 Runtime diagram
Source systems (Chargebee first; adapters later)
│
▼
[Orchestrator: Extract] ──writes──► [Object lake: raw/{tenant}/{stream}/{run}/]
│ │
│ [Normalize]
│ ▼
│ Canonical domain events
│ │
│ ▼
│ [Apply per contract key]
│ │
│ ┌──────────────┴──────────────┐
│ ▼ ▼
│ [Relational Ops SoR] [applied_events]
│ contracts, POs, idempotency+audit
│ schedules, billing…
│ │
├──── Close workflows ─────┤── recognize / post_journals / lock
│ │
│ ├──► outbox ──► [Analytics OLAP]
│ └──► recognition facts ──► [AC]
▼
[API / UI]
Defaults today: Temporal + Postgres + S3/GCS + ClickHouse + Python apply — substitutable within role.
6.4 Consistency & ordering
- Idempotency key:
(tenant_id, source, source_event_id)unique inapplied_events. - Ordering: total order per contract. Cross-contract concurrency OK.
- Apply transaction: mutate ops rows + insert applied_events (+ outbox) atomically.
- At-least-once extract + idempotent apply = safe replay.
6.5 Orchestrator & SoR (why these properties)
| Property needed | Default filler | Alternatives if properties held |
|---|---|---|
| Long backfill + per-contract concurrency + close visibility | Temporal | Other workflow engines with resume/visibility |
| Relational invariants on schedules/allocations/periods | PostgreSQL | NewSQL only if multi-region SoR is a hard requirement |
| Interactive waterfall/RPO | ClickHouse | BigQuery/Snowflake/Pinot |
| Immutable replay | Object storage | Any blob store with retention |
Ops SoR is never the analytics engine; analytics is never the write SoR.
6.6 Analytics path
Outbox → async projector → OLAP tables keyed by tenant/period/contract. Rebuild from Ops SoR/events required. Waterfalls must reconcile to SoR within tolerance.
6.7 API surface (serve)
- Command API: façade over apply/close (authz per tenant).
- Query API: contracts, schedules, waterfalls, audit chains.
- Optional compat projections: legacy-shaped DTOs read-only from Contract/PO.
7. Pipelines & workflows
7.1 Steady-state sync (F2)
- ExtractWorkflow wakes on schedule / webhook hint.
- Pull stream pages → lake objects + cursor update.
- Normalize batch → domain events.
- Fan-out ApplyWorkflow by
contract_key. - Each apply commits or no-ops if duplicate event id.
7.2 Close cycle (F3)
- Preconditions: extract lag below threshold; exceptions queue empty or waived.
recognize(period)for tenant (parallel by contract ranges).- Control reports: deferred rollforward, accounting identity checks.
post_journals(period)→ JournalBatch + export object.- Soft close → hard lock.
- Emit “RevRec closed” so AC can close revenue books.
7.3 Replay / correction (F7)
- Re-normalize from lake → re-apply (idempotent) after policy fix for open periods.
- Locked periods: PPA events in next period (ADR 0005).
- Shadow mode: dual-run legacy vs next; compare amounts by period/contract.
7.4 Onboarding backfill (F1)
Temporal backfill workflow pages historical CB data into lake, then apply in contract order. Progress and lag metrics are first-class.
8. Data stores (physical)
| Store | Role | SoR? |
|---|---|---|
| Raw lake (object store) | Verbatim source payloads | Source truth |
| Cursors | Extract bookmarks | Meta |
| Ops DB (relational) | Subledger state | Yes |
| Applied events | Idempotency + audit | Audit |
| Analytics OLAP | Waterfalls/metrics | No — rebuildable |
| Export objects | Journal/partner files | Files |
9. Domain model (summary)
Full detail: 14-domain-model.md.
Core: Customer, Contract, PerformanceObligation, Allocation, RecognitionScheduleLine, Modification, AppliedEvent, Period.
Billing: BillingDocument, BillingLine, CashApplication.
Close: JournalBatch (RevRec-side summary; AC may own richer JE model).
Balances: deferred (contract liability), unbilled (contract asset), AR, recognized-to-date — reconcilable to events.
Apply API: open_contract, amend_contract, record_billing, record_payment, record_credit, record_delivery, recognize, post_journals, terminate.
10. ASC 606 mapping
Engineering rules + codification pointers: 15-asc606-engineering-spec.md.
Five steps are mandatory; everything else is policy/implementation.
11. RevRec ↔ AC
17-revrec-ac-boundary.md + ADR 0006.
One line: RevRec decides how much and when for revenue/deferred/unbilled; AC turns those amounts (plus billing/cash/tax events) into balanced journals and GL close.
12. NFRs (enterprise)
| NFR | Target design response |
|---|---|
| Multi-tenant isolation | tenant_id everywhere; authz at API; optional RLS |
| Fat tenant | Parallel apply by contract; partitioned data; no org mutex |
| Throughput | Horizontal Temporal workers + Postgres read replicas for serve |
| Close time | Incremental recognize; only open lines for period |
| Durability | Lake + DB backups; PITR on Postgres |
| Audit | applied_events immutable; every balance change cites event+policy |
| Replay | Rebuild tenant from lake in shadow environment |
| Observability | Workflow IDs, lag metrics, apply error queue, close dashboards |
| Security | Secrets in vault; least-privilege CB keys; encryption at rest |
| DR | Multi-AZ DB; lake cross-region replication |
Exact numeric SLOs to be filled when production metrics from fat tenants are pulled (explicitly deferred measurement, not deferred architecture).
13. Tradeoffs
| Choice | Gain | Cost |
|---|---|---|
| Domain-first write model | Correctness, clarity | Compat projections if old UX required |
| Daily schedule grain | Amendment precision | More rows — mitigate with rollups |
| Workflow orchestrator | Operability of backfill/close | Ops skill for the chosen engine |
| Relational Ops SoR | Invariants | Shard story for ultra-fat tenants later |
| Separate analytics OLAP | Fast reports | Reconciliation discipline |
| Prospective default mods | Predictable SaaS | Must still support cumulative when required |
| Unify-but-separate AC | Clean boundaries | Two close knobs to teach users |
14. Migration strategy (design-level)
When moving existing tenants onto this engine:
- Shadow: feed the same commercial facts through Next; compare accounting equality (amounts by period/contract).
- Compat reads (optional): project familiar DTOs from Contract/PO if UX must stay frozen briefly.
- Cut over close so Next is SoR for recognition.
- AC consumes published recognition facts.
- Adapters for non-CB sources emit the same canonical events.
Curated smoke cases become early shadow fixtures (theme list in doc 16).
15. Risks
| Risk | Mitigation |
|---|---|
| Under-specified CB edge events | Event catalog + lake retention + quarantine queue |
| Policy explosion | Named policy registry (11); no spreadsheet branches |
| Dual-run drift | Accounting equality jobs; freeze sheet parity debates |
| Fat-tenant Postgres limits | Partition plan + apply sharding by contract hash |
| AC/RevRec close deadlocks | Explicit ordering ADR 0006 |
| Scope greed | Maturity labels; model complete, delivery sliced |
16. Open measurements (not open architecture)
- Concrete fat-tenant row counts / events/day (pull from prod later).
- Final CH vs warehouse for some existing reports during migration.
- UI information architecture (consume domain queries).
17. How to read / refine
- Skim this doc (13).
- Deep: 14, 15.
- Scope: 16.
- AC: 17.
- Locks: decisions/.
- Compiled HTML:
site/index.htmlafterpython3 build-site.py && python3 build-index.py.
Refine section-by-section against the HTML site; do not expand implementation code from this pass.