@usearazzo/runner 1.0.1-alpha.20 → 1.0.1-alpha.21

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.
@@ -37,6 +37,12 @@ export const STEP_TARGET_FIELDS = ['operationId', 'operationPath', 'workflowId']
37
37
  * @public
38
38
  */
39
39
 
40
+ /**
41
+ * The outcome of one criterion a step evaluated: one of its `successCriteria`,
42
+ * or one of the `criteria` of an action it considered.
43
+ * @public
44
+ */
45
+
40
46
  /**
41
47
  * The outcome of executing a single step.
42
48
  * @public
@@ -183,16 +189,22 @@ class StepExecutor {
183
189
  // evaluate criteria, outputs, and the next action against the post-request
184
190
  // context (with $request / $url / $method and $response / $statusCode).
185
191
  const postContext = state.toContext(this.#requestContext(response), this.#responseContext(response));
186
- const successful = this.evaluateCriteria(step, postContext);
192
+ const successCriteria = this.evaluateCriteria(step, postContext);
193
+ const successful = successCriteria.every(criterion => criterion.met);
187
194
  const outputs = this.#outputResolver.resolve(step.outputs, expression => this.#evaluate(postContext, expression), scope);
188
- const matchedActions = this.selectActions(step, successful, postContext);
195
+ const {
196
+ matchedActions,
197
+ actionCriteria
198
+ } = this.selectActions(step, successful, postContext);
189
199
  return {
190
200
  stepId,
191
201
  response,
192
202
  successful,
193
203
  outputs,
194
204
  action: matchedActions[0],
195
- matchedActions
205
+ matchedActions,
206
+ successCriteria,
207
+ actionCriteria
196
208
  };
197
209
  }
198
210
 
@@ -265,9 +277,12 @@ class StepExecutor {
265
277
  }
266
278
 
267
279
  /**
268
- * Evaluates the step's `successCriteria`; all criteria must pass. A step with
269
- * no criteria succeeds on a received response. An unreadable
270
- * `successCriteria` throws, see {@link StepExecutor.assertSuccessCriteria}.
280
+ * Evaluates the step's `successCriteria`, returning one record per criterion
281
+ * in list order; the step succeeds when every record is met. Every criterion
282
+ * is evaluated, so the records explain each unmet one, not only the first. A
283
+ * step with no criteria returns no records and succeeds on a received
284
+ * response. An unreadable `successCriteria` throws, see
285
+ * {@link StepExecutor.assertSuccessCriteria}.
271
286
  *
272
287
  * Public for the same reason as {@link StepExecutor.selectActions}: a step
273
288
  * targeting a `workflowId` is run by the workflow executor and never reaches
@@ -277,15 +292,16 @@ class StepExecutor {
277
292
  evaluateCriteria(step, context) {
278
293
  this.assertSuccessCriteria(step);
279
294
  const criteria = step.successCriteria;
280
- if (criteria === undefined) return true;
295
+ if (criteria === undefined) return [];
281
296
 
282
297
  // assertSuccessCriteria above guarantees every entry is a Criterion Object
283
- return [...criteria].every(criterion => this.#criterionEvaluator.evaluate(criterion, expression => this.#evaluate(context, expression)));
298
+ return [...criteria].map(criterion => this.#evaluateCriterion(criterion, context));
284
299
  }
285
300
 
286
301
  /**
287
302
  * Selects every matching `onSuccess` / `onFailure` action for the outcome, in
288
- * list order, gating each action's criteria against the context.
303
+ * list order, gating each action's criteria against the context, and records
304
+ * the outcome of every action criterion evaluated.
289
305
  *
290
306
  * The step's own list is the whole story here: a step that declares none of
291
307
  * its own already carries the workflow-level defaults, inherited by
@@ -304,25 +320,65 @@ class StepExecutor {
304
320
  * the expression context through this one method rather than wiring their own.
305
321
  */
