@usearazzo/runner 0.0.0-stage → 1.0.1-alpha.11

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 (154) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +20 -0
  4. package/README.md +163 -2
  5. package/dist/arazzo-runner.browser.js +113245 -0
  6. package/dist/arazzo-runner.browser.min.js +1 -0
  7. package/package.json +95 -4
  8. package/src/ArazzoRunner.cjs +122 -0
  9. package/src/ArazzoRunner.mjs +113 -0
  10. package/src/action/ActionResolver.cjs +160 -0
  11. package/src/action/ActionResolver.mjs +150 -0
  12. package/src/assembler/OpenAPI2DocumentAssembler.cjs +111 -0
  13. package/src/assembler/OpenAPI2DocumentAssembler.mjs +104 -0
  14. package/src/assembler/OpenAPI30DocumentAssembler.cjs +110 -0
  15. package/src/assembler/OpenAPI30DocumentAssembler.mjs +103 -0
  16. package/src/assembler/OpenAPI31DocumentAssembler.cjs +111 -0
  17. package/src/assembler/OpenAPI31DocumentAssembler.mjs +104 -0
  18. package/src/assembler/OpenAPIDocumentAssembler.cjs +58 -0
  19. package/src/assembler/OpenAPIDocumentAssembler.mjs +50 -0
  20. package/src/client/HTTPClient.cjs +5 -0
  21. package/src/client/HTTPClient.mjs +1 -0
  22. package/src/client/HTTPClientFetch.cjs +16 -0
  23. package/src/client/HTTPClientFetch.mjs +10 -0
  24. package/src/client/OpenAPIOperationRequest.cjs +5 -0
  25. package/src/client/OpenAPIOperationRequest.mjs +1 -0
  26. package/src/client/OpenAPIOperationResponse.cjs +42 -0
  27. package/src/client/OpenAPIOperationResponse.mjs +36 -0
  28. package/src/client/OpenAPIOperationResponseNormalizer.cjs +48 -0
  29. package/src/client/OpenAPIOperationResponseNormalizer.mjs +40 -0
  30. package/src/client/ParameterDelivery.cjs +119 -0
  31. package/src/client/ParameterDelivery.mjs +113 -0
  32. package/src/constants.cjs +9 -0
  33. package/src/constants.mjs +3 -0
  34. package/src/criterion/CriterionEvaluator.cjs +205 -0
  35. package/src/criterion/CriterionEvaluator.mjs +196 -0
  36. package/src/criterion/JSONPathCriterionEvaluator.cjs +47 -0
  37. package/src/criterion/JSONPathCriterionEvaluator.mjs +40 -0
  38. package/src/criterion/RegexCriterionEvaluator.cjs +67 -0
  39. package/src/criterion/RegexCriterionEvaluator.mjs +60 -0
  40. package/src/criterion/SimpleCriterionEvaluator.cjs +50 -0
  41. package/src/criterion/SimpleCriterionEvaluator.mjs +43 -0
  42. package/src/criterion/XPathCriterionEvaluator.cjs +117 -0
  43. package/src/criterion/XPathCriterionEvaluator.mjs +109 -0
  44. package/src/document/APIDocument.cjs +35 -0
  45. package/src/document/APIDocument.mjs +30 -0
  46. package/src/document/ArazzoDocument.cjs +81 -0
  47. package/src/document/ArazzoDocument.mjs +74 -0
  48. package/src/document/ArazzoSourceDescriptionIndex.cjs +26 -0
  49. package/src/document/ArazzoSourceDescriptionIndex.mjs +20 -0
  50. package/src/document/ArazzoStepIndex.cjs +25 -0
  51. package/src/document/ArazzoStepIndex.mjs +19 -0
  52. package/src/document/ArazzoWorkflowIndex.cjs +17 -0
  53. package/src/document/ArazzoWorkflowIndex.mjs +11 -0
  54. package/src/document/OpenAPIDocument.cjs +28 -0
  55. package/src/document/OpenAPIDocument.mjs +21 -0
  56. package/src/document/OpenAPIOperationIndex.cjs +24 -0
  57. package/src/document/OpenAPIOperationIndex.mjs +18 -0
  58. package/src/document/ParameterIdentity.cjs +93 -0
  59. package/src/document/ParameterIdentity.mjs +88 -0
  60. package/src/document/openapi-types.cjs +5 -0
  61. package/src/document/openapi-types.mjs +1 -0
  62. package/src/errors/ArazzoRunnerError.cjs +10 -0
  63. package/src/errors/ArazzoRunnerError.mjs +5 -0
  64. package/src/errors/AssemblerError.cjs +11 -0
  65. package/src/errors/AssemblerError.mjs +4 -0
  66. package/src/errors/ClientError.cjs +11 -0
  67. package/src/errors/ClientError.mjs +4 -0
  68. package/src/errors/CriterionError.cjs +11 -0
  69. package/src/errors/CriterionError.mjs +4 -0
  70. package/src/errors/ExecutionError.cjs +11 -0
  71. package/src/errors/ExecutionError.mjs +4 -0
  72. package/src/errors/ExtractionError.cjs +11 -0
  73. package/src/errors/ExtractionError.mjs +4 -0
  74. package/src/errors/InvalidEntryDocumentError.cjs +11 -0
  75. package/src/errors/InvalidEntryDocumentError.mjs +4 -0
  76. package/src/errors/NormalizationError.cjs +11 -0
  77. package/src/errors/NormalizationError.mjs +4 -0
  78. package/src/errors/ResolverError.cjs +11 -0
  79. package/src/errors/ResolverError.mjs +4 -0
  80. package/src/errors/RuntimeExpressionError.cjs +11 -0
  81. package/src/errors/RuntimeExpressionError.mjs +4 -0
  82. package/src/errors/UnmatchedProviderError.cjs +11 -0
  83. package/src/errors/UnmatchedProviderError.mjs +4 -0
  84. package/src/executor/ArazzoWorkflowLocatorNormalizer.cjs +147 -0
  85. package/src/executor/ArazzoWorkflowLocatorNormalizer.mjs +137 -0
  86. package/src/executor/OpenAPIOperationExecutor.cjs +458 -0
  87. package/src/executor/OpenAPIOperationExecutor.mjs +447 -0
  88. package/src/executor/OpenAPIOperationLocatorNormalizer.cjs +197 -0
  89. package/src/executor/OpenAPIOperationLocatorNormalizer.mjs +189 -0
  90. package/src/executor/SourceDescriptionsLocatorNormalizer.cjs +44 -0
  91. package/src/executor/SourceDescriptionsLocatorNormalizer.mjs +38 -0
  92. package/src/executor/StepExecutor.cjs +333 -0
  93. package/src/executor/StepExecutor.mjs +326 -0
  94. package/src/executor/StepRetryRunner.cjs +226 -0
  95. package/src/executor/StepRetryRunner.mjs +215 -0
  96. package/src/executor/StepTransitionInterpreter.cjs +159 -0
  97. package/src/executor/StepTransitionInterpreter.mjs +151 -0
  98. package/src/executor/WorkflowCallStack.cjs +116 -0
  99. package/src/executor/WorkflowCallStack.mjs +106 -0
  100. package/src/executor/WorkflowExecutor.cjs +1091 -0
  101. package/src/executor/WorkflowExecutor.mjs +1077 -0
  102. package/src/executor/abort.cjs +70 -0
  103. package/src/executor/abort.mjs +60 -0
  104. package/src/expression/RuntimeExpressionContext.cjs +5 -0
  105. package/src/expression/RuntimeExpressionContext.mjs +1 -0
  106. package/src/expression/RuntimeExpressionEvaluator.cjs +337 -0
  107. package/src/expression/RuntimeExpressionEvaluator.mjs +328 -0
  108. package/src/extractor/ArazzoStepExtractor.cjs +48 -0
  109. package/src/extractor/ArazzoStepExtractor.mjs +41 -0
  110. package/src/extractor/ArazzoWorkflowExtractor.cjs +50 -0
  111. package/src/extractor/ArazzoWorkflowExtractor.mjs +43 -0
  112. package/src/extractor/OpenAPIOperationExtractor.cjs +84 -0
  113. package/src/extractor/OpenAPIOperationExtractor.mjs +77 -0
  114. package/src/index.cjs +426 -0
  115. package/src/index.mjs +75 -0
  116. package/src/normalizer/ArazzoStepNormalizer.cjs +23 -0
  117. package/src/normalizer/ArazzoStepNormalizer.mjs +17 -0
  118. package/src/normalizer/ArazzoWorkflowNormalizer.cjs +214 -0
  119. package/src/normalizer/ArazzoWorkflowNormalizer.mjs +206 -0
  120. package/src/normalizer/OpenAPI2OperationNormalizer.cjs +128 -0
  121. package/src/normalizer/OpenAPI2OperationNormalizer.mjs +120 -0
  122. package/src/normalizer/OpenAPI30OperationNormalizer.cjs +129 -0
  123. package/src/normalizer/OpenAPI30OperationNormalizer.mjs +121 -0
  124. package/src/normalizer/OpenAPI31OperationNormalizer.cjs +131 -0
  125. package/src/normalizer/OpenAPI31OperationNormalizer.mjs +123 -0
  126. package/src/normalizer/OpenAPIOperationNormalizer.cjs +58 -0
  127. package/src/normalizer/OpenAPIOperationNormalizer.mjs +50 -0
  128. package/src/registry/DocumentRegistry.cjs +160 -0
  129. package/src/registry/DocumentRegistry.mjs +151 -0
  130. package/src/registry/providers/ArazzoDocumentRegistryProvider.cjs +154 -0
  131. package/src/registry/providers/ArazzoDocumentRegistryProvider.mjs +146 -0
  132. package/src/registry/providers/DocumentRegistryProvider.cjs +31 -0
  133. package/src/registry/providers/DocumentRegistryProvider.mjs +25 -0
  134. package/src/registry/providers/OpenAPIDocumentRegistryProvider.cjs +122 -0
  135. package/src/registry/providers/OpenAPIDocumentRegistryProvider.mjs +114 -0
  136. package/src/resolver/ArazzoValueResolver.cjs +42 -0
  137. package/src/resolver/ArazzoValueResolver.mjs +37 -0
  138. package/src/resolver/OpenAPIOperationParameterResolver.cjs +117 -0
  139. package/src/resolver/OpenAPIOperationParameterResolver.mjs +110 -0
  140. package/src/resolver/OutputResolver.cjs +71 -0
  141. package/src/resolver/OutputResolver.mjs +64 -0
  142. package/src/resolver/RequestBodyResolver.cjs +227 -0
  143. package/src/resolver/RequestBodyResolver.mjs +220 -0
  144. package/src/resolver/ResolverScope.cjs +5 -0
  145. package/src/resolver/ResolverScope.mjs +1 -0
  146. package/src/resolver/StepParameterResolver.cjs +22 -0
  147. package/src/resolver/StepParameterResolver.mjs +15 -0
  148. package/src/resolver/WorkflowParameterResolver.cjs +73 -0
  149. package/src/resolver/WorkflowParameterResolver.mjs +66 -0
  150. package/src/state/WorkflowExecutionState.cjs +99 -0
  151. package/src/state/WorkflowExecutionState.mjs +93 -0
  152. package/src/vendor/swagger-client.cjs +1 -0
  153. package/src/vendor/swagger-client.mjs +1 -0
  154. package/types/runner.d.ts +2410 -0
