@onlineapps/conn-orch-orchestrator 2.0.0 → 2.0.2
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 +94 -6
package/package.json
CHANGED
|
@@ -73,6 +73,35 @@ class WorkflowOrchestrator {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Extract correlation_id from an inbound MQ message envelope.
|
|
78
|
+
* Accepts the canonical body-level `correlation_id` and also a few known
|
|
79
|
+
* legacy placements from transports that move AMQP headers onto the
|
|
80
|
+
* parsed message object. Returns undefined if none are set - the caller
|
|
81
|
+
* is responsible for fail-fast.
|
|
82
|
+
* @private
|
|
83
|
+
* @param {Object} message - Inbound workflow message
|
|
84
|
+
* @returns {string|undefined}
|
|
85
|
+
*/
|
|
86
|
+
_extractCorrelationId(message) {
|
|
87
|
+
if (!message || typeof message !== 'object') return undefined;
|
|
88
|
+
if (typeof message.correlation_id === 'string' && message.correlation_id.length > 0) {
|
|
89
|
+
return message.correlation_id;
|
|
90
|
+
}
|
|
91
|
+
if (message.envelope && typeof message.envelope.correlation_id === 'string' && message.envelope.correlation_id.length > 0) {
|
|
92
|
+
return message.envelope.correlation_id;
|
|
93
|
+
}
|
|
94
|
+
if (message.headers) {
|
|
95
|
+
if (typeof message.headers.correlation_id === 'string' && message.headers.correlation_id.length > 0) {
|
|
96
|
+
return message.headers.correlation_id;
|
|
97
|
+
}
|
|
98
|
+
if (typeof message.headers.correlationId === 'string' && message.headers.correlationId.length > 0) {
|
|
99
|
+
return message.headers.correlationId;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
76
105
|
/**
|
|
77
106
|
* Get step identifier (V2.1 only: step_id)
|
|
78
107
|
* @private
|
|
@@ -144,8 +173,29 @@ class WorkflowOrchestrator {
|
|
|
144
173
|
async processWorkflowMessage(message, serviceName) {
|
|
145
174
|
const { workflow_id, cookbook: cookbookDef, current_step } = message;
|
|
146
175
|
const startTime = Date.now();
|
|
176
|
+
// correlation_id is required by RFC §5.8 — validated below inside the
|
|
177
|
+
// try block so malformed messages flow through the retry/DLQ pipeline
|
|
178
|
+
// and a `failed` monitoring event is emitted (no silent redelivery).
|
|
179
|
+
const correlationId = this._extractCorrelationId(message);
|
|
147
180
|
|
|
148
181
|
try {
|
|
182
|
+
// Fail-fast: correlation_id MUST be present on every workflow message.
|
|
183
|
+
// (ContextBuilder._validateMqMessage rejects empty strings too.)
|
|
184
|
+
// Gateway is expected to publish it; if it does not, that is a gateway
|
|
185
|
+
// bug we want to surface loudly rather than silently produce 500s
|
|
186
|
+
// inside invokeOperation's ContextBuilder.
|
|
187
|
+
if (typeof correlationId !== 'string' || correlationId.length === 0) {
|
|
188
|
+
const err = new Error(
|
|
189
|
+
'[WorkflowOrchestrator] message.correlation_id is required - ' +
|
|
190
|
+
`Expected non-empty string on inbound workflow message (workflow_id='${workflow_id}', step='${current_step}'). ` +
|
|
191
|
+
'Fix: ensure publisher (gateway / router) sets correlation_id on message body per RFC §5.8.'
|
|
192
|
+
);
|
|
193
|
+
err.errorCode = 'MISSING_CORRELATION_ID';
|
|
194
|
+
err.statusCode = 400;
|
|
195
|
+
err.type = 'VALIDATION';
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
|
|
149
199
|
// Validate cookbook structure
|
|
150
200
|
this.cookbook.validateCookbook(cookbookDef);
|
|
151
201
|
|
|
@@ -199,9 +249,24 @@ class WorkflowOrchestrator {
|
|
|
199
249
|
stepsMap[id] = s;
|
|
200
250
|
}
|
|
201
251
|
});
|
|
252
|
+
// Extract tenant/workspace/person context.
|
|
253
|
+
// Canonical source is cookbook._system (set by gateway); top-level
|
|
254
|
+
// message fields are accepted as an override for direct publishers
|
|
255
|
+
// that do not carry a _system block.
|
|
256
|
+
const sys = cookbookDef._system || {};
|
|
257
|
+
const tenantId = message.tenant_id !== undefined ? message.tenant_id : sys.tenant_id;
|
|
258
|
+
const workspaceId = message.workspace_id !== undefined
|
|
259
|
+
? message.workspace_id
|
|
260
|
+
: (sys.workspace_id !== undefined ? sys.workspace_id : sys.default_workspace_id);
|
|
261
|
+
const personId = message.person_id !== undefined ? message.person_id : sys.person_id;
|
|
262
|
+
|
|
202
263
|
const stepContext = {
|
|
203
264
|
workflow_id: workflow_id,
|
|
204
265
|
step_id: current_step,
|
|
266
|
+
correlation_id: correlationId,
|
|
267
|
+
tenant_id: tenantId,
|
|
268
|
+
workspace_id: workspaceId,
|
|
269
|
+
person_id: personId,
|
|
205
270
|
api_input: cookbookDef.api_input || {},
|
|
206
271
|
steps: stepsArrayCtx, // array (legacy consumers expect .map)
|
|
207
272
|
steps_by_id: stepsMap, // fast lookup by step_id for templating
|
|
@@ -274,9 +339,16 @@ class WorkflowOrchestrator {
|
|
|
274
339
|
});
|
|
275
340
|
}
|
|
276
341
|
|
|
277
|
-
// Route to next step or complete workflow
|
|
342
|
+
// Route to next step or complete workflow.
|
|
343
|
+
// Propagate correlation_id + tenancy so the next consumer can validate
|
|
344
|
+
// the envelope (RFC §5.8).
|
|
278
345
|
if (nextStep) {
|
|
279
|
-
await this._routeToNextStep(nextStep, updatedCookbook, workflow_id
|
|
346
|
+
await this._routeToNextStep(nextStep, updatedCookbook, workflow_id, {
|
|
347
|
+
correlation_id: correlationId,
|
|
348
|
+
tenant_id: tenantId,
|
|
349
|
+
workspace_id: workspaceId,
|
|
350
|
+
person_id: personId
|
|
351
|
+
});
|
|
280
352
|
} else {
|
|
281
353
|
await this._completeWorkflow(workflow_id, updatedCookbook, serviceName, current_step, currentIndex);
|
|
282
354
|
}
|
|
@@ -417,10 +489,19 @@ class WorkflowOrchestrator {
|
|
|
417
489
|
|
|
418
490
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
419
491
|
|
|
420
|
-
// Republish with UNIFIED cookbook
|
|
492
|
+
// Republish with UNIFIED cookbook.
|
|
493
|
+
// Propagate correlation_id + tenancy so the retry consumer passes
|
|
494
|
+
// ContextBuilder validation on the next attempt (RFC §5.8).
|
|
421
495
|
const serviceQueue = `${serviceName}.workflow`;
|
|
496
|
+
const sysRetry = updatedCookbook?._system || {};
|
|
422
497
|
await this.mqClient.publish(serviceQueue, {
|
|
423
498
|
workflow_id,
|
|
499
|
+
correlation_id: correlationId,
|
|
500
|
+
tenant_id: message.tenant_id !== undefined ? message.tenant_id : sysRetry.tenant_id,
|
|
501
|
+
workspace_id: message.workspace_id !== undefined
|
|
502
|
+
? message.workspace_id
|
|
503
|
+
: (sysRetry.workspace_id !== undefined ? sysRetry.workspace_id : sysRetry.default_workspace_id),
|
|
504
|
+
person_id: message.person_id !== undefined ? message.person_id : sysRetry.person_id,
|
|
424
505
|
cookbook: updatedCookbook,
|
|
425
506
|
current_step
|
|
426
507
|
});
|
|
@@ -1084,13 +1165,20 @@ class WorkflowOrchestrator {
|
|
|
1084
1165
|
* @param {Object} nextStep - Next step to execute
|
|
1085
1166
|
* @param {Object} cookbook - UNIFIED cookbook (contains everything)
|
|
1086
1167
|
* @param {string} workflow_id - Workflow ID
|
|
1168
|
+
* @param {Object} envelope - envelope to propagate (correlation_id, tenant_id, workspace_id, person_id)
|
|
1087
1169
|
*/
|
|
1088
|
-
async _routeToNextStep(nextStep, cookbook, workflow_id) {
|
|
1170
|
+
async _routeToNextStep(nextStep, cookbook, workflow_id, envelope = {}) {
|
|
1089
1171
|
const nextStepId = this._getStepId(nextStep);
|
|
1090
|
-
|
|
1091
|
-
// UNIFIED COOKBOOK: no separate context, everything is in cookbook
|
|
1172
|
+
|
|
1173
|
+
// UNIFIED COOKBOOK: no separate context, everything is in cookbook.
|
|
1174
|
+
// correlation_id + tenancy are propagated on the message envelope so
|
|
1175
|
+
// the next consumer's ContextBuilder validation succeeds (RFC §5.8).
|
|
1092
1176
|
const message = {
|
|
1093
1177
|
workflow_id,
|
|
1178
|
+
correlation_id: envelope.correlation_id,
|
|
1179
|
+
tenant_id: envelope.tenant_id,
|
|
1180
|
+
workspace_id: envelope.workspace_id,
|
|
1181
|
+
person_id: envelope.person_id,
|
|
1094
1182
|
cookbook: cookbook,
|
|
1095
1183
|
current_step: nextStepId
|
|
1096
1184
|
};
|