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

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": "@usearazzo/runner",
3
- "version": "1.0.1-alpha.20",
3
+ "version": "1.0.1-alpha.22",
4
4
  "description": "Runner for Arazzo Workflows.",
5
5
  "keywords": [
6
6
  "arazzo",
@@ -69,12 +69,12 @@
69
69
  "@speclynx/apidom-traverse": "^6.1.2",
70
70
  "@swaggerexpert/arazzo-criterion": "^1.0.1",
71
71
  "@swaggerexpert/arazzo-runtime-expression": "^3.3.0",
72
- "@swaggerexpert/cookie": "^2.0.2",
72
+ "@swaggerexpert/cookie": "^2.1.1",
73
73
  "@swaggerexpert/json-pointer": "^3.0.2",
74
74
  "@swaggerexpert/jsonpath": "^4.0.5",
75
75
  "@swaggerexpert/media-types": "^1.0.2",
76
- "@usearazzo/parser": "1.0.1-alpha.20",
77
- "@usearazzo/resolver": "1.0.1-alpha.20",
76
+ "@usearazzo/parser": "1.0.1-alpha.22",
77
+ "@usearazzo/resolver": "1.0.1-alpha.22",
78
78
  "@xmldom/xmldom": "^0.9.12",
79
79
  "fontoxpath": "^3.34.0",
80
80
  "ramda": "^0.32.0",
@@ -94,5 +94,5 @@
94
94
  "devDependencies": {
95
95
  "swagger-client": "^3.38.4"
96
96
  },
97
- "gitHead": "5af237fe28ae20de4d56e5c0ba6d2e17fcf90f73"
97
+ "gitHead": "e70dedbb2d249b9a6c16808048d9272609103f71"
98
98
  }
@@ -9,10 +9,11 @@ var _apidomDatamodel = require("@speclynx/apidom-datamodel");
9
9
  var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
10
10
  var _ResolverError = _interopRequireDefault(require("../errors/ResolverError.cjs"));
11
11
  /**
12
- * Determines whether a single criterion is met.
12
+ * Determines whether a single criterion of an action is met. Receives the
13
+ * action declaring the criterion, so a caller can attribute the outcome to it.
13
14
  *
14
15
  * Bridged to a criterion evaluator
15
- * (`(criterion) => criterionEvaluator.evaluate(criterion, resolve)`); an
16
+ * (`(criterion) => criterionEvaluator.evaluate(criterion, resolve).met`); an
16
17
  * unresolvable/failing criterion is `false`, not an error.
17
18
  * @public
18
19
  */
@@ -144,6 +145,7 @@ class ActionResolver {
144
145
  reason: 'malformed-criteria'
145
146
  });
146
147
  }
148
+ let met = true;
147
149
  for (const criterion of criteria) {
148
150
  if (!(0, _apidomNsArazzo.isCriterionElement)(criterion)) {
149
151
  throw new _ResolverError.default("An action's `criteria` contains an entry that is not a Criterion Object", {
@@ -152,9 +154,11 @@ class ActionResolver {
152
154
  reason: 'malformed-criterion'
153
155
  });
154
156
  }
155
- if (!isCriterionMet(criterion)) return false;
157
+ // every criterion is evaluated, not only up to the first unmet one, so
158
+ // a predicate that records outcomes sees the whole list.
159
+ if (!isCriterionMet(criterion, action)) met = false;
156
160
  }
157
- return true;
161
+ return met;
158
162
  }
159
163
  }
160
164
  var _default = exports.default = ActionResolver;
@@ -2,10 +2,11 @@ import { isArrayElement } from '@speclynx/apidom-datamodel';
2
2
  import { isSuccessActionElement, isFailureActionElement, isCriterionElement } from '@speclynx/apidom-ns-arazzo-1';
3
3
  import ResolverError from "../errors/ResolverError.mjs";
