Skip to content

Commit aea45d7

Browse files
bchapuisclaude
andcommitted
Add unlimited usage flag to bypass credit checks
Introduces an `unlimited_usage` column on organizations for internal/test accounts that should bypass all credit enforcement. Plumbs the flag from the DB through `WorkflowExecutorOptions` and `RuntimeParams` into the credit service, where it short-circuits `canExecute` before the KV usage fetch. Also extracts `resolveOrganizationBillingOptions` to centralise the billing-row -> executor-options mapping shared across 11 trigger sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7ef0877 commit aea45d7

18 files changed

Lines changed: 70 additions & 45 deletions
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- Add unlimited_usage flag for organizations exempted from credit limits
2+
-- (e.g., internal/test accounts without a Stripe subscription).
3+
ALTER TABLE `organizations` ADD COLUMN `unlimited_usage` integer DEFAULT false NOT NULL;

apps/api/src/db/queries.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,6 +1674,7 @@ export async function getOrganizationBillingInfo(
16741674
subscriptionStatus: string | null;
16751675
currentPeriodEnd: Date | null;
16761676
overageLimit: number | null;
1677+
unlimitedUsage: boolean;
16771678
}
16781679
| undefined
16791680
> {
@@ -1683,6 +1684,7 @@ export async function getOrganizationBillingInfo(
16831684
subscriptionStatus: organizations.subscriptionStatus,
16841685
currentPeriodEnd: organizations.currentPeriodEnd,
16851686
overageLimit: organizations.overageLimit,
1687+
unlimitedUsage: organizations.unlimitedUsage,
16861688
})
16871689
.from(organizations)
16881690
.where(eq(organizations.id, organizationId))
@@ -1713,6 +1715,26 @@ export function resolveOrganizationPlan(
17131715
return hasProAccess ? "pro" : "trial";
17141716
}
17151717