306
322
  selectActions(step, successful, context) {
307
- const isCriterionMet = criterion => {
308
- try {
309
- return this.#criterionEvaluator.evaluate(criterion, expression => this.#evaluate(context, expression));
310
- } catch (error) {
311
- // an action's criteria decide whether it applies to this outcome, and
312
- // actions are shared — inherited from the workflow, reused from
313
- // components. A condition on an HTTP exchange cannot apply to a step
314
- // that calls a workflow, so the action does not match; the run does not
315
- // abort over a default written for the operation steps.
316
- if (error instanceof RuntimeExpressionError && error.reason === 'unavailable-expression') {
317
- return false;
318
- }
319
- throw error;
320
- }
323
+ const actionCriteria = [];
324
+ const isCriterionMet = (criterion, action) => {
325
+ const record = {
326
+ ...this.#evaluateCriterion(criterion, context),
327
+ actionName: toValue(action.name)
328
+ };
329
+ actionCriteria.push(record);
330
+ return record.met;
321
331
  };
322
332
  const scope = {
323
333
  stepId: toValue(step.stepId)
324
334
  };
325
- return successful ? this.#actionResolver.resolveOnSuccess(step.onSuccess, isCriterionMet, scope) : this.#actionResolver.resolveOnFailure(step.onFailure, isCriterionMet, scope);
335
+ const matchedActions = successful ? this.#actionResolver.resolveOnSuccess(step.onSuccess, isCriterionMet, scope) : this.#actionResolver.resolveOnFailure(step.onFailure, isCriterionMet, scope);
336
+ return {
337
+ matchedActions,
338
+ actionCriteria
339
+ };
340
+ }
341
+
342
+ /**
343
+ * Evaluates one criterion into a {@link CriterionEvaluationRecord}. A
344
+ * condition that cannot be evaluated is unmet and records why; any other
345
+ * error propagates.
346
+ */
347
+ #evaluateCriterion(criterion, context) {
348
+ const condition = toValue(criterion.condition);
349
+ try {
350
+ const {
351
+ met,
352
+ error
353
+ } = this.#criterionEvaluator.evaluate(criterion, expression => this.#evaluate(context, expression));
354
+ if (error === undefined) return {
355
+ condition,
356
+ met
357
+ };
358
+ return {
359
+ condition,
360
+ met,
361
+ diagnostic: {
362
+ code: error.reason,
363
+ message: error.message
364
+ }
365
+ };
366
+ } catch (error) {
367
+ // a step that calls a workflow made no HTTP call, so a condition on one
368
+ // fails like any unevaluable condition; the diagnostic keeps the
369
+ // authoring mistake visible.
370
+ if (error instanceof RuntimeExpressionError && error.reason === 'unavailable-expression') {
371
+ return {
372
+ condition,
373
+ met: false,
374
+ diagnostic: {
375
+ code: error.reason,
376
+ message: error.message
377
+ }
378
+ };
379
+ }
380
+ throw error;
381
+ }
326
382
  }
327
383
 
