Skip to content

fix: resolve overlapping subscription entitlements consistently #520

Description

@jimmykane

Problem

Auth claims and subscription enforcement currently resolve an account's role by querying active/trialing subscriptions ordered by created desc and selecting only the first document.

If overlapping subscriptions exist, a newer lower-tier subscription can mask an older active Pro subscription. That can under-entitle a paying user and eventually disconnect Garmin, Suunto, or COROS even though a Pro subscription remains active.

The email lifecycle now suppresses account-wide upgrade/downgrade messages when the changed subscription does not define the current membership, but the underlying claims and enforcement behavior remains unchanged.

Scope

  • Define the supported account-level rule for overlapping active/trialing subscriptions.
  • Prefer a fail-safe rule that never under-entitles a user who still pays for a higher tier.
  • Implement one shared effective-subscription resolver.
  • Use it consistently in:
    • functions/src/stripe/claims.ts
    • functions/src/schedule/enforce-subscription-limits.ts
    • any other server-side entitlement checks that select one subscription
  • Preserve deletion guards and recursive cleanup behavior.
  • Add structured logging or diagnostics when overlapping active subscriptions are detected.

Acceptance criteria

  • An account with active Basic and Pro subscriptions retains Pro claims and device sync.
  • Subscription ordering or Firestore query order cannot change the effective entitlement.
  • Trialing subscriptions follow the documented product rule.
  • Unknown roles fail safely without granting unsupported access.
  • Multi-subscription tests cover claims, enforcement, grace periods, and service disconnection.
  • No connected service is disconnected while an effective Pro entitlement remains active.
  • The Functions suite and build pass.

Context

Discovered while tightening the email lifecycle refresh. This is intentionally tracked separately because it changes real access and service-disconnection behavior rather than email delivery alone.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions