@@ -56,8 +56,20 @@ export class WorkflowBlockHandler implements BlockHandler {
5656 // Add current execution to stack
5757 WorkflowBlockHandler . executionStack . add ( executionId )
5858
59- // Load the child workflow from API
60- const childWorkflow = await this . loadChildWorkflow ( workflowId )
59+ // In deployed contexts, enforce that child workflow has an active deployment
60+ if ( context . isDeployedContext ) {
61+ const hasActiveDeployment = await this . checkChildDeployment ( workflowId )
62+ if ( ! hasActiveDeployment ) {
63+ throw new Error (
64+ `Child workflow is not deployed. Please deploy the workflow before invoking it.`
65+ )
66+ }
67+ }
68+
69+ // Load the child workflow
70+ const childWorkflow = context . isDeployedContext
71+ ? await this . loadChildWorkflowDeployed ( workflowId )
72+ : await this . loadChildWorkflow ( workflowId )
6173
6274 if ( ! childWorkflow ) {
6375 throw new Error ( `Child workflow ${ workflowId } not found` )
@@ -93,6 +105,8 @@ export class WorkflowBlockHandler implements BlockHandler {
93105 workflowVariables : childWorkflow . variables || { } ,
94106 contextExtensions : {
95107 isChildExecution : true , // Prevent child executor from managing global state
108+ // Propagate deployed context down to child execution so nested children obey constraints
109+ isDeployedContext : context . isDeployedContext === true ,
96110 } ,
97111 } )
98112
@@ -214,6 +228,91 @@ export class WorkflowBlockHandler implements BlockHandler {
214228 }
215229 }
216230
231+ /**
232+ * Checks if a workflow has an active deployed version
233+ */
234+ private async checkChildDeployment ( workflowId : string ) : Promise < boolean > {
235+ try {
236+ const headers : Record < string , string > = {
237+ 'Content-Type' : 'application/json' ,
238+ }
239+ if ( typeof window === 'undefined' ) {
240+ const token = await generateInternalToken ( )
241+ headers . Authorization = `Bearer ${ token } `
242+ }
243+ const response = await fetch ( `${ getBaseUrl ( ) } /api/workflows/${ workflowId } /deployed` , {
244+ headers,
245+ cache : 'no-store' ,
246+ } )
247+ if ( ! response . ok ) return false
248+ const json = await response . json ( )
249+ // API returns { deployedState: state | null }
250+ return ! ! json ?. data ?. deployedState || ! ! json ?. deployedState
251+ } catch ( e ) {
252+ logger . error ( `Failed to check child deployment for ${ workflowId } :` , e )
253+ return false
254+ }
255+ }
256+
257+ /**
258+ * Loads child workflow using deployed state (for API/webhook/schedule/chat executions)
259+ */
260+ private async loadChildWorkflowDeployed ( workflowId : string ) {
261+ try {
262+ const headers : Record < string , string > = {
263+ 'Content-Type' : 'application/json' ,
264+ }
265+ if ( typeof window === 'undefined' ) {
266+ const token = await generateInternalToken ( )
267+ headers . Authorization = `Bearer ${ token } `
268+ }
269+
270+ // Fetch deployed state
271+ const deployedRes = await fetch ( `${ getBaseUrl ( ) } /api/workflows/${ workflowId } /deployed` , {
272+ headers,
273+ cache : 'no-store' ,
274+ } )
275+ if ( ! deployedRes . ok ) {
276+ return null
277+ }
278+ const deployedJson = await deployedRes . json ( )
279+ const deployedState = deployedJson ?. data ?. deployedState || deployedJson ?. deployedState
280+ if ( ! deployedState || ! deployedState . blocks ) {
281+ return null
282+ }
283+
284+ // Fetch variables and name from live metadata (variables are not stored in deployments)
285+ const metaRes = await fetch ( `${ getBaseUrl ( ) } /api/workflows/${ workflowId } ` , {
286+ headers,
287+ cache : 'no-store' ,
288+ } )
289+ if ( ! metaRes . ok ) {
290+ return null
291+ }
292+ const metaJson = await metaRes . json ( )
293+ const wfData = metaJson ?. data
294+
295+ const serializedWorkflow = this . serializer . serializeWorkflow (
296+ deployedState . blocks ,
297+ deployedState . edges || [ ] ,
298+ deployedState . loops || { } ,
299+ deployedState . parallels || { } ,
300+ true
301+ )
302+
303+ const workflowVariables = ( wfData ?. variables as Record < string , any > ) || { }
304+
305+ return {
306+ name : wfData ?. name || 'Workflow' ,
307+ serializedState : serializedWorkflow ,
308+ variables : workflowVariables ,
309+ }
310+ } catch ( error ) {
311+ logger . error ( `Error loading deployed child workflow ${ workflowId } :` , error )
312+ return null
313+ }
314+ }
315+
217316 /**
218317 * Captures and transforms child workflow logs into trace spans
219318 */
0 commit comments