Skip to content

AI gate can transiently close for a just-paid subscriber during the post-purchase verify window #52

Description

@tabmail-kmyi

⚠️ needs-verification — do not implement this as-specified. The straightforward fixes trade an always-run robustness property for a rare, self-healing blip. Re-analyze the trade-off (below) and prove the chosen fix preserves the authoritative close path before writing any code.

Summary

After a purchase/restore opens the AI gate locally from StoreKit truth, the backend entitlement write happens on a detached task (verifyPurchaseWithBackend). If a foreground authoritative revalidation (whoamiAISubscriptionGate.apply()) runs before the backend has processed the JWS, whoami returns has_subscription:false and apply() closes the gate a just-paid user opened. Self-heals on the next revalidation once the backend catches up.

Why this is needs-verification, not "just fix it"

Every straightforward fix either:

  1. weakens the always-run authoritative apply() / close path — the exact mechanism that promptly locks out revoked / expired / downgraded subscribers — or
  2. adds a per-purchase network dependency on an always-run path.

Trading that always-run robustness to patch a rare, self-healing blip is the wrong trade (owner constraint, 2026-08-19: "not trading edge case harmless minor prob for always runtime robustness"). That is why this is deferred and flagged for verification rather than implemented.

Required verification before any implementation

  • Quantify the residual window. The Support server-granted free trial in account and paywall UI #49 open-only seam (AISubscriptionGate.refreshAfterLocalPurchase) already reopens the gate when a racing whoami/backend confirms the subscription — the only remaining exposure is the window where the backend has not yet processed the JWS and a foreground whoami fires. Measure how reachable that actually is before designing anything.
  • Prove the candidate fix does not weaken the authoritative close: revoked / expired / downgraded users must still be locked out promptly by the always-run path.
  • Prove the candidate fix adds no always-run network call.

Acceptance (if a fix clears verification)

  • Red-first test reproducing the transient close during the verify window.
  • Two-sided green: a genuinely-unentitled user is still closed promptly by apply(); a just-paid user is not transiently closed.
  • Two-model vet on its own PR.

Related: #49 (open-only refreshAfterLocalPurchase seam), #50 (restore-path work).

Metadata

Metadata

Assignees

No one assigned

    Labels

    deferredOpen work intentionally deferred by owner decisionneeds-verificationIn-depth verification/design required before implementation — do not build as-specified

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions