@rytass/bpm-core-nestjs-module 0.18.1 → 0.20.0

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.
Files changed (149) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/package.json +3 -2
  3. package/src/index.d.ts +1 -0
  4. package/src/index.js +1 -0
  5. package/src/index.js.map +1 -1
  6. package/src/lib/attachment/attachment.service.d.ts +23 -1
  7. package/src/lib/attachment/attachment.service.js +111 -24
  8. package/src/lib/attachment/attachment.service.js.map +1 -1
  9. package/src/lib/bpm/bpm-root-options.d.ts +13 -0
  10. package/src/lib/bpm/bpm-root-options.js.map +1 -1
  11. package/src/lib/bpm/bpm-root.module.d.ts +12 -0
  12. package/src/lib/bpm/bpm-root.module.js +5 -0
  13. package/src/lib/bpm/bpm-root.module.js.map +1 -1
  14. package/src/lib/condition/adapters/cel-legacy.adapter.d.ts +62 -0
  15. package/src/lib/condition/adapters/cel-legacy.adapter.js +141 -0
  16. package/src/lib/condition/adapters/cel-legacy.adapter.js.map +1 -0
  17. package/src/lib/condition/adapters/cel-v2.adapter.d.ts +76 -0
  18. package/src/lib/condition/adapters/cel-v2.adapter.js +371 -0
  19. package/src/lib/condition/adapters/cel-v2.adapter.js.map +1 -0
  20. package/src/lib/condition/condition.service.d.ts +90 -5
  21. package/src/lib/condition/condition.service.js +287 -71
  22. package/src/lib/condition/condition.service.js.map +1 -1
  23. package/src/lib/condition/expression-ast.d.ts +176 -0
  24. package/src/lib/condition/expression-ast.js +1254 -0
  25. package/src/lib/condition/expression-ast.js.map +1 -0
  26. package/src/lib/condition/expression-budget.d.ts +44 -0
  27. package/src/lib/condition/expression-budget.js +61 -0
  28. package/src/lib/condition/expression-budget.js.map +1 -0
  29. package/src/lib/condition/expression-dialect.d.ts +29 -0
  30. package/src/lib/condition/expression-dialect.js +44 -0
  31. package/src/lib/condition/expression-dialect.js.map +1 -0
  32. package/src/lib/condition/expression-differential.corpus.d.ts +34 -0
  33. package/src/lib/condition/expression-differential.corpus.js +995 -0
  34. package/src/lib/condition/expression-differential.corpus.js.map +1 -0
  35. package/src/lib/condition/expression-differential.d.ts +33 -0
  36. package/src/lib/condition/expression-differential.js +48 -0
  37. package/src/lib/condition/expression-differential.js.map +1 -0
  38. package/src/lib/condition/expression-engine-failure.d.ts +15 -0
  39. package/src/lib/condition/expression-engine-failure.js +22 -0
  40. package/src/lib/condition/expression-engine-failure.js.map +1 -0
  41. package/src/lib/condition/expression-errors.d.ts +40 -0
  42. package/src/lib/condition/expression-errors.js +105 -0
  43. package/src/lib/condition/expression-errors.js.map +1 -0
  44. package/src/lib/condition/expression-function-invocation.d.ts +50 -0
  45. package/src/lib/condition/expression-function-invocation.js +290 -0
  46. package/src/lib/condition/expression-function-invocation.js.map +1 -0
  47. package/src/lib/condition/expression-result.walker.d.ts +18 -0
  48. package/src/lib/condition/expression-result.walker.js +152 -0
  49. package/src/lib/condition/expression-result.walker.js.map +1 -0
  50. package/src/lib/condition/expression-runtime.d.ts +70 -0
  51. package/src/lib/condition/expression-runtime.js +134 -0
  52. package/src/lib/condition/expression-runtime.js.map +1 -0
  53. package/src/lib/condition/index.d.ts +4 -0
  54. package/src/lib/condition/index.js +8 -0
  55. package/src/lib/condition/index.js.map +1 -1
  56. package/src/lib/delegation/delegation-rule.entity.d.ts +7 -0
  57. package/src/lib/delegation/delegation-rule.entity.js +10 -0
  58. package/src/lib/delegation/delegation-rule.entity.js.map +1 -1
  59. package/src/lib/delegation/delegation.service.d.ts +7 -1
  60. package/src/lib/delegation/delegation.service.js +22 -5
  61. package/src/lib/delegation/delegation.service.js.map +1 -1
  62. package/src/lib/delegation/dto/delegation-rule.input.d.ts +2 -0
  63. package/src/lib/delegation/dto/delegation-rule.input.js +10 -0
  64. package/src/lib/delegation/dto/delegation-rule.input.js.map +1 -1
  65. package/src/lib/expression-function/expression-function.built-ins.d.ts +9 -0
  66. package/src/lib/expression-function/expression-function.built-ins.js +117 -0
  67. package/src/lib/expression-function/expression-function.built-ins.js.map +1 -0
  68. package/src/lib/expression-function/expression-function.module.d.ts +19 -0
  69. package/src/lib/expression-function/expression-function.module.js +34 -0
  70. package/src/lib/expression-function/expression-function.module.js.map +1 -0
  71. package/src/lib/expression-function/expression-function.provider.d.ts +17 -0
  72. package/src/lib/expression-function/expression-function.provider.js +39 -0
  73. package/src/lib/expression-function/expression-function.provider.js.map +1 -0
  74. package/src/lib/expression-function/expression-function.registry.d.ts +36 -0
  75. package/src/lib/expression-function/expression-function.registry.js +231 -0
  76. package/src/lib/expression-function/expression-function.registry.js.map +1 -0
  77. package/src/lib/expression-function/expression-function.types.d.ts +30 -0
  78. package/src/lib/expression-function/expression-function.types.js +8 -0
  79. package/src/lib/expression-function/expression-function.types.js.map +1 -0
  80. package/src/lib/expression-function/index.d.ts +5 -0
  81. package/src/lib/expression-function/index.js +9 -0
  82. package/src/lib/expression-function/index.js.map +1 -0
  83. package/src/lib/form/form-schema.validator.js +53 -6
  84. package/src/lib/form/form-schema.validator.js.map +1 -1
  85. package/src/lib/form-data-source/form-data-source-value-resolver.service.d.ts +12 -1
  86. package/src/lib/form-data-source/form-data-source-value-resolver.service.js +45 -26
  87. package/src/lib/form-data-source/form-data-source-value-resolver.service.js.map +1 -1
  88. package/src/lib/form-data-source/form-data-source.expression.d.ts +95 -0
  89. package/src/lib/form-data-source/form-data-source.expression.js +267 -0
  90. package/src/lib/form-data-source/form-data-source.expression.js.map +1 -0
  91. package/src/lib/form-data-source/form-data-source.module.js +4 -0
  92. package/src/lib/form-data-source/form-data-source.module.js.map +1 -1
  93. package/src/lib/form-data-source/form-data-source.service.d.ts +22 -3
  94. package/src/lib/form-data-source/form-data-source.service.js +78 -18
  95. package/src/lib/form-data-source/form-data-source.service.js.map +1 -1
  96. package/src/lib/form-data-source/form-data-source.validation.d.ts +47 -4
  97. package/src/lib/form-data-source/form-data-source.validation.js +151 -23
  98. package/src/lib/form-data-source/form-data-source.validation.js.map +1 -1
  99. package/src/lib/migrations/0000000030000-delegation-scope-condition-dialect.d.ts +22 -0
  100. package/src/lib/migrations/0000000030000-delegation-scope-condition-dialect.js +76 -0
  101. package/src/lib/migrations/0000000030000-delegation-scope-condition-dialect.js.map +1 -0
  102. package/src/lib/migrations/index.d.ts +1 -0
  103. package/src/lib/migrations/index.js +3 -0
  104. package/src/lib/migrations/index.js.map +1 -1
  105. package/src/lib/notification/notification.service.d.ts +1 -1
  106. package/src/lib/notification/notification.service.js +7 -3
  107. package/src/lib/notification/notification.service.js.map +1 -1
  108. package/src/lib/template/dto/expression-designer.dto.d.ts +107 -0
  109. package/src/lib/template/dto/expression-designer.dto.js +415 -0
  110. package/src/lib/template/dto/expression-designer.dto.js.map +1 -0
  111. package/src/lib/template/expression-designer.queries.d.ts +16 -0
  112. package/src/lib/template/expression-designer.queries.js +66 -0
  113. package/src/lib/template/expression-designer.queries.js.map +1 -0
  114. package/src/lib/template/expression-designer.service.d.ts +72 -0
  115. package/src/lib/template/expression-designer.service.js +543 -0
  116. package/src/lib/template/expression-designer.service.js.map +1 -0
  117. package/src/lib/template/index.d.ts +2 -0
  118. package/src/lib/template/index.js +2 -0
  119. package/src/lib/template/index.js.map +1 -1
  120. package/src/lib/template/template.module.js +4 -0
  121. package/src/lib/template/template.module.js.map +1 -1
  122. package/src/lib/template/template.service.d.ts +37 -1
  123. package/src/lib/template/template.service.js +317 -29
  124. package/src/lib/template/template.service.js.map +1 -1
  125. package/src/lib/template/workflow-definition.validator.d.ts +7 -0
  126. package/src/lib/template/workflow-definition.validator.js +26 -2
  127. package/src/lib/template/workflow-definition.validator.js.map +1 -1
  128. package/src/lib/workflow-engine/time-budget.d.ts +13 -0
  129. package/src/lib/workflow-engine/time-budget.js +58 -0
  130. package/src/lib/workflow-engine/time-budget.js.map +1 -0
  131. package/src/lib/workflow-engine/workflow-engine.service.d.ts +24 -0
  132. package/src/lib/workflow-engine/workflow-engine.service.js +249 -65
  133. package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
  134. package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.js +4 -0
  135. package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.js.map +1 -1
  136. package/src/lib/workflow-webhook/workflow-webhook-delivery.service.d.ts +3 -1
  137. package/src/lib/workflow-webhook/workflow-webhook-delivery.service.js +11 -2
  138. package/src/lib/workflow-webhook/workflow-webhook-delivery.service.js.map +1 -1
  139. package/src/lib/workflow-webhook/workflow-webhook-enqueue.d.ts +8 -1
  140. package/src/lib/workflow-webhook/workflow-webhook-enqueue.js +70 -27
  141. package/src/lib/workflow-webhook/workflow-webhook-enqueue.js.map +1 -1
  142. package/src/lib/workflow-webhook/workflow-webhook-expression.d.ts +57 -0
  143. package/src/lib/workflow-webhook/workflow-webhook-expression.js +123 -0
  144. package/src/lib/workflow-webhook/workflow-webhook-expression.js.map +1 -0
  145. package/src/lib/workflow-webhook/workflow-webhook.module.js +4 -0
  146. package/src/lib/workflow-webhook/workflow-webhook.module.js.map +1 -1
  147. package/src/lib/workflow-webhook/workflow-webhook.validator.d.ts +7 -0
  148. package/src/lib/workflow-webhook/workflow-webhook.validator.js +29 -6
  149. package/src/lib/workflow-webhook/workflow-webhook.validator.js.map +1 -1
@@ -48,9 +48,17 @@ const task_entity_1 = require("./task.entity");
48
48
  const workflow_engine_enums_1 = require("./workflow-engine.enums");
49
49
  const workflow_condition_evaluator_1 = require("./workflow-condition-evaluator");
50
50
  const workflow_dry_run_object_1 = require("./workflow-dry-run.object");
51
+ const time_budget_1 = require("./time-budget");
52
+ const expression_budget_1 = require("../condition/expression-budget");
53
+ const expression_errors_1 = require("../condition/expression-errors");
54
+ const expression_dialect_1 = require("../condition/expression-dialect");
51
55
  const workflow_service_task_dispatcher_token_1 = require("./workflow-service-task-dispatcher.token");
52
56
  const workflow_token_entity_1 = require("./workflow-token.entity");
53
57
  const bpm_root_options_1 = require("../bpm/bpm-root-options");
58
+ const MAX_DRY_RUN_INPUT_LENGTH = 1_048_576;
59
+ const MAX_DRY_RUN_VISITS = 10_000;
60
+ const DRY_RUN_TIME_BUDGET_MS = 2_000;
61
+ const DRY_RUN_TOO_COMPLEX_MESSAGE = '流程或條件的運算量過大,無法完成試跑,請減少連續的閘道分支或簡化條件';
54
62
  const MAX_PROCESSING_STEPS = 500;
55
63
  exports.MAX_APPROVAL_INSTANCE_BUSINESS_KEY_LENGTH = 200;
56
64
  const APPROVAL_INSTANCE_BUSINESS_KEY_INDEX = 'approval_instances_business_key_key';
@@ -58,6 +66,40 @@ const APPROVAL_INSTANCE_BUSINESS_KEY_INDEX = 'approval_instances_business_key_ke
58
66
  const ORG_MANAGER_CHAIN_MAX_HOPS = 20;
59
67
  const DEFAULT_APPROVAL_HISTORY_TASK_LIMIT = 50;
60
68
  const SYSTEM_DRY_RUN_INSTANCE_ID = 'dry-run-instance';
69
+ // The dry run checks each expression against the roots its use point may read,
70
+ // as publish does. Keep these in step with `WORKFLOW_EXPRESSION_ROOT_IDENTIFIERS`
71
+ // in template.service.ts; P2 moves both to one place.
72
+ const DRY_RUN_WORKFLOW_ROOT_IDENTIFIERS = [
73
+ 'env',
74
+ 'form',
75
+ 'formData',
76
+ 'initiator',
77
+ 'instance',
78
+ ];
79
+ const DRY_RUN_EDGE_ROOT_IDENTIFIERS = [
80
+ ...DRY_RUN_WORKFLOW_ROOT_IDENTIFIERS,
81
+ 'lastDecision',
82
+ ];
83
+ const hasExpressionText = (expression) => typeof expression === 'string' && expression.trim() !== '';
84
+ /**
85
+ * Whether a definition's dialect can be read: missing (`cel-legacy`) or one
86
+ * this version knows. Never throws, unlike `readWorkflowExpressionDialect`.
87
+ */
88
+ const isReadableExpressionDialect = (definition) => {
89
+ const dialect = definition?.expressionDialect;
90
+ return (dialect === undefined || dialect === null || (0, expression_dialect_1.isExpressionDialect)(dialect));
91
+ };
92
+ /**
93
+ * Options for evaluating an expression that may be blank. The dialect is read
94
+ * only when there is an expression: an unknown dialect is refused because the
95
+ * expression cannot be evaluated as its author tested it, and a blank one is
96
+ * never evaluated at all. Publishing does not check the field yet (Phase 23
97
+ * P2), so reading it eagerly would turn a stray field on a version with no
98
+ * expressions into an error, where before dialects existed it was ignored.
99
+ */
100
+ const readExpressionDialectOptions = (expression, definition) => hasExpressionText(expression)
101
+ ? { dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(definition) }
102
+ : {};
61
103
  /**
62
104
  * Delegation-chain reason recorded for an approver-initiated transfer, as
63
105
  * opposed to {@link SLA_ESCALATION_DELEGATION_REASON}.
@@ -152,6 +194,8 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
152
194
  this.formDataSourceValueResolver = formDataSourceValueResolver;
153
195
  this.webhookDeliveryService = webhookDeliveryService;
154
196
  this.logger = new common_1.Logger(WorkflowEngineService_1.name);
197
+ /** Webhooks each open engine transaction will send once it has committed. */
198
+ this.deferredWebhookDispatches = new WeakMap();
155
199
  // A binding under the token wins: that is either the host's own provider
156
200
  // or `workflowServiceTaskDispatcherProvider`, both of them deliberate. The
157
201
  // runtime instance from `BPMRootModule`'s async factory comes next.
@@ -177,6 +221,59 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
177
221
  readOrganizationToday() {
178
222
  return (0, organization_time_zone_1.formatDateOnlyInTimeZone)(new Date(), this.organizationTimeZone);
179
223
  }
