fix(billing): deploy modal gates on workspace entitlement, not viewer plan (#5055)

* fix(billing): deploy modal gates on workspace entitlement, not viewer plan

The deploy modal showed the upgrade wall to a free user in a PAID workspace,
because it gated on the viewer's individual plan (useSubscriptionData) while the
server gates on the workspace billed account (rolled-up plan). Add a workspace
api-execution-entitlement endpoint that mirrors isWorkspaceApiExecutionEntitled,
and gate the API/MCP/A2A tabs on it so the UI matches the server exactly.

* fix(billing): key deploy gate on URL workspaceId + refetch entitlement on open

Address review findings:
- key useWorkspaceApiExecutionEntitlement on the URL workspaceId (available on
  mount) instead of workflowWorkspaceId (null until the workflow map resolves),
  so the gate fires immediately instead of leaving the tabs ungated until then
- staleTime 0 so reopening the deploy modal refetches entitlement; a plan upgrade
  happens outside this query's invalidation graph, so the gate self-heals on open

* refactor(billing): workspace owner access state instead of bespoke entitlement endpoint

Replace the single-purpose api-execution-entitlement endpoint with a reusable
workspace-owner billing/access concept — the workspace-scoped counterpart to the
viewer-scoped useSubscriptionData:

- getWorkspaceOwnerSubscriptionAccess(workspaceId): the billed account's rolled-up
  subscription access fields (mirrors getSimplifiedBillingSummary's flag derivation)
- GET /api/workspaces/[id]/owner-billing + useWorkspaceOwnerBilling hook
- deploy modal derives its gate via the existing getSubscriptionAccessState
  (hasUsablePaidAccess) on the owner data, exactly like every other paid feature

Audited the rest of the app: no other UI gates on the viewer's plan where the
server gates on the workspace owner — programmatic execution is the only
workspace-owner-scoped feature; inbox/KB-live-sync/credential-sets all gate
consistently on both sides.

* fix(billing): deploy gate on owner isPaid, not hasUsablePaidAccess

hasUsablePaidAccess rejects past_due and billing-blocked, but the server gate
(isWorkspaceApiExecutionEntitled) allows any paid plan in an entitled status
(active or past_due). Gate on the owner's isPaid so a past_due paid workspace
isn't shown the upgrade wall while the API still works.
This commit is contained in:
Theodore Li
2026-06-15 15:36:56 -04:00
committed by GitHub
parent 0673e3c0f7
commit 18edc94b2c
9 changed files with 306 additions and 10 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ const QUERY_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/queries')
const SELECTOR_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/selectors')
const BASELINE = {
totalRoutes: 827,
zodRoutes: 827,
totalRoutes: 828,
zodRoutes: 828,
nonZodRoutes: 0,
} as const