4
4
  /**
5
- * Determines whether a single criterion is met.
5
+ * Determines whether a single criterion of an action is met. Receives the
6
+ * action declaring the criterion, so a caller can attribute the outcome to it.
6
7
  *
7
8
  * Bridged to a criterion evaluator
8
- * (`(criterion) => criterionEvaluator.evaluate(criterion, resolve)`); an
9
+ * (`(criterion) => criterionEvaluator.evaluate(criterion, resolve).met`); an
9
10
  * unresolvable/failing criterion is `false`, not an error.
10
11
  * @public
11
12
  */
@@ -134,6 +135,7 @@ class ActionResolver {
134
135
  reason: 'malformed-criteria'
135
136
  });
136
137
  }
138
+ let met = true;
137
139
  for (const criterion of criteria) {
138
140
  if (!isCriterionElement(criterion)) {
139
141
  throw new ResolverError("An action's `criteria` contains an entry that is not a Criterion Object", {
@@ -142,9 +144,11 @@ class ActionResolver {
142
144
  reason: 'malformed-criterion'
143
145
  });
144
146
  }
145
- if (!isCriterionMet(criterion)) return false;
147
+ // every criterion is evaluated, not only up to the first unmet one, so
148
+ // a predicate that records outcomes sees the whole list.
149
+ if (!isCriterionMet(criterion, action)) met = false;
146
150
  }
147
- return true;
151
+ return met;
148
152
  }
149
153
  }
150
154
  export default ActionResolver;