224
+ /**
225
+ * Opens an engine transaction whose webhooks wait for its commit. A webhook
226
+ * sent from inside would hold the case lock and a pooled connection for as
227
+ * long as the far end takes to answer, so a slow endpoint could stall the
228
+ * case and then, a connection at a time, the whole API. A transaction that
229
+ * rolls back sends nothing.
230
+ *
231
+ * The caller still waits for the sends, so the outcome is in the activity log
232
+ * by the time a mutation returns, as it always has been.
233
+ */
234
+ async runEngineTransaction(work) {
235
+ // Mutable on purpose: every step of the transaction appends to it, and
236
+ // threading it through each of them would change a dozen signatures.
237
+ const dispatches = [];
238
+ // One transaction is one operation: its evaluations share one budget.
239
+ const result = await (0, expression_budget_1.runExpressionOperation)(() => this.approvalInstanceRepository.manager.transaction((manager) => {
240
+ this.deferredWebhookDispatches.set(manager, dispatches);
241
+ return work(manager);
242
+ }));
243
+ await dispatches.reduce(async (previous, dispatch) => {
244
+ await previous;
245
+ await this.sendCommittedWebhook(dispatch);
246
+ }, Promise.resolve());
247
+ return result;
248
+ }
249
+ /**
250
+ * Queues a webhook for after the commit. A manager this service did not open
251
+ * has no queue and no commit it can see, so that webhook goes out on the spot
252
+ * rather than being dropped.
253
+ */
254
+ async dispatchWebhookAfterCommit(manager, dispatch) {
255
+ const queue = this.deferredWebhookDispatches.get(manager);
256
+ if (queue) {
257
+ queue.push(dispatch);
258
+ return;
259
+ }
260
+ const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
261
+ const result = await dispatch.send();
262
+ await activityRepository.save(activityRepository.create(dispatch.readActivityLog(result)));
263
+ }
264
+ /**
265
+ * The transaction has committed and the mutation has succeeded, so nothing
266
+ * here may fail it: a result that cannot be recorded is only logged.
267
+ */
268
+ async sendCommittedWebhook(dispatch) {
269
+ try {
270
+ const result = await dispatch.send();
271
+ await this.activityLogRepository.save(this.activityLogRepository.create(dispatch.readActivityLog(result)));
272
+ }
273
+ catch (error) {
274
+ this.logger.error(`Could not record a webhook result for instance ${dispatch.instanceId}: ${error instanceof Error ? error.message : String(error)}`);
275
+ }
276
+ }
180
277
  async submitApprovalInstance(input, authContext) {
181
278
  if (input.submittedByMemberId !== undefined &&
182
279
  input.submittedByMemberId !== null &&
@@ -219,7 +316,9 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
219
316
  const initiatorMetadataSnapshot = input.initiatorMetadataSnapshotJson
220
317
  ? parseJsonObject(input.initiatorMetadataSnapshotJson, 'initiatorMetadataSnapshotJson')
221
318
  : await this.readDefaultInitiatorMetadataSnapshot(input.initiatorMemberId);
222
- if (!this.conditionService.evaluateBoolean(templateVersion.initiatorPolicyCel, buildInitiatorPolicyContext(initiatorMetadataSnapshot), 'initiatorPolicyCel')) {
319
+ if (!this.conditionService.evaluateBoolean(templateVersion.initiatorPolicyCel, buildInitiatorPolicyContext(initiatorMetadataSnapshot), 'initiatorPolicyCel',
320
+ // The policy is written in its version's dialect (ADR 22 §3.3).
321
+ readExpressionDialectOptions(templateVersion.initiatorPolicyCel, templateVersion.workflowDefinition))) {
223
322
  throw new common_1.ConflictException('Current member is not allowed to start this approval template');
224
323
  }
225
324
  // Named here, before the transaction, so the completion notice that later
@@ -233,7 +332,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
233
332
  if (!startNode) {
234
333
  throw new common_1.BadRequestException('Published workflow does not include a startEvent');
235
334
  }
236
- return this.approvalInstanceRepository.manager.transaction(async (manager) => {
335
+ return this.runEngineTransaction(async (manager) => {
237
336
  const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
238
337
  const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
239
338
  const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
@@ -261,6 +360,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
261
360
  await this.attachmentService.bindFormDataAttachmentsToInstance(manager, {
262
361
  formData,
263
362
  instanceId: instance.id,
363
+ schema: formDefinitionVersion.schema,
264
364
  });
265
365
  const token = await tokenRepository.save(tokenRepository.create({
266
366
  consumedAt: null,
@@ -369,7 +469,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
369
469
  return { formDataOptionSnapshot, instance };
370
470
  }
371
471
  async processInstance(instanceId) {
372
- await this.approvalInstanceRepository.manager.transaction(async (manager) => {
472
+ await this.runEngineTransaction(async (manager) => {
373
473
  await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
374
474
  instanceId,
375
475
  ]);
@@ -396,7 +496,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
396
496
  });
397
497
  }
398
498
  async decideTask(input, options = {}) {
399
- return this.approvalInstanceRepository.manager.transaction(async (manager) => {
499
+ return this.runEngineTransaction(async (manager) => {
400
500
  const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
401
501
  const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
402
502
  const taskDecisionRepository = manager.getRepository(task_decision_entity_1.TaskDecisionEntity);
@@ -759,7 +859,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
759
859
  });
760
860
  }
761
861
  async cancelApprovalInstance(input) {
762
- return this.approvalInstanceRepository.manager.transaction(async (manager) => {
862
+ return this.runEngineTransaction(async (manager) => {
763
863
  await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
764
864
  input.instanceId,
765
865
  ]);
@@ -800,7 +900,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
800
900
  * returned unchanged.
801
901
  */
802
902
  async expireApprovalInstance(input) {
803
- return this.approvalInstanceRepository.manager.transaction(async (manager) => {
903
+ return this.runEngineTransaction(async (manager) => {
804
904
  await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
805
905
  input.instanceId,
806
906
  ]);
@@ -857,7 +957,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
857
957
  const preparation = this.formDataSourceValueResolver
858
958
  ? await this.prepareResubmitFormDataOptions(input, submittedFormData, authContext ?? createWorkflowAuthContext(input.initiatorMemberId))
859
959
  : null;
860
- return this.approvalInstanceRepository.manager.transaction(async (manager) => {
960
+ return this.runEngineTransaction(async (manager) => {
861
961
  await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
862
962
  input.instanceId,
863
963
  ]);
@@ -911,6 +1011,14 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
911
1011
  await this.attachmentService.bindFormDataAttachmentsToInstance(manager, {
912
1012
  formData,
913
1013
  instanceId: instance.id,
1014
+ schema: resubmitSchema,
1015
+ });
1016
+ // A file the applicant removed from a field leaves the instance too,
1017
+ // so its attachment list matches the form it was resubmitted with.
1018
+ await this.attachmentService.releaseUnreferencedFormAttachments(manager, {
1019
+ formData,
1020
+ instanceId: instance.id,
1021
+ schema: resubmitSchema,
914
1022
  });
915
1023
  const returnedContext = await this.readLatestReturnedInstanceContext(manager, instance.id);
916
1024
  const runtimeInstance = returnedContext?.resubmitStrategy === 'FROM_RETURN_POINT'
@@ -938,7 +1046,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
938
1046
  });
939
1047
  }
940
1048
  async requestAdhocCountersign({ comment, requestedByMemberId, target, taskId, }) {
941
- return this.approvalInstanceRepository.manager.transaction(async (manager) => {
1049
+ return this.runEngineTransaction(async (manager) => {
942
1050
  const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: true });
943
1051
  const targetValue = buildAdhocTargetValue(target);
944
1052
  // Rejects WEBHOOK targets — countersigners must be members.
@@ -970,7 +1078,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
970
1078
  });
971
1079
  }
972
1080
  async requestAdhocPreApproval({ comment, onReject, requestedByMemberId, target, taskId, }) {
973
- return this.approvalInstanceRepository.manager.transaction(async (manager) => {
1081
+ return this.runEngineTransaction(async (manager) => {
974
1082
  const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: true });
975
1083
  const targetValue = buildAdhocTargetValue(target);
976
1084
  // Rejects WEBHOOK targets — pre-approvers must be members.
@@ -997,7 +1105,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
997
1105
  });
998
1106
  }
999
1107
  async configureAdhocNotification({ channels, requestedByMemberId, target, taskId, type, }) {
1000
- return this.approvalInstanceRepository.manager.transaction(async (manager) => {
1108
+ return this.runEngineTransaction(async (manager) => {
1001
1109
  const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: false });
1002
1110
  const targetValue = buildAdhocTargetValue(target);
1003
1111
  const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
@@ -1020,7 +1128,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
1020
1128
  });
1021
1129
  }
1022
1130
  async cancelAdhocDirective({ cancelledByMemberId, directiveId, }) {
1023
- return this.approvalInstanceRepository.manager.transaction(async (manager) => {
1131
+ return this.runEngineTransaction(async (manager) => {
1024
1132
  const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
1025
1133
  const directive = await directiveRepository.findOne({
1026
1134
  where: { id: directiveId },
@@ -1584,30 +1692,34 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
1584
1692
  const targetValue = readAdhocTargetValue(directive);
1585
1693
  const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
1586
1694
  if (targetValue.kind === adhoc_enums_1.AdhocTargetKindEnum.WEBHOOK) {
1587
- const result = await executeAdhocWebhookDispatch(this.serviceTaskDispatcher, targetValue, {
1695
+ const webhookPayload = {
1588
1696
  ...payload,
1589
1697
  instanceId: instance.id,
1590
1698
  instanceTitle: instance.title,
1591
1699
  message,
1592
1700
  triggeredAt: new Date().toISOString(),
1593
- });
1594
- await activityRepository.save(activityRepository.create({
1595
- actorMemberId: null,
1596
- eventType: result.ok
1597
- ? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
1598
- : workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
1701
+ };
1702
+ await this.dispatchWebhookAfterCommit(manager, {
1599
1703
  instanceId: instance.id,
1600
- nodeId: directive.originNodeId,
1601
- payload: {
1602
- action: 'ADHOC_WEBHOOK',
1603
- directiveId: directive.id,
1604
- error: result.error,
1605
- ok: result.ok,
1606
- status: result.status,
1607
- url: targetValue.webhookUrl,
1608
- },
1609
- taskId: null,
1610
- }));
1704
+ readActivityLog: (result) => ({
1705
+ actorMemberId: null,
1706
+ eventType: result.ok
1707
+ ? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
1708
+ : workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
1709
+ instanceId: instance.id,
1710
+ nodeId: directive.originNodeId,
1711
+ payload: {
1712
+ action: 'ADHOC_WEBHOOK',
1713
+ directiveId: directive.id,
1714
+ error: result.error,
1715
+ ok: result.ok,
1716
+ status: result.status,
1717
+ url: targetValue.webhookUrl,
1718
+ },
1719
+ taskId: null,
1720
+ }),
1721
+ send: () => executeAdhocWebhookDispatch(this.serviceTaskDispatcher, targetValue, webhookPayload),
1722
+ });
1611
1723
  return;
1612
1724
  }
1613
1725
  try {
@@ -1658,29 +1770,38 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
1658
1770
  })));
1659
1771
  }
1660
1772
  dryRunApprovalWorkflow(input) {
1773
+ // Checked first: parsing happens outside the time budget below.
1774
+ if ([
1775
+ input.workflowDefinitionJson,
1776
+ input.formDataJson,
1777
+ input.initiatorMetadataSnapshotJson ?? '',
1778
+ ].some((json) => json.length > MAX_DRY_RUN_INPUT_LENGTH)) {
1779
+ return createFailedDryRunResult('試跑資料過大,請縮小流程或測試資料後再試');
1780
+ }
1661
1781
  const workflowDefinition = parseWorkflowDefinition(input.workflowDefinitionJson);
1662
1782
  const formData = parseJsonObject(input.formDataJson, 'formDataJson');
1663
1783
  const initiatorMetadataSnapshot = input.initiatorMetadataSnapshotJson
1664
1784
  ? parseJsonObject(input.initiatorMetadataSnapshotJson, 'initiatorMetadataSnapshotJson')
1665
1785
  : readDefaultInitiatorMetadataSnapshot(input.initiatorMemberId);
1666
1786
  try {
1787
+ // The CEL engine loads outside the budget so an interrupt can never land
1788
+ // halfway through loading a module.
1789
+ this.conditionService.prepareEngine((0, expression_dialect_1.readWorkflowExpressionDialect)(workflowDefinition));
1790
+ // The outer budget bounds the whole walk, and the operation budget
1791
+ // bounds the expressions inside it. Both are charged to this dry run only.
1667
1792
  return Object.assign(new workflow_dry_run_object_1.WorkflowDryRunResultObject(), {
1668
1793
  errors: [],
1669
- steps: this.simulateWorkflow({
1794
+ steps: (0, expression_budget_1.runExpressionOperation)(() => (0, time_budget_1.runWithinTimeBudget)(() => this.simulateWorkflow({
1670
1795
  formData,
1671
1796
  initiatorMemberId: input.initiatorMemberId,
1672
1797
  initiatorMetadataSnapshot,
1673
1798
  workflowDefinition,
1674
- }),
1799
+ }), DRY_RUN_TIME_BUDGET_MS)),
1675
1800
  valid: true,
1676
1801
  });
1677
1802
  }
1678
1803
  catch (error) {
1679
- return Object.assign(new workflow_dry_run_object_1.WorkflowDryRunResultObject(), {
1680
- errors: [error instanceof Error ? error.message : 'Dry run failed'],
1681
- steps: [],
1682
- valid: false,
1683
- });
1804
+ return createFailedDryRunResult(readDryRunFailureMessage(error));
1684
1805
  }
1685
1806
  }
1686
1807
  simulateWorkflow(input) {
@@ -1688,8 +1809,18 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
1688
1809
  if (!startNode) {
1689
1810
  throw new common_1.BadRequestException('Workflow does not include a startEvent');
1690
1811
  }
1812
+ // Every branch re-walks whatever follows it, so a chain of gateways doubles
1813
+ // the work at each one while the depth limit never trips. Closure state
1814
+ // because all the branches of one dry run share this one count.
1815
+ let visits = 0;
1691
1816
  return this.simulateWorkflowPath({
1692
1817
  ...input,
1818
+ chargeVisit: () => {
1819
+ visits += 1;
1820
+ if (visits > MAX_DRY_RUN_VISITS) {
1821
+ throw new common_1.ConflictException(DRY_RUN_TOO_COMPLEX_MESSAGE);
1822
+ }
1823
+ },
1693
1824
  depth: 0,
1694
1825
  incomingEdge: null,
1695
1826
  lastDecision: null,
@@ -1702,6 +1833,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
1702
1833
  if (input.depth > MAX_PROCESSING_STEPS) {
1703
1834
  throw new common_1.ConflictException('Dry run exceeded maximum processing steps');
1704
1835
  }
1836
+ input.chargeVisit();
1705
1837
  const node = readWorkflowNodeOrThrow(input.workflowDefinition, input.nodeId);
1706
1838
  const visitKey = `${input.nodeId}:${input.incomingEdge?.id ?? 'start'}`;
1707
1839
  const entryCondition = readNodeEntryCondition(node);
@@ -1723,7 +1855,10 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
1723
1855
  ];
1724
1856
  }
1725
1857
  const nextVisitedNodeIds = new Set([...input.visitedNodeIds, visitKey]);
1726
- const entryConditionMet = this.conditionService.evaluateBoolean(entryCondition, buildDryRunExpressionContext(input, input.lastDecision), `workflow.nodes.${node.id}.data.entryCondition`);
1858
+ const entryConditionMet = this.conditionService.evaluateBoolean(entryCondition, buildDryRunExpressionContext(input, input.lastDecision), `workflow.nodes.${node.id}.data.entryCondition`, {
1859
+ allowedRootIdentifiers: DRY_RUN_WORKFLOW_ROOT_IDENTIFIERS,
1860
+ dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(input.workflowDefinition),
1861
+ });
1727
1862
  const assignment = entryConditionMet && node.type === 'userTask'
1728
1863
  ? this.resolveDryRunAssignment(input, node)
1729
1864
  : null;
@@ -1817,7 +1952,10 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
1817
1952
  evaluateDryRunEdge(input, edge, lastDecision) {
1818
1953
  const expression = edge.data.condition?.trim();
1819
1954
  if (expression && !isListValuedStructuredCondition(edge, input.formData)) {
1820
- return this.conditionService.evaluateBoolean(expression, buildDryRunExpressionContext(input, lastDecision), `workflow.edges.${edge.id}.data.condition`);
1955
+ return this.conditionService.evaluateBoolean(expression, buildDryRunExpressionContext(input, lastDecision), `workflow.edges.${edge.id}.data.condition`, {
1956
+ allowedRootIdentifiers: DRY_RUN_EDGE_ROOT_IDENTIFIERS,
1957
+ dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(input.workflowDefinition),
1958
+ });
1821
1959
  }
1822
1960
  return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, { formData: input.formData });
1823
1961
  }
@@ -1917,7 +2055,10 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
1917
2055
  return findMemberIdsInValue(readValueAtPath(input.formData, resolver.formPath));
1918
2056
  }
1919
2057
  if (resolver.type === 'EXPRESSION') {
1920
- return findMemberIdsInValue(this.conditionService.evaluateValue(resolver.expression, buildDryRunExpressionContext(input, null), `${label}.expression`));
2058
+ return findMemberIdsInValue(this.conditionService.evaluateValue(resolver.expression, buildDryRunExpressionContext(input, null), `${label}.expression`, {
2059
+ allowedRootIdentifiers: DRY_RUN_WORKFLOW_ROOT_IDENTIFIERS,
2060
+ dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(input.workflowDefinition),
2061
+ }));
1921
2062
  }
1922
2063
  if (resolver.type === 'ORG_MANAGER') {
1923
2064
  const managerMemberIds = findMemberIdsInValue(readManagerMemberIdFromInitiatorSnapshot(input.initiatorMetadataSnapshot, resolver.levelsUp));
@@ -2129,7 +2270,18 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
2129
2270
  if (!currentVersion?.formDefinitionVersionId) {
2130
2271
  return false;
2131
2272
  }
2132
- return this.conditionService.evaluateBoolean(currentVersion.initiatorPolicyCel, buildInitiatorPolicyContext(initiatorMetadataSnapshot), 'initiatorPolicyCel');
2273
+ // A policy in a dialect this version does not know cannot be evaluated,
2274
+ // so the template is not launchable (fail closed), but only that one:
2275
+ // publishing does not check the field yet (Phase 23 P2), and one such
2276
+ // version must not break the list for every member.
2277
+ if (hasExpressionText(currentVersion.initiatorPolicyCel) &&
2278
+ !isReadableExpressionDialect(currentVersion.workflowDefinition)) {
2279
+ this.logger.warn(`template=${template.id} version=${currentVersion.id} left out of launchable templates: unknown expression dialect`);
2280
+ return false;
2281
+ }
2282
+ // Each template is its own operation, so one slow template cannot use
2283
+ // up the allowance of the rest of the list.
2284
+ return (0, expression_budget_1.runExpressionOperation)(() => this.conditionService.evaluateBoolean(currentVersion.initiatorPolicyCel, buildInitiatorPolicyContext(initiatorMetadataSnapshot), 'initiatorPolicyCel', readExpressionDialectOptions(currentVersion.initiatorPolicyCel, currentVersion.workflowDefinition)));
2133
2285
  });
2134
2286
  }
2135
2287
  async listWorkflowTokens(instanceId, scope) {
@@ -2583,7 +2735,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
2583
2735
  }
2584
2736
  isNodeEntryConditionMet(instance, node) {
2585
2737
  const entryCondition = readNodeEntryCondition(node);
2586
- return this.conditionService.evaluateBoolean(entryCondition, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.entryCondition`);
2738
+ return this.conditionService.evaluateBoolean(entryCondition, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.entryCondition`, readExpressionDialectOptions(entryCondition, instance.workflowSnapshot));
2587
2739
  }
2588
2740
  async skipNodeByEntryCondition(manager, instance, token, node) {
2589
2741
  await this.skipNode(manager, instance, token, node, {
@@ -2656,7 +2808,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
2656
2808
  return this.conditionService.evaluateBoolean(expression, {
2657
2809
  ...buildWorkflowExpressionContext(instance),
2658
2810
  lastDecision: await this.readLastDecisionContext(manager, instance.id, node.id),
2659
- }, `workflow.edges.${edge.id}.data.condition`);
2811
+ }, `workflow.edges.${edge.id}.data.condition`, { dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(instance.workflowSnapshot) });
2660
2812
  }
2661
2813
  return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, { formData: instance.formData });
2662
2814
  }
@@ -3482,7 +3634,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
3482
3634
  });
3483
3635
  }
3484
3636
  async executeTaskReassignment(preRead, options) {
3485
- return this.approvalInstanceRepository.manager.transaction(async (manager) => {
3637
+ return this.runEngineTransaction(async (manager) => {
3486
3638
  try {
3487
3639
  const plan = await this.planTaskReassignment(manager, preRead.id, options);
3488
3640
  if (options.dryRun) {
@@ -3859,7 +4011,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
3859
4011
  async delegateResolvedCandidates(manager, instance, nodeId, resolvedCandidates, gradeGated) {
3860
4012
  // One batch for the node, so the host directory is asked once for every
3861
4013
  // candidate's delegation agents rather than once per candidate.
3862
- const resolutions = await this.delegationService.resolveAssignees(resolvedCandidates.map((candidate) => candidate.memberId), this.buildDelegationContext(instance, nodeId));
4014
+ const resolutions = await this.delegationService.resolveAssignees(resolvedCandidates.map((candidate) => candidate.memberId), { ...this.buildDelegationContext(instance, nodeId), manager });
3863
4015
  const resolved = resolvedCandidates.map((candidate, index) => ({
3864
4016
  candidate,
3865
4017
  resolution: resolutions[index],
@@ -3881,7 +4033,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
3881
4033
  }
3882
4034
  /** One member's delegation, grade-gated as {@link applyGradeGate} says. */
3883
4035
  async resolveDelegation(manager, principalMemberId, context, gradeGated) {
3884
- const resolution = await this.delegationService.resolveAssignee(principalMemberId, context);
4036
+ const resolution = await this.delegationService.resolveAssignee(principalMemberId, { ...context, manager });
3885
4037
  const memberIds = gradeGated
3886
4038
  ? readGradeGateMemberIds(principalMemberId, resolution)
3887
4039
  : [];
@@ -3937,7 +4089,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
3937
4089
  return readMemberIdsFromValue(readValueAtPath(instance.formData, resolver.formPath), label, subject, `依表單欄位「${resolver.formPath}」`);
3938
4090
  }
3939
4091
  if (resolver.type === 'EXPRESSION') {
3940
- return readMemberIdsFromValue(this.conditionService.evaluateValue(resolver.expression, buildWorkflowExpressionContext(instance), `${label}.expression`), label, subject, '依運算式');
4092
+ return readMemberIdsFromValue(this.conditionService.evaluateValue(resolver.expression, buildWorkflowExpressionContext(instance), `${label}.expression`, { dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(instance.workflowSnapshot) }), label, subject, '依運算式');
3941
4093
  }
3942
4094
  if (resolver.type === 'POSITION') {
3943
4095
  return this.resolvePositionCandidates(manager, resolver.positionId, label, subject);
@@ -4459,34 +4611,39 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
4459
4611
  const action = node.data.action;
4460
4612
  if (action.type === 'WEBHOOK') {
4461
4613
  const payload = typeof action.payload === 'string' && action.payload.trim()
4462
- ? this.conditionService.evaluateValue(action.payload, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.payload`)
4614
+ ? this.conditionService.evaluateValue(action.payload, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.payload`, {
4615
+ dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(instance.workflowSnapshot),
4616
+ })
4463
4617
  : {
4464
4618
  instance: buildWorkflowExpressionContext(instance).instance,
4465
4619
  triggeredAt: new Date().toISOString(),
4466
4620
  };
4467
- const result = await executeWebhookServiceAction(this.serviceTaskDispatcher, action, payload);
4468
- await activityRepository.save(activityRepository.create({
4469
- actorMemberId: null,
4470
- eventType: result.ok
4471
- ? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
4472
- : workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
4621
+ await this.dispatchWebhookAfterCommit(manager, {
4473
4622
  instanceId: instance.id,
4474
- nodeId: node.id,
4475
- payload: {
4476
- action: 'WEBHOOK',
4477
- error: result.error,
4478
- ok: result.ok,
4479
- status: result.status,
4480
- tokenId: token.id,
4481
- url: action.url,
4482
- },
4483
- taskId: null,
4484
- }));
4623
+ readActivityLog: (result) => ({
4624
+ actorMemberId: null,
4625
+ eventType: result.ok
4626
+ ? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
4627
+ : workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
4628
+ instanceId: instance.id,
4629
+ nodeId: node.id,
4630
+ payload: {
4631
+ action: 'WEBHOOK',
4632
+ error: result.error,
4633
+ ok: result.ok,
4634
+ status: result.status,
4635
+ tokenId: token.id,
4636
+ url: action.url,
4637
+ },
4638
+ taskId: null,
4639
+ }),
4640
+ send: () => executeWebhookServiceAction(this.serviceTaskDispatcher, action, payload),
4641
+ });
4485
4642
  await this.advanceTokenToOutgoingNodes(manager, instance, token, node);
4486
4643
  return;
4487
4644
  }
4488
4645
  if (action.type === 'SET_FORM_FIELD') {
4489
- const value = this.conditionService.evaluateValue(action.value, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.value`);
4646
+ const value = this.conditionService.evaluateValue(action.value, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.value`, { dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(instance.workflowSnapshot) });
4490
4647
  assertServiceTaskTextValue(instance.formDefinitionSnapshot, action.fieldPath, value, node.data.label);
4491
4648
  const updatedFormData = writeValueAtPath(instance.formData, action.fieldPath, value);
4492
4649
  // A service task runs inside this transaction, so it cannot call a host
@@ -5608,6 +5765,33 @@ function normalizeFormFieldPath(path) {
5608
5765
  }
5609
5766
  return segments;
5610
5767
  }
5768
+ /**
5769
+ * The message a failed dry run shows its caller. Only the fixed budget text, an
5770
+ * HTTP exception's own message, or an expression error's label and code reach
5771
+ * this point. Any other error is replaced with a generic message, since its text
5772
+ * may carry engine or context details.
5773
+ */
5774
+ function readDryRunFailureMessage(error) {
5775
+ if (error instanceof time_budget_1.TimeBudgetExceededError) {
5776
+ return DRY_RUN_TOO_COMPLEX_MESSAGE;
5777
+ }
5778
+ if (error instanceof expression_errors_1.ExpressionRuntimeError) {
5779
+ return error.keyName
5780
+ ? `${error.message}(找不到「${error.keyName}」)`
5781
+ : error.message;
5782
+ }
5783
+ if (error instanceof common_1.HttpException) {
5784
+ return error.message;
5785
+ }
5786
+ return 'Dry run failed';
5787
+ }
5788
+ function createFailedDryRunResult(message) {
5789
+ return Object.assign(new workflow_dry_run_object_1.WorkflowDryRunResultObject(), {
5790
+ errors: [message],
5791
+ steps: [],
5792
+ valid: false,
5793
+ });
5794
+ }
5611
5795
  async function executeWebhookServiceAction(serviceTaskDispatcher, action, payload) {
5612
5796
  try {
5613
5797
  return await serviceTaskDispatcher.dispatchWebhook({