Skip to content

fix(quota): elevate tier on quota-suspended resources during upgrade#240

Merged
mastermanas805 merged 3 commits into
masterfrom
fix/elevate-suspended-resource-tier-2026-06-04
Jun 4, 2026
Merged

fix(quota): elevate tier on quota-suspended resources during upgrade#240
mastermanas805 merged 3 commits into
masterfrom
fix/elevate-suspended-resource-tier-2026-06-04

Conversation

@mastermanas805
Copy link
Copy Markdown
Member

Summary

P1 (sweep finding #4) — quota-suspend recovery trap. models.ElevateResourceTiersByTeam (called from the Razorpay subscription.charged upgrade webhook) filtered on status IN ('active','paused'), so a tier upgrade did not raise the cap on a quota-suspended resource.

The worker's storage-quota enforcer flips a row to status='suspended' when it exceeds its tier cap (migration 049). Because the elevation skipped suspended rows, "upgrade to restore access" was a no-op for the exact resource that tripped the limit — it kept its old (smaller) tier and stayed below the new cap's reach.

Fix

Add 'suspended' to the status filter:

AND status IN ('active', 'paused', 'suspended')

This raises the cap only. It deliberately does not flip status back to 'active' or reverse the provider-side CONNECT/ACL REVOKE — that unsuspend transition (re-measure usage against the new cap → status='active' + provider re-grant + resource.quota_unsuspended audit) lives in the worker's storage scanner (sweep finding #3, out of this repo). I investigated the api tree: there is no api-side unsuspend path, and the suspend/unsuspend lifecycle audit kinds in audit_kinds.go are explicitly documented as worker-produced. Documented the worker follow-up in the function doc + test.

Caveat noted (from the sweep): for postgres/mongo a REVOKE-while-suspended can also block the customer from self-service deleting data to get under cap — so the worker's tier-aware re-measure (which an elevated tier now satisfies) is the recovery path, not customer delete. That's the #3 worker half.

Tests (pass against test Postgres)

  • TestElevate_Suspended_TierElevated — suspend@hobby-cap → ElevateResourceTiersByTeam(pro) → suspended row's tier IS elevated to pro (status stays suspended, pinning the worker boundary).
  • TestElevate_SuspendedFilterIncludesSuspended — registry-style guard: inserts one row per non-terminal status (active/paused/suspended), asserts all three are elevated; fails if a future edit drops any from the filter.

Worker follow-up (out of scope for this PR)

Sweep #3: worker storage scanner skips status='suspended' rows so usage freezes and it never auto-unsuspends. Fix the worker to scan status IN ('active','suspended') (or re-measure live in the unsuspend loop) so an elevated-tier suspended resource gets re-evaluated and unsuspended.

🤖 Generated with Claude Code

ElevateResourceTiersByTeam (Razorpay subscription.charged webhook) filtered
on status IN ('active','paused'), so a tier upgrade did NOT raise the cap on
a quota-suspended resource. The worker's storage-quota enforcer flips a row
to status='suspended' when it exceeds its tier cap; without the elevation
reaching that row, "upgrade to restore access" was a no-op for the very
resource that tripped the limit — it stayed below the new (larger) cap's
reach.

Add 'suspended' to the status filter so the upgrade lifts the suspended row
to the new tier. This raises the cap only — it does NOT flip status back to
'active' or reverse the provider-side CONNECT/ACL REVOKE. That unsuspend
transition (re-measure usage against the new cap, status->active, provider
re-grant, resource.quota_unsuspended audit) lives in the WORKER's storage
scanner (sweep finding #3, out of this repo). Documented as a worker
follow-up in the function doc + test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mastermanas805 mastermanas805 enabled auto-merge (squash) June 4, 2026 02:42
@mastermanas805 mastermanas805 merged commit 498f5cf into master Jun 4, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant