@onlineapps/conn-orch-orchestrator 1.0.48 → 1.0.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/WorkflowOrchestrator.js +155 -148
package/package.json
CHANGED
|
@@ -116,24 +116,19 @@ class WorkflowOrchestrator {
|
|
|
116
116
|
* }, 'hello-service');
|
|
117
117
|
*/
|
|
118
118
|
async processWorkflowMessage(message, serviceName) {
|
|
119
|
-
const { workflow_id, cookbook: cookbookDef, current_step
|
|
119
|
+
const { workflow_id, cookbook: cookbookDef, current_step } = message;
|
|
120
|
+
const startTime = Date.now();
|
|
120
121
|
|
|
121
122
|
try {
|
|
122
123
|
// Validate cookbook structure
|
|
123
124
|
this.cookbook.validateCookbook(cookbookDef);
|
|
124
125
|
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
const enrichedContext = {
|
|
128
|
-
...context,
|
|
129
|
-
// Preserve delivery from cookbook if not already in context
|
|
130
|
-
delivery: context?.delivery || cookbookDef?.delivery || { handler: 'none' }
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
// Find current step (supports both V1 'id' and V2 'step_id')
|
|
134
|
-
// V2 FORMAT: steps is an object keyed by step_id
|
|
126
|
+
// UNIFIED COOKBOOK: Everything is in cookbook, no separate context
|
|
127
|
+
// Find current step
|
|
135
128
|
const stepsArray = this._getStepsArray(cookbookDef);
|
|
136
|
-
const
|
|
129
|
+
const currentIndex = stepsArray.findIndex(s => this._getStepId(s) === current_step);
|
|
130
|
+
const step = stepsArray[currentIndex];
|
|
131
|
+
|
|
137
132
|
if (!step) {
|
|
138
133
|
throw new Error(`Step not found: ${current_step}`);
|
|
139
134
|
}
|
|
@@ -141,93 +136,94 @@ class WorkflowOrchestrator {
|
|
|
141
136
|
// Check if this step is for this service
|
|
142
137
|
if (step.type === 'task' && step.service !== serviceName) {
|
|
143
138
|
this.logger.warn(`Step ${current_step} is for ${step.service}, not ${serviceName}`);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
} else {
|
|
147
|
-
// Fallback: publish to service's workflow queue directly
|
|
148
|
-
const serviceQueue = `${step.service}.workflow`;
|
|
149
|
-
await this.mqClient.publish(serviceQueue, message);
|
|
150
|
-
}
|
|
139
|
+
const serviceQueue = `${step.service}.workflow`;
|
|
140
|
+
await this.mqClient.publish(serviceQueue, message);
|
|
151
141
|
return { skipped: true, reason: 'wrong_service' };
|
|
152
142
|
}
|
|
153
143
|
|
|
154
|
-
//
|
|
144
|
+
// Mark step as started
|
|
145
|
+
const updatedSteps = [...stepsArray];
|
|
146
|
+
updatedSteps[currentIndex] = {
|
|
147
|
+
...step,
|
|
148
|
+
_execution: {
|
|
149
|
+
status: 'in_progress',
|
|
150
|
+
started_at: new Date().toISOString()
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Execute the step (pass api_input from cookbook as context for operations)
|
|
155
|
+
const stepContext = {
|
|
156
|
+
...cookbookDef.api_input,
|
|
157
|
+
_system: cookbookDef._system,
|
|
158
|
+
delivery: cookbookDef.delivery
|
|
159
|
+
};
|
|
160
|
+
|
|
155
161
|
let result;
|
|
156
162
|
if (this.cache && step.type === 'task') {
|
|
157
|
-
const cacheKey = this._getCacheKey(step,
|
|
163
|
+
const cacheKey = this._getCacheKey(step, stepContext);
|
|
158
164
|
result = await this.cache.get(cacheKey);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.logger.info('Cache hit', { step: this._getStepId(step), cacheKey });
|
|
162
|
-
} else {
|
|
163
|
-
// Execute the step
|
|
164
|
-
result = await this._executeStep(step, enrichedContext, cookbookDef);
|
|
165
|
+
if (!result) {
|
|
166
|
+
result = await this._executeStep(step, stepContext, cookbookDef);
|
|
165
167
|
await this.cache.set(cacheKey, result, { ttl: 300 });
|
|
166
168
|
}
|
|
167
169
|
} else {
|
|
168
|
-
|
|
169
|
-
result = await this._executeStep(step, enrichedContext, cookbookDef);
|
|
170
|
+
result = await this._executeStep(step, stepContext, cookbookDef);
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
173
|
+
// Calculate duration
|
|
174
|
+
const duration_ms = Date.now() - startTime;
|
|
175
|
+
|
|
176
|
+
// Determine next step
|
|
177
|
+
const nextStep = stepsArray[currentIndex + 1];
|
|
178
|
+
const nextPointer = nextStep
|
|
179
|
+
? this._getStepId(nextStep)
|
|
180
|
+
: 'api_delivery_dispatcher'; // Last step -> dispatcher
|
|
181
|
+
|
|
182
|
+
// Update step with output and execution metadata
|
|
183
|
+
updatedSteps[currentIndex] = {
|
|
184
|
+
...step,
|
|
185
|
+
output: result,
|
|
186
|
+
_execution: {
|
|
187
|
+
status: 'completed',
|
|
188
|
+
started_at: updatedSteps[currentIndex]._execution?.started_at || new Date().toISOString(),
|
|
189
|
+
completed_at: new Date().toISOString(),
|
|
190
|
+
duration_ms
|
|
191
|
+
}
|
|
189
192
|
};
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
|
|
194
|
+
// Build UPDATED UNIFIED COOKBOOK
|
|
195
|
+
const updatedCookbook = {
|
|
196
|
+
...cookbookDef,
|
|
197
|
+
steps: updatedSteps,
|
|
198
|
+
_pointer: { next: nextStep ? this._getStepId(nextStep) : null }
|
|
194
199
|
};
|
|
195
200
|
|
|
196
|
-
// Publish workflow.progress
|
|
201
|
+
// Publish workflow.progress with UNIFIED COOKBOOK
|
|
197
202
|
const { publishToMonitoringWorkflow } = require('@onlineapps/mq-client-core').monitoring;
|
|
198
203
|
try {
|
|
199
|
-
console.log(`[WorkflowOrchestrator] [PROGRESS] Publishing progress for ${workflow_id}, step: ${current_step}, index: ${currentIndex}`);
|
|
200
204
|
await publishToMonitoringWorkflow(this.mqClient, {
|
|
201
205
|
event_type: 'progress',
|
|
202
206
|
workflow_id: workflow_id,
|
|
203
207
|
service_name: serviceName,
|
|
204
|
-
step_index: currentIndex
|
|
208
|
+
step_index: currentIndex,
|
|
205
209
|
step_id: current_step,
|
|
206
|
-
cookbook:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
status: 'completed', // Step completed (not in_progress)
|
|
210
|
+
cookbook: updatedCookbook, // UNIFIED - contains everything
|
|
211
|
+
output: result,
|
|
212
|
+
status: 'completed',
|
|
210
213
|
timestamp: new Date().toISOString()
|
|
211
214
|
}, this.logger, { workflow_id, step_id: current_step });
|
|
212
|
-
console.log(`[WorkflowOrchestrator] [PROGRESS] ✓ Progress published for ${workflow_id}`);
|
|
213
215
|
} catch (monitoringError) {
|
|
214
|
-
// Don't fail workflow if monitoring publish fails
|
|
215
|
-
console.error(`[WorkflowOrchestrator] [PROGRESS] ✗ Failed to publish progress:`, monitoringError);
|
|
216
216
|
this.logger.warn('Failed to publish workflow.progress to monitoring', {
|
|
217
217
|
workflow_id,
|
|
218
|
-
error: monitoringError.message
|
|
218
|
+
error: monitoringError.message
|
|
219
219
|
});
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
//
|
|
223
|
-
const nextStep = stepsArray[currentIndex + 1];
|
|
224
|
-
|
|
222
|
+
// Route to next step or complete workflow
|
|
225
223
|
if (nextStep) {
|
|
226
|
-
|
|
227
|
-
await this._routeToNextStep(nextStep, cookbookDef, updatedContext, workflow_id);
|
|
224
|
+
await this._routeToNextStep(nextStep, updatedCookbook, workflow_id);
|
|
228
225
|
} else {
|
|
229
|
-
|
|
230
|
-
await this._completeWorkflow(workflow_id, updatedContext, cookbookDef, serviceName, current_step, currentIndex);
|
|
226
|
+
await this._completeWorkflow(workflow_id, updatedCookbook, serviceName, current_step, currentIndex);
|
|
231
227
|
}
|
|
232
228
|
|
|
233
229
|
return {
|
|
@@ -238,35 +234,55 @@ class WorkflowOrchestrator {
|
|
|
238
234
|
};
|
|
239
235
|
|
|
240
236
|
} catch (error) {
|
|
241
|
-
//
|
|
242
|
-
const
|
|
243
|
-
const stepAttempts = dlqHistory.filter(h => h.step_id === current_step && h.attempt).length;
|
|
244
|
-
const attemptNumber = stepAttempts + 1;
|
|
237
|
+
// UNIFIED COOKBOOK: _retry_history is stored INSIDE the step, not globally
|
|
238
|
+
const crypto = require('crypto');
|
|
245
239
|
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
240
|
+
// Find current step index
|
|
241
|
+
const stepsArray = this._getStepsArray(cookbookDef);
|
|
242
|
+
const currentIndex = stepsArray.findIndex(s => this._getStepId(s) === current_step);
|
|
243
|
+
const currentStepDef = stepsArray[currentIndex] || {};
|
|
244
|
+
|
|
245
|
+
// Get existing retry history from the step itself
|
|
246
|
+
const stepRetryHistory = currentStepDef._retry_history || [];
|
|
247
|
+
const attemptNumber = stepRetryHistory.length + 1;
|
|
248
|
+
|
|
249
|
+
// Generate error reference for log lookup (instead of full stack trace)
|
|
250
|
+
const errorRef = `err-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
251
|
+
|
|
252
|
+
// Log full stack trace with error_ref for debugging
|
|
253
|
+
this.logger.error(`[${errorRef}] Step execution failed`, {
|
|
254
|
+
workflow_id,
|
|
255
|
+
step_id: current_step,
|
|
256
|
+
service: serviceName,
|
|
257
|
+
attempt: attemptNumber,
|
|
258
|
+
error: error.message,
|
|
259
|
+
stack: error.stack
|
|
260
|
+
});
|
|
255
261
|
|
|
256
|
-
// Add attempt to
|
|
262
|
+
// Add attempt record to step's _retry_history
|
|
257
263
|
const attemptRecord = {
|
|
258
|
-
step_id: current_step,
|
|
259
|
-
step_index: failedStepIndex,
|
|
260
264
|
attempt: attemptNumber,
|
|
261
|
-
service: serviceName,
|
|
262
265
|
error: error.message,
|
|
266
|
+
error_ref: errorRef, // Reference to full stack in logs
|
|
263
267
|
at: new Date().toISOString()
|
|
264
268
|
};
|
|
265
269
|
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
// Update steps array with retry history
|
|
271
|
+
const updatedSteps = [...stepsArray];
|
|
272
|
+
updatedSteps[currentIndex] = {
|
|
273
|
+
...currentStepDef,
|
|
274
|
+
_execution: {
|
|
275
|
+
...currentStepDef._execution,
|
|
276
|
+
status: 'retrying'
|
|
277
|
+
},
|
|
278
|
+
_retry_history: [...stepRetryHistory, attemptRecord]
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// Build updated UNIFIED COOKBOOK
|
|
282
|
+
const updatedCookbook = {
|
|
283
|
+
...cookbookDef,
|
|
284
|
+
steps: updatedSteps,
|
|
285
|
+
_pointer: { next: current_step } // Still pointing to this step (retrying)
|
|
270
286
|
};
|
|
271
287
|
|
|
272
288
|
this.logger.warn(`[WorkflowOrchestrator] Step failed, attempt ${attemptNumber}/${this.retryConfig.maxAttempts}`, {
|
|
@@ -274,29 +290,27 @@ class WorkflowOrchestrator {
|
|
|
274
290
|
current_step,
|
|
275
291
|
service: serviceName,
|
|
276
292
|
error: error.message,
|
|
293
|
+
error_ref: errorRef,
|
|
277
294
|
attempt: attemptNumber
|
|
278
295
|
});
|
|
279
296
|
|
|
280
|
-
// Publish progress event for EACH retry attempt
|
|
297
|
+
// Publish progress event for EACH retry attempt
|
|
281
298
|
const { publishToMonitoringWorkflow } = require('@onlineapps/mq-client-core').monitoring;
|
|
282
299
|
try {
|
|
283
300
|
await publishToMonitoringWorkflow(this.mqClient, {
|
|
284
301
|
event_type: 'progress',
|
|
285
302
|
workflow_id: workflow_id,
|
|
286
303
|
service_name: serviceName,
|
|
287
|
-
step_index:
|
|
304
|
+
step_index: currentIndex,
|
|
288
305
|
step_id: current_step,
|
|
289
|
-
cookbook:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
error: { message: error.message },
|
|
293
|
-
status: 'retry', // Mark as retry attempt
|
|
306
|
+
cookbook: updatedCookbook, // UNIFIED cookbook with _retry_history in step
|
|
307
|
+
error: { message: error.message, error_ref: errorRef },
|
|
308
|
+
status: 'retry',
|
|
294
309
|
attempt: attemptNumber,
|
|
295
310
|
max_attempts: this.retryConfig.maxAttempts,
|
|
296
311
|
timestamp: new Date().toISOString()
|
|
297
312
|
}, this.logger, { workflow_id, step_id: current_step });
|
|
298
313
|
} catch (monitoringError) {
|
|
299
|
-
// Don't fail if monitoring publish fails
|
|
300
314
|
this.logger.warn('Failed to publish retry progress to monitoring', {
|
|
301
315
|
workflow_id,
|
|
302
316
|
error: monitoringError.message
|
|
@@ -305,7 +319,6 @@ class WorkflowOrchestrator {
|
|
|
305
319
|
|
|
306
320
|
// Check if we should retry
|
|
307
321
|
if (attemptNumber < this.retryConfig.maxAttempts) {
|
|
308
|
-
// Calculate delay with exponential backoff
|
|
309
322
|
const delay = Math.min(
|
|
310
323
|
this.retryConfig.baseDelay * Math.pow(this.retryConfig.backoffMultiplier, attemptNumber - 1),
|
|
311
324
|
this.retryConfig.maxDelay
|
|
@@ -317,16 +330,14 @@ class WorkflowOrchestrator {
|
|
|
317
330
|
delay
|
|
318
331
|
});
|
|
319
332
|
|
|
320
|
-
// Wait for delay
|
|
321
333
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
322
334
|
|
|
323
|
-
// Republish
|
|
335
|
+
// Republish with UNIFIED cookbook
|
|
324
336
|
const serviceQueue = `${serviceName}.workflow`;
|
|
325
337
|
await this.mqClient.publish(serviceQueue, {
|
|
326
338
|
workflow_id,
|
|
327
|
-
cookbook:
|
|
328
|
-
current_step
|
|
329
|
-
context: updatedContext
|
|
339
|
+
cookbook: updatedCookbook,
|
|
340
|
+
current_step
|
|
330
341
|
});
|
|
331
342
|
|
|
332
343
|
this.logger.info(`[WorkflowOrchestrator] Message republished for retry`, {
|
|
@@ -336,25 +347,31 @@ class WorkflowOrchestrator {
|
|
|
336
347
|
nextAttempt: attemptNumber + 1
|
|
337
348
|
});
|
|
338
349
|
|
|
339
|
-
// Don't throw - message will be retried
|
|
340
350
|
return { retrying: true, attempt: attemptNumber, nextAttempt: attemptNumber + 1 };
|
|
341
351
|
}
|
|
342
352
|
|
|
343
|
-
// Max retries exhausted - send to
|
|
353
|
+
// Max retries exhausted - mark step as failed and send to DLQ
|
|
344
354
|
const dlqEntryRecord = {
|
|
345
355
|
dlq_entered: true,
|
|
346
|
-
step_id: current_step,
|
|
347
|
-
step_index: failedStepIndex,
|
|
348
|
-
service: serviceName,
|
|
349
356
|
reason: 'max_retries_exceeded',
|
|
350
357
|
total_attempts: attemptNumber,
|
|
351
358
|
at: new Date().toISOString()
|
|
352
359
|
};
|
|
353
360
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
361
|
+
updatedSteps[currentIndex] = {
|
|
362
|
+
...updatedSteps[currentIndex],
|
|
363
|
+
_execution: {
|
|
364
|
+
...updatedSteps[currentIndex]._execution,
|
|
365
|
+
status: 'failed',
|
|
366
|
+
failed_at: new Date().toISOString()
|
|
367
|
+
},
|
|
368
|
+
_retry_history: [...updatedSteps[currentIndex]._retry_history, dlqEntryRecord]
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const finalCookbook = {
|
|
372
|
+
...cookbookDef,
|
|
373
|
+
steps: updatedSteps,
|
|
374
|
+
_pointer: { next: null } // Workflow stuck - waiting for human intervention
|
|
358
375
|
};
|
|
359
376
|
|
|
360
377
|
try {
|
|
@@ -363,41 +380,32 @@ class WorkflowOrchestrator {
|
|
|
363
380
|
workflow_id,
|
|
364
381
|
current_step,
|
|
365
382
|
attempts: attemptNumber,
|
|
366
|
-
|
|
383
|
+
error_ref: errorRef // Reference to full stack in logs
|
|
367
384
|
});
|
|
368
385
|
|
|
369
386
|
// Publish to workflow.failed (DLQ queue - message stays until manual requeue/discard)
|
|
387
|
+
// UNIFIED COOKBOOK contains everything including _retry_history in the step
|
|
370
388
|
await this.mqClient.publish('workflow.failed', {
|
|
371
389
|
workflow_id,
|
|
372
390
|
current_step,
|
|
373
|
-
|
|
374
|
-
step_index: failedStepIndex,
|
|
375
|
-
service: serviceName,
|
|
376
|
-
cookbook: cookbookDef,
|
|
377
|
-
context: finalContext,
|
|
378
|
-
error: error.message,
|
|
379
|
-
errorStack: error.stack,
|
|
380
|
-
_dlq_history: finalDlqHistory,
|
|
391
|
+
cookbook: finalCookbook, // UNIFIED cookbook with full state
|
|
381
392
|
failed_at: new Date().toISOString()
|
|
382
393
|
});
|
|
383
394
|
|
|
384
395
|
// ALSO publish to monitoring.workflow so dashboard shows the DLQ entry
|
|
385
|
-
// This is separate from the DLQ message - it's just for visibility
|
|
386
396
|
try {
|
|
387
397
|
await publishToMonitoringWorkflow(this.mqClient, {
|
|
388
398
|
event_type: 'failed',
|
|
389
399
|
workflow_id: workflow_id,
|
|
390
400
|
service_name: serviceName,
|
|
391
|
-
step_index:
|
|
401
|
+
step_index: currentIndex,
|
|
392
402
|
step_id: current_step,
|
|
393
|
-
cookbook:
|
|
394
|
-
|
|
395
|
-
error: { message: error.message },
|
|
403
|
+
cookbook: finalCookbook, // UNIFIED cookbook
|
|
404
|
+
error: { message: error.message, error_ref: errorRef },
|
|
396
405
|
status: 'failed',
|
|
397
406
|
timestamp: new Date().toISOString()
|
|
398
407
|
}, this.logger, { workflow_id, step_id: current_step });
|
|
399
408
|
} catch (monitoringError) {
|
|
400
|
-
// Don't fail if monitoring publish fails
|
|
401
409
|
this.logger.warn('[WorkflowOrchestrator] Failed to publish DLQ entry to monitoring', {
|
|
402
410
|
workflow_id,
|
|
403
411
|
error: monitoringError.message
|
|
@@ -600,17 +608,17 @@ class WorkflowOrchestrator {
|
|
|
600
608
|
* @private
|
|
601
609
|
* @async
|
|
602
610
|
* @param {Object} nextStep - Next step to execute
|
|
603
|
-
* @param {Object}
|
|
604
|
-
* @param {Object} context - Updated context
|
|
611
|
+
* @param {Object} cookbook - UNIFIED cookbook (contains everything)
|
|
605
612
|
* @param {string} workflow_id - Workflow ID
|
|
606
613
|
*/
|
|
607
|
-
async _routeToNextStep(nextStep,
|
|
614
|
+
async _routeToNextStep(nextStep, cookbook, workflow_id) {
|
|
608
615
|
const nextStepId = this._getStepId(nextStep);
|
|
616
|
+
|
|
617
|
+
// UNIFIED COOKBOOK: no separate context, everything is in cookbook
|
|
609
618
|
const message = {
|
|
610
619
|
workflow_id,
|
|
611
|
-
cookbook:
|
|
612
|
-
current_step: nextStepId
|
|
613
|
-
context
|
|
620
|
+
cookbook: cookbook,
|
|
621
|
+
current_step: nextStepId
|
|
614
622
|
};
|
|
615
623
|
|
|
616
624
|
if (nextStep.type === 'task') {
|
|
@@ -642,38 +650,37 @@ class WorkflowOrchestrator {
|
|
|
642
650
|
* @private
|
|
643
651
|
* @async
|
|
644
652
|
* @param {string} workflow_id - Workflow ID
|
|
645
|
-
* @param {Object}
|
|
653
|
+
* @param {Object} cookbook - UNIFIED cookbook (contains everything)
|
|
646
654
|
* @param {string} serviceName - Service name that completed the workflow
|
|
647
655
|
* @param {string} lastStepId - ID of the last completed step
|
|
648
656
|
* @param {number} lastStepIndex - Index of the last completed step
|
|
649
657
|
*/
|
|
650
|
-
async _completeWorkflow(workflow_id,
|
|
658
|
+
async _completeWorkflow(workflow_id, cookbook, serviceName = 'unknown', lastStepId = null, lastStepIndex = 0) {
|
|
651
659
|
try {
|
|
652
660
|
console.log(`[WorkflowOrchestrator] [PUBLISH] Preparing to publish workflow.completed for ${workflow_id}`);
|
|
653
661
|
this.logger.info(`[WorkflowOrchestrator] [PUBLISH] Preparing to publish workflow.completed`, {
|
|
654
662
|
workflow_id,
|
|
655
663
|
serviceName,
|
|
656
664
|
lastStepId,
|
|
657
|
-
lastStepIndex
|
|
658
|
-
hasMqClient: !!this.mqClient,
|
|
659
|
-
mqClientConnected: this.mqClient?._connected
|
|
665
|
+
lastStepIndex
|
|
660
666
|
});
|
|
661
667
|
|
|
662
|
-
//
|
|
663
|
-
|
|
664
|
-
|
|
668
|
+
// Update pointer to dispatcher (final step)
|
|
669
|
+
const finalCookbook = {
|
|
670
|
+
...cookbook,
|
|
671
|
+
_pointer: { next: 'api_delivery_dispatcher' }
|
|
672
|
+
};
|
|
665
673
|
|
|
666
674
|
// Build message in format expected by Delivery Dispatcher
|
|
675
|
+
// UNIFIED COOKBOOK contains everything including delivery config
|
|
667
676
|
const workflowCompletedMessage = {
|
|
668
677
|
workflow_id,
|
|
669
678
|
status: 'completed',
|
|
670
|
-
service: serviceName,
|
|
671
|
-
step_id: lastStepId,
|
|
672
|
-
step_index: lastStepIndex,
|
|
673
|
-
cookbook:
|
|
674
|
-
delivery: delivery
|
|
675
|
-
context: finalContext, // Full context for output resolution
|
|
676
|
-
steps: finalContext?.steps || [], // Steps results (array) for output resolution
|
|
679
|
+
service: serviceName,
|
|
680
|
+
step_id: lastStepId,
|
|
681
|
+
step_index: lastStepIndex,
|
|
682
|
+
cookbook: finalCookbook, // UNIFIED cookbook with full state
|
|
683
|
+
delivery: cookbook.delivery || { handler: 'none' },
|
|
677
684
|
completed_at: new Date().toISOString()
|
|
678
685
|
};
|
|
679
686
|
|