@ankhorage/flow 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/LICENSE +21 -0
  3. package/README.md +19 -2
  4. package/dist/ai.d.ts +7 -0
  5. package/dist/ai.d.ts.map +1 -0
  6. package/dist/ai.js +7 -0
  7. package/dist/ai.js.map +1 -0
  8. package/dist/execution.d.ts +5 -0
  9. package/dist/execution.d.ts.map +1 -0
  10. package/dist/execution.js +5 -0
  11. package/dist/execution.js.map +1 -0
  12. package/dist/features/execution/adapters/FileFlowPersistenceAdapter.d.ts +4 -0
  13. package/dist/features/execution/adapters/FileFlowPersistenceAdapter.d.ts.map +1 -0
  14. package/dist/features/execution/adapters/FileFlowPersistenceAdapter.js +219 -0
  15. package/dist/features/execution/adapters/FileFlowPersistenceAdapter.js.map +1 -0
  16. package/dist/features/execution/application/cancelDurableRunAsync.d.ts +8 -0
  17. package/dist/features/execution/application/cancelDurableRunAsync.d.ts.map +1 -0
  18. package/dist/features/execution/application/cancelDurableRunAsync.js +22 -0
  19. package/dist/features/execution/application/cancelDurableRunAsync.js.map +1 -0
  20. package/dist/features/execution/application/createDurableRunAsync.d.ts +12 -0
  21. package/dist/features/execution/application/createDurableRunAsync.d.ts.map +1 -0
  22. package/dist/features/execution/application/createDurableRunAsync.js +8 -0
  23. package/dist/features/execution/application/createDurableRunAsync.js.map +1 -0
  24. package/dist/features/execution/application/executeFlowAsync.d.ts +4 -0
  25. package/dist/features/execution/application/executeFlowAsync.d.ts.map +1 -0
  26. package/dist/features/execution/application/executeFlowAsync.js +211 -0
  27. package/dist/features/execution/application/executeFlowAsync.js.map +1 -0
  28. package/dist/features/execution/application/runDurableWorkerAsync.d.ts +52 -0
  29. package/dist/features/execution/application/runDurableWorkerAsync.d.ts.map +1 -0
  30. package/dist/features/execution/application/runDurableWorkerAsync.js +195 -0
  31. package/dist/features/execution/application/runDurableWorkerAsync.js.map +1 -0
  32. package/dist/features/execution/domain/assertFlowGraphSchemasSupported.d.ts +5 -0
  33. package/dist/features/execution/domain/assertFlowGraphSchemasSupported.d.ts.map +1 -0
  34. package/dist/features/execution/domain/assertFlowGraphSchemasSupported.js +11 -0
  35. package/dist/features/execution/domain/assertFlowGraphSchemasSupported.js.map +1 -0
  36. package/dist/features/execution/domain/calculateRetryDueAt.d.ts +4 -0
  37. package/dist/features/execution/domain/calculateRetryDueAt.d.ts.map +1 -0
  38. package/dist/features/execution/domain/calculateRetryDueAt.js +14 -0
  39. package/dist/features/execution/domain/calculateRetryDueAt.js.map +1 -0
  40. package/dist/features/execution/domain/compileFlowGraph.d.ts +5 -0
  41. package/dist/features/execution/domain/compileFlowGraph.d.ts.map +1 -0
  42. package/dist/features/execution/domain/compileFlowGraph.js +76 -0
  43. package/dist/features/execution/domain/compileFlowGraph.js.map +1 -0
  44. package/dist/features/execution/domain/createDefaultNodeRegistry.d.ts +4 -0
  45. package/dist/features/execution/domain/createDefaultNodeRegistry.d.ts.map +1 -0
  46. package/dist/features/execution/domain/createDefaultNodeRegistry.js +15 -0
  47. package/dist/features/execution/domain/createDefaultNodeRegistry.js.map +1 -0
  48. package/dist/features/execution/domain/createDurableRun.d.ts +7 -0
  49. package/dist/features/execution/domain/createDurableRun.d.ts.map +1 -0
  50. package/dist/features/execution/domain/createDurableRun.js +10 -0
  51. package/dist/features/execution/domain/createDurableRun.js.map +1 -0
  52. package/dist/features/execution/domain/createRetryWork.d.ts +4 -0
  53. package/dist/features/execution/domain/createRetryWork.d.ts.map +1 -0
  54. package/dist/features/execution/domain/createRetryWork.js +17 -0
  55. package/dist/features/execution/domain/createRetryWork.js.map +1 -0
  56. package/dist/features/execution/domain/isCronDueAt.d.ts +7 -0
  57. package/dist/features/execution/domain/isCronDueAt.d.ts.map +1 -0
  58. package/dist/features/execution/domain/isCronDueAt.js +114 -0
  59. package/dist/features/execution/domain/isCronDueAt.js.map +1 -0
  60. package/dist/features/execution/domain/validateFlowDataSchema.d.ts +6 -0
  61. package/dist/features/execution/domain/validateFlowDataSchema.d.ts.map +1 -0
  62. package/dist/features/execution/domain/validateFlowDataSchema.js +144 -0
  63. package/dist/features/execution/domain/validateFlowDataSchema.js.map +1 -0
  64. package/dist/features/execution/ports/FlowPersistencePort.d.ts +34 -0
  65. package/dist/features/execution/ports/FlowPersistencePort.d.ts.map +1 -0
  66. package/dist/features/execution/ports/FlowPersistencePort.js +2 -0
  67. package/dist/features/execution/ports/FlowPersistencePort.js.map +1 -0
  68. package/dist/features/mcp/McpToolError.d.ts +6 -0
  69. package/dist/features/mcp/McpToolError.d.ts.map +1 -0
  70. package/dist/features/mcp/McpToolError.js +10 -0
  71. package/dist/features/mcp/McpToolError.js.map +1 -0
  72. package/dist/features/mcp/createMcpRemoteTransport.d.ts +8 -0
  73. package/dist/features/mcp/createMcpRemoteTransport.d.ts.map +1 -0
  74. package/dist/features/mcp/createMcpRemoteTransport.js +23 -0
  75. package/dist/features/mcp/createMcpRemoteTransport.js.map +1 -0
  76. package/dist/features/mcp/createMcpToolRegistry.d.ts +10 -0
  77. package/dist/features/mcp/createMcpToolRegistry.d.ts.map +1 -0
  78. package/dist/features/mcp/createMcpToolRegistry.js +185 -0
  79. package/dist/features/mcp/createMcpToolRegistry.js.map +1 -0
  80. package/dist/features/mcp/loadMcpSdkAsync.d.ts +12 -0
  81. package/dist/features/mcp/loadMcpSdkAsync.d.ts.map +1 -0
  82. package/dist/features/mcp/loadMcpSdkAsync.js +20 -0
  83. package/dist/features/mcp/loadMcpSdkAsync.js.map +1 -0
  84. package/dist/features/mcp/prepareMcpProviders.d.ts +4 -0
  85. package/dist/features/mcp/prepareMcpProviders.d.ts.map +1 -0
  86. package/dist/features/mcp/prepareMcpProviders.js +97 -0
  87. package/dist/features/mcp/prepareMcpProviders.js.map +1 -0
  88. package/dist/features/mcp/validateMcpSchema.d.ts +6 -0
  89. package/dist/features/mcp/validateMcpSchema.d.ts.map +1 -0
  90. package/dist/features/mcp/validateMcpSchema.js +220 -0
  91. package/dist/features/mcp/validateMcpSchema.js.map +1 -0
  92. package/dist/features/security/FlowSecurityError.d.ts +6 -0
  93. package/dist/features/security/FlowSecurityError.d.ts.map +1 -0
  94. package/dist/features/security/FlowSecurityError.js +10 -0
  95. package/dist/features/security/FlowSecurityError.js.map +1 -0
  96. package/dist/features/security/application/createFlowSecurityPort.d.ts +12 -0
  97. package/dist/features/security/application/createFlowSecurityPort.d.ts.map +1 -0
  98. package/dist/features/security/application/createFlowSecurityPort.js +208 -0
  99. package/dist/features/security/application/createFlowSecurityPort.js.map +1 -0
  100. package/dist/features/security/domain/validateFlowSecurityTarget.d.ts +4 -0
  101. package/dist/features/security/domain/validateFlowSecurityTarget.d.ts.map +1 -0
  102. package/dist/features/security/domain/validateFlowSecurityTarget.js +57 -0
  103. package/dist/features/security/domain/validateFlowSecurityTarget.js.map +1 -0
  104. package/dist/index.d.ts +16 -0
  105. package/dist/index.d.ts.map +1 -0
  106. package/dist/index.js +12 -0
  107. package/dist/index.js.map +1 -0
  108. package/dist/mcp.d.ts +4 -0
  109. package/dist/mcp.d.ts.map +1 -0
  110. package/dist/mcp.js +3 -0
  111. package/dist/mcp.js.map +1 -0
  112. package/dist/social.d.ts +7 -0
  113. package/dist/social.d.ts.map +1 -0
  114. package/dist/social.js +7 -0
  115. package/dist/social.js.map +1 -0
  116. package/dist/types/durableExecution.d.ts +55 -0
  117. package/dist/types/durableExecution.d.ts.map +1 -0
  118. package/dist/types/durableExecution.js +2 -0
  119. package/dist/types/durableExecution.js.map +1 -0
  120. package/dist/types/execution.d.ts +33 -0
  121. package/dist/types/execution.d.ts.map +1 -0
  122. package/dist/types/execution.js +2 -0
  123. package/dist/types/execution.js.map +1 -0
  124. package/dist/types/mcp.d.ts +61 -0
  125. package/dist/types/mcp.d.ts.map +1 -0
  126. package/dist/types/mcp.js +2 -0
  127. package/dist/types/mcp.js.map +1 -0
  128. package/dist/types/security.d.ts +123 -0
  129. package/dist/types/security.d.ts.map +1 -0
  130. package/dist/types/security.js +2 -0
  131. package/dist/types/security.js.map +1 -0
  132. package/package.json +90 -4
  133. package/src/ai.ts +6 -0
  134. package/src/execution.ts +9 -0
  135. package/src/features/execution/adapters/FileFlowPersistenceAdapter.ts +333 -0
  136. package/src/features/execution/application/cancelDurableRunAsync.ts +28 -0
  137. package/src/features/execution/application/createDurableRunAsync.ts +14 -0
  138. package/src/features/execution/application/executeFlowAsync.ts +338 -0
  139. package/src/features/execution/application/runDurableWorkerAsync.ts +337 -0
  140. package/src/features/execution/domain/assertFlowGraphSchemasSupported.ts +17 -0
  141. package/src/features/execution/domain/calculateRetryDueAt.ts +21 -0
  142. package/src/features/execution/domain/compileFlowGraph.ts +97 -0
  143. package/src/features/execution/domain/createDefaultNodeRegistry.ts +26 -0
  144. package/src/features/execution/domain/createDurableRun.ts +14 -0
  145. package/src/features/execution/domain/createRetryWork.ts +22 -0
  146. package/src/features/execution/domain/isCronDueAt.ts +131 -0
  147. package/src/features/execution/domain/validateFlowDataSchema.ts +218 -0
  148. package/src/features/execution/ports/FlowPersistencePort.ts +38 -0
  149. package/src/features/mcp/McpToolError.ts +10 -0
  150. package/src/features/mcp/createMcpRemoteTransport.ts +34 -0
  151. package/src/features/mcp/createMcpToolRegistry.ts +324 -0
  152. package/src/features/mcp/loadMcpSdkAsync.ts +32 -0
  153. package/src/features/mcp/prepareMcpProviders.ts +135 -0
  154. package/src/features/mcp/validateMcpSchema.ts +269 -0
  155. package/src/features/security/FlowSecurityError.ts +17 -0
  156. package/src/features/security/application/createFlowSecurityPort.ts +339 -0
  157. package/src/features/security/domain/validateFlowSecurityTarget.ts +73 -0
  158. package/src/index.ts +49 -0
  159. package/src/mcp.ts +11 -0
  160. package/src/social.ts +6 -0
  161. package/src/types/durableExecution.ts +61 -0
  162. package/src/types/execution.ts +41 -0
  163. package/src/types/mcp.ts +70 -0
  164. package/src/types/security.ts +148 -0
