11 — Rules reference (the policy catalog)
Status: draft Purpose: One place for the rules that drive recognition, allocation, modifications, credits, close, and journals. Use this to make product decisions and to configure a tenant. Principle: Rules are named config + policy, resolved deterministically — not logic buried in joins or spreadsheet branches.
Read with: 01-domain-asc606.md (why) · 03-event-catalog.md (policy ids) · 04-engine-scenarios.md (numbers) · 10-use-cases-and-flows.md (where rules fire)
Naming/precedence here are design proposals. Lock any rule that affects a booked number via an ADR.
0. Rule resolution order (precedence)
When multiple levels set a rule, the most specific wins:
line override > contract/PO > product/SKU > org default > system default
Every effective rule value is stamped on the entity it affects, so audit (F5) can show which rule applied and from which level.
1. Recognition method rules (Step 5)
recog.method — when/how control transfers.
| Method | Use when | How revenue is spread | Key inputs |
|---|---|---|---|
ratable (default) |
SaaS access over a term (series of distinct services) | Even over service window (daily or monthly calendar) | service_start, service_end, allocated amount |
point_in_time |
One-time delivery (setup fee at go-live, hardware) | 100% on delivery/transfer date | delivery date/event |
proportional |
Professional services / delivery-based | % complete or units delivered | delivery feed (record_delivery) |
milestone |
Fixed-fee stages | On milestone acceptance | milestone events |
usage (later) |
Metered / variable | As usage occurs, constrained | usage events + constraint policy |
Calendar sub-rule: daily (precise proration) vs monthly (simpler). Pick per tenant; affects amendment/cancel proration. Lock in ADR.
2. Transaction price rules (Step 3)
| Rule | Meaning | MVP |
|---|---|---|
| Fixed consideration | Known contracted amount | yes |
| Variable consideration | Usage/discounts/rebates estimated | later |
| Constraint | Only include variable amount not likely to significantly reverse | later (with usage) |
| Significant financing | Adjust for time value if payment timing is long | out |
| Non-cash consideration | Fair value of non-cash | out |
TP source order (Chargebee): subscription/item price → contract amount → read first invoice amount as an input (never as contract creator). See 03 §3.1 open question.
3. Allocation rules (Step 4)
| Rule | Use when | Method |
|---|---|---|
| Single PO 100% | One promise (most SaaS) | All TP to the PO |
| Relative SSP | Multiple distinct POs | Allocate by standalone selling price ratio |
| Residual | One PO has no observable SSP | Others at SSP, remainder to that PO |
| Discount allocation | Bundle discount | Proportional to SSP unless discount clearly belongs to specific PO |
SSP source (config): observable list price → adjusted market → residual. SSP library is tenant config; multi-PO is a deferred feature (08 §8).
4. Modification rules (contract changes)
Decision tree for amend_contract (ASC 606-10-25-10..13):
Is added scope a distinct good/service AT its SSP?
YES → separate contract (new PO, no touch to existing)
NO → is remaining scope distinct from what's transferred?
YES → PROSPECTIVE (reallocate remaining TP over remaining POs; no restatement)
NO → CUMULATIVE CATCH-UP (adjust revenue-to-date immediately)
| Policy ID | Trigger | Effect | MVP |
|---|---|---|---|
mod.upgrade |
mid-term increase | prospective: reflow remaining schedule | yes (prospective) |
mod.downgrade |
mid-term decrease | prospective | yes (prospective) |
mod.separate_contract |
distinct add-on at SSP | new PO, existing untouched | later |
mod.cumulative |
not-distinct change | catch-up adjustment to date | later |
MVP does prospective only; cumulative + separate-contract are ADR-gated additions.
5. Credit note / return rules
record_credit resolves a CB reason_code → named policy (config map, not join logic).
| Policy ID | Substance | Effect on revenue | Effect on deferred/AR |
|---|---|---|---|
cn.refund_unrecog (default) |
Price reduction on unrecognized portion | unchanged | deferred ↓ / remaining schedule ↓ |
cn.reverse_recog |
Return / cancellation of delivered value | reverse recognized portion | revenue ↓, adjust balances |
cn.ar_only |
Billing-only credit (re-bill, admin) | unchanged | AR credit only; recognition untouched |
Overflow rule (CN > unrecognized): define whether excess reverses recognized revenue or becomes AR credit — ADR (08 §4.5). Steal ideas from Hotglue apply_accounting_treatment; do not steal its control flow.
6. Cancellation / termination rules
terminate with policy term.cancel:
| Option | Remaining deferred becomes | Use when |
|---|---|---|
recognize_remaining |
Revenue now (obligation released) | Non-refundable, obligation satisfied/waived |
refund |
AR/refund liability | Refundable prepayment |
write_off |
Reversed (no revenue) | Contract void / never earned |
Proration to effective date follows the recognition calendar (§1). Default is ADR-gated — do not hardcode.
7. Contract-existence rules (Step 1 gate)
A contract is recognized only when ASC 606-10-25-1 criteria hold, notably collectibility probable.
| Rule | Effect |
|---|---|
| Collectibility probable | Normal recognition |
payment_required_for_revenue_recognition |
Defer recognition until cash/collectibility established (delayed revenue) |
| Not probable | Hold / cash-basis deposit method until threshold met |
This is the clean home for today’s payment_required_for_revenue_recognition flag and "Delayed Revenue" — a Step-1 policy, not a pipeline hack.
8. Period & close rules
| Rule | Statement |
|---|---|
| Recognize idempotency | recognize(period) repeatable while period unlocked; same result |
| Lock | Locked period is immutable; balances frozen |
| Reopen | Allowed only pre-lock, or via controlled restatement with audit note |
| Late fact (locked period) | Route to next open period as catch-up/adjustment; no silent rewrite |
| Cutoff | Period membership by service/recognition date, not arrival time |
| Restatement | Explicit, logged, and disclosed — never in-place |
State machine: OPEN → READY → RECOGNIZED → POSTED → LOCKED (see 10 §5).
9. Currency & rounding rules
| Rule | Statement |
|---|---|
| Functional currency | Each contract recognized in its currency; report in tenant functional currency |
| FX rate source | Period/spot per policy; store rate used on each entry (audit) |
| Zero-decimal currencies | JPY, KRW, VND, CLP, XAF, XOF, KRW — no minor units |
| Penny reconciliation | Schedule lines must sum exactly to allocated TP; put rounding remainder on last (or first) period deterministically |
| Multi-decimal | Support configurable precision where source needs it |
Invariant: ∑ recognized + deferred_remaining = allocated TP (after amendments/credits per policy), to the cent.
10. Journal / account mapping rules
post_journals(period) maps subledger movements to GL accounts (config per tenant).
| Movement | Dr | Cr |
|---|---|---|
| Bill prepaid (paid) | Cash / Clearing | Deferred revenue |
| Bill on credit | AR | Deferred revenue |
| Payment | Cash | AR |
| Recognize | Deferred revenue | Revenue |
| Recognize-before-bill | Unbilled AR / Contract asset | Revenue |
Credit (refund_unrecog) |
Deferred revenue | AR / Credit |
Credit (reverse_recog) |
Revenue | AR / Refund liability |
Only summarized batches post to GL; line-level detail stays in the subledger (that’s the "subledger" promise in 00).
11. Config surface (what an admin sets per tenant)
| Config | Example |
|---|---|
| Recognition calendar | daily | monthly |
| Default method by product | plan→ratable, setup→point_in_time |
| SSP library | product → SSP amount/source |
| CN reason → policy map | subscription_change→cn.refund_unrecog, product_unsatisfactory→cn.reverse_recog |
| Modification defaults | upgrade/downgrade → prospective |
| Cancellation policy | term.cancel = recognize_remaining | refund | write_off |
| Collectibility rule | payment_required_for_revenue_recognition on/off |
| Currency + FX source | functional currency, rate table |
| GL account map | subledger movement → account codes |
Rule of thumb: if a behavior changes a booked number, it must be (a) a named rule here, (b) settable as config, and (c) covered by a golden scenario in 04.
12. Rules → scenarios coverage
| Rule area | Golden scenario |
|---|---|
| Ratable recognize | S01 / S02 |
| Billing vs recognition | S02 |
| Modification prospective | S03 |
| CN refund_unrecog | S04 |
| Cancellation | S05 (todo) |
| Multi-PO SSP | S06 (later) |
| Usage constraint | S07 (later) |
| Proportional delivery | S08 (later) |
Any new rule needs a scenario before it ships.