@@ -4,10 +4,22 @@ import { createLogger } from '@sim/logger'
44import { and , eq , isNull } from 'drizzle-orm'
55import type { DbOrTx } from '@/lib/db/types'
66import { loadDeployedWorkflowState } from '@/lib/workflows/persistence/utils'
7+ import { ForkError } from '@/lib/workspaces/fork/lineage/authz'
78import type { Variable , WorkflowState } from '@/stores/workflows/workflow/types'
89
910const logger = createLogger ( 'WorkspaceForkDeployBridge' )
1011
12+ /**
13+ * Hard ceiling on how many deployed workflows one fork/promote loads into memory at
14+ * once (each as a full `WorkflowState`). There is no per-workspace workflow cap in
15+ * the product, so this is the safety valve: real workspaces hold tens to low
16+ * hundreds, making this ~5-10x headroom that never blocks legitimate use, it sits
17+ * below the fork feature's other item caps (resource selection 2000, mapping
18+ * entries 5000 - both lighter-weight than full states), and it bounds a pathological
19+ * workspace to a few hundred MB of transient state instead of an unbounded load.
20+ */
21+ export const MAX_FORK_DEPLOYED_WORKFLOWS = 1000
22+
1123export interface DeployedWorkflowSummary {
1224 id : string
1325 name : string
@@ -74,6 +86,13 @@ export async function loadSourceDeployedStates(sourceWorkspaceId: string): Promi
7486 sourceStates : Map < string , WorkflowState >
7587} > {
7688 const deployedWorkflows = await listDeployedWorkflows ( db , sourceWorkspaceId )
89+ // Fail fast on the cheap count before loading any heavy state into memory.
90+ if ( deployedWorkflows . length > MAX_FORK_DEPLOYED_WORKFLOWS ) {
91+ throw new ForkError (
92+ `This workspace has ${ deployedWorkflows . length } deployed workflows, which exceeds the fork/sync limit of ${ MAX_FORK_DEPLOYED_WORKFLOWS } .` ,
93+ 400
94+ )
95+ }
7796 const sourceStates = new Map < string , WorkflowState > ( )
7897 for ( const wf of deployedWorkflows ) {
7998 const state = await readDeployedState ( wf . id , sourceWorkspaceId )
0 commit comments