@@ -0,0 +1,211 @@
1
+ import { assertFlowGraphSchemasSupported } from '../domain/assertFlowGraphSchemasSupported.js';
2
+ import { validateFlowDataSchema } from '../domain/validateFlowDataSchema.js';
3
+ /*** Execute a compiled graph in deterministic order within one process. */
4
+ export const executeFlowAsync = async (graph, registry, request, clock = { now: () => new Date() }) => {
5
+ const trigger = graph.definition.triggers.find(({ id }) => id === request.triggerId);
6
+ if (trigger?.kind !== 'manual')
7
+ throw new Error('FLOW_TRIGGER_NOT_MANUAL');
8
+ assertFlowGraphSchemasSupported(graph.definition.nodes, request.schemaRegistry);
9
+ const startedAt = clock.now().toISOString();
10
+ const state = await executeNodesAsync(graph, registry, request, clock);
11
+ return {
12
+ run: createRun(graph, request, state.steps, startedAt, clock.now().toISOString(), state.status ?? 'succeeded', state.error),
13
+ };
14
+ };
15
+ /*** Iterate compiled nodes in deterministic order until completion or cancellation. */
16
+ const executeNodesAsync = async (graph, registry, request, clock) => {
17
+ const state = {
18
+ activatedNodeIds: new Set(graph.topologicalNodeIds.filter((nodeId) => !graph.definition.edges.some((edge) => edge.target.nodeId === nodeId))),
19
+ inputs: {},
20
+ steps: [],
21
+ };
22
+ for (const nodeId of graph.topologicalNodeIds) {
23
+ if (request.signal?.aborted)
24
+ return { ...state, status: 'cancelled' };
25
+ const node = graph.definition.nodes.find((candidate) => candidate.id === nodeId);
26
+ if (!node)
27
+ throw new Error(`FLOW_GRAPH_UNKNOWN_NODE:${nodeId}`);
28
+ const result = await executeAvailableNodeAsync(node, state, registry, request, clock);
29
+ if (!result)
30
+ continue;
31
+ state.steps.push(result.step);
32
+ if (result.step.status === 'cancelled')
33
+ return { ...state, status: 'cancelled' };
34
+ propagateOutput(graph.edgesBySourceNodeId[node.id] ?? [], result.output, result.step.status === 'failed' ? 'failure' : 'success', state);
35
+ }
36
+ const failedStep = state.steps.find((step) => step.status === 'failed');
37
+ return { ...state, status: failedStep ? 'failed' : 'succeeded', error: failedStep?.error };
38
+ };
39
+ /*** Execute one node only when all of its required inputs were propagated. */
40
+ const executeAvailableNodeAsync = async (node, state, registry, request, clock) => {
41
+ if (!state.activatedNodeIds.has(node.id))
42
+ return undefined;
43
+ const input = state.inputs[node.id] ?? request.input ?? {};
44
+ if (!hasRequiredInput(node, input))
45
+ return undefined;
46
+ return executeNodeAsync(node, input, registry, request.signal, clock, request.schemaRegistry);
47
+ };
48
+ /*** Determine whether every required input port has a propagated value. */
49
+ const hasRequiredInput = (node, input) => node.ports
50
+ .filter((port) => port.direction === 'input' && port.required)
51
+ .every((port) => input[port.id] !== undefined);
52
+ /*** Execute one node and convert failures to redaction-safe contract errors. */
53
+ const executeNodeAsync = async (node, input, registry, signal, clock, schemas) => {
54
+ const startedAt = clock.now().toISOString();
55
+ try {
56
+ assertPortSchemas(node, input, 'input', schemas);
57
+ const operationId = `${node.capability.providerId}:${node.capability.capabilityId}`;
58
+ const operation = Object.entries(registry).find(([id]) => id === operationId)?.[1];
59
+ if (!operation)
60
+ throw new Error(`FLOW_OPERATION_UNREGISTERED:${operationId}`);
61
+ const output = (await operation.executeAsync({
62
+ config: node.config,
63
+ input,
64
+ node,
65
+ signal: signal ?? new AbortController().signal,
66
+ })).output ?? {};
67
+ if (signal?.aborted)
68
+ return { output: {}, step: createCancelledStep(node, input, startedAt, clock) };
69
+ assertOutputPorts(node, output);
70
+ assertPortSchemas(node, output, 'output', schemas);
71
+ return { output, step: createSucceededStep(node, input, output, startedAt, clock) };
72
+ }
73
+ catch (reason) {
74
+ const error = asFlowError(reason);
75
+ return {
76
+ output: {},
77
+ step: {
78
+ id: `${node.id}:1`,
79
+ nodeId: node.id,
80
+ status: 'failed',
81
+ attempt: 1,
82
+ startedAt,
83
+ completedAt: clock.now().toISOString(),
84
+ input,
85
+ error,
86
+ },
87
+ };
88
+ }
89
+ };
90
+ /*** Create an immutable successful step snapshot. */
91
+ const createSucceededStep = (node, input, output, startedAt, clock) => ({
92
+ id: `${node.id}:1`,
93
+ nodeId: node.id,
94
+ status: 'succeeded',
95
+ attempt: 1,
96
+ startedAt,
97
+ completedAt: clock.now().toISOString(),
98
+ input,
99
+ output,
100
+ });
101
+ /*** Create a terminal snapshot when cancellation occurs during asynchronous node execution. */
102
+ const createCancelledStep = (node, input, startedAt, clock) => ({
103
+ id: `${node.id}:1`,
104
+ nodeId: node.id,
105
+ status: 'cancelled',
106
+ attempt: 1,
107
+ startedAt,
108
+ completedAt: clock.now().toISOString(),
109
+ input,
110
+ });
111
+ /*** Reject operation outputs that do not correspond to declared output ports. */
112
+ const assertOutputPorts = (node, output) => {
113
+ const outputIds = new Set(node.ports.filter((port) => port.direction === 'output').map(({ id }) => id));
114
+ if (Object.keys(output).some((portId) => !outputIds.has(portId)))
115
+ throw new Error(`FLOW_OUTPUT_UNDECLARED_PORT:${node.id}`);
116
+ const missingRequired = node.ports.some((port) => port.direction === 'output' && port.required && output[port.id] === undefined);
117
+ if (missingRequired)
118
+ throw new Error(`FLOW_OUTPUT_REQUIRED_PORT_MISSING:${node.id}`);
119
+ };
120
+ /*** Validate inline port values against their declared portable schemas. */
121
+ const assertPortSchemas = (node, values, direction, schemas) => {
122
+ for (const port of node.ports.filter((candidate) => candidate.direction === direction)) {
123
+ const value = values[port.id];
124
+ const schema = port.data?.schema ?? (port.data?.schemaRef ? { ref: port.data.schemaRef } : undefined);
125
+ if (value?.kind === 'inline' && schema && !validateFlowDataSchema(value.value, schema, schemas))
126
+ throw new Error(`FLOW_PORT_SCHEMA_MISMATCH:${node.id}:${port.id}`);
127
+ }
128
+ };
129
+ /*** Propagate outputs only over edges whose declared condition matches the output value. */
130
+ const propagateOutput = (edges, output, outcome, state) => {
131
+ for (const edge of edges) {
132
+ const value = output[edge.source.portId];
133
+ if (!matchesEdge(edge, value, outcome))
134
+ continue;
135
+ state.activatedNodeIds.add(edge.target.nodeId);
136
+ if (value)
137
+ state.inputs[edge.target.nodeId] = {
138
+ ...state.inputs[edge.target.nodeId],
139
+ [edge.target.portId]: value,
140
+ };
141
+ }
142
+ };
143
+ /*** Evaluate an edge condition without executing arbitrary expressions. */
144
+ const matchesEdge = (edge, value, outcome) => {
145
+ const condition = edge.condition ?? { kind: 'always' };
146
+ if (condition.kind === 'always')
147
+ return outcome === 'success';
148
+ if (condition.kind === 'outcome')
149
+ return condition.outcome === outcome;
150
+ if (value?.kind !== 'inline')
151
+ return false;
152
+ const selected = resolvePointer(value.value, condition.path);
153
+ return selected !== undefined && structurallyEqual(selected, condition.equals);
154
+ };
155
+ /*** Resolve an RFC 6901 pointer against serializable inline data. */
156
+ const resolvePointer = (value, pointer) => {
157
+ if (pointer === '')
158
+ return value;
159
+ if (!pointer.startsWith('/'))
160
+ return undefined;
161
+ return pointer
162
+ .slice(1)
163
+ .split('/')
164
+ .reduce((current, segment) => {
165
+ if (current === undefined || current === null || typeof current !== 'object')
166
+ return undefined;
167
+ const key = segment.replaceAll('~1', '/').replaceAll('~0', '~');
168
+ if (isDataArray(current))
169
+ return current.find((_item, index) => index === Number(key));
170
+ return isDataRecord(current)
171
+ ? Object.entries(current).find(([candidateKey]) => candidateKey === key)?.[1]
172
+ : undefined;
173
+ }, value);
174
+ };
175
+ /*** Compare JSON-compatible values with object-key-order independence. */
176
+ const structurallyEqual = (left, right) => JSON.stringify(normalizeValue(left)) === JSON.stringify(normalizeValue(right));
177
+ /*** Canonicalize object member ordering for structural equality. */
178
+ const normalizeValue = (value) => {
179
+ if (Array.isArray(value))
180
+ return value.map(normalizeValue);
181
+ if (value !== null && typeof value === 'object')
182
+ return Object.fromEntries(Object.entries(value)
183
+ .sort(([left], [right]) => left.localeCompare(right))
184
+ .map(([key, item]) => [key, normalizeValue(item)]));
185
+ return value;
186
+ };
187
+ /*** Narrow a serializable non-array object to an indexable data record. */
188
+ const isDataRecord = (value) => !Array.isArray(value);
189
+ /*** Narrow a serializable array without losing its element type. */
190
+ const isDataArray = (value) => Array.isArray(value);
191
+ /*** Translate host failures to structured contract errors without exposing arbitrary objects. */
192
+ const asFlowError = (reason) => ({
193
+ code: reason instanceof Error && /^FLOW_[A-Z0-9_]+(?::|$)/.test(reason.message)
194
+ ? (reason.message.split(':')[0] ?? 'FLOW_OPERATION_FAILED')
195
+ : 'FLOW_OPERATION_FAILED',
196
+ message: 'The operation failed.',
197
+ retryable: false,
198
+ });
199
+ /*** Build the immutable run result emitted by the single-process executor. */
200
+ const createRun = (graph, request, steps, startedAt, completedAt, status, error) => ({
201
+ id: request.runId,
202
+ flow: { id: graph.definition.id, version: graph.definition.version },
203
+ status,
204
+ trigger: { triggerId: request.triggerId, kind: 'manual' },
205
+ createdAt: startedAt,
206
+ startedAt,
207
+ completedAt,
208
+ steps,
209
+ error,
210
+ });
211
+ //# sourceMappingURL=executeFlowAsync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executeFlowAsync.js","sourceRoot":"","sources":["../../../../src/features/execution/application/executeFlowAsync.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DataContractValue } from '@ankhorage/contracts/data';\nimport type {\n FlowDataRef,\n FlowEdge,\n FlowError,\n FlowNode,\n FlowPortId,\n FlowRunSnapshot,\n FlowStepSnapshot,\n} from '@ankhorage/contracts/flow';\n\nimport type {\n FlowClock,\n FlowCompiledGraph,\n FlowExecutionResult,\n FlowNodeRegistry,\n FlowRunRequest,\n} from '../../../types/execution.js';\nimport { assertFlowGraphSchemasSupported } from '../domain/assertFlowGraphSchemasSupported.js';\nimport { validateFlowDataSchema } from '../domain/validateFlowDataSchema.js';\n\n/*** Execute a compiled graph in deterministic order within one process. */\nexport const executeFlowAsync = async (\n graph: FlowCompiledGraph,\n registry: FlowNodeRegistry,\n request: FlowRunRequest,\n clock: FlowClock = { now: () => new Date() },\n): Promise<FlowExecutionResult> => {\n const trigger = graph.definition.triggers.find(({ id }) => id === request.triggerId);\n if (trigger?.kind !== 'manual') throw new Error('FLOW_TRIGGER_NOT_MANUAL');\n assertFlowGraphSchemasSupported(graph.definition.nodes, request.schemaRegistry);\n const startedAt = clock.now().toISOString();\n const state = await executeNodesAsync(graph, registry, request, clock);\n return {\n run: createRun(\n graph,\n request,\n state.steps,\n startedAt,\n clock.now().toISOString(),\n state.status ?? 'succeeded',\n state.error,\n ),\n };\n};\n\n/*** Iterate compiled nodes in deterministic order until completion or cancellation. */\nconst executeNodesAsync = async (\n graph: FlowCompiledGraph,\n registry: FlowNodeRegistry,\n request: FlowRunRequest,\n clock: FlowClock,\n): Promise<ExecutionState> => {\n const state: ExecutionState = {\n activatedNodeIds: new Set(\n graph.topologicalNodeIds.filter(\n (nodeId) => !graph.definition.edges.some((edge) => edge.target.nodeId === nodeId),\n ),\n ),\n inputs: {},\n steps: [],\n };\n for (const nodeId of graph.topologicalNodeIds) {\n if (request.signal?.aborted) return { ...state, status: 'cancelled' };\n const node = graph.definition.nodes.find((candidate) => candidate.id === nodeId);\n if (!node) throw new Error(`FLOW_GRAPH_UNKNOWN_NODE:${nodeId}`);\n const result = await executeAvailableNodeAsync(node, state, registry, request, clock);\n if (!result) continue;\n state.steps.push(result.step);\n if (result.step.status === 'cancelled') return { ...state, status: 'cancelled' };\n propagateOutput(\n graph.edgesBySourceNodeId[node.id] ?? [],\n result.output,\n result.step.status === 'failed' ? 'failure' : 'success',\n state,\n );\n }\n const failedStep = state.steps.find((step) => step.status === 'failed');\n return { ...state, status: failedStep ? 'failed' : 'succeeded', error: failedStep?.error };\n};\n\ninterface ExecutionState {\n readonly activatedNodeIds: Set<string>;\n readonly error?: FlowError;\n readonly inputs: Record<string, Record<FlowPortId, FlowDataRef>>;\n readonly status?: FlowRunSnapshot['status'];\n readonly steps: FlowStepSnapshot[];\n}\n\n/*** Execute one node only when all of its required inputs were propagated. */\nconst executeAvailableNodeAsync = async (\n node: FlowNode,\n state: ExecutionState,\n registry: FlowNodeRegistry,\n request: FlowRunRequest,\n clock: FlowClock,\n): Promise<\n | { readonly output: Readonly<Record<FlowPortId, FlowDataRef>>; readonly step: FlowStepSnapshot }\n | undefined\n> => {\n if (!state.activatedNodeIds.has(node.id)) return undefined;\n const input = state.inputs[node.id] ?? request.input ?? {};\n if (!hasRequiredInput(node, input)) return undefined;\n return executeNodeAsync(node, input, registry, request.signal, clock, request.schemaRegistry);\n};\n\n/*** Determine whether every required input port has a propagated value. */\nconst hasRequiredInput = (\n node: FlowNode,\n input: Readonly<Record<FlowPortId, FlowDataRef>>,\n): boolean =>\n node.ports\n .filter((port) => port.direction === 'input' && port.required)\n .every((port) => input[port.id] !== undefined);\n\n/*** Execute one node and convert failures to redaction-safe contract errors. */\nconst executeNodeAsync = async (\n node: FlowNode,\n input: Readonly<Record<FlowPortId, FlowDataRef>>,\n registry: FlowNodeRegistry,\n signal: AbortSignal | undefined,\n clock: FlowClock,\n schemas: FlowRunRequest['schemaRegistry'],\n): Promise<{\n readonly output: Readonly<Record<FlowPortId, FlowDataRef>>;\n readonly step: FlowStepSnapshot;\n}> => {\n const startedAt = clock.now().toISOString();\n try {\n assertPortSchemas(node, input, 'input', schemas);\n const operationId = `${node.capability.providerId}:${node.capability.capabilityId}`;\n const operation = Object.entries(registry).find(([id]) => id === operationId)?.[1];\n if (!operation) throw new Error(`FLOW_OPERATION_UNREGISTERED:${operationId}`);\n const output =\n (\n await operation.executeAsync({\n config: node.config,\n input,\n node,\n signal: signal ?? new AbortController().signal,\n })\n ).output ?? {};\n if (signal?.aborted)\n return { output: {}, step: createCancelledStep(node, input, startedAt, clock) };\n assertOutputPorts(node, output);\n assertPortSchemas(node, output, 'output', schemas);\n return { output, step: createSucceededStep(node, input, output, startedAt, clock) };\n } catch (reason) {\n const error = asFlowError(reason);\n return {\n output: {},\n step: {\n id: `${node.id}:1`,\n nodeId: node.id,\n status: 'failed',\n attempt: 1,\n startedAt,\n completedAt: clock.now().toISOString(),\n input,\n error,\n },\n };\n }\n};\n\n/*** Create an immutable successful step snapshot. */\nconst createSucceededStep = (\n node: FlowNode,\n input: Readonly<Record<FlowPortId, FlowDataRef>>,\n output: Readonly<Record<FlowPortId, FlowDataRef>>,\n startedAt: string,\n clock: FlowClock,\n): FlowStepSnapshot => ({\n id: `${node.id}:1`,\n nodeId: node.id,\n status: 'succeeded',\n attempt: 1,\n startedAt,\n completedAt: clock.now().toISOString(),\n input,\n output,\n});\n\n/*** Create a terminal snapshot when cancellation occurs during asynchronous node execution. */\nconst createCancelledStep = (\n node: FlowNode,\n input: Readonly<Record<FlowPortId, FlowDataRef>>,\n startedAt: string,\n clock: FlowClock,\n): FlowStepSnapshot => ({\n id: `${node.id}:1`,\n nodeId: node.id,\n status: 'cancelled',\n attempt: 1,\n startedAt,\n completedAt: clock.now().toISOString(),\n input,\n});\n\n/*** Reject operation outputs that do not correspond to declared output ports. */\nconst assertOutputPorts = (\n node: FlowNode,\n output: Readonly<Record<FlowPortId, FlowDataRef>>,\n): void => {\n const outputIds = new Set(\n node.ports.filter((port) => port.direction === 'output').map(({ id }) => id),\n );\n if (Object.keys(output).some((portId) => !outputIds.has(portId)))\n throw new Error(`FLOW_OUTPUT_UNDECLARED_PORT:${node.id}`);\n const missingRequired = node.ports.some(\n (port) => port.direction === 'output' && port.required && output[port.id] === undefined,\n );\n if (missingRequired) throw new Error(`FLOW_OUTPUT_REQUIRED_PORT_MISSING:${node.id}`);\n};\n\n/*** Validate inline port values against their declared portable schemas. */\nconst assertPortSchemas = (\n node: FlowNode,\n values: Readonly<Record<FlowPortId, FlowDataRef>>,\n direction: 'input' | 'output',\n schemas: FlowRunRequest['schemaRegistry'],\n): void => {\n for (const port of node.ports.filter((candidate) => candidate.direction === direction)) {\n const value = values[port.id];\n const schema =\n port.data?.schema ?? (port.data?.schemaRef ? { ref: port.data.schemaRef } : undefined);\n if (value?.kind === 'inline' && schema && !validateFlowDataSchema(value.value, schema, schemas))\n throw new Error(`FLOW_PORT_SCHEMA_MISMATCH:${node.id}:${port.id}`);\n }\n};\n\n/*** Propagate outputs only over edges whose declared condition matches the output value. */\nconst propagateOutput = (\n edges: readonly FlowEdge[],\n output: Readonly<Record<FlowPortId, FlowDataRef>>,\n outcome: 'failure' | 'success',\n state: ExecutionState,\n): void => {\n for (const edge of edges) {\n const value = output[edge.source.portId];\n if (!matchesEdge(edge, value, outcome)) continue;\n state.activatedNodeIds.add(edge.target.nodeId);\n if (value)\n state.inputs[edge.target.nodeId] = {\n ...state.inputs[edge.target.nodeId],\n [edge.target.portId]: value,\n };\n }\n};\n\n/*** Evaluate an edge condition without executing arbitrary expressions. */\nconst matchesEdge = (\n edge: FlowEdge,\n value: FlowDataRef | undefined,\n outcome: 'failure' | 'success',\n): boolean => {\n const condition = edge.condition ?? { kind: 'always' as const };\n if (condition.kind === 'always') return outcome === 'success';\n if (condition.kind === 'outcome') return condition.outcome === outcome;\n if (value?.kind !== 'inline') return false;\n const selected = resolvePointer(value.value, condition.path);\n return selected !== undefined && structurallyEqual(selected, condition.equals);\n};\n\n/*** Resolve an RFC 6901 pointer against serializable inline data. */\nconst resolvePointer = (\n value: DataContractValue,\n pointer: string,\n): DataContractValue | undefined => {\n if (pointer === '') return value;\n if (!pointer.startsWith('/')) return undefined;\n return pointer\n .slice(1)\n .split('/')\n .reduce<DataContractValue | undefined>((current, segment) => {\n if (current === undefined || current === null || typeof current !== 'object')\n return undefined;\n const key = segment.replaceAll('~1', '/').replaceAll('~0', '~');\n if (isDataArray(current)) return current.find((_item, index) => index === Number(key));\n return isDataRecord(current)\n ? Object.entries(current).find(([candidateKey]) => candidateKey === key)?.[1]\n : undefined;\n }, value);\n};\n\n/*** Compare JSON-compatible values with object-key-order independence. */\nconst structurallyEqual = (left: DataContractValue, right: DataContractValue): boolean =>\n JSON.stringify(normalizeValue(left)) === JSON.stringify(normalizeValue(right));\n\n/*** Canonicalize object member ordering for structural equality. */\nconst normalizeValue = (value: DataContractValue): DataContractValue => {\n if (Array.isArray(value)) return value.map(normalizeValue);\n if (value !== null && typeof value === 'object')\n return Object.fromEntries(\n Object.entries(value)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, item]) => [key, normalizeValue(item)]),\n );\n return value;\n};\n\n/*** Narrow a serializable non-array object to an indexable data record. */\nconst isDataRecord = (value: object): value is Readonly<Record<string, DataContractValue>> =>\n !Array.isArray(value);\n\n/*** Narrow a serializable array without losing its element type. */\nconst isDataArray = (value: object): value is readonly DataContractValue[] => Array.isArray(value);\n\n/*** Translate host failures to structured contract errors without exposing arbitrary objects. */\nconst asFlowError = (reason: unknown): FlowError => ({\n code:\n reason instanceof Error && /^FLOW_[A-Z0-9_]+(?::|$)/.test(reason.message)\n ? (reason.message.split(':')[0] ?? 'FLOW_OPERATION_FAILED')\n : 'FLOW_OPERATION_FAILED',\n message: 'The operation failed.',\n retryable: false,\n});\n\n/*** Build the immutable run result emitted by the single-process executor. */\nconst createRun = (\n graph: FlowCompiledGraph,\n request: FlowRunRequest,\n steps: readonly FlowStepSnapshot[],\n startedAt: string,\n completedAt: string,\n status: FlowRunSnapshot['status'],\n error?: FlowError,\n): FlowRunSnapshot => ({\n id: request.runId,\n flow: { id: graph.definition.id, version: graph.definition.version },\n status,\n trigger: { triggerId: request.triggerId, kind: 'manual' },\n createdAt: startedAt,\n startedAt,\n completedAt,\n steps,\n error,\n});\n"]}
@@ -0,0 +1,52 @@
1
+ import type { FlowDurableRun, FlowDurableWork, FlowRetryPolicy } from '../../../types/durableExecution.js';
2
+ import type { FlowClock } from '../../../types/execution.js';
3
+ import type { FlowSecurityPort } from '../../../types/security.js';
4
+ import type { FlowPersistencePort } from '../ports/FlowPersistencePort.js';
5
+ /** Claim bounded durable work and execute it with fenced recovery, retry, and approval handling. */
6
+ export declare const runDurableWorkerAsync: (input: {
7
+ readonly approvalPollIntervalMs: number;
8
+ readonly concurrency: number;
9
+ readonly leaseDurationMs: number;
10
+ readonly limit: number;
11
+ readonly retry: FlowRetryPolicy;
12
+ readonly workerId: string;
13
+ }, dependencies: {
14
+ readonly clock: FlowClock;
15
+ readonly effects: FlowDurableEffectPort;
16
+ readonly persistence: FlowPersistencePort;
17
+ readonly security?: FlowSecurityPort;
18
+ }) => Promise<FlowWorkerResult>;
19
+ /** Application-owned effect boundary; adapters must apply the supplied idempotency key at their side-effect boundary. */
20
+ export interface FlowDurableEffectPort {
21
+ readonly compensateAsync?: (input: {
22
+ readonly run: FlowDurableRun;
23
+ readonly work: FlowDurableWork;
24
+ }) => Promise<void>;
25
+ readonly executeAsync: (input: {
26
+ readonly idempotencyKey: string;
27
+ readonly run: FlowDurableRun;
28
+ readonly work: FlowDurableWork;
29
+ }) => Promise<void>;
30
+ /**
31
+ * Query the provider's idempotency boundary before a protected replay. A provider must return
32
+ * `delivered` only when the stable key proves that the original effect completed.
33
+ */
34
+ readonly reconcileAsync?: (input: {
35
+ readonly idempotencyKey: string;
36
+ readonly run: FlowDurableRun;
37
+ readonly work: FlowDurableWork;
38
+ }) => Promise<'delivered' | 'unknown'>;
39
+ }
40
+ /** Observable outcome for every delivery claimed by one bounded worker tick. */
41
+ export interface FlowWorkerOutcome {
42
+ readonly error?: string;
43
+ readonly status: 'already_completed' | 'cancelled' | 'failed' | 'lost_lease' | 'retry_scheduled' | 'succeeded' | 'waiting_approval';
44
+ readonly workId: string;
45
+ }
46
+ /** Summary of one worker tick; delivery remains at-least-once until fenced completion. */
47
+ export interface FlowWorkerResult {
48
+ readonly claimedWorkIds: readonly string[];
49
+ readonly outcomes: readonly FlowWorkerOutcome[];
50
+ readonly recoveredWorkIds: readonly string[];
51
+ }
52
+ //# sourceMappingURL=runDurableWorkerAsync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runDurableWorkerAsync.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/application/runDurableWorkerAsync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,eAAe,EAEhB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAGnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAE3E,oGAAoG;AACpG,eAAO,MAAM,qBAAqB,GAChC,OAAO;IACL,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,EACD,cAAc;IACZ,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CACtC,KACA,OAAO,CAAC,gBAAgB,CAc1B,CAAC;AA6PF,yHAAyH;AACzH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QACjC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;QAC7B,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;KAChC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE;QAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;QAC7B,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;KAChC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;QAC7B,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;KAChC,KAAK,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;CACxC;AAED,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EACX,mBAAmB,GACnB,WAAW,GACX,QAAQ,GACR,YAAY,GACZ,iBAAiB,GACjB,WAAW,GACX,kBAAkB,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,0FAA0F;AAC1F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAChD,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C"}
@@ -0,0 +1,195 @@
1
+ import { FlowSecurityError } from '../../security/FlowSecurityError.js';
2
+ import { createRetryWork } from '../domain/createRetryWork.js';
3
+ /** Claim bounded durable work and execute it with fenced recovery, retry, and approval handling. */
4
+ export const runDurableWorkerAsync = async (input, dependencies) => {
5
+ assertWorkerInput(input);
6
+ const now = dependencies.clock.now();
7
+ const recoveredWorkIds = await dependencies.persistence.recoverExpiredClaimsAsync(now);
8
+ const claimed = await dependencies.persistence.claimDueWorkAsync({
9
+ leaseDurationMs: input.leaseDurationMs,
10
+ limit: Math.min(input.limit, input.concurrency),
11
+ now,
12
+ workerId: input.workerId,
13
+ });
14
+ const outcomes = await Promise.all(claimed.map((work) => processWorkAsync(work, input, dependencies)));
15
+ return { claimedWorkIds: claimed.map((work) => work.id), outcomes, recoveredWorkIds };
16
+ };
17
+ /** Executes one effect through the durable step and security boundaries. */
18
+ const processWorkAsync = async (work, input, dependencies) => {
19
+ const { lease } = work;
20
+ if (lease === undefined)
21
+ return { status: 'lost_lease', workId: work.id };
22
+ const run = await dependencies.persistence.readRunAsync(work.runId);
23
+ if (run === undefined || run.status === 'cancelled') {
24
+ await completeAsync(work, input.workerId, dependencies);
25
+ return { status: 'cancelled', workId: work.id };
26
+ }
27
+ if (isCompleted(run.checkpoint, work.stepId)) {
28
+ await completeAsync(work, input.workerId, dependencies);
29
+ return { status: 'already_completed', workId: work.id };
30
+ }
31
+ try {
32
+ const outbox = await prepareOutboxAsync(work, dependencies);
33
+ if (outbox.status === 'delivered') {
34
+ const transitioned = await transitionAsync(run, 'succeeded', completedCheckpoint(run.checkpoint, work.stepId), dependencies);
35
+ const completed = await completeAsync(work, input.workerId, dependencies);
36
+ return {
37
+ status: transitioned && completed ? 'already_completed' : 'lost_lease',
38
+ workId: work.id,
39
+ };
40
+ }
41
+ const effectStatus = await executeEffectAsync(work, run, dependencies);
42
+ await dependencies.persistence.markOutboxDeliveredAsync(work.id);
43
+ const transitioned = await transitionAsync(run, 'succeeded', completedCheckpoint(run.checkpoint, work.stepId), dependencies);
44
+ if (!transitioned || !(await completeAsync(work, input.workerId, dependencies)))
45
+ return { status: 'lost_lease', workId: work.id };
46
+ return { status: effectStatus, workId: work.id };
47
+ }
48
+ catch (error) {
49
+ if (isApprovalPending(error))
50
+ return waitForApprovalAsync(work, run, input, dependencies);
51
+ return retryOrFailAsync(work, run, input, dependencies, error);
52
+ }
53
+ };
54
+ /** Create an idempotency-keyed outbox record before delivery, or resume its pending record after a crash. */
55
+ const prepareOutboxAsync = async (work, dependencies) => {
56
+ const existing = await dependencies.persistence.readOutboxAsync(work.id);
57
+ if (existing !== undefined)
58
+ return existing;
59
+ const now = dependencies.clock.now().toISOString();
60
+ await dependencies.persistence.createOutboxAsync({
61
+ createdAt: now,
62
+ id: work.id,
63
+ idempotencyKey: work.idempotencyKey ?? work.id,
64
+ runId: work.runId,
65
+ status: 'pending',
66
+ workId: work.id,
67
+ });
68
+ const created = await dependencies.persistence.readOutboxAsync(work.id);
69
+ if (created === undefined)
70
+ throw new Error('FLOW_OUTBOX_CREATE_FAILED');
71
+ return created;
72
+ };
73
+ /** Runs internal work directly, while protected provider work requires reconciliation and a canonical permit. */
74
+ const executeEffectAsync = async (work, run, dependencies) => {
75
+ const execute = () => dependencies.effects.executeAsync({
76
+ idempotencyKey: work.idempotencyKey ?? work.id,
77
+ run,
78
+ work,
79
+ });
80
+ if (work.effectKind === 'internal') {
81
+ if (work.securityOperation !== undefined)
82
+ throw new Error('FLOW_INTERNAL_EFFECT_SECURITY_OPERATION');
83
+ await execute();
84
+ return 'succeeded';
85
+ }
86
+ if (work.securityOperation === undefined)
87
+ throw new Error('FLOW_PROTECTED_EFFECT_SECURITY_OPERATION');
88
+ if (dependencies.security === undefined)
89
+ throw new Error('FLOW_SECURITY_PORT_REQUIRED');
90
+ if (dependencies.effects.reconcileAsync === undefined)
91
+ throw new Error('FLOW_PROTECTED_EFFECT_RECONCILIATION_REQUIRED');
92
+ if ((await dependencies.effects.reconcileAsync({
93
+ idempotencyKey: work.idempotencyKey ?? work.id,
94
+ run,
95
+ work,
96
+ })) === 'delivered')
97
+ return 'succeeded';
98
+ const permit = await dependencies.security.authorizeAsync(work.securityOperation);
99
+ await permit.executeAsync(async () => execute());
100
+ return 'succeeded';
101
+ };
102
+ /** Persist a waiting approval and defer its at-least-once delivery without consuming the lease. */
103
+ const waitForApprovalAsync = async (work, run, input, dependencies) => {
104
+ const transitioned = await transitionAsync(run, 'waiting_approval', run.checkpoint, dependencies);
105
+ const released = await dependencies.persistence.releaseWorkAsync({
106
+ dueAt: new Date(dependencies.clock.now().getTime() + input.approvalPollIntervalMs).toISOString(),
107
+ id: work.id,
108
+ leaseFencingToken: claimFencingToken(work),
109
+ workerId: input.workerId,
110
+ });
111
+ return { status: transitioned && released ? 'waiting_approval' : 'lost_lease', workId: work.id };
112
+ };
113
+ /** Persist a bounded retry or terminal failure before releasing the claimed delivery. */
114
+ const retryOrFailAsync = async (work, run, input, dependencies, error) => {
115
+ if (work.attempt >= input.retry.maxAttempts) {
116
+ await dependencies.effects.compensateAsync?.({ run, work });
117
+ const transitioned = await transitionAsync(run, 'failed', failedCheckpoint(run.checkpoint, work.stepId), dependencies);
118
+ const completed = await completeAsync(work, input.workerId, dependencies);
119
+ return {
120
+ error: errorMessage(error),
121
+ status: transitioned && completed ? 'failed' : 'lost_lease',
122
+ workId: work.id,
123
+ };
124
+ }
125
+ await dependencies.persistence.enqueueWorkAsync(createRetryWork(work, dependencies.clock.now(), input.retry));
126
+ const completed = await completeAsync(work, input.workerId, dependencies);
127
+ return {
128
+ error: errorMessage(error),
129
+ status: completed ? 'retry_scheduled' : 'lost_lease',
130
+ workId: work.id,
131
+ };
132
+ };
133
+ /** Compare-and-swap a run state while preserving its immutable flow revision. */
134
+ const transitionAsync = (run, status, checkpoint, dependencies) => dependencies.persistence.transitionRunAsync({
135
+ expectedRevision: run.revision,
136
+ run: {
137
+ ...run,
138
+ ...(checkpoint === undefined ? {} : { checkpoint }),
139
+ revision: run.revision + 1,
140
+ status,
141
+ updatedAt: dependencies.clock.now().toISOString(),
142
+ },
143
+ });
144
+ /** Complete a work lease only when its owner and fencing token still match. */
145
+ const completeAsync = (work, workerId, dependencies) => dependencies.persistence.completeWorkAsync({
146
+ id: work.id,
147
+ leaseFencingToken: claimFencingToken(work),
148
+ now: dependencies.clock.now(),
149
+ workerId,
150
+ });
151
+ /** Read the lease token only from a claimed work item. */
152
+ const claimFencingToken = (work) => {
153
+ if (work.lease === undefined)
154
+ throw new Error('FLOW_WORK_LEASE_MISSING');
155
+ return work.lease.fencingToken;
156
+ };
157
+ /** Add a successful step exactly once to an immutable checkpoint. */
158
+ const completedCheckpoint = (checkpoint, stepId) => ({
159
+ completedStepIds: unique([
160
+ ...(checkpoint?.completedStepIds ?? []),
161
+ ...(stepId === undefined ? [] : [stepId]),
162
+ ]),
163
+ ...(checkpoint?.data === undefined ? {} : { data: checkpoint.data }),
164
+ failedStepIds: checkpoint?.failedStepIds ?? [],
165
+ });
166
+ /** Add a terminally failed step to an immutable checkpoint. */
167
+ const failedCheckpoint = (checkpoint, stepId) => ({
168
+ completedStepIds: checkpoint?.completedStepIds ?? [],
169
+ ...(checkpoint?.data === undefined ? {} : { data: checkpoint.data }),
170
+ failedStepIds: unique([
171
+ ...(checkpoint?.failedStepIds ?? []),
172
+ ...(stepId === undefined ? [] : [stepId]),
173
+ ]),
174
+ });
175
+ /** Check whether a completed checkpoint makes a duplicate delivery a no-op. */
176
+ const isCompleted = (checkpoint, stepId) => stepId !== undefined && checkpoint?.completedStepIds.includes(stepId) === true;
177
+ /** Validate finite worker bounds before touching persistence. */
178
+ const assertWorkerInput = (input) => {
179
+ for (const value of [
180
+ input.approvalPollIntervalMs,
181
+ input.concurrency,
182
+ input.leaseDurationMs,
183
+ input.limit,
184
+ input.retry.maxAttempts,
185
+ ])
186
+ if (!Number.isSafeInteger(value) || value < 1)
187
+ throw new Error('FLOW_WORKER_INPUT_INVALID');
188
+ };
189
+ /** Identify the canonical pending-approval error without interpreting untrusted error shapes. */
190
+ const isApprovalPending = (error) => error instanceof FlowSecurityError && error.code === 'approval_pending';
191
+ /** Preserve a concise, provider-neutral failure signal in the worker result. */
192
+ const errorMessage = (error) => error instanceof Error ? error.message : 'FLOW_EFFECT_FAILED';
193
+ /** Keep persisted checkpoint arrays deterministic. */
194
+ const unique = (values) => [...new Set(values)];
195
+ //# sourceMappingURL=runDurableWorkerAsync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runDurableWorkerAsync.js","sourceRoot":"","sources":["../../../../src/features/execution/application/runDurableWorkerAsync.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n FlowDurableRun,\n FlowDurableWork,\n FlowRetryPolicy,\n FlowRunCheckpoint,\n} from '../../../types/durableExecution.js';\nimport type { FlowClock } from '../../../types/execution.js';\nimport type { FlowSecurityPort } from '../../../types/security.js';\nimport { FlowSecurityError } from '../../security/FlowSecurityError.js';\nimport { createRetryWork } from '../domain/createRetryWork.js';\nimport type { FlowPersistencePort } from '../ports/FlowPersistencePort.js';\n\n/** Claim bounded durable work and execute it with fenced recovery, retry, and approval handling. */\nexport const runDurableWorkerAsync = async (\n input: {\n readonly approvalPollIntervalMs: number;\n readonly concurrency: number;\n readonly leaseDurationMs: number;\n readonly limit: number;\n readonly retry: FlowRetryPolicy;\n readonly workerId: string;\n },\n dependencies: {\n readonly clock: FlowClock;\n readonly effects: FlowDurableEffectPort;\n readonly persistence: FlowPersistencePort;\n readonly security?: FlowSecurityPort;\n },\n): Promise<FlowWorkerResult> => {\n assertWorkerInput(input);\n const now = dependencies.clock.now();\n const recoveredWorkIds = await dependencies.persistence.recoverExpiredClaimsAsync(now);\n const claimed = await dependencies.persistence.claimDueWorkAsync({\n leaseDurationMs: input.leaseDurationMs,\n limit: Math.min(input.limit, input.concurrency),\n now,\n workerId: input.workerId,\n });\n const outcomes = await Promise.all(\n claimed.map((work) => processWorkAsync(work, input, dependencies)),\n );\n return { claimedWorkIds: claimed.map((work) => work.id), outcomes, recoveredWorkIds };\n};\n\n/** Executes one effect through the durable step and security boundaries. */\nconst processWorkAsync = async (\n work: FlowDurableWork,\n input: Parameters<typeof runDurableWorkerAsync>[0],\n dependencies: Parameters<typeof runDurableWorkerAsync>[1],\n): Promise<FlowWorkerOutcome> => {\n const { lease } = work;\n if (lease === undefined) return { status: 'lost_lease', workId: work.id };\n const run = await dependencies.persistence.readRunAsync(work.runId);\n if (run === undefined || run.status === 'cancelled') {\n await completeAsync(work, input.workerId, dependencies);\n return { status: 'cancelled', workId: work.id };\n }\n if (isCompleted(run.checkpoint, work.stepId)) {\n await completeAsync(work, input.workerId, dependencies);\n return { status: 'already_completed', workId: work.id };\n }\n try {\n const outbox = await prepareOutboxAsync(work, dependencies);\n if (outbox.status === 'delivered') {\n const transitioned = await transitionAsync(\n run,\n 'succeeded',\n completedCheckpoint(run.checkpoint, work.stepId),\n dependencies,\n );\n const completed = await completeAsync(work, input.workerId, dependencies);\n return {\n status: transitioned && completed ? 'already_completed' : 'lost_lease',\n workId: work.id,\n };\n }\n const effectStatus = await executeEffectAsync(work, run, dependencies);\n await dependencies.persistence.markOutboxDeliveredAsync(work.id);\n const transitioned = await transitionAsync(\n run,\n 'succeeded',\n completedCheckpoint(run.checkpoint, work.stepId),\n dependencies,\n );\n if (!transitioned || !(await completeAsync(work, input.workerId, dependencies)))\n return { status: 'lost_lease', workId: work.id };\n return { status: effectStatus, workId: work.id };\n } catch (error) {\n if (isApprovalPending(error)) return waitForApprovalAsync(work, run, input, dependencies);\n return retryOrFailAsync(work, run, input, dependencies, error);\n }\n};\n\n/** Create an idempotency-keyed outbox record before delivery, or resume its pending record after a crash. */\nconst prepareOutboxAsync = async (\n work: FlowDurableWork,\n dependencies: Parameters<typeof runDurableWorkerAsync>[1],\n) => {\n const existing = await dependencies.persistence.readOutboxAsync(work.id);\n if (existing !== undefined) return existing;\n const now = dependencies.clock.now().toISOString();\n await dependencies.persistence.createOutboxAsync({\n createdAt: now,\n id: work.id,\n idempotencyKey: work.idempotencyKey ?? work.id,\n runId: work.runId,\n status: 'pending',\n workId: work.id,\n });\n const created = await dependencies.persistence.readOutboxAsync(work.id);\n if (created === undefined) throw new Error('FLOW_OUTBOX_CREATE_FAILED');\n return created;\n};\n\n/** Runs internal work directly, while protected provider work requires reconciliation and a canonical permit. */\nconst executeEffectAsync = async (\n work: FlowDurableWork,\n run: FlowDurableRun,\n dependencies: Parameters<typeof runDurableWorkerAsync>[1],\n): Promise<'succeeded'> => {\n const execute = () =>\n dependencies.effects.executeAsync({\n idempotencyKey: work.idempotencyKey ?? work.id,\n run,\n work,\n });\n if (work.effectKind === 'internal') {\n if (work.securityOperation !== undefined)\n throw new Error('FLOW_INTERNAL_EFFECT_SECURITY_OPERATION');\n await execute();\n return 'succeeded';\n }\n if (work.securityOperation === undefined)\n throw new Error('FLOW_PROTECTED_EFFECT_SECURITY_OPERATION');\n if (dependencies.security === undefined) throw new Error('FLOW_SECURITY_PORT_REQUIRED');\n if (dependencies.effects.reconcileAsync === undefined)\n throw new Error('FLOW_PROTECTED_EFFECT_RECONCILIATION_REQUIRED');\n if (\n (await dependencies.effects.reconcileAsync({\n idempotencyKey: work.idempotencyKey ?? work.id,\n run,\n work,\n })) === 'delivered'\n )\n return 'succeeded';\n const permit = await dependencies.security.authorizeAsync(work.securityOperation);\n await permit.executeAsync(async () => execute());\n return 'succeeded';\n};\n\n/** Persist a waiting approval and defer its at-least-once delivery without consuming the lease. */\nconst waitForApprovalAsync = async (\n work: FlowDurableWork,\n run: FlowDurableRun,\n input: Parameters<typeof runDurableWorkerAsync>[0],\n dependencies: Parameters<typeof runDurableWorkerAsync>[1],\n): Promise<FlowWorkerOutcome> => {\n const transitioned = await transitionAsync(run, 'waiting_approval', run.checkpoint, dependencies);\n const released = await dependencies.persistence.releaseWorkAsync({\n dueAt: new Date(\n dependencies.clock.now().getTime() + input.approvalPollIntervalMs,\n ).toISOString(),\n id: work.id,\n leaseFencingToken: claimFencingToken(work),\n workerId: input.workerId,\n });\n return { status: transitioned && released ? 'waiting_approval' : 'lost_lease', workId: work.id };\n};\n\n/** Persist a bounded retry or terminal failure before releasing the claimed delivery. */\nconst retryOrFailAsync = async (\n work: FlowDurableWork,\n run: FlowDurableRun,\n input: Parameters<typeof runDurableWorkerAsync>[0],\n dependencies: Parameters<typeof runDurableWorkerAsync>[1],\n error: unknown,\n): Promise<FlowWorkerOutcome> => {\n if (work.attempt >= input.retry.maxAttempts) {\n await dependencies.effects.compensateAsync?.({ run, work });\n const transitioned = await transitionAsync(\n run,\n 'failed',\n failedCheckpoint(run.checkpoint, work.stepId),\n dependencies,\n );\n const completed = await completeAsync(work, input.workerId, dependencies);\n return {\n error: errorMessage(error),\n status: transitioned && completed ? 'failed' : 'lost_lease',\n workId: work.id,\n };\n }\n await dependencies.persistence.enqueueWorkAsync(\n createRetryWork(work, dependencies.clock.now(), input.retry),\n );\n const completed = await completeAsync(work, input.workerId, dependencies);\n return {\n error: errorMessage(error),\n status: completed ? 'retry_scheduled' : 'lost_lease',\n workId: work.id,\n };\n};\n\n/** Compare-and-swap a run state while preserving its immutable flow revision. */\nconst transitionAsync = (\n run: FlowDurableRun,\n status: FlowDurableRun['status'],\n checkpoint: FlowRunCheckpoint | undefined,\n dependencies: Parameters<typeof runDurableWorkerAsync>[1],\n): Promise<boolean> =>\n dependencies.persistence.transitionRunAsync({\n expectedRevision: run.revision,\n run: {\n ...run,\n ...(checkpoint === undefined ? {} : { checkpoint }),\n revision: run.revision + 1,\n status,\n updatedAt: dependencies.clock.now().toISOString(),\n },\n });\n\n/** Complete a work lease only when its owner and fencing token still match. */\nconst completeAsync = (\n work: FlowDurableWork,\n workerId: string,\n dependencies: Parameters<typeof runDurableWorkerAsync>[1],\n): Promise<boolean> =>\n dependencies.persistence.completeWorkAsync({\n id: work.id,\n leaseFencingToken: claimFencingToken(work),\n now: dependencies.clock.now(),\n workerId,\n });\n\n/** Read the lease token only from a claimed work item. */\nconst claimFencingToken = (work: FlowDurableWork): number => {\n if (work.lease === undefined) throw new Error('FLOW_WORK_LEASE_MISSING');\n return work.lease.fencingToken;\n};\n\n/** Add a successful step exactly once to an immutable checkpoint. */\nconst completedCheckpoint = (\n checkpoint: FlowRunCheckpoint | undefined,\n stepId: string | undefined,\n): FlowRunCheckpoint => ({\n completedStepIds: unique([\n ...(checkpoint?.completedStepIds ?? []),\n ...(stepId === undefined ? [] : [stepId]),\n ]),\n ...(checkpoint?.data === undefined ? {} : { data: checkpoint.data }),\n failedStepIds: checkpoint?.failedStepIds ?? [],\n});\n\n/** Add a terminally failed step to an immutable checkpoint. */\nconst failedCheckpoint = (\n checkpoint: FlowRunCheckpoint | undefined,\n stepId: string | undefined,\n): FlowRunCheckpoint => ({\n completedStepIds: checkpoint?.completedStepIds ?? [],\n ...(checkpoint?.data === undefined ? {} : { data: checkpoint.data }),\n failedStepIds: unique([\n ...(checkpoint?.failedStepIds ?? []),\n ...(stepId === undefined ? [] : [stepId]),\n ]),\n});\n\n/** Check whether a completed checkpoint makes a duplicate delivery a no-op. */\nconst isCompleted = (\n checkpoint: FlowRunCheckpoint | undefined,\n stepId: string | undefined,\n): boolean => stepId !== undefined && checkpoint?.completedStepIds.includes(stepId) === true;\n\n/** Validate finite worker bounds before touching persistence. */\nconst assertWorkerInput = (input: Parameters<typeof runDurableWorkerAsync>[0]): void => {\n for (const value of [\n input.approvalPollIntervalMs,\n input.concurrency,\n input.leaseDurationMs,\n input.limit,\n input.retry.maxAttempts,\n ])\n if (!Number.isSafeInteger(value) || value < 1) throw new Error('FLOW_WORKER_INPUT_INVALID');\n};\n\n/** Identify the canonical pending-approval error without interpreting untrusted error shapes. */\nconst isApprovalPending = (error: unknown): boolean =>\n error instanceof FlowSecurityError && error.code === 'approval_pending';\n\n/** Preserve a concise, provider-neutral failure signal in the worker result. */\nconst errorMessage = (error: unknown): string =>\n error instanceof Error ? error.message : 'FLOW_EFFECT_FAILED';\n\n/** Keep persisted checkpoint arrays deterministic. */\nconst unique = (values: readonly string[]): readonly string[] => [...new Set(values)];\n\n/** Application-owned effect boundary; adapters must apply the supplied idempotency key at their side-effect boundary. */\nexport interface FlowDurableEffectPort {\n readonly compensateAsync?: (input: {\n readonly run: FlowDurableRun;\n readonly work: FlowDurableWork;\n }) => Promise<void>;\n readonly executeAsync: (input: {\n readonly idempotencyKey: string;\n readonly run: FlowDurableRun;\n readonly work: FlowDurableWork;\n }) => Promise<void>;\n /**\n * Query the provider's idempotency boundary before a protected replay. A provider must return\n * `delivered` only when the stable key proves that the original effect completed.\n */\n readonly reconcileAsync?: (input: {\n readonly idempotencyKey: string;\n readonly run: FlowDurableRun;\n readonly work: FlowDurableWork;\n }) => Promise<'delivered' | 'unknown'>;\n}\n\n/** Observable outcome for every delivery claimed by one bounded worker tick. */\nexport interface FlowWorkerOutcome {\n readonly error?: string;\n readonly status:\n | 'already_completed'\n | 'cancelled'\n | 'failed'\n | 'lost_lease'\n | 'retry_scheduled'\n | 'succeeded'\n | 'waiting_approval';\n readonly workId: string;\n}\n\n/** Summary of one worker tick; delivery remains at-least-once until fenced completion. */\nexport interface FlowWorkerResult {\n readonly claimedWorkIds: readonly string[];\n readonly outcomes: readonly FlowWorkerOutcome[];\n readonly recoveredWorkIds: readonly string[];\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import type { DataSchemaRegistry } from '@ankhorage/contracts/data';
2
+ import type { FlowNode } from '@ankhorage/contracts/flow';
3
+ /*** Validate every declared graph port schema before any operation can perform a side effect. */
4
+ export declare const assertFlowGraphSchemasSupported: (nodes: readonly FlowNode[], schemas: DataSchemaRegistry | undefined) => void;
5
+ //# sourceMappingURL=assertFlowGraphSchemasSupported.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertFlowGraphSchemasSupported.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/domain/assertFlowGraphSchemasSupported.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAI1D,iGAAiG;AACjG,eAAO,MAAM,+BAA+B,GAC1C,OAAO,SAAS,QAAQ,EAAE,EAC1B,SAAS,kBAAkB,GAAG,SAAS,KACtC,IAOF,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { assertFlowDataSchemaSupported } from './validateFlowDataSchema.js';
2
+ /*** Validate every declared graph port schema before any operation can perform a side effect. */
3
+ export const assertFlowGraphSchemasSupported = (nodes, schemas) => {
4
+ for (const node of nodes)
5
+ for (const port of node.ports) {
6
+ const schema = port.data?.schema ?? (port.data?.schemaRef ? { ref: port.data.schemaRef } : undefined);
7
+ if (schema)
8
+ assertFlowDataSchemaSupported(schema, schemas);
9
+ }
10
+ };
11
+ //# sourceMappingURL=assertFlowGraphSchemasSupported.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertFlowGraphSchemasSupported.js","sourceRoot":"","sources":["../../../../src/features/execution/domain/assertFlowGraphSchemasSupported.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAE5E,iGAAiG;AACjG,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAC7C,KAA0B,EAC1B,OAAuC,EACjC,EAAE;IACR,KAAK,MAAM,IAAI,IAAI,KAAK;QACtB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,MAAM,GACV,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACzF,IAAI,MAAM;gBAAE,6BAA6B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC7D,CAAC;AACL,CAAC,CAAC","sourcesContent":["import type { DataSchemaRegistry } from '@ankhorage/contracts/data';\nimport type { FlowNode } from '@ankhorage/contracts/flow';\n\nimport { assertFlowDataSchemaSupported } from './validateFlowDataSchema.js';\n\n/*** Validate every declared graph port schema before any operation can perform a side effect. */\nexport const assertFlowGraphSchemasSupported = (\n nodes: readonly FlowNode[],\n schemas: DataSchemaRegistry | undefined,\n): void => {\n for (const node of nodes)\n for (const port of node.ports) {\n const schema =\n port.data?.schema ?? (port.data?.schemaRef ? { ref: port.data.schemaRef } : undefined);\n if (schema) assertFlowDataSchemaSupported(schema, schemas);\n }\n};\n"]}
@@ -0,0 +1,4 @@
1
+ import type { FlowRetryPolicy } from '../../../types/durableExecution.js';
2
+ /*** Calculate an exponential retry time from persisted attempt state without ambient time. */
3
+ export declare const calculateRetryDueAt: (now: Date, completedAttempt: number, policy: FlowRetryPolicy) => string;
4
+ //# sourceMappingURL=calculateRetryDueAt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calculateRetryDueAt.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/domain/calculateRetryDueAt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAE1E,8FAA8F;AAC9F,eAAO,MAAM,mBAAmB,GAC9B,KAAK,IAAI,EACT,kBAAkB,MAAM,EACxB,QAAQ,eAAe,KACtB,MAaF,CAAC"}
@@ -0,0 +1,14 @@
1
+ /*** Calculate an exponential retry time from persisted attempt state without ambient time. */
2
+ export const calculateRetryDueAt = (now, completedAttempt, policy) => {
3
+ if (!Number.isSafeInteger(completedAttempt) || completedAttempt < 1)
4
+ throw new Error('FLOW_RETRY_ATTEMPT_INVALID');
5
+ if (!Number.isSafeInteger(policy.baseDelayMs) || policy.baseDelayMs < 0)
6
+ throw new Error('FLOW_RETRY_BASE_DELAY_INVALID');
7
+ if (!Number.isSafeInteger(policy.maxDelayMs) ||
8
+ policy.maxDelayMs < policy.baseDelayMs ||
9
+ policy.maxDelayMs < 0)
10
+ throw new Error('FLOW_RETRY_MAX_DELAY_INVALID');
11
+ const delay = Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** (completedAttempt - 1));
12
+ return new Date(now.getTime() + delay).toISOString();
13
+ };
14
+ //# sourceMappingURL=calculateRetryDueAt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calculateRetryDueAt.js","sourceRoot":"","sources":["../../../../src/features/execution/domain/calculateRetryDueAt.ts"],"names":[],"mappings":"AAEA,8FAA8F;AAC9F,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,GAAS,EACT,gBAAwB,EACxB,MAAuB,EACf,EAAE;IACV,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,IAAI,gBAAgB,GAAG,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC,WAAW,GAAG,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,IACE,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,UAAU,CAAC;QACxC,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW;QACtC,MAAM,CAAC,UAAU,GAAG,CAAC;QAErB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,WAAW,GAAG,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,CAAC;IAC5F,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;AACvD,CAAC,CAAC","sourcesContent":["import type { FlowRetryPolicy } from '../../../types/durableExecution.js';\n\n/*** Calculate an exponential retry time from persisted attempt state without ambient time. */\nexport const calculateRetryDueAt = (\n now: Date,\n completedAttempt: number,\n policy: FlowRetryPolicy,\n): string => {\n if (!Number.isSafeInteger(completedAttempt) || completedAttempt < 1)\n throw new Error('FLOW_RETRY_ATTEMPT_INVALID');\n if (!Number.isSafeInteger(policy.baseDelayMs) || policy.baseDelayMs < 0)\n throw new Error('FLOW_RETRY_BASE_DELAY_INVALID');\n if (\n !Number.isSafeInteger(policy.maxDelayMs) ||\n policy.maxDelayMs < policy.baseDelayMs ||\n policy.maxDelayMs < 0\n )\n throw new Error('FLOW_RETRY_MAX_DELAY_INVALID');\n const delay = Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** (completedAttempt - 1));\n return new Date(now.getTime() + delay).toISOString();\n};\n"]}