Skip to main content

Context

Tiers are the retention mechanism of a loyalty program: they give a member something to lose. That also makes them the most emotionally charged feature in the module — a downgrade a member did not see coming is a support ticket and often a churned customer. Two decisions carry most of the weight. The qualification window — rolling twelve months versus calendar year — changes the entire feel of the program: rolling is continuous and fair, calendar creates a year-end push and a January cliff. And the downgrade policy, which needs a grace period not for correctness but because dropping someone the instant they fall below a threshold is a product failure even when it is arithmetically right. This is what status points exist for (ADR-011). Qualifying on redeemable points would mean a member loses status by spending, which punishes exactly the behaviour the program wants.

Scope (normative)

  • loyalty.tier_definitions: ordered levels with thresholds against a qualification metric.
  • loyalty.tier_qualification_metrics: parametric, is_system seeds points_earned, spend, event_count.
  • Window modes rolling_days and calendar_year.
  • loyalty.tier_memberships with full history: entered, exited, reason.
  • Explicit downgrade policy with a configurable grace period.
  • loyalty.tier_overrides: earning multipliers, reward pricing, expiration extension per tier.
  • Recomputation driven by ledger events, with a scheduled safety net.

Non-scope (normative)

  • Tier-based notifications. This feature emits the events; the campaigns are messaging.
  • Manual tier assignment by staff — a later console feature with its own audit semantics.
  • Tier-gated reward visibility beyond the pricing override, which is FS-LOY-0005’s concern.

Behaviour (normative)

  1. Tiers are ordered by level, unique per program, with strictly increasing thresholds. Two tiers at the same level are FORBIDDEN.
  2. Qualification uses the configured metric over the configured window. Redeemable points never qualify: spending must not cost status.
  3. Upgrades are immediate on crossing a threshold. A member should never wait to receive good news.
  4. Downgrades are never immediate. The member enters a grace period; if they requalify before it ends, no downgrade is recorded at all. Only on expiry does the downgrade apply and emit.
  5. Membership history is append-only. A member’s tier at any past date must be reconstructible — this is what answers “why was I charged that price”.
  6. tier_overrides apply at the moment of the transaction, not retroactively. Reaching Gold does not re-price yesterday’s redemption.
  7. Recomputation is event-driven on ledger writes and re-verified by a scheduled job at the tenant’s freshness tier. Divergence is alerted, never silently corrected. 7b. When the status currency is configured to expire (OQ-LOY-02, FS-LOY-0002), the qualification metric falls on its own and a member can drop a tier without having stopped buying. In that configuration the grace period and the tier.grace_started event are mandatory, and members/me/tier MUST show the projected drop and its date before it happens. Flexibility that surprises a member is a support ticket, not a feature.
  8. tier_qualification_metrics is not tenant-extensible: the metric governs system logic.

Data (normative)

API (normative)

members/me/tier returns the current tier, progress toward the next, and — when in grace — the date the downgrade would apply. Hiding that date would be the single most user-hostile choice available here.

Events (normative)

loyalty.tier.upgraded and loyalty.tier.downgraded, both available as outgoing webhooks. Entering grace emits no event in F1; whether it should is an open question below.

Acceptance criteria (normative)

  1. Crossing a threshold upgrades within 5 s p95 of the qualifying ledger write and emits once.
  2. Falling below a threshold starts grace and does not downgrade. Requalifying inside grace records no downgrade at all.
  3. Grace expiry downgrades and emits exactly once.
  4. Rolling-window correctness: a member whose qualifying activity is 366 days old no longer counts it, verified against a seeded timeline.
  5. Membership history reconstructs the tier held on any given past date.
  6. A tier override multiplier applies to earnings after the upgrade and not to earlier ones.
  7. Negative: redeeming points never lowers a member’s qualification metric.

Execution

Asynchronous pipeline, driven by ledger events; the safety-net recomputation is a scheduled per-tenant fan-out job.

Open questions

None outstanding. Every question this spec carried was answered in the consolidated register (../../../design/open-questions-v1.md, v1.1) and folded into the normative sections above.

Changelog

Delivery record

Not implemented yet.