@@ -28,6 +28,11 @@ var _XPathCriterionEvaluator = _interopRequireDefault(require("./XPathCriterionE
28
28
  * @public
29
29
  */
30
30
 
31
+ /**
32
+ * The outcome of evaluating one criterion.
33
+ * @public
34
+ */
35
+
31
36
  /**
32
37
  * Options for the CriterionEvaluator.
33
38
  *
@@ -38,7 +43,7 @@ var _XPathCriterionEvaluator = _interopRequireDefault(require("./XPathCriterionE
38
43
  */
39
44
 
40
45
  /**
41
- * Evaluates an Arazzo Criterion Object to a boolean.
46
+ * Evaluates an Arazzo Criterion Object to a {@link CriterionOutcome}.
42
47
  *
43
48
  * Dispatches on the criterion's `type` (defaulting to `simple` when omitted):
44
49
  * - `simple` — evaluates the condition directly, resolving the runtime
@@ -58,12 +63,13 @@ var _XPathCriterionEvaluator = _interopRequireDefault(require("./XPathCriterionE
58
63
  * the 1.1.0 rule to every document, which accepts more but never evaluates a
59
64
  * query under the wrong dialect.
60
65
  *
61
- * A condition that cannot be evaluated fails the criterion (`false`), as
66
+ * A condition that cannot be evaluated fails the criterion (`met: false`), as
62
67
  * Arazzo 1.1.0 requires: a syntax error in the condition, a context the
63
68
  * condition cannot apply to (non-XML data for `xpath`), or an XPath evaluation
64
69
  * error. The type-specific evaluators report these as a
65
- * {@link CriterionEvaluationError}, which this facade absorbs; any other error
66
- * they raise propagates. A criterion that is malformed as written still throws: a
70
+ * {@link CriterionEvaluationError}, which this facade returns as the
71
+ * outcome's `error` so the caller can report it; any other error they raise
72
+ * propagates. A criterion that is malformed as written still throws: a
67
73
  * missing `condition`, an unknown or unreadable `type`, an unsupported
68
74
  * expression `version`, or a `context` that is missing or not a runtime
69
75
  * expression.
@@ -87,7 +93,8 @@ class CriterionEvaluator {
87
93
  }
88
94
 
89
95
  /**
90
- * Evaluates a criterion element, returning whether the condition is met.
96
+ * Evaluates a criterion element, returning whether the condition is met and,
97
+ * when it cannot be evaluated, why.
91
98
  *
92
99
  * `resolve` turns a runtime expression into a value: for `simple` it resolves
93
100
  * the expressions embedded in the condition; for the other types it resolves
@@ -109,23 +116,28 @@ class CriterionEvaluator {
109
116
  }
110
117
  const type = this.#resolveType(criterion);
111
118
  this.#requireValidContext(criterion, type);
119
+ let met;
112
120
  try {
113
121
  switch (type) {
114
122
  case 'simple':
115
- return this.#simple.evaluate(condition, resolve);
123
+ met = this.#simple.evaluate(condition, resolve);
124
+ break;
116
125
  case 'regex':
117
- return this.#regex.evaluate(condition, this.#resolveContext(criterion, type, resolve));
126
+ met = this.#regex.evaluate(condition, this.#resolveContext(criterion, type, resolve));
127
+ break;
118
128
  case 'jsonpath':
119
129
  // only rfc9535 is supported, Arazzo 1.1.0's default, so an omitted
120
130
  // version is accepted. draft-goessner has different semantics and is
121
131
  // rejected rather than mis-evaluated.
122
132
  this.#requireVersion(criterion, type, condition, 'rfc9535');
123
- return this.#jsonpath.evaluate(condition, this.#resolveContext(criterion, type, resolve));
133
+ met = this.#jsonpath.evaluate(condition, this.#resolveContext(criterion, type, resolve));
134
+ break;
124
135
  case 'xpath':
125
136
  // only XPath 3.1 is supported; xpath-31 is Arazzo 1.1.0's default, so
126
137
  // an omitted version is accepted. Older versions are rejected.
127
138
  this.#requireVersion(criterion, type, condition, 'xpath-31');
128
- return this.#xpath.evaluate(condition, this.#resolveContext(criterion, type, resolve));
139
+ met = this.#xpath.evaluate(condition, this.#resolveContext(criterion, type, resolve));
140
+ break;
129
141
  default:
130
142
  throw new _CriterionError.default(`Unknown criterion type "${type}"`, {
131
143
  condition,
@@ -136,9 +148,15 @@ class CriterionEvaluator {
136
148
  } catch (error) {
137
149
  // Arazzo 1.1.0: a condition that cannot be evaluated MUST evaluate to fail.
138
150
  // The structural checks throw a plain CriterionError and still propagate.
139
- if (error instanceof _CriterionEvaluationError.default) return false;
151
+ if (error instanceof _CriterionEvaluationError.default) return {
152
+ met: false,
153
+ error
154
+ };
140
155
  throw error;
141
156
  }
157
+ return {
158
+ met
159
+ };
142
160
  }
143
161
 
144
162
  /**
@@ -20,6 +20,10 @@ import XPathCriterionEvaluator from "./XPathCriterionEvaluator.mjs";
20
20
  * lenient evaluator).
21
21
  * @public
22
22
  */
23
+ /**
24
+ * The outcome of evaluating one criterion.
25
+ * @public
26
+ */
23
27
  /**
24
28
  * Options for the CriterionEvaluator.
25
29
  *
@@ -29,7 +33,7 @@ import XPathCriterionEvaluator from "./XPathCriterionEvaluator.mjs";
29
33
  * @public
30
34
  */
31
35
  /**
32
- * Evaluates an Arazzo Criterion Object to a boolean.
36
+ * Evaluates an Arazzo Criterion Object to a {@link CriterionOutcome}.
33
37
  *
34
38
  * Dispatches on the criterion's `type` (defaulting to `simple` when omitted):
35
39
  * - `simple` — evaluates the condition directly, resolving the runtime
@@ -49,12 +53,13 @@ import XPathCriterionEvaluator from "./XPathCriterionEvaluator.mjs";
49
53
  * the 1.1.0 rule to every document, which accepts more but never evaluates a
50
54
  * query under the wrong dialect.
51
55
  *
52
- * A condition that cannot be evaluated fails the criterion (`false`), as
56
+ * A condition that cannot be evaluated fails the criterion (`met: false`), as
53
57
  * Arazzo 1.1.0 requires: a syntax error in the condition, a context the
54
58
  * condition cannot apply to (non-XML data for `xpath`), or an XPath evaluation
55
59
  * error. The type-specific evaluators report these as a
56
- * {@link CriterionEvaluationError}, which this facade absorbs; any other error
57
- * they raise propagates. A criterion that is malformed as written still throws: a
60
+ * {@link CriterionEvaluationError}, which this facade returns as the
61
+ * outcome's `error` so the caller can report it; any other error they raise
62
+ * propagates. A criterion that is malformed as written still throws: a
58
63
  * missing `condition`, an unknown or unreadable `type`, an unsupported
59
64
  * expression `version`, or a `context` that is missing or not a runtime
60
65
  * expression.
@@ -78,7 +83,8 @@ class CriterionEvaluator {
78
83
  }
79
84
 
80
85
  /**
81
- * Evaluates a criterion element, returning whether the condition is met.
86
+ * Evaluates a criterion element, returning whether the condition is met and,
87
+ * when it cannot be evaluated, why.
82
88
  *
83
89
  * `resolve` turns a runtime expression into a value: for `simple` it resolves
84
90
  * the expressions embedded in the condition; for the other types it resolves
@@ -100,23 +106,28 @@ class CriterionEvaluator {
100
106
  }
101
107
  const type = this.#resolveType(criterion);
102
108
  this.#requireValidContext(criterion, type);
109
+ let met;
103
110
  try {
104
111
  switch (type) {
105
112
  case 'simple':
106
- return this.#simple.evaluate(condition, resolve);
113
+ met = this.#simple.evaluate(condition, resolve);
114
+ break;
107
115
  case 'regex':
108
- return this.#regex.evaluate(condition, this.#resolveContext(criterion, type, resolve));
116
+ met = this.#regex.evaluate(condition, this.#resolveContext(criterion, type, resolve));
117
+ break;
109
118
  case 'jsonpath':
110
119
  // only rfc9535 is supported, Arazzo 1.1.0's default, so an omitted
111
120
  // version is accepted. draft-goessner has different semantics and is
112
121
  // rejected rather than mis-evaluated.
113
122
  this.#requireVersion(criterion, type, condition, 'rfc9535');
114
- return this.#jsonpath.evaluate(condition, this.#resolveContext(criterion, type, resolve));
123
+ met = this.#jsonpath.evaluate(condition, this.#resolveContext(criterion, type, resolve));
124
+ break;
115
125
  case 'xpath':
116
126
  // only XPath 3.1 is supported; xpath-31 is Arazzo 1.1.0's default, so
117
127
  // an omitted version is accepted. Older versions are rejected.
118
128
  this.#requireVersion(criterion, type, condition, 'xpath-31');
119
- return this.#xpath.evaluate(condition, this.#resolveContext(criterion, type, resolve));
129
+ met = this.#xpath.evaluate(condition, this.#resolveContext(criterion, type, resolve));
130
+ break;
120
131
  default:
121
132
  throw new CriterionError(`Unknown criterion type "${type}"`, {
122
133
  condition,
@@ -127,9 +138,15 @@ class CriterionEvaluator {
127
138
  } catch (error) {
128
139
  // Arazzo 1.1.0: a condition that cannot be evaluated MUST evaluate to fail.
129
140
  // The structural checks throw a plain CriterionError and still propagate.
130
- if (error instanceof CriterionEvaluationError) return false;
141
+ if (error instanceof CriterionEvaluationError) return {
142
+ met: false,
143
+ error
144
+ };
131
145
  throw error;
132
146
  }
147
+ return {
148
+ met
149
+ };
133
150
  }
134
151
 
135
152
  /**
@@ -34,7 +34,10 @@ class JSONPathCriterionEvaluator {
34
34
  return (0, _jsonpath.evaluate)(context, condition).length > 0;
35
35
  } catch (error) {
36
36
  if (error instanceof _jsonpath.JSONPathError) {
37
- throw new _CriterionEvaluationError.default(`Invalid jsonpath criterion condition "${condition}"`, {
37
+ // a parse error describes the query only; an evaluation error ran
38
+ // against the context, so its message is not surfaced.
39
+ const detail = error instanceof _jsonpath.JSONPathParseError ? `: ${error.message}` : '';
40
+ throw new _CriterionEvaluationError.default(`Invalid jsonpath criterion condition "${condition}"${detail}`, {
38
41
  cause: error,
39
42
  condition,
40
43
  type: 'jsonpath',
@@ -1,4 +1,4 @@
1
- import { evaluate as evaluateJSONPath, JSONPathError } from '@swaggerexpert/jsonpath';
1
+ import { evaluate as evaluateJSONPath, JSONPathError, JSONPathParseError } from '@swaggerexpert/jsonpath';
2
2
  import CriterionEvaluationError from "../errors/CriterionEvaluationError.mjs";
3
3
  import CriterionErrorReason from "./CriterionErrorReason.mjs";
4
4
  /**
@@ -27,7 +27,10 @@ class JSONPathCriterionEvaluator {
27
27
  return evaluateJSONPath(context, condition).length > 0;
28
28
  } catch (error) {
29
29
  if (error instanceof JSONPathError) {
30
- throw new CriterionEvaluationError(`Invalid jsonpath criterion condition "${condition}"`, {
30
+ // a parse error describes the query only; an evaluation error ran
31
+ // against the context, so its message is not surfaced.
32
+ const detail = error instanceof JSONPathParseError ? `: ${error.message}` : '';
33
+ throw new CriterionEvaluationError(`Invalid jsonpath criterion condition "${condition}"${detail}`, {
31
34
  cause: error,
32
35
  condition,
33
36
  type: 'jsonpath',
@@ -43,7 +43,9 @@ class RegexCriterionEvaluator {
43
43
  try {
44
44
  regExp = new RegExp(condition);
45
45
  } catch (error) {
46
- throw new _CriterionEvaluationError.default(`Invalid regex criterion condition "${condition}"`, {
46
+ // the engine has seen only the pattern, never the context, so its
47
+ // message is safe to surface.
48
+ throw new _CriterionEvaluationError.default(`Invalid regex criterion condition "${condition}": ${error.message}`, {
47
49
  cause: error,
48
50
  condition,
49
51
  type: 'regex',
@@ -36,7 +36,9 @@ class RegexCriterionEvaluator {
36
36
  try {
37
37
  regExp = new RegExp(condition);
38
38
  } catch (error) {
39
- throw new CriterionEvaluationError(`Invalid regex criterion condition "${condition}"`, {
39
+ // the engine has seen only the pattern, never the context, so its
40
+ // message is safe to surface.
41
+ throw new CriterionEvaluationError(`Invalid regex criterion condition "${condition}": ${error.message}`, {
40
42
  cause: error,
41
43
  condition,
42
44
  type: 'regex',
@@ -48,6 +48,9 @@ class XPathCriterionEvaluator {
48
48
  language: evaluateXPath.XPATH_3_1_LANGUAGE
49
49
  });
50
50
  } catch (error) {
51
+ // no engine detail: the query has run against the context, and it can
52
+ // put context values into its own error (a dynamic error, or fn:error
53
+ // with a code and description of the query's choosing).
51
54
  throw new _CriterionEvaluationError.default(`Invalid xpath criterion condition "${condition}"`, {
52
55
  cause: error,
53
56
  condition,
@@ -40,6 +40,9 @@ class XPathCriterionEvaluator {
40
40
  language: evaluateXPath.XPATH_3_1_LANGUAGE
41
41
  });
42
42
  } catch (error) {
43
+ // no engine detail: the query has run against the context, and it can
44
+ // put context values into its own error (a dynamic error, or fn:error
45
+ // with a code and description of the query's choosing).
43
46
  throw new CriterionEvaluationError(`Invalid xpath criterion condition "${condition}"`, {
44
47
  cause: error,
45
48
  condition,
@@ -52,11 +52,12 @@ var _SourceDescriptionsLocatorNormalizer = _interopRequireDefault(require("./Sou
52
52
  * The `type` declared on a source description is not trusted (it may be
53
53
  * absent or wrong); a source is classified by what it actually parses to,
54
54
  * the same convention the OpenAPI operation locator follows. Source
55
- * documents are acquired from the registry on demand. Whether the resolved
56
- * document *defines* the workflow is deliberately left to the caller, which
57
- * owns the timing of that check (eager for a `dependsOn` list validated
58
- * before any prerequisite runs, lazy for a sub-workflow step resolved per
59
- * attempt).
55
+ * documents are acquired from the registry on demand. A reference to a
56
+ * workflow the resolved document does not define is rejected here, so every
57
+ * returned locator names an existing workflow; a caller controls when that
58
+ * check happens by when it normalizes (eagerly for a `dependsOn` list
59
+ * validated before any prerequisite runs, lazily for a sub-workflow step
60
+ * resolved on its first attempt).
60
61
  * @public
61
62
  */
62
63
  class ArazzoWorkflowLocatorNormalizer extends _SourceDescriptionsLocatorNormalizer.default {
@@ -64,10 +65,20 @@ class ArazzoWorkflowLocatorNormalizer extends _SourceDescriptionsLocatorNormaliz
64
65
  * Resolves a reference — a plain workflowId or a
65
66
  * `$sourceDescriptions.{name}.{workflowId}` expression, as written — to the
66
67
  * canonical locator: the Arazzo document that owns the target workflow,
67
- * and the bare id within it.
68
+ * and the bare id within it. Rejects a workflow that document does not
69
+ * define as `workflow-not-found`, attributed to the referencing context
70
+ * like every other error raised here.
68
71
  */
69
72
  async normalize(reference, document, context) {
70
- return this.#resolve(this.#parseReference(reference, context), document, context);
73
+ const locator = await this.#resolve(this.#parseReference(reference, context), document, context);
74
+ if (!locator.document.workflowIndex.has(locator.workflowId)) {
75
+ throw new _ExecutionError.default(`workflow reference "${reference}" in workflow "${context.workflowId}" names workflow "${locator.workflowId}", which the Arazzo document at "${locator.document.uri}" does not define`, {
76
+ workflowId: context.workflowId,
77
+ stepId: context.stepId,
78
+ reason: 'workflow-not-found'
79
+ });
80
+ }
81
+ return locator;
71
82
  }
72
83
 
73
84
  /**
@@ -42,11 +42,12 @@ import SourceDescriptionsLocatorNormalizer from "./SourceDescriptionsLocatorNorm
42
42
  * The `type` declared on a source description is not trusted (it may be
43
43
  * absent or wrong); a source is classified by what it actually parses to,
44
44
  * the same convention the OpenAPI operation locator follows. Source
45
- * documents are acquired from the registry on demand. Whether the resolved
46
- * document *defines* the workflow is deliberately left to the caller, which
47
- * owns the timing of that check (eager for a `dependsOn` list validated
48
- * before any prerequisite runs, lazy for a sub-workflow step resolved per
49
- * attempt).
45
+ * documents are acquired from the registry on demand. A reference to a
46
+ * workflow the resolved document does not define is rejected here, so every
47
+ * returned locator names an existing workflow; a caller controls when that
48
+ * check happens by when it normalizes (eagerly for a `dependsOn` list
49
+ * validated before any prerequisite runs, lazily for a sub-workflow step
50
+ * resolved on its first attempt).
50
51
  * @public
51
52
  */
52
53
  class ArazzoWorkflowLocatorNormalizer extends SourceDescriptionsLocatorNormalizer {
@@ -54,10 +55,20 @@ class ArazzoWorkflowLocatorNormalizer extends SourceDescriptionsLocatorNormalize
54
55
  * Resolves a reference — a plain workflowId or a
55
56
  * `$sourceDescriptions.{name}.{workflowId}` expression, as written — to the
56
57
  * canonical locator: the Arazzo document that owns the target workflow,
57
- * and the bare id within it.
58
+ * and the bare id within it. Rejects a workflow that document does not
59
+ * define as `workflow-not-found`, attributed to the referencing context
60
+ * like every other error raised here.
58
61
  */
59
62
  async normalize(reference, document, context) {
60
- return this.#resolve(this.#parseReference(reference, context), document, context);
63
+ const locator = await this.#resolve(this.#parseReference(reference, context), document, context);
64
+ if (!locator.document.workflowIndex.has(locator.workflowId)) {
65
+ throw new ExecutionError(`workflow reference "${reference}" in workflow "${context.workflowId}" names workflow "${locator.workflowId}", which the Arazzo document at "${locator.document.uri}" does not define`, {
66
+ workflowId: context.workflowId,
67
+ stepId: context.stepId,
68
+ reason: 'workflow-not-found'
69
+ });
70
+ }
71
+ return locator;
61
72
  }
62
73
 
63
74
  /**
@@ -44,11 +44,27 @@ const STEP_TARGET_FIELDS = exports.STEP_TARGET_FIELDS = ['operationId', 'operati
44
44
  * @public
45
45
  */
46
46
 
47
+ /**
48
+ * The outcome of one criterion a step evaluated: one of its `successCriteria`,
49
+ * or one of the `criteria` of an action it considered.
50
+ * @public
51
+ */
52
+
47
53
  /**
48
54
  * The outcome of executing a single step.
49
55
  * @public
50
56
  */
51
57
 
58
+ /**
59
+ * What one attempt at a step produced.
60
+ *
61
+ * Both kinds of step reduce to this — an operation step (whose richer
62
+ * {@link StepExecutionResult} satisfies this shape) and a sub-workflow step
63
+ * (synthesized from the sub-run's outcome) — so the retry loop drives them
64
+ * identically and knows nothing about either.
65
+ * @internal
66
+ */
67
+
52
68
  /**
53
69
  * Executes a single Arazzo step that invokes an OpenAPI operation.
54
70
  *
@@ -190,16 +206,22 @@ class StepExecutor {
190
206
  // evaluate criteria, outputs, and the next action against the post-request
191
207
  // context (with $request / $url / $method and $response / $statusCode).
192
208
  const postContext = state.toContext(this.#requestContext(response), this.#responseContext(response));
193
- const successful = this.evaluateCriteria(step, postContext);
209
+ const successCriteria = this.evaluateCriteria(step, postContext);
210
+ const successful = successCriteria.every(criterion => criterion.met);
194
211
  const outputs = this.#outputResolver.resolve(step.outputs, expression => this.#evaluate(postContext, expression), scope);
195
- const matchedActions = this.selectActions(step, successful, postContext);
212
+ const {
213
+ matchedActions,
214
+ actionCriteria
215
+ } = this.selectActions(step, successful, postContext);
196
216
  return {
197
217
  stepId,
198
218
  response,
199
219
  successful,
200
220
  outputs,
201
221
  action: matchedActions[0],
202
- matchedActions
222
+ matchedActions,
223
+ successCriteria,
224
+ actionCriteria
203
225
  };
204
226
  }
205
227
 
@@ -272,9 +294,12 @@ class StepExecutor {
272
294
  }
273
295
 
274
296
  /**
275
- * Evaluates the step's `successCriteria`; all criteria must pass. A step with
276
- * no criteria succeeds on a received response. An unreadable
277
- * `successCriteria` throws, see {@link StepExecutor.assertSuccessCriteria}.
297
+ * Evaluates the step's `successCriteria`, returning one record per criterion
298
+ * in list order; the step succeeds when every record is met. Every criterion
299
+ * is evaluated, so the records explain each unmet one, not only the first. A
300
+ * step with no criteria returns no records and succeeds on a received
301
+ * response. An unreadable `successCriteria` throws, see
302
+ * {@link StepExecutor.assertSuccessCriteria}.
278
303
  *
279
304
  * Public for the same reason as {@link StepExecutor.selectActions}: a step
280
305
  * targeting a `workflowId` is run by the workflow executor and never reaches
@@ -284,15 +309,16 @@ class StepExecutor {
284
309
  evaluateCriteria(step, context) {
285
310
  this.assertSuccessCriteria(step);
286
311
  const criteria = step.successCriteria;
287
- if (criteria === undefined) return true;
312
+ if (criteria === undefined) return [];
288
313
 
289
314
  // assertSuccessCriteria above guarantees every entry is a Criterion Object
290
- return [...criteria].every(criterion => this.#criterionEvaluator.evaluate(criterion, expression => this.#evaluate(context, expression)));
315
+ return [...criteria].map(criterion => this.#evaluateCriterion(criterion, context));
291
316
  }
292
317
 
293
318
  /**
294
319
  * Selects every matching `onSuccess` / `onFailure` action for the outcome, in
295
- * list order, gating each action's criteria against the context.
320
+ * list order, gating each action's criteria against the context, and records
321
+ * the outcome of every action criterion evaluated.
296
322
  *
297
323
  * The step's own list is the whole story here: a step that declares none of
298
324
  * its own already carries the workflow-level defaults, inherited by
@@ -311,25 +337,65 @@ class StepExecutor {
311
337
  * the expression context through this one method rather than wiring their own.
312
338
  */
313
339
  selectActions(step, successful, context) {
314
- const isCriterionMet = criterion => {
315
- try {
316
- return this.#criterionEvaluator.evaluate(criterion, expression => this.#evaluate(context, expression));
317
- } catch (error) {
318
- // an action's criteria decide whether it applies to this outcome, and
319
- // actions are shared — inherited from the workflow, reused from
320
- // components. A condition on an HTTP exchange cannot apply to a step
321
- // that calls a workflow, so the action does not match; the run does not
322
- // abort over a default written for the operation steps.
323
- if (error instanceof _RuntimeExpressionError.default && error.reason === 'unavailable-expression') {
324
- return false;
325
- }
326
- throw error;
327
- }
340
+ const actionCriteria = [];
341
+ const isCriterionMet = (criterion, action) => {
342
+ const record = {
343
+ ...this.#evaluateCriterion(criterion, context),
344
+ actionName: (0, _apidomCore.toValue)(action.name)
345
+ };
346
+ actionCriteria.push(record);
347
+ return record.met;
328
348
  };
329
349
  const scope = {
330
350
  stepId: (0, _apidomCore.toValue)(step.stepId)
331
351
  };
332
- return successful ? this.#actionResolver.resolveOnSuccess(step.onSuccess, isCriterionMet, scope) : this.#actionResolver.resolveOnFailure(step.onFailure, isCriterionMet, scope);
352
+ const matchedActions = successful ? this.#actionResolver.resolveOnSuccess(step.onSuccess, isCriterionMet, scope) : this.#actionResolver.resolveOnFailure(step.onFailure, isCriterionMet, scope);
353
+ return {
354
+ matchedActions,
355
+ actionCriteria
356
+ };
357
+ }
358
+
359
+ /**
360
+ * Evaluates one criterion into a {@link CriterionEvaluationRecord}. A
361
+ * condition that cannot be evaluated is unmet and records why; any other
362
+ * error propagates.
363
+ */
364
+ #evaluateCriterion(criterion, context) {
365
+ const condition = (0, _apidomCore.toValue)(criterion.condition);
366
+ try {
367
+ const {
368
+ met,
369
+ error
370
+ } = this.#criterionEvaluator.evaluate(criterion, expression => this.#evaluate(context, expression));
371
+ if (error === undefined) return {
372
+ condition,
373
+ met
374
+ };
375
+ return {
376
+ condition,
377
+ met,
378
+ diagnostic: {
379
+ code: error.reason,
380
+ message: error.message
381
+ }
382
+ };
383
+ } catch (error) {
384
+ // a step that calls a workflow made no HTTP call, so a condition on one
385
+ // fails like any unevaluable condition; the diagnostic keeps the
386
+ // authoring mistake visible.
387
+ if (error instanceof _RuntimeExpressionError.default && error.reason === 'unavailable-expression') {
388
+ return {
389
+ condition,
390
+ met: false,
391
+ diagnostic: {
392
+ code: error.reason,
393
+ message: error.message
394
+ }
395
+ };
396
+ }
397
+ throw error;
398
+ }
333
399
  }
334
400
 
335
401
  /**