1718+
/**
1719+
* Build the billing-related options consumed by WorkflowExecutor and Runtime
1720+
* from a raw organization billing row. Centralises null coercion and plan
1721+
* resolution so all execution call sites stay in sync as billing fields evolve.
1722+
*/
1723+
export function resolveOrganizationBillingOptions(
1724+
billingInfo: NonNullable<
1725+
Awaited<ReturnType<typeof getOrganizationBillingInfo>>
1726+
>,
1727+
cloudflareEnv?: string
1728+
) {
1729+
return {
1730+
computeCredits: billingInfo.computeCredits,
1731+
subscriptionStatus: billingInfo.subscriptionStatus ?? undefined,
1732+
overageLimit: billingInfo.overageLimit ?? null,
1733+
unlimitedUsage: billingInfo.unlimitedUsage,
1734+
userPlan: resolveOrganizationPlan(billingInfo, cloudflareEnv),
1735+
};
1736+
}
1737+
17161738
/**
17171739
* Create a new secret for an organization
17181740
*

apps/api/src/db/schema/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ export const organizations = sqliteTable(
154154
currentPeriodStart: integer("current_period_start", { mode: "timestamp" }),
155155
currentPeriodEnd: integer("current_period_end", { mode: "timestamp" }),
156156
overageLimit: integer("overage_limit"), // null = unlimited
157+
unlimitedUsage: integer("unlimited_usage", { mode: "boolean" })
158+
.notNull()
159+
.default(false),
157160
createdAt: createCreatedAt(),
158161
updatedAt: createUpdatedAt(),
159162
},

apps/api/src/email.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Bindings } from "./context";
44
import {
55
createDatabase,
66
getOrganizationBillingInfo,
7-
resolveOrganizationPlan,
7+
resolveOrganizationBillingOptions,
88
} from "./db";
99
import { getAgentByName } from "./durable-objects/agent-utils";
1010
import { createWorkerRuntime } from "./runtime/cloudflare-worker-runtime";
@@ -178,8 +178,7 @@ async function triggerWorkflowForEmail({
178178
const executionParams = {
179179
userId: "email_trigger",
180180
organizationId,
181-
computeCredits: billingInfo.computeCredits,
182-
userPlan: resolveOrganizationPlan(billingInfo, env.CLOUDFLARE_ENV),
181+
...resolveOrganizationBillingOptions(billingInfo, env.CLOUDFLARE_ENV),
183182
workflow: {
184183
id: workflow.id,
185184
name: workflow.name,

apps/api/src/queue.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { createDatabase } from "./db";
44
import {
55
getOrganizationBillingInfo,
66
getQueueTriggersByQueue,
7-
resolveOrganizationPlan,
7+
resolveOrganizationBillingOptions,
88
} from "./db/queries";
99
import { getAgentByName } from "./durable-objects/agent-utils";
1010
import { createWorkerRuntime } from "./runtime/cloudflare-worker-runtime";
@@ -41,8 +41,7 @@ async function executeWorkflow(
4141
const executionParams = {
4242
userId: "queue_trigger",
4343
organizationId: workflowInfo.organizationId,
44-
computeCredits: billingInfo.computeCredits,
45-
userPlan: resolveOrganizationPlan(billingInfo, env.CLOUDFLARE_ENV),
44+
...resolveOrganizationBillingOptions(billingInfo, env.CLOUDFLARE_ENV),
4645
workflow: {
4746
id: workflowInfo.id,
4847
name: workflowData.name,

apps/api/src/routes/discord-webhook.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
getBotById,
99
getBotTriggersByBot,
1010
getOrganizationBillingInfo,
11-
resolveOrganizationPlan,
11+
resolveOrganizationBillingOptions,
1212
} from "../db";
1313
import { getAgentByName } from "../durable-objects/agent-utils";
1414
import { createWorkerRuntime } from "../runtime/cloudflare-worker-runtime";
@@ -298,8 +298,7 @@ async function executeWorkflow(
298298
const executionParams = {
299299
userId: "discord_trigger",
300300
organizationId,
301-
computeCredits: billingInfo.computeCredits,
302-
userPlan: resolveOrganizationPlan(billingInfo, env.CLOUDFLARE_ENV),
301+
...resolveOrganizationBillingOptions(billingInfo, env.CLOUDFLARE_ENV),
303302
workflow: {
304303
id: workflow.id,
305304
name: workflow.name,

apps/api/src/routes/endpoint-execute.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
getEndpointById,
88
getEndpointTriggersByEndpoint,
99
getOrganizationBillingInfo,
10-
resolveOrganizationPlan,
10+
resolveOrganizationBillingOptions,
1111
verifyApiKey,
1212
} from "../db";
1313
import { createRateLimitMiddleware } from "../middleware/rate-limit";
@@ -72,7 +72,6 @@ endpointExecuteRoutes.on(
7272
if (!billingInfo) {
7373
return c.json({ error: "Organization not found" }, 404);
7474
}
75-
const { computeCredits, subscriptionStatus, overageLimit } = billingInfo;
7675

7776
const workflowStore = new WorkflowStore(c.env);
7877
const executions: ExecuteWorkflowResponse[] = [];
@@ -112,11 +111,8 @@ endpointExecuteRoutes.on(
112111
},
113112
userId: "api_key",
114113
organizationId,
115-
computeCredits,
116-
subscriptionStatus: subscriptionStatus ?? undefined,
117-
overageLimit: overageLimit ?? null,
114+
...resolveOrganizationBillingOptions(billingInfo, c.env.CLOUDFLARE_ENV),
118115
parameters,
119-
userPlan: resolveOrganizationPlan(billingInfo, c.env.CLOUDFLARE_ENV),
120116
env: c.env,
121117
});
122118

apps/api/src/routes/endpoints.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
getEndpoints,
2424
getEndpointTriggersByEndpoint,
2525
getOrganizationBillingInfo,
26-
resolveOrganizationPlan,
26+
resolveOrganizationBillingOptions,
2727
updateEndpoint,
2828
} from "../db";
2929
import { createRateLimitMiddleware } from "../middleware/rate-limit";
@@ -230,7 +230,6 @@ endpointRoutes.on(
230230
if (!billingInfo) {
231231
return c.json({ error: "Organization not found" }, 404);
232232
}
233-
const { computeCredits, subscriptionStatus, overageLimit } = billingInfo;
234233

235234
const workflowStore = new WorkflowStore(c.env);
236235
const executions: ExecuteWorkflowResponse[] = [];
@@ -273,11 +272,8 @@ endpointRoutes.on(
273272
},
274273
userId,
275274
organizationId,
276-
computeCredits,
277-
subscriptionStatus: subscriptionStatus ?? undefined,
278-
overageLimit: overageLimit ?? null,
275+
...resolveOrganizationBillingOptions(billingInfo, c.env.CLOUDFLARE_ENV),
279276
parameters,
280-
userPlan: resolveOrganizationPlan(billingInfo, c.env.CLOUDFLARE_ENV),
281277
env: c.env,
282278
});
283279

apps/api/src/routes/slack-webhook.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
getBotById,
99
getBotTriggersByBot,
1010
getOrganizationBillingInfo,
11-
resolveOrganizationPlan,
11+
resolveOrganizationBillingOptions,
1212
} from "../db";
1313
import { getAgentByName } from "../durable-objects/agent-utils";
1414
import { createWorkerRuntime } from "../runtime/cloudflare-worker-runtime";
@@ -293,8 +293,7 @@ async function executeWorkflow(
293293
const executionParams = {
294294
userId: "slack_trigger",
295295
organizationId,
296-
computeCredits: billingInfo.computeCredits,
297-
userPlan: resolveOrganizationPlan(billingInfo, env.CLOUDFLARE_ENV),
296+
...resolveOrganizationBillingOptions(billingInfo, env.CLOUDFLARE_ENV),
298297
workflow: {
299298
id: workflow.id,
300299
name: workflow.name,

apps/api/src/routes/telegram-webhook.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
getBot,
88
getBotTriggersByBot,
99
getOrganizationBillingInfo,
10-
resolveOrganizationPlan,
10+
resolveOrganizationBillingOptions,
1111
} from "../db";
1212
import { getAgentByName } from "../durable-objects/agent-utils";
1313
import { createWorkerRuntime } from "../runtime/cloudflare-worker-runtime";
@@ -220,8 +220,7 @@ async function executeWorkflow(
220220
const executionParams = {
221221
userId: "telegram_trigger",
222222
organizationId,
223-
computeCredits: billingInfo.computeCredits,
224-
userPlan: resolveOrganizationPlan(billingInfo, env.CLOUDFLARE_ENV),
223+
...resolveOrganizationBillingOptions(billingInfo, env.CLOUDFLARE_ENV),
225224
workflow: {
226225
id: workflow.id,
227226
name: workflow.name,

0 commit comments

Comments
 (0)