02 — Target architecture & compare/migrate
Status: draft
Date: 2026-07-29
Canonical home: this file in revrec-next
Thesis: RevRec can be a simpler, nicer product. Most current complexity is accidental. Design the optimal system first; compare; move pieces only as needed.
Visual: ../visuals/revrec-simple-target.html
Product: 00-product.md · Domain: 01-domain-asc606.md · ADRs: decisions/
1. Product in one sentence
ASC 606 revenue subledger that turns Chargebee (and later other) monetization facts into recognition schedules, deferred/unbilled, close, and clean GL journals — with AR as a sibling view.
Not: a pandas contract-reconstruction factory feeding Camunda file jobs.
2. Optimal architecture (simple)
┌──────────────┐ ┌─────────────────┐ ┌──────────────────────┐
│ Source │ │ Platform │ │ Domain │
│ adapters │────►│ Temporal runs │────►│ ASC 606 engine │
│ (CB first) │ │ raw lake (S3) │ │ + AR projector │
│ bulk|poll| │ │ cursors (PG) │ │ │
│ realtime │ │ │ │ ops DB (contracts, │
└──────────────┘ └────────┬────────┘ │ POs, schedules, AR) │
│ └──────────┬───────────┘
│ │
│ ┌──────────▼───────────┐
└─────────────►│ Serve │
│ API · close · journals │
│ ClickHouse analytics │
└────────────────────────┘
Four layers only
| Layer | Job | Tech opinion (not locked) |
|---|---|---|
| Extract | Own Chargebee pull; pluggable later | Temporal POC shape (~/work/temporal_poc) |
| Apply | Event → ASC 606 / AR state | Python domain services + transactional ops DB |
| Close | Period recognize + recon + GL post | Same domain; scheduled workflows |
| Serve | UI/API + waterfalls/metrics | API on ops DB; CH for heavy analytics |
Domain API (the whole product brain)
open_contract / amend_contract
record_billing / record_payment / record_credit
record_delivery # only if proportional / PIT needs it
recognize(period)
post_journals(period)
Default path for most SaaS: one PO, ratable over term, invoices update AR/deferred — no joins required.
Complexity (SSP multi-PO, usage, ramps, fancy CN policy) is feature flags on that API, not a second universe of ETL.
3. Compare: accidental now vs optimal
| Concern | Today (accidental) | Target (simple) |
|---|---|---|
| Contract | Inferred via invoice↔term asof | Explicit from sub/terms/catalog (+ CPQ later) |
| Recognition | Buried in sheet builders + Redis SO processing | recognize(period) on PO schedules |
| Billing | Same pipeline invents orders | Separate record_billing |
| Orchestration | Hotglue + Camunda + governor + locks | Temporal per-tenant extract + domain workflows |
| State | Redis + SingleStore + S3 + CSV snapshots | Ops DB + raw lake + analytics sink |
| Scale lever | Org-wide job mutex, file splits | Event apply + period jobs; parallel by contract |
| Extensibility | New tap = new pandas dialect | New SourceAdapter → same events |
| AC Premium | Parallel file dialect + SQL job | AR projector on same facts (or shared ledger core) |
Most of Hotglue contracts.py / Camunda / dual drivers may never need a 1:1 port — only the accounting outcomes that customers still need.
4. Compare & move — how to migrate without boiling everything
Principle
Target is the north star. Current system is a quarry: steal tests, merchant configs, and edge policies — don’t clone architecture.
Phases
| Phase | Move | Leave behind |
|---|---|---|
| A. Extract | Temporal + Chargebee adapter → raw lake | Hotglue schedule/webhooks (optional: keep Python worker only if needed short-term) |
| B. Thin engine | Single-PO ratable + billing + one amend + one CN policy on ops DB | OrderDetails manufacturing |
| C. Serve/close | Period recognize, deferred waterfall, journal export | Camunda sync job for that cohort |
| D. Expand domain | Multi-PO/SSP, usage, ramps — only with real demand evidence | Remaining reconstruction flags |
| E. AR / AC | AR projector (or shared ledger) | AC_* file reshape as permanent design |
| F. Retire | Cut Hotglue/Camunda paths per tenant cohort | — |
Comparison gate (per piece)
Before moving a piece, ask:
- Does ASC 606 / close / audit require it?
- Does a measurable merchant cohort need it?
- Can the target API express it as an event + policy?
If (1) and (2) are no → don’t move it. Document as deprecated behavior.
If yes → implement on target; shadow-compare numbers for a cohort; flip traffic; delete old path.
Shadow compare (the honest bridge)
Same Chargebee tenant
→ old path → old schedules / deferred
→ new path → new schedules / deferred
Diff report (amounts by period, by subscription)
Parity is accounting equality, not Excel sheet equality.
5. What we might never need
Candidates to leave in the quarry (validate with product/finance):
- Invoice-driven OrderDetails invention (
match_invoices_with_contracts) - Subscription closure full-history reload every job
- XL vs DB dual drivers
- Camunda governor weight math
- Passive HG job + active job lock theater
- Recon-of-recon against reconstructed sheets
- 200+ ETL fixtures as definition of done (replace with ~20 ASC scenarios + merchant golden set)
6. Immediate design checklist
- Freeze target diagram (this doc) as the architecture we’re building toward.
- Spec event catalog Chargebee →
open/amend/bill/pay/credit/cancel/usage. - Spec thin engine state machine + 5–8 golden ASC scenarios.
- Stand up extract from Temporal POC + CB adapter (no domain yet).
- Only then decide what current code is worth reading for policy edge cases.
7. Bottom line
Yes: simpler product, nicer architecture.
Optimal = extract platform + ASC 606 apply API + ops DB + analytics.
Compare against today; move pieces when the target can replace them; leave the rest to die.
Docs in this repo:
- Product: 00-product.md
- Domain: 01-domain-asc606.md
- Events / scenarios (build next): 03, 04
- Extract: 05-extract.md · ~/work/temporal_poc/
- Extend: 06-how-to-extend.md