328
384
  /**
@@ -359,6 +359,8 @@ class WorkflowExecutor {
359
359
  action,
360
360
  attempts,
361
361
  durationMs: this.#now() - stepStartedAt,
362
+ successCriteria: outcome.successCriteria,
363
+ actionCriteria: outcome.actionCriteria,
362
364
  ...(subWorkflows.length > 0 ? {
363
365
  subWorkflows
364
366
  } : {}),
@@ -889,19 +891,26 @@ class WorkflowExecutor {
889
891
  // apply on top — they are the author's assertion about this step, and
890
892
  // dropping them because the step happens to target a workflow would
891
893
  // silently discard it. They see the sub-run's own outputs (`{}` when it
892
- // transferred) through `$outputs` and `$workflows`.
894
+ // transferred) through `$outputs` and `$workflows`, and are evaluated
895
+ // even when the sub-run failed, so the record explains them either way.
893
896
  // the criteria and actions are the *calling* step's, so they are
894
897
  // evaluated by the parent document's step executor — only the sub-run
895
898
  // itself switched documents.
896
899
  const stepExecutor = this.#stepExecutor.forDocument(document);
897
- const successful = !this.#settledFailed(result) && stepExecutor.evaluateCriteria(step, context);
898
- const matchedActions = stepExecutor.selectActions(step, successful, context);
900
+ const successCriteria = stepExecutor.evaluateCriteria(step, context);
901
+ const successful = !this.#settledFailed(result) && successCriteria.every(criterion => criterion.met);
902
+ const {
903
+ matchedActions,
904
+ actionCriteria
905
+ } = stepExecutor.selectActions(step, successful, context);
899
906
  return {
900
907
  stepId,
901
908
  successful,
902
909
  outputs,
903
910
  action: matchedActions[0],
904
- matchedActions
911
+ matchedActions,
912
+ successCriteria,
913
+ actionCriteria
905
914
  };
906
915
  };
907
916
  }
@@ -345,6 +345,8 @@ class WorkflowExecutor {
345
345
  action,
346
346
  attempts,
347
347
  durationMs: this.#now() - stepStartedAt,
348
+ successCriteria: outcome.successCriteria,
349
+ actionCriteria: outcome.actionCriteria,
348
350
  ...(subWorkflows.length > 0 ? {
349
351
  subWorkflows
350
352
  } : {}),
@@ -875,19 +877,26 @@ class WorkflowExecutor {
875
877
  // apply on top — they are the author's assertion about this step, and
876
878
  // dropping them because the step happens to target a workflow would
877
879
  // silently discard it. They see the sub-run's own outputs (`{}` when it
878
- // transferred) through `$outputs` and `$workflows`.
880
+ // transferred) through `$outputs` and `$workflows`, and are evaluated
881
+ // even when the sub-run failed, so the record explains them either way.
879
882
  // the criteria and actions are the *calling* step's, so they are
880
883
  // evaluated by the parent document's step executor — only the sub-run
881
884
  // itself switched documents.
882
885
  const stepExecutor = this.#stepExecutor.forDocument(document);
883
- const successful = !this.#settledFailed(result) && stepExecutor.evaluateCriteria(step, context);
884
- const matchedActions = stepExecutor.selectActions(step, successful, context);
886
+ const successCriteria = stepExecutor.evaluateCriteria(step, context);
887
+ const successful = !this.#settledFailed(result) && successCriteria.every(criterion => criterion.met);
888
+ const {
889
+ matchedActions,
890
+ actionCriteria
891
+ } = stepExecutor.selectActions(step, successful, context);
885
892
  return {
886
893
  stepId,
887
894
  successful,
888
895
  outputs,
889
896
  action: matchedActions[0],
890
- matchedActions
897
+ matchedActions,
898
+ successCriteria,
899
+ actionCriteria
891
900
  };
892
901
  };
893
902
  }
package/types/runner.d.ts CHANGED
@@ -534,7 +534,45 @@ export declare class CriterionEvaluationError extends CriterionError {
534
534
  }
535
535
 
536
536
  /**
537
- * Evaluates an Arazzo Criterion Object to a boolean.
537
+ * The outcome of one criterion a step evaluated: one of its `successCriteria`,
538
+ * or one of the `criteria` of an action it considered.
539
+ * @public
540
+ */
541
+ export declare interface CriterionEvaluationRecord {
542
+ /**
543
+ * The criterion's `condition`, as written.
544
+ */
545
+ readonly condition: string;
546
+ /**
547
+ * Whether the condition is met.
548
+ */
549
+ readonly met: boolean;
550
+ /**
551
+ * The `name` of the action declaring the criterion — present only on an
552
+ * action criterion.
553
+ */
554
+ readonly actionName?: string;
555
+ /**
556
+ * Why the condition could not be evaluated — present only then, with `met`
557
+ * `false`.
558
+ */
559
+ readonly diagnostic?: {
560
+ /**
561
+ * Machine-readable: the `reason` of the error that failed the criterion, a
562
+ * `CriterionErrorReason` value or `'unavailable-expression'` for an
563
+ * HTTP-exchange expression in a step that calls a workflow.
564
+ */
565
+ readonly code?: CriterionErrorReason | 'unavailable-expression';
566
+ /**
567
+ * Human-readable: names the condition or expression and, for a syntax
568
+ * error, what is wrong with it; never request or response data.
569
+ */
570
+ readonly message: string;
571
+ };
572
+ }
573
+
574
+ /**
575
+ * Evaluates an Arazzo Criterion Object to a {@link CriterionOutcome}.
538
576
  *
539
577
  * Dispatches on the criterion's `type` (defaulting to `simple` when omitted):
540
578
  * - `simple` — evaluates the condition directly, resolving the runtime
@@ -554,12 +592,13 @@ export declare class CriterionEvaluationError extends CriterionError {
554
592
  * the 1.1.0 rule to every document, which accepts more but never evaluates a
555
593
  * query under the wrong dialect.
556
594
  *
557
- * A condition that cannot be evaluated fails the criterion (`false`), as
595
+ * A condition that cannot be evaluated fails the criterion (`met: false`), as
558
596
  * Arazzo 1.1.0 requires: a syntax error in the condition, a context the
559
597
  * condition cannot apply to (non-XML data for `xpath`), or an XPath evaluation
560
598
  * error. The type-specific evaluators report these as a
561
- * {@link CriterionEvaluationError}, which this facade absorbs; any other error
562
- * they raise propagates. A criterion that is malformed as written still throws: a
599
+ * {@link CriterionEvaluationError}, which this facade returns as the
600
+ * outcome's `error` so the caller can report it; any other error they raise
601
+ * propagates. A criterion that is malformed as written still throws: a
563
602
  * missing `condition`, an unknown or unreadable `type`, an unsupported
564
603
  * expression `version`, or a `context` that is missing or not a runtime
565
604
  * expression.
@@ -574,13 +613,14 @@ export declare class CriterionEvaluator {
574
613
  #private;
575
614
  constructor(options?: CriterionEvaluatorOptions);
576
615
  /**
577
- * Evaluates a criterion element, returning whether the condition is met.
616
+ * Evaluates a criterion element, returning whether the condition is met and,
617
+ * when it cannot be evaluated, why.
578
618
  *
579
619
  * `resolve` turns a runtime expression into a value: for `simple` it resolves
580
620
  * the expressions embedded in the condition; for the other types it resolves
581
621
  * the criterion's `context` expression that the condition is applied to.
582
622
  */
