Skip to content

fix: make scheduled grace deadlines resilient to exhausted subscription retries #521

Description

@jimmykane

Problem

The email lifecycle records scheduledGracePeriodUntil as the exact current_period_end + 30 days deadline communicated in cancellation and expiry emails. onSubscriptionUpdated promotes that value to gracePeriodUntil when paid access actually ends and now retries transient failures.

If subscription-event processing still exhausts its retry window, the existing enforceSubscriptionLimits fail-safe ignores the scheduled deadline and creates a new grace period from detection time. That is conservative for the user but can make UI, Auth claims, enforcement, and the communicated date diverge.

An earlier implementation attempted to promote the scheduled deadline inside enforcement, but it was reverted from the email refresh because the enforcement job's account-level subscription resolver must be corrected first.

Scope

  • Make enforcement consume scheduledGracePeriodUntil only after the account has no continuing paid entitlement.
  • Coordinate this with the effective-subscription resolver tracked separately.
  • Define behavior when the stored deadline is already expired.
  • Clear stale scheduled deadlines when membership renews or another paid entitlement continues.
  • Preserve the existing user-deletion guard and missing-Auth-user cleanup behavior.

Acceptance criteria

  • A future scheduled deadline is promoted exactly when paid access ends.
  • An already-expired scheduled deadline does not silently grant a fresh 30 days unless that is an explicit documented product decision.
  • Continuing active/trialing entitlement prevents grace promotion and service disconnection.
  • Renewal clears stale scheduled grace state.
  • Missing or malformed scheduled state falls back safely.
  • Tests cover multiple subscriptions, delayed events, expired deadlines, renewal, deletion races, and missing Auth users.
  • Operational documentation states the fallback behavior and deployment order.
  • The Functions suite and build pass.

Dependency

Implement after, or together with, the shared effective-subscription resolver so the fail-safe cannot disconnect a user who still has a valid higher-tier entitlement.

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