@@ -0,0 +1,1091 @@
1
+ "use strict";
2
+
3
+ var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
4
+ var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.default = void 0;
9
+ exports.settledResult = settledResult;
10
+ var _apidomCore = require("@speclynx/apidom-core");
11
+ var _apidomDatamodel = require("@speclynx/apidom-datamodel");
12
+ var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
13
+ var _RuntimeExpressionEvaluator = _interopRequireDefault(require("../expression/RuntimeExpressionEvaluator.cjs"));
14
+ var _ArazzoWorkflowExtractor = _interopRequireDefault(require("../extractor/ArazzoWorkflowExtractor.cjs"));
15
+ var _ArazzoWorkflowNormalizer = _interopRequireDefault(require("../normalizer/ArazzoWorkflowNormalizer.cjs"));
16
+ var _OutputResolver = _interopRequireDefault(require("../resolver/OutputResolver.cjs"));
17
+ var _WorkflowParameterResolver = _interopRequireDefault(require("../resolver/WorkflowParameterResolver.cjs"));
18
+ var _WorkflowExecutionState = _interopRequireDefault(require("../state/WorkflowExecutionState.cjs"));
19
+ var _StepExecutor = require("./StepExecutor.cjs");
20
+ var _ArazzoWorkflowLocatorNormalizer = _interopRequireDefault(require("./ArazzoWorkflowLocatorNormalizer.cjs"));
21
+ var _StepRetryRunner = _interopRequireDefault(require("./StepRetryRunner.cjs"));
22
+ var _WorkflowCallStack = _interopRequireDefault(require("./WorkflowCallStack.cjs"));
23
+ var _StepTransitionInterpreter = _interopRequireWildcard(require("./StepTransitionInterpreter.cjs"));
24
+ var _ExecutionError = _interopRequireDefault(require("../errors/ExecutionError.cjs"));
25
+ var _ResolverError = _interopRequireDefault(require("../errors/ResolverError.cjs"));
26
+ var _abort = require("./abort.cjs");
27
+ /**
28
+ * Options for the WorkflowExecutor.
29
+ * @public
30
+ */
31
+
32
+ /**
33
+ * Per-call options for {@link WorkflowExecutor.execute}.
34
+ * @public
35
+ */
36
+
37
+ /**
38
+ * One run of a `retry` action's `stepId` / `workflowId` reference — "the
39
+ * reference is executed and the context is returned, after which the current
40
+ * step is retried". Fires once per firing of its action (i.e. once before every
41
+ * attempt the action grants, not once for the whole retry chain).
42
+ * @public
43
+ */
44
+
45
+ /**
46
+ * The trace of a single step execution within a workflow run.
47
+ * @public
48
+ */
49
+
50
+ /**
51
+ * The outcome of executing a workflow.
52
+ *
53
+ * `status` is `completed` when the steps ran to the end of the list, `ended`
54
+ * when an `end` action stopped the run early, `failed` when a step failed and no
55
+ * matching `onFailure` action redirected it (the break-and-return default) — or
56
+ * when a `dependsOn` workflow did not complete — and `transferred` when a `goto`
57
+ * action handed control one-way to another workflow: this run's own steps stop
58
+ * at that point, and its `outputs` declaration is never evaluated, since the run
59
+ * never reaches its own end. See {@link WorkflowExecutionResult.settledStatus}
60
+ * for the success/failure verdict a `'transferred'` result defers to.
61
+ * @public
62
+ */
63
+
64
+ /**
65
+ * The result a transfer chain settled on: `result` itself when its `status`
66
+ * isn't `'transferred'`, otherwise the terminal, non-`'transferred'` result at
67
+ * the end of its `transferredTo` chain.
68
+ *
69
+ * For the success/failure verdict alone, prefer
70
+ * {@link WorkflowExecutionResult.settledStatus} — every result already
71
+ * carries it, computed eagerly, so no call is needed. Reach for this function
72
+ * when the terminal result *itself* is wanted (its `outputs`, `steps`,
73
+ * `workflowId`), not just where it landed. Iterative, not recursive: a
74
+ * chain's length is bounded only by `maxWorkflowDepth`, and this must not add
75
+ * a matching bound on call-stack depth.
76
+ * @public
77
+ */
78
+ function settledResult(result) {
79
+ let current = result;
80
+ while (current.transferredTo !== undefined) {
81
+ current = current.transferredTo;
82
+ }
83
+ return current;
84
+ }
85
+
86
+ /**
87
+ * How a `#run` invocation ended, handed to `#result` to assemble the final
88
+ * {@link WorkflowExecutionResult} from.
89
+ *
90
+ * A discriminated union rather than a `status` string plus a separately-set
91
+ * `transferredTo` — the shape this replaced, where nothing but convention
92
+ * kept the two in sync. Here, `status: 'transferred'` with no
93
+ * `transferredTo` (or the reverse) is not a state a caller of `#result` can
94
+ * construct, not merely one they are expected not to.
95
+ * @internal
96
+ */
97
+
98
+ /**
99
+ * One completed `dependsOn` run, remembered with the inputs it was actually
100
+ * given — see {@link RunScope.dependencyRuns}.
101
+ */
102
+
103
+ /**
104
+ * The state shared by every workflow invocation of a single
105
+ * {@link WorkflowExecutor.execute} call — the caller's per-run options plus the
106
+ * two mutable ledgers that must span the whole call tree.
107
+ */
108
+
109
+ /**
110
+ * One workflow invocation in progress: the values fixed for a single `#run` that
111
+ * travel together into everything it delegates to.
112
+ */
113
+
114
+ /**
115
+ * Executes an Arazzo workflow: the stateful loop that turns "run one step" into
116
+ * "run a workflow".
117
+ *
118
+ * It iterates a workflow's steps in list order, delegating each to
119
+ * {@link StepExecutor}, records the resolved step outputs into a
120
+ * {@link WorkflowExecutionState} so later steps read `$steps.{id}.outputs.{name}`,
121
+ * and interprets the {@link SelectedAction} the step executor selects but does
122
+ * not act on — advancing to the next step, jumping via `goto`, transferring
123
+ * one-way to another workflow via a `goto` naming a `workflowId`, or stopping on
124
+ * `end` / the failure break-default. After the loop it resolves the workflow's
125
+ * `outputs` against the final state — unless the run transferred, in which case
126
+ * it never reaches its own end and `outputs` stays `{}`.
127
+ *
128
+ * A step targeting a `workflowId` is a sub-workflow call the executor runs
129
+ * itself, recursively; the workflows named by `dependsOn` are run to completion
130
+ * before the workflow's own steps. Both recurse through one call tree, guarded
131
+ * by a shared step budget, a nesting-depth ceiling, and cycle detection. A
132
+ * `goto`'s transfer joins the same call tree, entered via `'goto'`, but is not
133
+ * charged against the step budget: it fires once per step evaluation, unlike a
134
+ * retry reference, which can fire repeatedly from the same frame.
135
+ *
136
+ * State is created fresh per {@link WorkflowExecutor.execute} call — and per
137
+ * workflow invocation within it — and owned here; the returned result is
138
+ * read-only. Authoring errors (missing workflow, unknown `goto` target, a cycle,
139
+ * budget overflow) throw {@link ExecutionError}, as does a run the caller
140
+ * cancels through {@link WorkflowExecuteOptions.signal}; a step that
141
+ * legitimately fails and breaks, or a dependency that fails, is a normal
142
+ * `status: 'failed'` result, not a throw — the same split {@link StepExecutor}
143
+ * draws.
144
+ *
145
+ * A workflow reference — a sub-workflow step's `workflowId`, a `dependsOn`
146
+ * entry, a `retry` action's reference, a `goto`'s transfer — may also name a
147
+ * workflow of another Arazzo document, as
148
+ * `$sourceDescriptions.{name}.{workflowId}`. The run then follows that
149
+ * document for the foreign workflow: its id lookups, its steps' operations,
150
+ * and its expressions all resolve against the document that owns it, while
151
+ * the `$workflows.{workflowId}` run state stays keyed by the bare id — the
152
+ * only form the runtime-expression grammar can express.
153
+ * @public
154
+ */
155
+ class WorkflowExecutor {
156
+ static #DEFAULT_MAX_STEPS = 1000;
157
+ static #DEFAULT_MAX_WORKFLOW_DEPTH = 32;
158
+ static #DEFAULT_NOW = () => performance.now();
159
+ static #EXECUTE_OPTION_KEYS = new Set(['inputs', 'executeOptions', 'dependencyInputs', 'runDependencies', 'signal']);
160
+ #document;
161
+ #registry;
162
+ #maxSteps;
163
+ #maxWorkflowDepth;
164
+ #now;
165
+ #extractor = new _ArazzoWorkflowExtractor.default();
166
+ #normalizer = new _ArazzoWorkflowNormalizer.default();
167
+ #outputResolver = new _OutputResolver.default();
168
+ #parameterResolver = new _WorkflowParameterResolver.default();
169
+ #stepExecutor;
170
+ #retryRunner;
171
+ #interpreter = new _StepTransitionInterpreter.default();
172
+ #workflowLocatorNormalizer;
173
+ constructor(options) {
174
+ this.#document = options.document;
175
+ this.#registry = options.registry;
176
+ this.#workflowLocatorNormalizer = new _ArazzoWorkflowLocatorNormalizer.default(options.registry);
177
+ this.#maxSteps = options.maxSteps ?? WorkflowExecutor.#DEFAULT_MAX_STEPS;
178
+ this.#maxWorkflowDepth = options.maxWorkflowDepth ?? WorkflowExecutor.#DEFAULT_MAX_WORKFLOW_DEPTH;
179
+ this.#now = options.now ?? WorkflowExecutor.#DEFAULT_NOW;
180
+ this.#stepExecutor = options.stepExecutor;
181
+ this.#retryRunner = new _StepRetryRunner.default({
182
+ sleep: options.sleep
183
+ });
184
+ }
185
+
186
+ /**
187
+ * Runs the named workflow to completion, returning its outcome.
188
+ *
189
+ * The workflows the target names in `dependsOn` are run first (see
190
+ * {@link WorkflowExecuteOptions.runDependencies}), then its own steps.
191
+ *
192
+ * All run-scoped state — the execution state, the step trace, the control-flow
193
+ * position, the call stack, the step budget, and the completed-dependency
194
+ * ledger — is local to this call, so concurrent `execute` calls on one executor
195
+ * do not interfere.
196
+ */
197
+ async execute(workflowId, options = {}) {
198
+ this.#rejectUnknownOptions(options);
199
+
200
+ // a signal in the opaque bag was the only channel before this option existed,
201
+ // and it still reaches the transport. Absorbing it here is what makes it a
202
+ // whole cancellation rather than half of one: read only at dispatch, it would
203
+ // abort requests while the loop walked on through the remaining steps.
204
+ const signal = options.signal ?? (0, _abort.readAbortSignal)(options.executeOptions ?? {});
205
+ const scope = {
206
+ // the first-class signal is also spread into the opaque bag, because that
207
+ // bag is how anything reaches the transport: the executor's own checks
208
+ // stop the run at the next boundary, and this is what cancels the request
209
+ // already in flight. Spread last, so it wins over one passed in the bag.
210
+ executeOptions: {
211
+ ...options.executeOptions,
212
+ ...(signal === undefined ? {} : {
213
+ signal
214
+ })
215
+ },
216
+ dependencyInputs: options.dependencyInputs ?? {},
217
+ runDependencies: options.runDependencies ?? true,
218
+ signal,
219
+ budget: {
220
+ spent: 0
221
+ },
222
+ dependencyRuns: new Map(),
223
+ workflows: new Map()
224
+ };
225
+ return this.#run({
226
+ document: this.#document,
227
+ workflowId
228
+ }, options.inputs ?? {}, scope, new _WorkflowCallStack.default({
229
+ maxDepth: this.#maxWorkflowDepth,
230
+ entryDocumentURI: this.#document.uri
231
+ }), 'root');
232
+ }
233
+
234
+ /**
235
+ * Rejects an options bag carrying keys this method does not recognize.
236
+ *
237
+ * `execute` used to take `inputs` as its second positional argument, and that
238
+ * bag is now the options object — so the old call reaches here as an object of
239
+ * unrecognized keys. Left unchecked it would run with no inputs at all, and
240
+ * every `$inputs.*` would resolve to `undefined` under lenient evaluation:
241
+ * a completed run issuing wrong requests. A typo in an option name fails the
242
+ * same silent way. Both are worth a loud error.
243
+ */
244
+ #rejectUnknownOptions(options) {
245
+ const unknown = Object.keys(options).filter(key => !WorkflowExecutor.#EXECUTE_OPTION_KEYS.has(key));
246
+ if (unknown.length === 0) return;
247
+ throw new _ExecutionError.default(`execute received unknown option(s) ${unknown.join(', ')}; it takes (workflowId, { inputs, executeOptions, dependencyInputs, runDependencies, signal }) — workflow inputs go under "inputs"`, {
248
+ reason: 'unknown-execute-option'
249
+ });
250
+ }
251
+
252
+ /**
253
+ * Runs one workflow invocation — the recursive worker every entry point funnels
254
+ * through: the public {@link WorkflowExecutor.execute}, a sub-workflow step,
255
+ * and a `dependsOn` prerequisite alike, each entering the caller's call stack
256
+ * with its own edge.
257
+ */
258
+ async #run(locator, inputs, scope, callStack, via) {
259
+ const startedAt = this.#now();
260
+ const {
261
+ workflowId
262
+ } = locator;
263
+ // entering yields a new stack rather than mutating the caller's, so leaving is
264
+ // implicit — and a cycle or over-deep nesting throws here, before any of this
265
+ // workflow's own work begins. The entry is identified by the owning
266
+ // document's URI as well as the id, so a foreign document's reference
267
+ // back into the entry document closes a detectable cycle, and two
268
+ // documents' workflows sharing an id do not falsely form one.
269
+ const nested = callStack.enter(workflowId, via, locator.document.uri);
270
+ // the workflow boundary, checked in its own right and not only per step: a
271
+ // run cancelled before any step — or between a prerequisite and the workflow
272
+ // that needed it — must not go on to normalize and enter a workflow nobody
273
+ // is waiting for.
274
+ (0, _abort.throwIfAborted)(scope.signal, {
275
+ workflowId,
276
+ callStack: nested
277
+ });
278
+ const workflow = await this.#resolveWorkflow(locator, scope);
279
+ const state = new _WorkflowExecutionState.default({
280
+ inputs
281
+ });
282
+ const invocation = {
283
+ workflowId,
284
+ document: locator.document,
285
+ state,
286
+ callStack: nested,
287
+ via
288
+ };
289
+
290
+ // validated before any prerequisite runs: a malformed `steps` is an
291
+ // authoring error, and discovering it only after the dependencies have
292
+ // fired would mean live side effects on the way to a throw.
293
+ const steps = this.#orderedSteps(workflow, workflowId);
294
+ // same reasoning extends to `outputs`: a workflow that cannot possibly
295
+ // produce them is unrunnable from the start, and only a shape check is
296
+ // needed to know that — no run state is required for it. Checking now, not
297
+ // at `#result` where it used to be resolved, means the throw lands before
298
+ // the first request rather than after the last.
299
+ this.#validateWorkflowOutputsShape(workflow, workflowId);
300
+ const dependencies = await this.#runDependencies(workflow, invocation, scope);
301
+ if (dependencies.some(dependency => this.#settledFailed(dependency))) {
302
+ // a declared prerequisite did not complete, so this workflow cannot be
303
+ // processed. That is a runtime failure like any failing step — a `failed`
304
+ // result carrying the dependency trace, not a throw — and none of its own
305
+ // steps run. Unless the run was cancelled, in which case the prerequisite
306
+ // did not fail on its own terms either.
307
+ (0, _abort.throwIfAborted)(scope.signal, {
308
+ workflowId,
309
+ callStack: nested
310
+ });
311
+ return this.#result(invocation, workflow, [], {
312
+ kind: 'settled',
313
+ status: 'failed'
314
+ }, dependencies, startedAt);
315
+ }
316
+ const trace = [];
317
+ let index = 0;
318
+ // named distinctly from each step's own `outcome` (a StepAttemptOutcome,
319
+ // destructured fresh every iteration below) — the two are unrelated
320
+ // values that happen to share a natural name.
321
+ let runOutcome = {
322
+ kind: 'settled',
323
+ status: 'completed'
324
+ };
325
+ stepLoop: while (index < steps.length) {
326
+ const step = steps[index];
327
+ const stepId = (0, _apidomCore.toValue)(step.stepId);
328
+ const stepStartedAt = this.#now();
329
+ // the sub-workflow runs this step produces — one per attempt, so a retried
330
+ // sub-workflow step keeps every attempt's trace rather than only the last.
331
+ const subWorkflows = [];
332
+ // the retry reference runs this step's attempts produced, one per firing —
333
+ // separate from `subWorkflows` because a reference belongs to the retry
334
+ // that invoked it, not to the step's own target.
335
+ const retryReferences = [];
336
+ const attempt = this.#chargedAttempt(this.#stepAttempt(step, stepId, invocation, scope, subWorkflows), invocation, scope, stepId);
337
+
338
+ // the retry runner settles any `retry` actions, so `action` is the terminal
339
+ // action a retry chain resolved to and `attempts` is how many times the step
340
+ // ran.
341
+ const {
342
+ outcome,
343
+ action,
344
+ attempts
345
+ } = await this.#retryRunner.run(attempt, {
346
+ stepId,
347
+ workflowId,
348
+ signal: scope.signal,
349
+ runReference: this.#retryReference(steps, stepId, invocation, scope, retryReferences)
350
+ });
351
+ state.setStepOutputs(outcome.stepId, outcome.outputs);
352
+ trace.push({
353
+ stepId: outcome.stepId,
354
+ successful: outcome.successful,
355
+ action,
356
+ attempts,
357
+ durationMs: this.#now() - stepStartedAt,
358
+ ...(subWorkflows.length > 0 ? {
359
+ subWorkflows
360
+ } : {}),
361
+ ...(retryReferences.length > 0 ? {
362
+ retryReferences
363
+ } : {})
364
+ });
365
+ const transition = this.#interpreter.interpret(action, outcome.successful, {
366
+ workflowId,
367
+ stepId
368
+ });
369
+ switch (transition.kind) {
370
+ case 'next':
371
+ index += 1;
372
+ break;
373
+ case 'goto':
374
+ index = this.#interpreter.indexOfStep(steps, transition.stepId, workflowId, {
375
+ reason: 'goto-target-not-found',
376
+ label: 'goto target'
377
+ });
378
+ break;
379
+ case 'transfer':
380
+ {
381
+ // a one-way transfer: this run ends here, and the target's run is
382
+ // nested rather than spliced into this one. Checked for cancellation
383
+ // before running the target — same boundary convention as every
384
+ // other nested call — ahead of the reference helper's own
385
+ // resolution, so an aborted run reports `aborted` rather than
386
+ // whatever resolving the target (a malformed reference, a missing
387
+ // source) would have thrown.
388
+ (0, _abort.throwIfAborted)(scope.signal, {
389
+ workflowId,
390
+ stepId,
391
+ callStack: nested
392
+ });
393
+ const transferredTo = await this.#runReferencedWorkflow(transition.workflowId, stepId, invocation, scope, 'goto');
394
+ runOutcome = {
395
+ kind: 'transferred',
396
+ transferredTo
397
+ };
398
+ break stepLoop;
399
+ }
400
+ case 'end':
401
+ runOutcome = {
402
+ kind: 'settled',
403
+ status: 'ended'
404
+ };
405
+ break stepLoop;
406
+ case 'break':
407
+ runOutcome = {
408
+ kind: 'settled',
409
+ status: 'failed'
410
+ };
411
+ break stepLoop;
412
+ default:
413
+ {
414
+ // exhaustiveness guard: a Transition kind added above without a case
415
+ // here would otherwise fall through silently instead of failing
416
+ // loudly — `transition` is provably `never` once every kind above
417
+ // has its own case, which TypeScript checks at compile time.
418
+ const unreachable = transition;
419
+ throw new _ExecutionError.default(`step "${stepId}" in workflow "${workflowId}" produced an unrecognized transition ${JSON.stringify(unreachable)}`, {
420
+ workflowId,
421
+ stepId,
422
+ reason: 'unknown-transition-kind'
423
+ });
424
+ }
425
+ }
426
+ }
427
+
428
+ // the closing boundary. Nothing follows the last step, so without a check
429
+ // here a run abandoned while that step was in flight would resolve as though
430
+ // the caller had waited for it — and whether it did would depend on the
431
+ // transport, since one honoring the signal fails the request instead.
432
+ // Cancellation must not be the one outcome that reads differently for the
433
+ // last step than for every step before it.
434
+ (0, _abort.throwIfAborted)(scope.signal, {
435
+ workflowId,
436
+ callStack: nested
437
+ });
438
+ return this.#result(invocation, workflow, trace, runOutcome, dependencies, startedAt);
439
+ }
440
+
441
+ /**
442
+ * Assembles the run's result from how it ended, resolving the workflow's
443
+ * `outputs` against the final state (unless it transferred) and stamping
444
+ * the elapsed time. `dependencies` is reported only when the workflow
445
+ * actually had some.
446
+ *
447
+ * `outcome` is the single source every derived field reads from — `status`,
448
+ * `settledStatus`, whether `outputs` gets resolved at all, whether
449
+ * `transferredTo` is present — so there is no second place any of them
450
+ * could drift from it. A `'transferred'` run never reaches its own end, so
451
+ * its `outputs` declaration is never evaluated — `{}` is the only honest
452
+ * value; the target's own outputs live at `transferredTo.outputs`. Skipping
453
+ * resolution loses no validation:
454
+ * {@link WorkflowExecutor.#validateWorkflowOutputsShape} already checked
455
+ * the shape up front, independent of resolving it.
456
+ */
457
+ #result(invocation, workflow, steps, outcome, dependencies, startedAt) {
458
+ const shared = {
459
+ workflowId: invocation.workflowId,
460
+ steps,
461
+ durationMs: this.#now() - startedAt,
462
+ ...(dependencies.length > 0 ? {
463
+ dependencies
464
+ } : {})
465
+ };
466
+ if (outcome.kind === 'transferred') {
467
+ return {
468
+ ...shared,
469
+ outputs: {},
470
+ status: 'transferred',
471
+ // computed once, here, rather than left for a caller to chase via
472
+ // `settledResult` — `transferredTo` already carries its own
473
+ // `settledStatus`, itself computed the same way when it was built,
474
+ // so this is O(1) per hop rather than a walk of the whole chain.
475
+ settledStatus: outcome.transferredTo.settledStatus,
476
+ transferredTo: outcome.transferredTo
477
+ };
478
+ }
479
+ return {
480
+ ...shared,
481
+ outputs: this.#resolveWorkflowOutputs(workflow, invocation),
482
+ status: outcome.status,
483
+ settledStatus: outcome.status
484
+ };
485
+ }
486
+
487
+ /**
488
+ * Whether a nested run's transfer chain settled on `'failed'` — reading
489
+ * {@link WorkflowExecutionResult.settledStatus} directly, which every
490
+ * result already carries, rather than walking `transferredTo` itself.
491
+ */
492
+ #settledFailed(result) {
493
+ return result.settledStatus === 'failed';
494
+ }
495
+
496
+ /**
497
+ * Charges one step attempt to the run-wide budget, throwing when it is spent.
498
+ *
499
+ * This is what bounds a runaway `retry`, a runaway `goto` loop, and a runaway
500
+ * tree of sub-workflow calls alike. The budget spans the whole call tree, so a
501
+ * sub-workflow cannot start afresh; the error carries the chain in progress,
502
+ * because the leaf that happened to spend the last unit is rarely the workflow
503
+ * that is looping.
504
+ */
505
+ #chargeBudget(scope, invocation, stepId) {
506
+ if (++scope.budget.spent <= this.#maxSteps) return;
507
+ throw new _ExecutionError.default(`workflow "${invocation.workflowId}" exceeded its budget of ${this.#maxSteps} step attempts (a goto loop, excessive retries, or runaway sub-workflow calls)`, {
508
+ workflowId: invocation.workflowId,
509
+ stepId,
510
+ reason: 'step-budget',
511
+ path: invocation.callStack.path
512
+ });
513
+ }
514
+
515
+ /**
516
+ * Wraps a step-attempt thunk with the checks every attempt needs at its
517
+ * boundary: charge the run-wide budget, and check for cancellation both
518
+ * before the attempt runs and, if it throws, again on the way out — a
519
+ * cancellation noticed inside the attempt (StepExecutor is handed no
520
+ * workflow or call chain) is re-raised here with both attached, which keeps
521
+ * an abort surfacing mid-request as informative as one caught between two
522
+ * attempts.
523
+ *
524
+ * Shared by the step's own attempts and a `retry` action's `stepId`
525
+ * reference, which is itself one more step attempt against this same run.
526
+ */
527
+ #chargedAttempt(runAttempt, invocation, scope, stepId) {
528
+ const {
529
+ workflowId,
530
+ callStack
531
+ } = invocation;
532
+ return async () => {
533
+ (0, _abort.throwIfAborted)(scope.signal, {
534
+ workflowId,
535
+ stepId,
536
+ callStack
537
+ });
538
+ this.#chargeBudget(scope, invocation, stepId);
539
+ try {
540
+ return await runAttempt();
541
+ } catch (error) {
542
+ (0, _abort.throwIfAborted)(scope.signal, {
543
+ workflowId,
544
+ stepId,
545
+ callStack
546
+ });
547
+ throw error;
548
+ }
549
+ };
550
+ }
551
+
552
+ /**
553
+ * Builds the {@link StepRetryRunContext.runReference} callback a `retry`
554
+ * action's `stepId` / `workflowId` reference is run through.
555
+ *
556
+ * The runner that calls this back owns *when* — once per firing, after the
557
+ * `retryAfter` wait, before the step is re-run; this owns *how*. A retry
558
+ * action naming both fields is malformed — the same mutual-exclusivity
559
+ * `#subWorkflowId` enforces for a step's own target — rejected before either
560
+ * branch below runs, rather than silently preferring one.
561
+ *
562
+ * - a `workflowId` reference runs that workflow to completion through
563
+ * `#run`, on the same shared budget, call stack, and cycle/depth guards
564
+ * as a sub-workflow step — entered via `'retry'` so a loop closed through
565
+ * it is named a call cycle, not a dependency one, and charged one unit
566
+ * for entering the reference itself, matching the "1 for the step itself
567
+ * plus whatever its sub-run spends" convention a sub-workflow step
568
+ * already follows (without it, a reference to a workflow with few or no
569
+ * steps of its own would cost nothing to fire). It runs with no inputs:
570
+ * the specification gives a retry reference no input-mapping mechanism
571
+ * of its own (the same gap `dependsOn` has), and `{}` is the only
572
+ * spec-honest fallback — see issue `#62`. Its outputs are recorded via
573
+ * `state.setWorkflow`, which *is* "context transfers back" — but its
574
+ * `inputs` are preserved rather than overwritten with `{}` when this
575
+ * workflowId was already recorded (by an earlier `dependsOn` prerequisite
576
+ * or sub-workflow step sharing the id): this call's own lack of a real
577
+ * inputs value must not erase one a different, better-informed caller
578
+ * already gave.
579
+ * - a `stepId` reference must name a step of the current workflow (checked
580
+ * lazily, at fire time, via the same lookup a `goto` target uses) and
581
+ * runs as one more attempt at that step, through
582
+ * {@link WorkflowExecutor.#stepAttempt} and
583
+ * {@link WorkflowExecutor.#chargedAttempt} — so a step referencing a
584
+ * `workflowId` itself is handled the normal way, and the reference counts
585
+ * against the same budget. Its outputs are recorded via
586
+ * `state.setStepOutputs` — that recording *is* "context transfers back".
587
+ *
588
+ * Neither kind's own `onSuccess` / `onFailure` (or, for a step reference, its
589
+ * `successCriteria`-derived action) is acted on: "context transfers back upon
590
+ * completion" makes a reference a call, not a redirect, and a failed
591
+ * reference does not break the retry chain — the spec speaks of the
592
+ * reference's *completion*, not its success, and a retry that turns out
593
+ * futile is bounded by `retryLimit` regardless.
594
+ */
595
+ #retryReference(steps, stepId, invocation, scope, retryReferences) {
596
+ const {
597
+ workflowId,
598
+ state,
599
+ callStack
600
+ } = invocation;
601
+ return async action => {
602
+ (0, _abort.throwIfAborted)(scope.signal, {
603
+ workflowId,
604
+ stepId,
605
+ callStack
606
+ });
607
+ const {
608
+ hasStepId,
609
+ hasWorkflowId
610
+ } = (0, _StepTransitionInterpreter.actionTargets)(action);
611
+ if (hasWorkflowId && hasStepId) {
612
+ throw new _ExecutionError.default(`retry action on step "${stepId}" in workflow "${workflowId}" declares both a stepId and a workflowId (mutually exclusive)`, {
613
+ stepId,
614
+ workflowId,
615
+ reason: 'ambiguous-target'
616
+ });
617
+ }
618
+ if (hasWorkflowId) {
619
+ const reference = (0, _apidomCore.toValue)(action.workflowId);
620
+ this.#chargeBudget(scope, invocation, stepId);
621
+ const result = await this.#runReferencedWorkflow(reference, stepId, invocation, scope, 'retry');
622
+ // the result's workflowId is the bare id the reference resolved to —
623
+ // the key run state is recorded under, whichever document owns it.
624
+ const refId = result.workflowId;
625
+ state.setWorkflow(refId, {
626
+ inputs: state.getWorkflow(refId)?.inputs ?? {},
627
+ outputs: result.outputs
628
+ });
629
+ retryReferences.push({
630
+ kind: 'workflow',
631
+ id: reference,
632
+ successful: !this.#settledFailed(result),
633
+ subWorkflow: result
634
+ });
635
+ return;
636
+ }
637
+ const refStepId = (0, _apidomCore.toValue)(action.stepId);
638
+ const refIndex = this.#interpreter.indexOfStep(steps, refStepId, workflowId, {
639
+ reason: 'retry-target-not-found',
640
+ label: 'retry reference'
641
+ });
642
+ const refStep = steps[refIndex];
643
+ const refSubWorkflows = [];
644
+ const outcome = await this.#chargedAttempt(this.#stepAttempt(refStep, refStepId, invocation, scope, refSubWorkflows), invocation, scope, refStepId)();
645
+ state.setStepOutputs(outcome.stepId, outcome.outputs);
646
+ retryReferences.push({
647
+ kind: 'step',
648
+ id: refStepId,
649
+ successful: outcome.successful,
650
+ ...(refSubWorkflows.length > 0 ? {
651
+ subWorkflow: refSubWorkflows[0]
652
+ } : {})
653
+ });
654
+ };
655
+ }
656
+
657
+ /**
658
+ * Runs the workflows this workflow `dependsOn`, in declaration order, before
659
+ * any of its own steps — the Arazzo "MUST be completed before this workflow can
660
+ * be processed" precondition, satisfied on demand.
661
+ *
662
+ * Each dependency's outputs are recorded into the dependent's state so
663
+ * `$workflows.{id}.outputs` resolves; they are not merged into the dependent's
664
+ * own outputs. Runs stop at the first dependency that fails — the dependent
665
+ * cannot be processed, so running the rest would be pointless work with live
666
+ * side effects.
667
+ *
668
+ * Only workflows run *as prerequisites* are remembered as satisfying later
669
+ * ones. A workflow that already ran in this tree as a sub-workflow **step**
670
+ * target does not settle a later `dependsOn` on the same id, and runs again:
671
+ * a step call is an explicit invocation carrying that step's parameters as
672
+ * inputs, whereas a prerequisite runs with whatever the caller supplied in
673
+ * `dependencyInputs`. Treating one as having discharged the other would
674
+ * silently accept a run made with different inputs than the precondition asks
675
+ * for.
676
+ */
677
+ async #runDependencies(workflow, invocation, scope) {
678
+ const {
679
+ state,
680
+ callStack,
681
+ via
682
+ } = invocation;
683
+ // `runDependencies: false` is the caller vouching for the prerequisites of
684
+ // the workflow they named. It stops there: a sub-workflow's own `dependsOn`
685
+ // is an implementation detail the caller may not know exists, so skipping it
686
+ // on their behalf would run that workflow against state nobody claimed to
687
+ // have prepared — silently producing a wrong-but-`completed` result.
688
+ if (via === 'root' && !scope.runDependencies) return [];
689
+ if (!workflow.hasKey('dependsOn')) return [];
690
+
691
+ // every entry is validated before the first one runs: an authoring error
692
+ // found halfway down the list would otherwise be raised only after earlier
693
+ // prerequisites had already fired live requests.
694
+ const dependencyRefs = await this.#dependencyRefs(workflow, invocation);
695
+ const results = [];
696
+ for (const dependency of dependencyRefs) {
697
+ const qualifiedId = this.#qualifiedId(dependency.locator);
698
+ // a dependency already completed in this run is satisfied, not repeated: a
699
+ // precondition holds once met, and a diamond (two dependents sharing one
700
+ // dependency) must not duplicate its live side effects. Every dependent
701
+ // still reports and reads that one run — including the inputs the run
702
+ // was actually given, not whatever this dependent's own
703
+ // `dependencyInputs` entry would have supplied had it run it.
704
+ const memoized = scope.dependencyRuns.get(qualifiedId);
705
+ const inputs = memoized?.inputs ?? scope.dependencyInputs[dependency.inputsKey] ?? {};
706
+ const result = memoized?.result ?? (await this.#run(dependency.locator, inputs, scope, callStack, 'dependsOn'));
707
+ results.push(result);
708
+ // recorded whether or not it completed: a failed prerequisite still
709
+ // resolved (possibly partial) outputs, and the parent resolves its own
710
+ // outputs against this state on its way out, so `$workflows.{id}` is
711
+ // uniformly readable for every dependency that ran. `outputs` is the
712
+ // dependency's own (`{}` when it transferred) — never the settled
713
+ // workflow's, which would misattribute values to a declaration that
714
+ // never ran. Keyed by the bare id even for a cross-document dependency:
715
+ // `$workflows.{id}` is the only form an expression can read it back by.
716
+ state.setWorkflow(dependency.locator.workflowId, {
717
+ inputs,
718
+ outputs: result.outputs
719
+ });
720
+ if (this.#settledFailed(result)) return results;
721
+
722
+ // only a dependency whose chain settled non-`failed` is remembered as
723
+ // satisfied — a failure, transferred or not, must never let a later
724
+ // dependent skip running it.
725
+ scope.dependencyRuns.set(qualifiedId, {
726
+ result,
727
+ inputs
728
+ });
729
+ }
730
+ return results;
731
+ }
732
+
733
+ /**
734
+ * The workflows a workflow declares in `dependsOn`, each resolved to its
735
+ * locator, with every entry checked before any of them is run: the list
736
+ * must be a list of strings, and each entry — a plain id or a
737
+ * cross-document `$sourceDescriptions.{name}.{workflowId}` reference —
738
+ * must name a workflow its document defines.
739
+ *
740
+ * Checking the whole list up front is the point. These are authoring errors,
741
+ * and validating them one at a time as they are run would let a bad entry
742
+ * halfway down the list throw only after the entries before it had already
743
+ * made live requests.
744
+ *
745
+ * `inputsKey` is the entry as written, the key
746
+ * {@link WorkflowExecuteOptions.dependencyInputs} addresses the dependency
747
+ * by.
748
+ */
749
+ async #dependencyRefs(workflow, invocation) {
750
+ const {
751
+ workflowId,
752
+ document
753
+ } = invocation;
754
+ const dependsOn = workflow.dependsOn;
755
+ if (!(0, _apidomDatamodel.isArrayElement)(dependsOn)) {
756
+ throw new _ExecutionError.default(`workflow "${workflowId}" has a non-list "dependsOn"`, {
757
+ workflowId,
758
+ reason: 'malformed-dependsOn'
759
+ });
760
+ }
761
+ const references = [...dependsOn].map((entry, index) => {
762
+ if (!(0, _apidomDatamodel.isStringElement)(entry)) {
763
+ throw new _ExecutionError.default(`workflow "${workflowId}" has a non-string entry at dependsOn[${index}]`, {
764
+ workflowId,
765
+ reason: 'malformed-dependsOn'
766
+ });
767
+ }
768
+ return (0, _apidomCore.toValue)(entry);
769
+ });
770
+ const refs = [];
771
+ for (const reference of references) {
772
+ const locator = await this.#workflowLocatorNormalizer.normalize(reference, document, {
773
+ workflowId
774
+ });
775
+ if (!locator.document.workflowIndex.has(locator.workflowId)) {
776
+ throw new _ExecutionError.default(`workflow "${workflowId}" depends on "${reference}", which the Arazzo document at "${locator.document.uri}" does not define`, {
777
+ workflowId,
778
+ reason: 'workflow-not-found'
779
+ });
780
+ }
781
+ refs.push({
782
+ locator,
783
+ inputsKey: reference
784
+ });
785
+ }
786
+ return refs;
787
+ }
788
+
789
+ /**
790
+ * Builds the thunk that runs one attempt at a step, hiding which kind of step
791
+ * it is from the retry loop.
792
+ *
793
+ * An operation step is delegated to {@link StepExecutor}. A step targeting a
794
+ * `workflowId` is a sub-workflow call this executor runs itself — the case
795
+ * StepExecutor refuses — mapping the step's `parameters` to the sub-workflow's
796
+ * inputs, recording the sub-run under `$workflows`, then resolving the step's
797
+ * own `outputs` and selecting its actions against that updated state. Because
798
+ * both reduce to a {@link StepAttemptOutcome}, `retry` on a sub-workflow step
799
+ * works as it does on an operation step: each attempt re-runs the sub-workflow
800
+ * — its steps, that is — charged against the same budget. Prerequisites it
801
+ * already completed stay satisfied and are not run again, since a completed
802
+ * `dependsOn` workflow is memoized for the whole run; a retry re-runs the
803
+ * work, not the preconditions.
804
+ */
805
+ #stepAttempt(step, stepId, invocation, scope, subWorkflows) {
806
+ const {
807
+ workflowId,
808
+ document,
809
+ state,
810
+ callStack
811
+ } = invocation;
812
+ if (!(0, _apidomDatamodel.isStringElement)(step.workflowId)) {
813
+ return () => this.#stepExecutor.forDocument(document).execute(step, state, scope.executeOptions);
814
+ }
815
+ const reference = this.#subWorkflowReference(step, stepId, workflowId);
816
+ const stepScope = {
817
+ stepId
818
+ };
819
+ // the reference resolves to the same locator on every attempt, so it is
820
+ // resolved once for the whole retry chain, on the first attempt; that
821
+ // same first-attempt moment — before this call has written anything —
822
+ // is when this call's own $workflows entry is pinned (typically absent,
823
+ // but a legitimate value when an earlier, different step already called
824
+ // the same workflowId), held fixed across every attempt below so a retry
825
+ // never sees this call's *own* prior-attempt output. Everything else is
826
+ // read live per attempt.
827
+ let locator;
828
+ let priorWorkflowEntry;
829
+ return async () => {
830
+ if (locator === undefined) {
831
+ locator = await this.#workflowLocatorNormalizer.normalize(reference, document, {
832
+ workflowId,
833
+ stepId
834
+ });
835
+ priorWorkflowEntry = state.getWorkflow(locator.workflowId);
836
+ }
837
+ const subWorkflowId = locator.workflowId;
838
+
839
+ // the sub-workflow's inputs come from the step's parameters, mapped by
840
+ // bare name — per the specification, "all parameters map to workflow
841
+ // inputs" for such a step. That includes an inherited parameter that
842
+ // carries an `in` (the step's own carry none): it still arrives as an
843
+ // input under its name, not under the '{in}.{name}' key an operation
844
+ // step would deliver it by.
845
+ //
846
+ // Resolved fresh on every attempt, not once outside this thunk: a
847
+ // `retry` action's reference can now mutate `$steps` / `$workflows`
848
+ // between attempts (a repair a retried step's own parameters should
849
+ // see), and freezing inputs once would hide that. The one thing that
850
+ // must NOT drift attempt to attempt is this call's own target —
851
+ // `#inputContext` pins `subWorkflowId`'s entry back to
852
+ // `priorWorkflowEntry` so a parameter reading `$workflows.{subWorkflowId}`
853
+ // sees the same (usually absent) value on every attempt, not the
854
+ // previous attempt's own result.
855
+ const preContext = this.#inputContext(state, subWorkflowId, priorWorkflowEntry);
856
+ const inputs = this.#parameterResolver.resolve(step.parameters, expression => this.#evaluate(preContext, expression, document), stepScope);
857
+ const result = await this.#run(locator, inputs, scope, callStack, 'step');
858
+ subWorkflows.push(result);
859
+ state.setWorkflow(subWorkflowId, {
860
+ inputs,
861
+ outputs: result.outputs
862
+ });
863
+
864
+ // resolved after the sub-run is recorded, so the step's outputs can map
865
+ // out of `$workflows.{subWorkflowId}.outputs`. The called workflow is the
866
+ // step's result: `$outputs` names its outputs — the enclosing workflow's
867
+ // own outputs do not exist until it ends, so that reading could never
868
+ // resolve here — and, the step having made no HTTP call, `calledWorkflow`
869
+ // makes `$statusCode`, `$response` and the other HTTP-exchange roots
870
+ // authoring errors rather than silent misses.
871
+ const context = {
872
+ ...state.toContext(),
873
+ outputs: result.outputs,
874
+ calledWorkflow: {
875
+ stepId,
876
+ workflowId: subWorkflowId
877
+ }
878
+ };
879
+ const outputs = this.#outputResolver.resolve(step.outputs, expression => this.#evaluate(context, expression, document), stepScope);
880
+ // an `end`ed sub-workflow returned to its caller with outputs, so it took
881
+ // the success path like a completed one; only a chain settling `failed` is
882
+ // a failure — a sub-workflow that transferred is judged by where its
883
+ // chain settled, via `#settledFailed`, not by the intermediate
884
+ // `'transferred'` status itself. The step's own `successCriteria` still
885
+ // apply on top — they are the author's assertion about this step, and
886
+ // dropping them because the step happens to target a workflow would
887
+ // silently discard it. They see the sub-run's own outputs (`{}` when it
888
+ // transferred) through `$outputs` and `$workflows`.
889
+ // the criteria and actions are the *calling* step's, so they are
890
+ // evaluated by the parent document's step executor — only the sub-run
891
+ // itself switched documents.
892
+ const stepExecutor = this.#stepExecutor.forDocument(document);
893
+ const successful = !this.#settledFailed(result) && stepExecutor.evaluateCriteria(step, context);
894
+ const matchedActions = stepExecutor.selectActions(step, successful, context);
895
+ return {
896
+ stepId,
897
+ successful,
898
+ outputs,
899
+ action: matchedActions[0],
900
+ matchedActions
901
+ };
902
+ };
903
+ }
904
+
905
+ /**
906
+ * The live run context, with `workflowId`'s own `$workflows` entry pinned to
907
+ * `entry` rather than whatever `state` holds right now.
908
+ *
909
+ * Used to resolve a sub-workflow step's inputs on a retry: every other path
910
+ * (`$steps`, every *other* `$workflows` entry, `$inputs`) must be read live,
911
+ * so a `retry` reference's repair between attempts is visible — but this
912
+ * call's own target must not, or a retried attempt would read its own
913
+ * previous attempt's output instead of the value it had before this step's
914
+ * retry chain began. `toContext` builds a fresh plain object per call, so
915
+ * overriding one key here never touches `state` itself.
916
+ */
917
+ #inputContext(state, workflowId, entry) {
918
+ const context = state.toContext();
919
+ const {
920
+ [workflowId]: _current,
921
+ ...workflows
922
+ } = context.workflows ?? {};
923
+ return {
924
+ ...context,
925
+ workflows: entry === undefined ? workflows : {
926
+ ...workflows,
927
+ [workflowId]: entry
928
+ }
929
+ };
930
+ }
931
+
932
+ /**
933
+ * The reference a sub-workflow step targets, as written — a plain
934
+ * workflowId of the current document, or a cross-document
935
+ * `$sourceDescriptions.{name}.{workflowId}` runtime expression.
936
+ */
937
+ #subWorkflowReference(step, stepId, workflowId) {
938
+ // a step names its target once: declaring any other target alongside a
939
+ // workflow is malformed and has no defined resolution. StepExecutor makes
940
+ // the same check, but a sub-workflow step never reaches it — so both read
941
+ // the one list of target fields, and a target added to the specification
942
+ // cannot be rejected by one and silently accepted by the other.
943
+ const conflicting = _StepExecutor.STEP_TARGET_FIELDS.filter(field => field !== 'workflowId' && (0, _apidomDatamodel.isStringElement)(step[field]));
944
+ if (conflicting.length > 0) {
945
+ throw new _ExecutionError.default(`step "${stepId}" in workflow "${workflowId}" declares a workflowId alongside ${conflicting.join(', ')} (mutually exclusive)`, {
946
+ stepId,
947
+ workflowId,
948
+ reason: 'ambiguous-target'
949
+ });
950
+ }
951
+ return (0, _apidomCore.toValue)(step.workflowId);
952
+ }
953
+
954
+ /**
955
+ * Runs a workflow an action references directly — a retry's `workflowId`
956
+ * reference or a goto's transfer, plain id or cross-document expression —
957
+ * with `{}` inputs, since neither has an input-mapping mechanism of its own
958
+ * (issue `#62`).
959
+ *
960
+ * Shared rather than duplicated by its two callers, which otherwise repeat
961
+ * the identical "resolve the reference, then run with no inputs" sequence.
962
+ * Rejects a target its document does not define with the calling `stepId`
963
+ * attached, unlike {@link WorkflowExecutor.#resolveWorkflow}'s own
964
+ * `workflow-not-found` (reached only once `#run` itself resolves the
965
+ * target) — which carries no caller context at all, since it is reached
966
+ * generically from every kind of nested call.
967
+ */
968
+ async #runReferencedWorkflow(reference, stepId, invocation, scope, via) {
969
+ const locator = await this.#workflowLocatorNormalizer.normalize(reference, invocation.document, {
970
+ workflowId: invocation.workflowId,
971
+ stepId
972
+ });
973
+ if (!locator.document.workflowIndex.has(locator.workflowId)) {
974
+ throw new _ExecutionError.default(`workflow "${locator.workflowId}" not found in Arazzo document at "${locator.document.uri}"`, {
975
+ workflowId: locator.workflowId,
976
+ stepId,
977
+ reason: 'workflow-not-found'
978
+ });
979
+ }
980
+ return this.#run(locator, {}, scope, invocation.callStack, via);
981
+ }
982
+
983
+ /**
984
+ * Extracts and normalizes the workflow a locator names. A workflow its
985
+ * document does not define is the executor-level `workflow-not-found`
986
+ * authoring error, raised here rather than leaking the extractor's
987
+ * `ExtractionError`.
988
+ */
989
+ async #resolveWorkflow(locator, scope) {
990
+ const {
991
+ document,
992
+ workflowId
993
+ } = locator;
994
+ const qualifiedId = this.#qualifiedId(locator);
995
+ const cached = scope.workflows.get(qualifiedId);
996
+ if (cached !== undefined) return cached;
997
+ if (!document.workflowIndex.has(workflowId)) {
998
+ throw new _ExecutionError.default(`workflow "${workflowId}" not found in Arazzo document at "${document.uri}"`, {
999
+ workflowId,
1000
+ reason: 'workflow-not-found'
1001
+ });
1002
+ }
1003
+ const workflow = await this.#normalizer.normalize(this.#extractor.extract(document, workflowId), document);
1004
+ scope.workflows.set(qualifiedId, workflow);
1005
+ return workflow;
1006
+ }
1007
+
1008
+ /**
1009
+ * The run-wide identity of the workflow a locator names —
1010
+ * `{documentURI}#{workflowId}`, the key the normalized-workflow cache and
1011
+ * the dependency ledger share (the call stack compares the same pair,
1012
+ * unformatted). Qualified because a bare workflowId is only unique within
1013
+ * its own document.
1014
+ */
1015
+ #qualifiedId(locator) {
1016
+ return `${locator.document.uri}#${locator.workflowId}`;
1017
+ }
1018
+
1019
+ /**
1020
+ * The workflow's steps as an array in list order. An absent `steps` is a
1021
+ * workflow with no steps — an empty list, a completed no-op run. A present but
1022
+ * malformed `steps` (not a list, or holding a non-step entry) is an authoring
1023
+ * error and throws rather than being silently treated as empty or partial.
1024
+ */
1025
+ #orderedSteps(workflow, workflowId) {
1026
+ if (!workflow.hasKey('steps')) return [];
1027
+ const steps = workflow.steps;
1028
+ if (!(0, _apidomDatamodel.isArrayElement)(steps)) {
1029
+ throw new _ExecutionError.default(`workflow "${workflowId}" has a non-list "steps"`, {
1030
+ workflowId,
1031
+ reason: 'malformed-steps'
1032
+ });
1033
+ }
1034
+ return [...steps].map((step, index) => {
1035
+ if (!(0, _apidomNsArazzo.isStepElement)(step)) {
1036
+ throw new _ExecutionError.default(`workflow "${workflowId}" has a non-step entry at steps[${index}]`, {
1037
+ workflowId,
1038
+ reason: 'malformed-steps'
1039
+ });
1040
+ }
1041
+ return step;
1042
+ });
1043
+ }
1044
+
1045
+ /**
1046
+ * Checks that the workflow's `outputs`, if present, is a map — without
1047
+ * resolving any of its values. The same check {@link OutputResolver.resolve}
1048
+ * makes on its way to actually resolving them, run here early instead: the
1049
+ * shape needs no run state, so this can — and, to keep a malformed `outputs`
1050
+ * from costing the run its live side effects, must — happen alongside
1051
+ * {@link WorkflowExecutor.#orderedSteps}, before any prerequisite or step of
1052
+ * the workflow runs.
1053
+ */
1054
+ #validateWorkflowOutputsShape(workflow, workflowId) {
1055
+ if (!workflow.hasKey('outputs')) return;
1056
+ const outputs = workflow.outputs;
1057
+ if ((0, _apidomDatamodel.isObjectElement)(outputs)) return;
1058
+ throw new _ResolverError.default('`outputs` is present but is not a map', {
1059
+ workflowId,
1060
+ target: 'outputs',
1061
+ reason: 'malformed-outputs'
1062
+ });
1063
+ }
1064
+
1065
+ /**
1066
+ * Resolves the workflow's `outputs` declaration against the final run state,
1067
+ * mirroring how {@link StepExecutor} resolves a step's outputs.
1068
+ */
1069
+ #resolveWorkflowOutputs(workflow, invocation) {
1070
+ const context = invocation.state.toContext();
1071
+ return this.#outputResolver.resolve(workflow.outputs, expression => this.#evaluate(context, expression, invocation.document), {
1072
+ workflowId: (0, _apidomCore.toValue)(workflow.workflowId)
1073
+ });
1074
+ }
1075
+
1076
+ /**
1077
+ * Resolves a runtime expression leniently against a context, forwarding
1078
+ * `$components` / `$sourceDescriptions` resolution to the given document —
1079
+ * the invocation's, so a foreign workflow's expressions resolve against
1080
+ * the document that owns it — and the registry; the workflow-scoped
1081
+ * counterpart of {@link StepExecutor}'s bridge.
1082
+ */
1083
+ #evaluate(context, expression, document) {
1084
+ return new _RuntimeExpressionEvaluator.default(context, {
1085
+ strict: false,
1086
+ document,
1087
+ registry: this.#registry
1088
+ }).evaluate(expression);
1089
+ }
1090
+ }
1091
+ var _default = exports.default = WorkflowExecutor;