583
- evaluate(criterion: CriterionElement, resolve: CriterionContextResolver): boolean;
623
+ evaluate(criterion: CriterionElement, resolve: CriterionContextResolver): CriterionOutcome;
584
624
  }
585
625
 
586
626
  /**
@@ -611,14 +651,32 @@ export declare interface CriterionEvaluatorOptions {
611
651
  }
612
652
 
613
653
  /**
614
- * Determines whether a single criterion is met.
654
+ * The outcome of evaluating one criterion.
655
+ * @public
656
+ */
657
+ export declare interface CriterionOutcome {
658
+ /**
659
+ * Whether the condition is met. `false` when it evaluates to false or cannot
660
+ * be evaluated; only in the latter case is {@link CriterionOutcome.error}
661
+ * present, saying why.
662
+ */
663
+ readonly met: boolean;
664
+ /**
665
+ * Why the condition could not be evaluated: present only when it was not.
666
+ */
667
+ readonly error?: CriterionEvaluationError;
668
+ }
669
+
670
+ /**
671
+ * Determines whether a single criterion of an action is met. Receives the
672
+ * action declaring the criterion, so a caller can attribute the outcome to it.
615
673
  *
616
674
  * Bridged to a criterion evaluator
617
- * (`(criterion) => criterionEvaluator.evaluate(criterion, resolve)`); an
675
+ * (`(criterion) => criterionEvaluator.evaluate(criterion, resolve).met`); an
618
676
  * unresolvable/failing criterion is `false`, not an error.
619
677
  * @public
620
678
  */
621
- export declare type CriterionPredicate = (criterion: CriterionElement) => boolean;
679
+ export declare type CriterionPredicate = (criterion: CriterionElement, action: SelectedAction) => boolean;
622
680
 
623
681
  /** @public */
