@onlineapps/conn-orch-orchestrator 1.0.18 → 1.0.20

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-orchestrator",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "Workflow orchestration connector for OA Drive - handles message routing and workflow execution",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -75,6 +75,9 @@ class WorkflowOrchestrator {
75
75
  const { workflow_id, cookbook: cookbookDef, current_step, context } = message;
76
76
 
77
77
  try {
78
+ // FAIL-FAST: Validate V2 format BEFORE anything else
79
+ this._validateV2Format(cookbookDef);
80
+
78
81
  // Validate cookbook structure
79
82
  this.cookbook.validateCookbook(cookbookDef);
80
83
 
@@ -86,14 +89,14 @@ class WorkflowOrchestrator {
86
89
  delivery: context?.delivery || cookbookDef?.delivery || { handler: 'none' }
87
90
  };
88
91
 
89
- // Find current step (V2: step_id, V1: id - but V1 is deprecated)
90
- const step = cookbookDef.steps.find(s => (s.step_id || s.id) === current_step);
92
+ // Find current step (V2: step_id only)
93
+ const step = cookbookDef.steps.find(s => s.step_id === current_step);
91
94
  if (!step) {
92
- throw new Error(`Step not found: ${current_step}`);
95
+ throw new Error(`Step not found: ${current_step}. Available steps: ${cookbookDef.steps.map(s => s.step_id).join(', ')}`);
93
96
  }
94
97
 
95
- // Get step_id (V2) or id (V1 fallback)
96
- const stepId = step.step_id || step.id;
98
+ // Get step_id (V2)
99
+ const stepId = step.step_id;
97
100
 
98
101
  // Check if this step is for this service
99
102
  if (step.type === 'task' && step.service !== serviceName) {
@@ -128,28 +131,18 @@ class WorkflowOrchestrator {
128
131
 
129
132
  // Update context with result - steps as OBJECT (V2 format, keyed by step_id)
130
133
  // Each step preserves its definition (step_id, type, service, operation, input) and adds output
131
- const currentIndex = cookbookDef.steps.findIndex(s => (s.step_id || s.id) === current_step);
134
+ const currentIndex = cookbookDef.steps.findIndex(s => s.step_id === current_step);
132
135
  const stepDefinition = cookbookDef.steps[currentIndex];
133
136
 
134
137
  // Initialize steps object from cookbook if not present
135
138
  // V2: steps is an object keyed by step_id, not an array
136
139
  let existingSteps = enrichedContext.steps || {};
137
140
 
138
- // If steps is still an array (legacy), convert to object
139
- if (Array.isArray(existingSteps)) {
140
- existingSteps = {};
141
- cookbookDef.steps.forEach(s => {
142
- const sid = s.step_id || s.id;
143
- if (sid) {
144
- existingSteps[sid] = { ...s };
145
- }
146
- });
147
- } else if (Object.keys(existingSteps).length === 0) {
148
- // Initialize from cookbook
141
+ // Initialize from cookbook if empty
142
+ if (Object.keys(existingSteps).length === 0) {
149
143
  cookbookDef.steps.forEach(s => {
150
- const sid = s.step_id || s.id;
151
- if (sid) {
152
- existingSteps[sid] = { ...s };
144
+ if (s.step_id) {
145
+ existingSteps[s.step_id] = { ...s };
153
146
  }
154
147
  });
155
148
  }
@@ -198,7 +191,7 @@ class WorkflowOrchestrator {
198
191
 
199
192
  if (nextStep) {
200
193
  // Route to next step
201
- const nextStepId = nextStep.step_id || nextStep.id;
194
+ const nextStepId = nextStep.step_id;
202
195
  await this._routeToNextStep(nextStep, cookbookDef, updatedContext, workflow_id, nextStepId);
203
196
  } else {
204
197
  // Workflow completed - pass serviceName, cookbook and last step info for monitoring
@@ -293,13 +286,13 @@ class WorkflowOrchestrator {
293
286
 
294
287
  // Use API mapper to call the service with resolved input
295
288
  const result = await this.apiMapper.callOperation(
296
- step.operation || step.step_id || step.id,
289
+ step.operation,
297
290
  resolvedInput,
298
291
  context
299
292
  );
300
293
 
301
294
  this.logger.info(`Task step executed`, {
302
- step: step.step_id || step.id,
295
+ step: step.step_id,
303
296
  service: step.service,
304
297
  operation: step.operation,
305
298
  inputResolved: !!resolvedInput
@@ -420,7 +413,7 @@ class WorkflowOrchestrator {
420
413
  const result = await executor.executeStep(step);
421
414
 
422
415
  this.logger.info(`Control flow step executed`, {
423
- step: step.step_id || step.id,
416
+ step: step.step_id,
424
417
  type: step.type
425
418
  });
426
419
 
@@ -441,7 +434,7 @@ class WorkflowOrchestrator {
441
434
  const message = {
442
435
  workflow_id,
443
436
  cookbook: cookbookDef,
444
- current_step: nextStepId || (nextStep.step_id || nextStep.id),
437
+ current_step: nextStepId,
445
438
  context
446
439
  };
447
440
 
@@ -450,21 +443,21 @@ class WorkflowOrchestrator {
450
443
  if (this.router?.routeToService) {
451
444
  await this.router.routeToService(nextStep.service, message);
452
445
  this.logger.info(`Routed to service: ${nextStep.service}`, {
453
- step: nextStepId || (nextStep.step_id || nextStep.id)
446
+ step: nextStepId
454
447
  });
455
448
  } else {
456
449
  // Fallback: publish to service's workflow queue directly
457
450
  const serviceQueue = `${nextStep.service}.workflow`;
458
451
  await this.mqClient.publish(serviceQueue, message);
459
452
  this.logger.info(`Published to queue: ${serviceQueue}`, {
460
- step: nextStepId || (nextStep.step_id || nextStep.id)
453
+ step: nextStepId
461
454
  });
462
455
  }
463
456
  } else {
464
457
  // Control flow steps handled by workflow init queue
465
458
  await this.mqClient.publish('workflow.init', message);
466
459
  this.logger.info(`Routed control flow to workflow.init`, {
467
- step: nextStepId || (nextStep.step_id || nextStep.id)
460
+ step: nextStepId
468
461
  });
469
462
  }
470
463
  }
@@ -535,12 +528,12 @@ class WorkflowOrchestrator {
535
528
  const crypto = require('crypto');
536
529
  const data = JSON.stringify({
537
530
  service: step.service,
538
- operation: step.operation || step.step_id || step.id,
531
+ operation: step.operation,
539
532
  input: step.input,
540
533
  contextInput: context.api_input
541
534
  });
542
535
  const hash = crypto.createHash('sha256').update(data).digest('hex');
543
- return `workflow:step:${step.service}:${step.operation || step.step_id || step.id}:${hash}`;
536
+ return `workflow:step:${step.service}:${step.operation}:${hash}`;
544
537
  }
545
538
 
546
539
  /**
@@ -578,6 +571,63 @@ class WorkflowOrchestrator {
578
571
  const mapper = new ResponseMapper();
579
572
  return mapper.mapResponse(response, mapping);
580
573
  }
574
+
575
+ /**
576
+ * FAIL-FAST: Validate V2 format strictly
577
+ * Rejects V1 format immediately with clear error message
578
+ * @private
579
+ * @param {Object} cookbook - Cookbook definition
580
+ * @throws {Error} If cookbook is not V2 format
581
+ */
582
+ _validateV2Format(cookbook) {
583
+ if (!cookbook) {
584
+ throw new Error('FAIL-FAST: Cookbook is required');
585
+ }
586
+
587
+ // Check version format
588
+ const version = cookbook.version;
589
+ if (!version) {
590
+ throw new Error('FAIL-FAST: Cookbook version is required');
591
+ }
592
+
593
+ // V1 format detection - REJECT immediately
594
+ if (/^1\.\d+\.\d+$/.test(version)) {
595
+ throw new Error(`FAIL-FAST: V1 format (${version}) is DEPRECATED and no longer supported. Use V2 format (2.x.x) with step_id instead of id.`);
596
+ }
597
+
598
+ // Must be V2 format
599
+ if (!/^2\.\d+\.\d+$/.test(version)) {
600
+ throw new Error(`FAIL-FAST: Invalid version format '${version}'. Must be 2.x.x (e.g., 2.0.0)`);
601
+ }
602
+
603
+ // Check steps
604
+ if (!cookbook.steps || !Array.isArray(cookbook.steps) || cookbook.steps.length === 0) {
605
+ throw new Error('FAIL-FAST: Cookbook must have at least one step');
606
+ }
607
+
608
+ // Validate each step has step_id (V2) not id (V1)
609
+ const invalidSteps = [];
610
+ cookbook.steps.forEach((step, index) => {
611
+ if (step.id && !step.step_id) {
612
+ invalidSteps.push(`Step ${index}: has 'id' (V1) but missing 'step_id' (V2)`);
613
+ }
614
+ if (!step.step_id) {
615
+ invalidSteps.push(`Step ${index}: missing required 'step_id'`);
616
+ }
617
+ if (!step.type) {
618
+ invalidSteps.push(`Step ${index} (${step.step_id || 'unknown'}): missing required 'type'`);
619
+ }
620
+ });
621
+
622
+ if (invalidSteps.length > 0) {
623
+ throw new Error(`FAIL-FAST: Invalid V2 cookbook format:\n${invalidSteps.join('\n')}`);
624
+ }
625
+
626
+ this.logger.info('[WorkflowOrchestrator] V2 format validation passed', {
627
+ version: cookbook.version,
628
+ steps: cookbook.steps.map(s => s.step_id)
629
+ });
630
+ }
581
631
  }
582
632
 
583
633
  module.exports = WorkflowOrchestrator;