Skip to main content
Status: Proposed · Refs: v0.2 §2.2, benchmark (Open Loyalty locked points, Voucherify pending periods)

Decision

  • loyalty.ledger_transactions is append-only; types (parametric): earn|redeem|expire|revoke|adjust; states pending→available (earn confirms after the tenant-configured return window). A mutable balance column is FORBIDDEN.
  • Two currencies per program minimum: redeemable and status (XP). Never mixed (airline model).
  • Earns create loyalty.point_lots with expires_at; redemptions consume lots FIFO. Expiration modes per program (tier-overridable): rolling N days | end-of-month | end-of-year. points.expiring_soon scan feeds retention campaigns.
  • loyalty.contact_balances is a projection updated in the SAME transaction as the ledger write (near-real-time, non-negotiable); nightly reconciliation detects drift (data-freshness add-on raises frequency of the reconciliation, never of the balance itself).
  • Redemptions are parent/child (multi-redeemable requests); rollback at parent level with revert|keep modes; Idempotency-Key mandatory. money_component (amount+currency, nullable) reserves the mixed points+money seam (DEC-H3) — unused in F1.

Consequences

  • Auditable liability, replayable balances, standard industry semantics. − More rows than a naive balance; mitigated by partitioning evaluation and projections.