624
682
  export declare class DocumentLoadError extends ArazzoRunnerError {
@@ -1929,6 +1987,16 @@ export declare interface StepExecutionResult {
1929
1987
  * matching action. Empty when none matched.
1930
1988
  */
1931
1989
  readonly matchedActions: readonly SelectedAction[];
1990
+ /**
1991
+ * The outcome of each of the step's `successCriteria`, in list order.
1992
+ */
1993
+ readonly successCriteria: readonly CriterionEvaluationRecord[];
1994
+ /**
1995
+ * The outcome of each criterion of the actions the step considered — its
1996
+ * `onSuccess` actions when it succeeded, its `onFailure` actions otherwise —
1997
+ * in list order.
1998
+ */
1999
+ readonly actionCriteria: readonly CriterionEvaluationRecord[];
1932
2000
  }
1933
2001
 
1934
2002
  /**
@@ -1996,19 +2064,23 @@ export declare class StepExecutor {
1996
2064
  */
1997
2065
  assertSuccessCriteria(step: StepElement, workflowId?: string): void;
1998
2066
  /**
1999
- * Evaluates the step's `successCriteria`; all criteria must pass. A step with
2000
- * no criteria succeeds on a received response. An unreadable
2001
- * `successCriteria` throws, see {@link StepExecutor.assertSuccessCriteria}.
2067
+ * Evaluates the step's `successCriteria`, returning one record per criterion
2068
+ * in list order; the step succeeds when every record is met. Every criterion
2069
+ * is evaluated, so the records explain each unmet one, not only the first. A
2070
+ * step with no criteria returns no records and succeeds on a received
2071
+ * response. An unreadable `successCriteria` throws, see
2072
+ * {@link StepExecutor.assertSuccessCriteria}.
2002
2073
  *
2003
2074
  * Public for the same reason as {@link StepExecutor.selectActions}: a step
2004
2075
  * targeting a `workflowId` is run by the workflow executor and never reaches
2005
2076
  * {@link StepExecutor.execute}, but its criteria are still the author's
2006
2077
  * assertion about the step and must be evaluated somewhere.
2007
2078
  */
2008
- evaluateCriteria(step: StepElement, context: RuntimeExpressionContext): boolean;
2079
+ evaluateCriteria(step: StepElement, context: RuntimeExpressionContext): CriterionEvaluationRecord[];
2009
2080
  /**
2010
2081
  * Selects every matching `onSuccess` / `onFailure` action for the outcome, in
2011
- * list order, gating each action's criteria against the context.
2082
+ * list order, gating each action's criteria against the context, and records
2083
+ * the outcome of every action criterion evaluated.
2012
2084
  *
2013
2085
  * The step's own list is the whole story here: a step that declares none of
2014
2086
  * its own already carries the workflow-level defaults, inherited by
@@ -2026,7 +2098,7 @@ export declare class StepExecutor {
2026
2098
  * {@link StepExecutor.execute}. Both callers reach the criteria evaluator and
2027
2099
  * the expression context through this one method rather than wiring their own.
2028
2100
  */
2029
- selectActions(step: StepElement, successful: boolean, context: RuntimeExpressionContext): SelectedAction[];
2101
+ selectActions(step: StepElement, successful: boolean, context: RuntimeExpressionContext): Pick<StepExecutionResult, 'matchedActions' | 'actionCriteria'>;
2030
2102
  }
2031
2103
 
2032
2104
  /**
@@ -2104,6 +2176,17 @@ export declare interface StepRunRecord {
2104
2176
  * fractional and is unaffected by system clock changes.
2105
2177
  */
2106
2178
  readonly durationMs: number;
2179
+ /**
2180
+ * The outcome of each of the step's `successCriteria` in its last attempt, in
2181
+ * list order — why the step succeeded or failed.
2182
+ */
2183
+ readonly successCriteria: readonly CriterionEvaluationRecord[];
2184
+ /**
2185
+ * The outcome of each criterion of the actions the last attempt considered —
2186
+ * its `onSuccess` actions when it succeeded, its `onFailure` actions
2187
+ * otherwise — in list order: why an action matched or did not.
2188
+ */
2189
+ readonly actionCriteria: readonly CriterionEvaluationRecord[];
2107
2190
  /**
2108
2191
  * The sub-workflow runs this step produced, in attempt order — present only on
2109
2192
  * a step targeting a `workflowId`.