@smthrs/flow 0.0.0-stage → 1.0.0-rc.13

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 (654) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/LICENSE +21 -0
  3. package/README.md +212 -2
  4. package/THIRD_PARTY_NOTICES.md +52 -0
  5. package/dist/cjs/Action/Action.d.ts +250 -0
  6. package/dist/cjs/Action/Action.d.ts.map +1 -0
  7. package/dist/cjs/Action/Action.js +42 -0
  8. package/dist/cjs/Action/Action.js.map +7 -0
  9. package/dist/cjs/Action/AttemptTimedOut.d.ts +33 -0
  10. package/dist/cjs/Action/AttemptTimedOut.d.ts.map +1 -0
  11. package/dist/cjs/Action/AttemptTimedOut.js +51 -0
  12. package/dist/cjs/Action/AttemptTimedOut.js.map +7 -0
  13. package/dist/cjs/Action/BoundaryMode.d.ts +29 -0
  14. package/dist/cjs/Action/BoundaryMode.d.ts.map +1 -0
  15. package/dist/cjs/Action/BoundaryMode.js +36 -0
  16. package/dist/cjs/Action/BoundaryMode.js.map +7 -0
  17. package/dist/cjs/Action/CacheEnvironment.d.ts +158 -0
  18. package/dist/cjs/Action/CacheEnvironment.d.ts.map +1 -0
  19. package/dist/cjs/Action/CacheEnvironment.js +57 -0
  20. package/dist/cjs/Action/CacheEnvironment.js.map +7 -0
  21. package/dist/cjs/Action/ConcurrentKeylessDispatch.d.ts +34 -0
  22. package/dist/cjs/Action/ConcurrentKeylessDispatch.d.ts.map +1 -0
  23. package/dist/cjs/Action/ConcurrentKeylessDispatch.js +46 -0
  24. package/dist/cjs/Action/ConcurrentKeylessDispatch.js.map +7 -0
  25. package/dist/cjs/Action/Context.d.ts +131 -0
  26. package/dist/cjs/Action/Context.d.ts.map +1 -0
  27. package/dist/cjs/Action/Context.js +68 -0
  28. package/dist/cjs/Action/Context.js.map +7 -0
  29. package/dist/cjs/Action/DuplicateImplementation.d.ts +20 -0
  30. package/dist/cjs/Action/DuplicateImplementation.d.ts.map +1 -0
  31. package/dist/cjs/Action/DuplicateImplementation.js +46 -0
  32. package/dist/cjs/Action/DuplicateImplementation.js.map +7 -0
  33. package/dist/cjs/Action/FileBoundary.d.ts +59 -0
  34. package/dist/cjs/Action/FileBoundary.d.ts.map +1 -0
  35. package/dist/cjs/Action/FileBoundary.js +81 -0
  36. package/dist/cjs/Action/FileBoundary.js.map +7 -0
  37. package/dist/cjs/Action/FileInput.d.ts +28 -0
  38. package/dist/cjs/Action/FileInput.d.ts.map +1 -0
  39. package/dist/cjs/Action/FileInput.js +42 -0
  40. package/dist/cjs/Action/FileInput.js.map +7 -0
  41. package/dist/cjs/Action/Filegroup.d.ts +16 -0
  42. package/dist/cjs/Action/Filegroup.d.ts.map +1 -0
  43. package/dist/cjs/Action/Filegroup.js +27 -0
  44. package/dist/cjs/Action/Filegroup.js.map +7 -0
  45. package/dist/cjs/Action/Glob.d.ts +8 -0
  46. package/dist/cjs/Action/Glob.d.ts.map +1 -0
  47. package/dist/cjs/Action/Glob.js +28 -0
  48. package/dist/cjs/Action/Glob.js.map +7 -0
  49. package/dist/cjs/Action/ImplementationVersionMismatch.d.ts +26 -0
  50. package/dist/cjs/Action/ImplementationVersionMismatch.d.ts.map +1 -0
  51. package/dist/cjs/Action/ImplementationVersionMismatch.js +45 -0
  52. package/dist/cjs/Action/ImplementationVersionMismatch.js.map +7 -0
  53. package/dist/cjs/Action/Implementations.d.ts +114 -0
  54. package/dist/cjs/Action/Implementations.d.ts.map +1 -0
  55. package/dist/cjs/Action/Implementations.js +65 -0
  56. package/dist/cjs/Action/Implementations.js.map +7 -0
  57. package/dist/cjs/Action/InfraInterrupt.d.ts +26 -0
  58. package/dist/cjs/Action/InfraInterrupt.d.ts.map +1 -0
  59. package/dist/cjs/Action/InfraInterrupt.js +46 -0
  60. package/dist/cjs/Action/InfraInterrupt.js.map +7 -0
  61. package/dist/cjs/Action/InfraInterruptRetriesExhausted.d.ts +33 -0
  62. package/dist/cjs/Action/InfraInterruptRetriesExhausted.d.ts.map +1 -0
  63. package/dist/cjs/Action/InfraInterruptRetriesExhausted.js +54 -0
  64. package/dist/cjs/Action/InfraInterruptRetriesExhausted.js.map +7 -0
  65. package/dist/cjs/Action/IrreversibleRetryRequiresIdempotencyKey.d.ts +26 -0
  66. package/dist/cjs/Action/IrreversibleRetryRequiresIdempotencyKey.d.ts.map +1 -0
  67. package/dist/cjs/Action/IrreversibleRetryRequiresIdempotencyKey.js +47 -0
  68. package/dist/cjs/Action/IrreversibleRetryRequiresIdempotencyKey.js.map +7 -0
  69. package/dist/cjs/Action/StepIdentity.d.ts +153 -0
  70. package/dist/cjs/Action/StepIdentity.d.ts.map +1 -0
  71. package/dist/cjs/Action/StepIdentity.js +75 -0
  72. package/dist/cjs/Action/StepIdentity.js.map +7 -0
  73. package/dist/cjs/Action/TreeArtifact.d.ts +8 -0
  74. package/dist/cjs/Action/TreeArtifact.d.ts.map +1 -0
  75. package/dist/cjs/Action/TreeArtifact.js +25 -0
  76. package/dist/cjs/Action/TreeArtifact.js.map +7 -0
  77. package/dist/cjs/Action/TypeId.d.ts +8 -0
  78. package/dist/cjs/Action/TypeId.d.ts.map +1 -0
  79. package/dist/cjs/Action/TypeId.js +25 -0
  80. package/dist/cjs/Action/TypeId.js.map +7 -0
  81. package/dist/cjs/Action/UncanonicalIdempotencyKey.d.ts +32 -0
  82. package/dist/cjs/Action/UncanonicalIdempotencyKey.d.ts.map +1 -0
  83. package/dist/cjs/Action/UncanonicalIdempotencyKey.js +51 -0
  84. package/dist/cjs/Action/UncanonicalIdempotencyKey.js.map +7 -0
  85. package/dist/cjs/Action/idempotencyKey.d.ts +21 -0
  86. package/dist/cjs/Action/idempotencyKey.d.ts.map +1 -0
  87. package/dist/cjs/Action/idempotencyKey.js +59 -0
  88. package/dist/cjs/Action/idempotencyKey.js.map +7 -0
  89. package/dist/cjs/Action/index.d.ts +29 -0
  90. package/dist/cjs/Action/index.d.ts.map +1 -0
  91. package/dist/cjs/Action/index.js +60 -0
  92. package/dist/cjs/Action/index.js.map +7 -0
  93. package/dist/cjs/Action/make.d.ts +128 -0
  94. package/dist/cjs/Action/make.d.ts.map +1 -0
  95. package/dist/cjs/Action/make.js +387 -0
  96. package/dist/cjs/Action/make.js.map +7 -0
  97. package/dist/cjs/Action/raceAll.d.ts +21 -0
  98. package/dist/cjs/Action/raceAll.d.ts.map +1 -0
  99. package/dist/cjs/Action/raceAll.js +46 -0
  100. package/dist/cjs/Action/raceAll.js.map +7 -0
  101. package/dist/cjs/Action/retry.d.ts +19 -0
  102. package/dist/cjs/Action/retry.d.ts.map +1 -0
  103. package/dist/cjs/Action/retry.js +75 -0
  104. package/dist/cjs/Action/retry.js.map +7 -0
  105. package/dist/cjs/Deadline.d.ts +110 -0
  106. package/dist/cjs/Deadline.d.ts.map +1 -0
  107. package/dist/cjs/Deadline.js +112 -0
  108. package/dist/cjs/Deadline.js.map +7 -0
  109. package/dist/cjs/DurableClock.d.ts +60 -0
  110. package/dist/cjs/DurableClock.d.ts.map +1 -0
  111. package/dist/cjs/DurableClock.js +107 -0
  112. package/dist/cjs/DurableClock.js.map +7 -0
  113. package/dist/cjs/DurableDeferred.d.ts +311 -0
  114. package/dist/cjs/DurableDeferred.d.ts.map +1 -0
  115. package/dist/cjs/DurableDeferred.js +322 -0
  116. package/dist/cjs/DurableDeferred.js.map +7 -0
  117. package/dist/cjs/DurableQueue.d.ts +160 -0
  118. package/dist/cjs/DurableQueue.d.ts.map +1 -0
  119. package/dist/cjs/DurableQueue.js +229 -0
  120. package/dist/cjs/DurableQueue.js.map +7 -0
  121. package/dist/cjs/ExternalJob.d.ts +179 -0
  122. package/dist/cjs/ExternalJob.d.ts.map +1 -0
  123. package/dist/cjs/ExternalJob.js +339 -0
  124. package/dist/cjs/ExternalJob.js.map +7 -0
  125. package/dist/cjs/Fault.d.ts +131 -0
  126. package/dist/cjs/Fault.d.ts.map +1 -0
  127. package/dist/cjs/Fault.js +157 -0
  128. package/dist/cjs/Fault.js.map +7 -0
  129. package/dist/cjs/Flow/Annotations.d.ts +245 -0
  130. package/dist/cjs/Flow/Annotations.d.ts.map +1 -0
  131. package/dist/cjs/Flow/Annotations.js +72 -0
  132. package/dist/cjs/Flow/Annotations.js.map +7 -0
  133. package/dist/cjs/Flow/CapabilityCeiling.d.ts +21 -0
  134. package/dist/cjs/Flow/CapabilityCeiling.d.ts.map +1 -0
  135. package/dist/cjs/Flow/CapabilityCeiling.js +49 -0
  136. package/dist/cjs/Flow/CapabilityCeiling.js.map +7 -0
  137. package/dist/cjs/Flow/DeadlineExceeded.d.ts +33 -0
  138. package/dist/cjs/Flow/DeadlineExceeded.d.ts.map +1 -0
  139. package/dist/cjs/Flow/DeadlineExceeded.js +52 -0
  140. package/dist/cjs/Flow/DeadlineExceeded.js.map +7 -0
  141. package/dist/cjs/Flow/ExecutionIdRequired.d.ts +25 -0
  142. package/dist/cjs/Flow/ExecutionIdRequired.d.ts.map +1 -0
  143. package/dist/cjs/Flow/ExecutionIdRequired.js +46 -0
  144. package/dist/cjs/Flow/ExecutionIdRequired.js.map +7 -0
  145. package/dist/cjs/Flow/ExecutionIds.d.ts +80 -0
  146. package/dist/cjs/Flow/ExecutionIds.d.ts.map +1 -0
  147. package/dist/cjs/Flow/ExecutionIds.js +66 -0
  148. package/dist/cjs/Flow/ExecutionIds.js.map +7 -0
  149. package/dist/cjs/Flow/Flow.d.ts +422 -0
  150. package/dist/cjs/Flow/Flow.d.ts.map +1 -0
  151. package/dist/cjs/Flow/Flow.js +17 -0
  152. package/dist/cjs/Flow/Flow.js.map +7 -0
  153. package/dist/cjs/Flow/MaxRoundsExceeded.d.ts +35 -0
  154. package/dist/cjs/Flow/MaxRoundsExceeded.d.ts.map +1 -0
  155. package/dist/cjs/Flow/MaxRoundsExceeded.js +50 -0
  156. package/dist/cjs/Flow/MaxRoundsExceeded.js.map +7 -0
  157. package/dist/cjs/Flow/Outcome.d.ts +162 -0
  158. package/dist/cjs/Flow/Outcome.d.ts.map +1 -0
  159. package/dist/cjs/Flow/Outcome.js +80 -0
  160. package/dist/cjs/Flow/Outcome.js.map +7 -0
  161. package/dist/cjs/Flow/Result.d.ts +186 -0
  162. package/dist/cjs/Flow/Result.d.ts.map +1 -0
  163. package/dist/cjs/Flow/Result.js +159 -0
  164. package/dist/cjs/Flow/Result.js.map +7 -0
  165. package/dist/cjs/Flow/Runtime.d.ts +98 -0
  166. package/dist/cjs/Flow/Runtime.d.ts.map +1 -0
  167. package/dist/cjs/Flow/Runtime.js +186 -0
  168. package/dist/cjs/Flow/Runtime.js.map +7 -0
  169. package/dist/cjs/Flow/TypeId.d.ts +8 -0
  170. package/dist/cjs/Flow/TypeId.d.ts.map +1 -0
  171. package/dist/cjs/Flow/TypeId.js +25 -0
  172. package/dist/cjs/Flow/TypeId.js.map +7 -0
  173. package/dist/cjs/Flow/index.d.ts +17 -0
  174. package/dist/cjs/Flow/index.d.ts.map +1 -0
  175. package/dist/cjs/Flow/index.js +29 -0
  176. package/dist/cjs/Flow/index.js.map +7 -0
  177. package/dist/cjs/Flow/internal.d.ts +34 -0
  178. package/dist/cjs/Flow/internal.d.ts.map +1 -0
  179. package/dist/cjs/Flow/internal.js +187 -0
  180. package/dist/cjs/Flow/internal.js.map +7 -0
  181. package/dist/cjs/Flow/make.d.ts +149 -0
  182. package/dist/cjs/Flow/make.d.ts.map +1 -0
  183. package/dist/cjs/Flow/make.js +107 -0
  184. package/dist/cjs/Flow/make.js.map +7 -0
  185. package/dist/cjs/FlowRuntime/CancelRequestFailed.d.ts +55 -0
  186. package/dist/cjs/FlowRuntime/CancelRequestFailed.d.ts.map +1 -0
  187. package/dist/cjs/FlowRuntime/CancelRequestFailed.js +51 -0
  188. package/dist/cjs/FlowRuntime/CancelRequestFailed.js.map +7 -0
  189. package/dist/cjs/FlowRuntime/ExecutionIdentityConflict.d.ts +23 -0
  190. package/dist/cjs/FlowRuntime/ExecutionIdentityConflict.d.ts.map +1 -0
  191. package/dist/cjs/FlowRuntime/ExecutionIdentityConflict.js +55 -0
  192. package/dist/cjs/FlowRuntime/ExecutionIdentityConflict.js.map +7 -0
  193. package/dist/cjs/FlowRuntime/FlowCycleDetected.d.ts +28 -0
  194. package/dist/cjs/FlowRuntime/FlowCycleDetected.d.ts.map +1 -0
  195. package/dist/cjs/FlowRuntime/FlowCycleDetected.js +49 -0
  196. package/dist/cjs/FlowRuntime/FlowCycleDetected.js.map +7 -0
  197. package/dist/cjs/FlowRuntime/FlowExecutionNotFound.d.ts +38 -0
  198. package/dist/cjs/FlowRuntime/FlowExecutionNotFound.d.ts.map +1 -0
  199. package/dist/cjs/FlowRuntime/FlowExecutionNotFound.js +53 -0
  200. package/dist/cjs/FlowRuntime/FlowExecutionNotFound.js.map +7 -0
  201. package/dist/cjs/FlowRuntime/FlowInstance.d.ts +115 -0
  202. package/dist/cjs/FlowRuntime/FlowInstance.d.ts.map +1 -0
  203. package/dist/cjs/FlowRuntime/FlowInstance.js +37 -0
  204. package/dist/cjs/FlowRuntime/FlowInstance.js.map +7 -0
  205. package/dist/cjs/FlowRuntime/FlowRuntime.d.ts +204 -0
  206. package/dist/cjs/FlowRuntime/FlowRuntime.d.ts.map +1 -0
  207. package/dist/cjs/FlowRuntime/FlowRuntime.js +37 -0
  208. package/dist/cjs/FlowRuntime/FlowRuntime.js.map +7 -0
  209. package/dist/cjs/FlowRuntime/NodeRecord.d.ts +177 -0
  210. package/dist/cjs/FlowRuntime/NodeRecord.d.ts.map +1 -0
  211. package/dist/cjs/FlowRuntime/NodeRecord.js +17 -0
  212. package/dist/cjs/FlowRuntime/NodeRecord.js.map +7 -0
  213. package/dist/cjs/FlowRuntime/WaitingAnnotation.d.ts +69 -0
  214. package/dist/cjs/FlowRuntime/WaitingAnnotation.d.ts.map +1 -0
  215. package/dist/cjs/FlowRuntime/WaitingAnnotation.js +53 -0
  216. package/dist/cjs/FlowRuntime/WaitingAnnotation.js.map +7 -0
  217. package/dist/cjs/FlowRuntime/index.d.ts +14 -0
  218. package/dist/cjs/FlowRuntime/index.d.ts.map +1 -0
  219. package/dist/cjs/FlowRuntime/index.js +26 -0
  220. package/dist/cjs/FlowRuntime/index.js.map +7 -0
  221. package/dist/cjs/Graph.d.ts +325 -0
  222. package/dist/cjs/Graph.d.ts.map +1 -0
  223. package/dist/cjs/Graph.js +1020 -0
  224. package/dist/cjs/Graph.js.map +7 -0
  225. package/dist/cjs/HumanTask.d.ts +442 -0
  226. package/dist/cjs/HumanTask.d.ts.map +1 -0
  227. package/dist/cjs/HumanTask.js +422 -0
  228. package/dist/cjs/HumanTask.js.map +7 -0
  229. package/dist/cjs/Interpreter.d.ts +223 -0
  230. package/dist/cjs/Interpreter.d.ts.map +1 -0
  231. package/dist/cjs/Interpreter.js +919 -0
  232. package/dist/cjs/Interpreter.js.map +7 -0
  233. package/dist/cjs/Poll.d.ts +240 -0
  234. package/dist/cjs/Poll.d.ts.map +1 -0
  235. package/dist/cjs/Poll.js +154 -0
  236. package/dist/cjs/Poll.js.map +7 -0
  237. package/dist/cjs/RetryPolicy.d.ts +273 -0
  238. package/dist/cjs/RetryPolicy.d.ts.map +1 -0
  239. package/dist/cjs/RetryPolicy.js +251 -0
  240. package/dist/cjs/RetryPolicy.js.map +7 -0
  241. package/dist/cjs/Sleep.d.ts +98 -0
  242. package/dist/cjs/Sleep.d.ts.map +1 -0
  243. package/dist/cjs/Sleep.js +141 -0
  244. package/dist/cjs/Sleep.js.map +7 -0
  245. package/dist/cjs/Stall.d.ts +173 -0
  246. package/dist/cjs/Stall.d.ts.map +1 -0
  247. package/dist/cjs/Stall.js +92 -0
  248. package/dist/cjs/Stall.js.map +7 -0
  249. package/dist/cjs/WaitFor.d.ts +109 -0
  250. package/dist/cjs/WaitFor.d.ts.map +1 -0
  251. package/dist/cjs/WaitFor.js +117 -0
  252. package/dist/cjs/WaitFor.js.map +7 -0
  253. package/dist/cjs/index.d.ts +107 -0
  254. package/dist/cjs/index.d.ts.map +1 -0
  255. package/dist/cjs/index.js +69 -0
  256. package/dist/cjs/index.js.map +7 -0
  257. package/dist/cjs/internal/BoundedJson.d.ts +48 -0
  258. package/dist/cjs/internal/BoundedJson.d.ts.map +1 -0
  259. package/dist/cjs/internal/BoundedJson.js +96 -0
  260. package/dist/cjs/internal/BoundedJson.js.map +7 -0
  261. package/dist/cjs/internal/DeclarationSite.d.ts +135 -0
  262. package/dist/cjs/internal/DeclarationSite.d.ts.map +1 -0
  263. package/dist/cjs/internal/DeclarationSite.js +89 -0
  264. package/dist/cjs/internal/DeclarationSite.js.map +7 -0
  265. package/dist/cjs/internal/Declarations.d.ts +35 -0
  266. package/dist/cjs/internal/Declarations.d.ts.map +1 -0
  267. package/dist/cjs/internal/Declarations.js +50 -0
  268. package/dist/cjs/internal/Declarations.js.map +7 -0
  269. package/dist/cjs/internal/ExecutionMiddleware.d.ts +21 -0
  270. package/dist/cjs/internal/ExecutionMiddleware.d.ts.map +1 -0
  271. package/dist/cjs/internal/ExecutionMiddleware.js +39 -0
  272. package/dist/cjs/internal/ExecutionMiddleware.js.map +7 -0
  273. package/dist/cjs/internal/JsonSchemaSubset.d.ts +133 -0
  274. package/dist/cjs/internal/JsonSchemaSubset.d.ts.map +1 -0
  275. package/dist/cjs/internal/JsonSchemaSubset.js +229 -0
  276. package/dist/cjs/internal/JsonSchemaSubset.js.map +7 -0
  277. package/dist/cjs/internal/OutcomeMarker.d.ts +18 -0
  278. package/dist/cjs/internal/OutcomeMarker.d.ts.map +1 -0
  279. package/dist/cjs/internal/OutcomeMarker.js +27 -0
  280. package/dist/cjs/internal/OutcomeMarker.js.map +7 -0
  281. package/dist/cjs/package.json +1 -0
  282. package/dist/esm/Action/Action.d.ts +250 -0
  283. package/dist/esm/Action/Action.d.ts.map +1 -0
  284. package/dist/esm/Action/Action.js +30 -0
  285. package/dist/esm/Action/Action.js.map +1 -0
  286. package/dist/esm/Action/AttemptTimedOut.d.ts +33 -0
  287. package/dist/esm/Action/AttemptTimedOut.d.ts.map +1 -0
  288. package/dist/esm/Action/AttemptTimedOut.js +32 -0
  289. package/dist/esm/Action/AttemptTimedOut.js.map +1 -0
  290. package/dist/esm/Action/BoundaryMode.d.ts +29 -0
  291. package/dist/esm/Action/BoundaryMode.d.ts.map +1 -0
  292. package/dist/esm/Action/BoundaryMode.js +23 -0
  293. package/dist/esm/Action/BoundaryMode.js.map +1 -0
  294. package/dist/esm/Action/CacheEnvironment.d.ts +158 -0
  295. package/dist/esm/Action/CacheEnvironment.d.ts.map +1 -0
  296. package/dist/esm/Action/CacheEnvironment.js +130 -0
  297. package/dist/esm/Action/CacheEnvironment.js.map +1 -0
  298. package/dist/esm/Action/ConcurrentKeylessDispatch.d.ts +34 -0
  299. package/dist/esm/Action/ConcurrentKeylessDispatch.d.ts.map +1 -0
  300. package/dist/esm/Action/ConcurrentKeylessDispatch.js +33 -0
  301. package/dist/esm/Action/ConcurrentKeylessDispatch.js.map +1 -0
  302. package/dist/esm/Action/Context.d.ts +131 -0
  303. package/dist/esm/Action/Context.d.ts.map +1 -0
  304. package/dist/esm/Action/Context.js +99 -0
  305. package/dist/esm/Action/Context.js.map +1 -0
  306. package/dist/esm/Action/DuplicateImplementation.d.ts +20 -0
  307. package/dist/esm/Action/DuplicateImplementation.d.ts.map +1 -0
  308. package/dist/esm/Action/DuplicateImplementation.js +20 -0
  309. package/dist/esm/Action/DuplicateImplementation.js.map +1 -0
  310. package/dist/esm/Action/FileBoundary.d.ts +59 -0
  311. package/dist/esm/Action/FileBoundary.d.ts.map +1 -0
  312. package/dist/esm/Action/FileBoundary.js +60 -0
  313. package/dist/esm/Action/FileBoundary.js.map +1 -0
  314. package/dist/esm/Action/FileInput.d.ts +28 -0
  315. package/dist/esm/Action/FileInput.d.ts.map +1 -0
  316. package/dist/esm/Action/FileInput.js +32 -0
  317. package/dist/esm/Action/FileInput.js.map +1 -0
  318. package/dist/esm/Action/Filegroup.d.ts +16 -0
  319. package/dist/esm/Action/Filegroup.d.ts.map +1 -0
  320. package/dist/esm/Action/Filegroup.js +15 -0
  321. package/dist/esm/Action/Filegroup.js.map +1 -0
  322. package/dist/esm/Action/Glob.d.ts +8 -0
  323. package/dist/esm/Action/Glob.d.ts.map +1 -0
  324. package/dist/esm/Action/Glob.js +7 -0
  325. package/dist/esm/Action/Glob.js.map +1 -0
  326. package/dist/esm/Action/ImplementationVersionMismatch.d.ts +26 -0
  327. package/dist/esm/Action/ImplementationVersionMismatch.d.ts.map +1 -0
  328. package/dist/esm/Action/ImplementationVersionMismatch.js +24 -0
  329. package/dist/esm/Action/ImplementationVersionMismatch.js.map +1 -0
  330. package/dist/esm/Action/Implementations.d.ts +114 -0
  331. package/dist/esm/Action/Implementations.d.ts.map +1 -0
  332. package/dist/esm/Action/Implementations.js +94 -0
  333. package/dist/esm/Action/Implementations.js.map +1 -0
  334. package/dist/esm/Action/InfraInterrupt.d.ts +26 -0
  335. package/dist/esm/Action/InfraInterrupt.d.ts.map +1 -0
  336. package/dist/esm/Action/InfraInterrupt.js +25 -0
  337. package/dist/esm/Action/InfraInterrupt.js.map +1 -0
  338. package/dist/esm/Action/InfraInterruptRetriesExhausted.d.ts +33 -0
  339. package/dist/esm/Action/InfraInterruptRetriesExhausted.d.ts.map +1 -0
  340. package/dist/esm/Action/InfraInterruptRetriesExhausted.js +32 -0
  341. package/dist/esm/Action/InfraInterruptRetriesExhausted.js.map +1 -0
  342. package/dist/esm/Action/IrreversibleRetryRequiresIdempotencyKey.d.ts +26 -0
  343. package/dist/esm/Action/IrreversibleRetryRequiresIdempotencyKey.d.ts.map +1 -0
  344. package/dist/esm/Action/IrreversibleRetryRequiresIdempotencyKey.js +25 -0
  345. package/dist/esm/Action/IrreversibleRetryRequiresIdempotencyKey.js.map +1 -0
  346. package/dist/esm/Action/StepIdentity.d.ts +153 -0
  347. package/dist/esm/Action/StepIdentity.d.ts.map +1 -0
  348. package/dist/esm/Action/StepIdentity.js +139 -0
  349. package/dist/esm/Action/StepIdentity.js.map +1 -0
  350. package/dist/esm/Action/TreeArtifact.d.ts +8 -0
  351. package/dist/esm/Action/TreeArtifact.d.ts.map +1 -0
  352. package/dist/esm/Action/TreeArtifact.js +7 -0
  353. package/dist/esm/Action/TreeArtifact.js.map +1 -0
  354. package/dist/esm/Action/TypeId.d.ts +8 -0
  355. package/dist/esm/Action/TypeId.d.ts.map +1 -0
  356. package/dist/esm/Action/TypeId.js +9 -0
  357. package/dist/esm/Action/TypeId.js.map +1 -0
  358. package/dist/esm/Action/UncanonicalIdempotencyKey.d.ts +32 -0
  359. package/dist/esm/Action/UncanonicalIdempotencyKey.d.ts.map +1 -0
  360. package/dist/esm/Action/UncanonicalIdempotencyKey.js +31 -0
  361. package/dist/esm/Action/UncanonicalIdempotencyKey.js.map +1 -0
  362. package/dist/esm/Action/idempotencyKey.d.ts +21 -0
  363. package/dist/esm/Action/idempotencyKey.d.ts.map +1 -0
  364. package/dist/esm/Action/idempotencyKey.js +50 -0
  365. package/dist/esm/Action/idempotencyKey.js.map +1 -0
  366. package/dist/esm/Action/index.d.ts +29 -0
  367. package/dist/esm/Action/index.d.ts.map +1 -0
  368. package/dist/esm/Action/index.js +30 -0
  369. package/dist/esm/Action/index.js.map +1 -0
  370. package/dist/esm/Action/make.d.ts +128 -0
  371. package/dist/esm/Action/make.d.ts.map +1 -0
  372. package/dist/esm/Action/make.js +413 -0
  373. package/dist/esm/Action/make.js.map +1 -0
  374. package/dist/esm/Action/raceAll.d.ts +21 -0
  375. package/dist/esm/Action/raceAll.d.ts.map +1 -0
  376. package/dist/esm/Action/raceAll.js +20 -0
  377. package/dist/esm/Action/raceAll.js.map +1 -0
  378. package/dist/esm/Action/retry.d.ts +19 -0
  379. package/dist/esm/Action/retry.d.ts.map +1 -0
  380. package/dist/esm/Action/retry.js +72 -0
  381. package/dist/esm/Action/retry.js.map +1 -0
  382. package/dist/esm/Deadline.d.ts +110 -0
  383. package/dist/esm/Deadline.d.ts.map +1 -0
  384. package/dist/esm/Deadline.js +164 -0
  385. package/dist/esm/Deadline.js.map +1 -0
  386. package/dist/esm/DurableClock.d.ts +60 -0
  387. package/dist/esm/DurableClock.d.ts.map +1 -0
  388. package/dist/esm/DurableClock.js +122 -0
  389. package/dist/esm/DurableClock.js.map +1 -0
  390. package/dist/esm/DurableDeferred.d.ts +311 -0
  391. package/dist/esm/DurableDeferred.d.ts.map +1 -0
  392. package/dist/esm/DurableDeferred.js +357 -0
  393. package/dist/esm/DurableDeferred.js.map +1 -0
  394. package/dist/esm/DurableQueue.d.ts +160 -0
  395. package/dist/esm/DurableQueue.d.ts.map +1 -0
  396. package/dist/esm/DurableQueue.js +303 -0
  397. package/dist/esm/DurableQueue.js.map +1 -0
  398. package/dist/esm/ExternalJob.d.ts +179 -0
  399. package/dist/esm/ExternalJob.d.ts.map +1 -0
  400. package/dist/esm/ExternalJob.js +296 -0
  401. package/dist/esm/ExternalJob.js.map +1 -0
  402. package/dist/esm/Fault.d.ts +131 -0
  403. package/dist/esm/Fault.d.ts.map +1 -0
  404. package/dist/esm/Fault.js +198 -0
  405. package/dist/esm/Fault.js.map +1 -0
  406. package/dist/esm/Flow/Annotations.d.ts +245 -0
  407. package/dist/esm/Flow/Annotations.d.ts.map +1 -0
  408. package/dist/esm/Flow/Annotations.js +110 -0
  409. package/dist/esm/Flow/Annotations.js.map +1 -0
  410. package/dist/esm/Flow/CapabilityCeiling.d.ts +21 -0
  411. package/dist/esm/Flow/CapabilityCeiling.d.ts.map +1 -0
  412. package/dist/esm/Flow/CapabilityCeiling.js +27 -0
  413. package/dist/esm/Flow/CapabilityCeiling.js.map +1 -0
  414. package/dist/esm/Flow/DeadlineExceeded.d.ts +33 -0
  415. package/dist/esm/Flow/DeadlineExceeded.d.ts.map +1 -0
  416. package/dist/esm/Flow/DeadlineExceeded.js +34 -0
  417. package/dist/esm/Flow/DeadlineExceeded.js.map +1 -0
  418. package/dist/esm/Flow/ExecutionIdRequired.d.ts +25 -0
  419. package/dist/esm/Flow/ExecutionIdRequired.d.ts.map +1 -0
  420. package/dist/esm/Flow/ExecutionIdRequired.js +30 -0
  421. package/dist/esm/Flow/ExecutionIdRequired.js.map +1 -0
  422. package/dist/esm/Flow/ExecutionIds.d.ts +80 -0
  423. package/dist/esm/Flow/ExecutionIds.d.ts.map +1 -0
  424. package/dist/esm/Flow/ExecutionIds.js +91 -0
  425. package/dist/esm/Flow/ExecutionIds.js.map +1 -0
  426. package/dist/esm/Flow/Flow.d.ts +422 -0
  427. package/dist/esm/Flow/Flow.d.ts.map +1 -0
  428. package/dist/esm/Flow/Flow.js +3 -0
  429. package/dist/esm/Flow/Flow.js.map +1 -0
  430. package/dist/esm/Flow/MaxRoundsExceeded.d.ts +35 -0
  431. package/dist/esm/Flow/MaxRoundsExceeded.d.ts.map +1 -0
  432. package/dist/esm/Flow/MaxRoundsExceeded.js +34 -0
  433. package/dist/esm/Flow/MaxRoundsExceeded.js.map +1 -0
  434. package/dist/esm/Flow/Outcome.d.ts +162 -0
  435. package/dist/esm/Flow/Outcome.d.ts.map +1 -0
  436. package/dist/esm/Flow/Outcome.js +115 -0
  437. package/dist/esm/Flow/Outcome.js.map +1 -0
  438. package/dist/esm/Flow/Result.d.ts +186 -0
  439. package/dist/esm/Flow/Result.d.ts.map +1 -0
  440. package/dist/esm/Flow/Result.js +171 -0
  441. package/dist/esm/Flow/Result.js.map +1 -0
  442. package/dist/esm/Flow/Runtime.d.ts +98 -0
  443. package/dist/esm/Flow/Runtime.d.ts.map +1 -0
  444. package/dist/esm/Flow/Runtime.js +252 -0
  445. package/dist/esm/Flow/Runtime.js.map +1 -0
  446. package/dist/esm/Flow/TypeId.d.ts +8 -0
  447. package/dist/esm/Flow/TypeId.d.ts.map +1 -0
  448. package/dist/esm/Flow/TypeId.js +9 -0
  449. package/dist/esm/Flow/TypeId.js.map +1 -0
  450. package/dist/esm/Flow/index.d.ts +17 -0
  451. package/dist/esm/Flow/index.d.ts.map +1 -0
  452. package/dist/esm/Flow/index.js +18 -0
  453. package/dist/esm/Flow/index.js.map +1 -0
  454. package/dist/esm/Flow/internal.d.ts +34 -0
  455. package/dist/esm/Flow/internal.d.ts.map +1 -0
  456. package/dist/esm/Flow/internal.js +149 -0
  457. package/dist/esm/Flow/internal.js.map +1 -0
  458. package/dist/esm/Flow/make.d.ts +149 -0
  459. package/dist/esm/Flow/make.d.ts.map +1 -0
  460. package/dist/esm/Flow/make.js +111 -0
  461. package/dist/esm/Flow/make.js.map +1 -0
  462. package/dist/esm/FlowRuntime/CancelRequestFailed.d.ts +55 -0
  463. package/dist/esm/FlowRuntime/CancelRequestFailed.d.ts.map +1 -0
  464. package/dist/esm/FlowRuntime/CancelRequestFailed.js +53 -0
  465. package/dist/esm/FlowRuntime/CancelRequestFailed.js.map +1 -0
  466. package/dist/esm/FlowRuntime/ExecutionIdentityConflict.d.ts +23 -0
  467. package/dist/esm/FlowRuntime/ExecutionIdentityConflict.d.ts.map +1 -0
  468. package/dist/esm/FlowRuntime/ExecutionIdentityConflict.js +24 -0
  469. package/dist/esm/FlowRuntime/ExecutionIdentityConflict.js.map +1 -0
  470. package/dist/esm/FlowRuntime/FlowCycleDetected.d.ts +28 -0
  471. package/dist/esm/FlowRuntime/FlowCycleDetected.d.ts.map +1 -0
  472. package/dist/esm/FlowRuntime/FlowCycleDetected.js +35 -0
  473. package/dist/esm/FlowRuntime/FlowCycleDetected.js.map +1 -0
  474. package/dist/esm/FlowRuntime/FlowExecutionNotFound.d.ts +38 -0
  475. package/dist/esm/FlowRuntime/FlowExecutionNotFound.d.ts.map +1 -0
  476. package/dist/esm/FlowRuntime/FlowExecutionNotFound.js +40 -0
  477. package/dist/esm/FlowRuntime/FlowExecutionNotFound.js.map +1 -0
  478. package/dist/esm/FlowRuntime/FlowInstance.d.ts +115 -0
  479. package/dist/esm/FlowRuntime/FlowInstance.d.ts.map +1 -0
  480. package/dist/esm/FlowRuntime/FlowInstance.js +24 -0
  481. package/dist/esm/FlowRuntime/FlowInstance.js.map +1 -0
  482. package/dist/esm/FlowRuntime/FlowRuntime.d.ts +204 -0
  483. package/dist/esm/FlowRuntime/FlowRuntime.d.ts.map +1 -0
  484. package/dist/esm/FlowRuntime/FlowRuntime.js +34 -0
  485. package/dist/esm/FlowRuntime/FlowRuntime.js.map +1 -0
  486. package/dist/esm/FlowRuntime/NodeRecord.d.ts +177 -0
  487. package/dist/esm/FlowRuntime/NodeRecord.d.ts.map +1 -0
  488. package/dist/esm/FlowRuntime/NodeRecord.js +21 -0
  489. package/dist/esm/FlowRuntime/NodeRecord.js.map +1 -0
  490. package/dist/esm/FlowRuntime/WaitingAnnotation.d.ts +69 -0
  491. package/dist/esm/FlowRuntime/WaitingAnnotation.d.ts.map +1 -0
  492. package/dist/esm/FlowRuntime/WaitingAnnotation.js +53 -0
  493. package/dist/esm/FlowRuntime/WaitingAnnotation.js.map +1 -0
  494. package/dist/esm/FlowRuntime/index.d.ts +14 -0
  495. package/dist/esm/FlowRuntime/index.d.ts.map +1 -0
  496. package/dist/esm/FlowRuntime/index.js +15 -0
  497. package/dist/esm/FlowRuntime/index.js.map +1 -0
  498. package/dist/esm/Graph.d.ts +325 -0
  499. package/dist/esm/Graph.d.ts.map +1 -0
  500. package/dist/esm/Graph.js +1485 -0
  501. package/dist/esm/Graph.js.map +1 -0
  502. package/dist/esm/HumanTask.d.ts +442 -0
  503. package/dist/esm/HumanTask.d.ts.map +1 -0
  504. package/dist/esm/HumanTask.js +810 -0
  505. package/dist/esm/HumanTask.js.map +1 -0
  506. package/dist/esm/Interpreter.d.ts +223 -0
  507. package/dist/esm/Interpreter.d.ts.map +1 -0
  508. package/dist/esm/Interpreter.js +1301 -0
  509. package/dist/esm/Interpreter.js.map +1 -0
  510. package/dist/esm/Poll.d.ts +240 -0
  511. package/dist/esm/Poll.d.ts.map +1 -0
  512. package/dist/esm/Poll.js +289 -0
  513. package/dist/esm/Poll.js.map +1 -0
  514. package/dist/esm/RetryPolicy.d.ts +273 -0
  515. package/dist/esm/RetryPolicy.d.ts.map +1 -0
  516. package/dist/esm/RetryPolicy.js +429 -0
  517. package/dist/esm/RetryPolicy.js.map +1 -0
  518. package/dist/esm/Sleep.d.ts +98 -0
  519. package/dist/esm/Sleep.d.ts.map +1 -0
  520. package/dist/esm/Sleep.js +256 -0
  521. package/dist/esm/Sleep.js.map +1 -0
  522. package/dist/esm/Stall.d.ts +173 -0
  523. package/dist/esm/Stall.d.ts.map +1 -0
  524. package/dist/esm/Stall.js +138 -0
  525. package/dist/esm/Stall.js.map +1 -0
  526. package/dist/esm/WaitFor.d.ts +109 -0
  527. package/dist/esm/WaitFor.d.ts.map +1 -0
  528. package/dist/esm/WaitFor.js +184 -0
  529. package/dist/esm/WaitFor.js.map +1 -0
  530. package/dist/esm/index.d.ts +107 -0
  531. package/dist/esm/index.d.ts.map +1 -0
  532. package/dist/esm/index.js +108 -0
  533. package/dist/esm/index.js.map +1 -0
  534. package/dist/esm/internal/BoundedJson.d.ts +48 -0
  535. package/dist/esm/internal/BoundedJson.d.ts.map +1 -0
  536. package/dist/esm/internal/BoundedJson.js +107 -0
  537. package/dist/esm/internal/BoundedJson.js.map +1 -0
  538. package/dist/esm/internal/DeclarationSite.d.ts +135 -0
  539. package/dist/esm/internal/DeclarationSite.d.ts.map +1 -0
  540. package/dist/esm/internal/DeclarationSite.js +196 -0
  541. package/dist/esm/internal/DeclarationSite.js.map +1 -0
  542. package/dist/esm/internal/Declarations.d.ts +35 -0
  543. package/dist/esm/internal/Declarations.d.ts.map +1 -0
  544. package/dist/esm/internal/Declarations.js +43 -0
  545. package/dist/esm/internal/Declarations.js.map +1 -0
  546. package/dist/esm/internal/ExecutionMiddleware.d.ts +21 -0
  547. package/dist/esm/internal/ExecutionMiddleware.d.ts.map +1 -0
  548. package/dist/esm/internal/ExecutionMiddleware.js +13 -0
  549. package/dist/esm/internal/ExecutionMiddleware.js.map +1 -0
  550. package/dist/esm/internal/JsonSchemaSubset.d.ts +133 -0
  551. package/dist/esm/internal/JsonSchemaSubset.d.ts.map +1 -0
  552. package/dist/esm/internal/JsonSchemaSubset.js +363 -0
  553. package/dist/esm/internal/JsonSchemaSubset.js.map +1 -0
  554. package/dist/esm/internal/OutcomeMarker.d.ts +18 -0
  555. package/dist/esm/internal/OutcomeMarker.d.ts.map +1 -0
  556. package/dist/esm/internal/OutcomeMarker.js +18 -0
  557. package/dist/esm/internal/OutcomeMarker.js.map +1 -0
  558. package/docs/README.md +152 -0
  559. package/docs/api.md +440 -0
  560. package/docs/concepts/bodies-and-plans.md +167 -0
  561. package/docs/concepts/execution-identity.md +142 -0
  562. package/docs/concepts/flows-and-actions.md +129 -0
  563. package/docs/concepts/suspension-and-replay.md +130 -0
  564. package/docs/concepts/the-runtime-port.md +110 -0
  565. package/docs/concepts/trampoline-rounds.md +151 -0
  566. package/docs/guides/ask-a-person.md +184 -0
  567. package/docs/guides/build-a-body.md +164 -0
  568. package/docs/guides/cancel-and-roll-back.md +112 -0
  569. package/docs/guides/implement-an-action.md +155 -0
  570. package/docs/guides/inspect-the-plan.md +235 -0
  571. package/docs/guides/poll-until-ready.md +169 -0
  572. package/docs/guides/queue-work-to-a-worker.md +122 -0
  573. package/docs/guides/retry-a-failing-action.md +243 -0
  574. package/docs/guides/reuse-a-recorded-result.md +129 -0
  575. package/docs/guides/run-a-child-flow.md +108 -0
  576. package/docs/guides/run-an-external-job.md +86 -0
  577. package/docs/guides/use-a-prompt.md +38 -0
  578. package/docs/guides/wait-for-a-deadline.md +141 -0
  579. package/docs/guides/wait-for-an-external-signal.md +151 -0
  580. package/docs/installation.md +87 -0
  581. package/docs/quickstart.md +164 -0
  582. package/docs/reference/flow.md +1835 -0
  583. package/docs/testing.md +157 -0
  584. package/docs/troubleshooting.md +286 -0
  585. package/package.json +327 -3
  586. package/src/Action/Action.ts +333 -0
  587. package/src/Action/AttemptTimedOut.ts +37 -0
  588. package/src/Action/BoundaryMode.ts +33 -0
  589. package/src/Action/CacheEnvironment.ts +170 -0
  590. package/src/Action/ConcurrentKeylessDispatch.ts +38 -0
  591. package/src/Action/Context.ts +156 -0
  592. package/src/Action/DuplicateImplementation.ts +24 -0
  593. package/src/Action/FileBoundary.ts +77 -0
  594. package/src/Action/FileInput.ts +42 -0
  595. package/src/Action/Filegroup.ts +16 -0
  596. package/src/Action/Glob.ts +8 -0
  597. package/src/Action/ImplementationVersionMismatch.ts +27 -0
  598. package/src/Action/Implementations.ts +149 -0
  599. package/src/Action/InfraInterrupt.ts +30 -0
  600. package/src/Action/InfraInterruptRetriesExhausted.ts +37 -0
  601. package/src/Action/IrreversibleRetryRequiresIdempotencyKey.ts +32 -0
  602. package/src/Action/StepIdentity.ts +219 -0
  603. package/src/Action/TreeArtifact.ts +8 -0
  604. package/src/Action/TypeId.ts +9 -0
  605. package/src/Action/UncanonicalIdempotencyKey.ts +36 -0
  606. package/src/Action/idempotencyKey.ts +67 -0
  607. package/src/Action/index.ts +31 -0
  608. package/src/Action/make.ts +671 -0
  609. package/src/Action/raceAll.ts +48 -0
  610. package/src/Action/retry.ts +92 -0
  611. package/src/Deadline.ts +201 -0
  612. package/src/DurableClock.ts +176 -0
  613. package/src/DurableDeferred.ts +753 -0
  614. package/src/DurableQueue.ts +519 -0
  615. package/src/ExternalJob.ts +437 -0
  616. package/src/Fault.ts +279 -0
  617. package/src/Flow/Annotations.ts +151 -0
  618. package/src/Flow/CapabilityCeiling.ts +37 -0
  619. package/src/Flow/DeadlineExceeded.ts +40 -0
  620. package/src/Flow/ExecutionIdRequired.ts +36 -0
  621. package/src/Flow/ExecutionIds.ts +133 -0
  622. package/src/Flow/Flow.ts +576 -0
  623. package/src/Flow/MaxRoundsExceeded.ts +39 -0
  624. package/src/Flow/Outcome.ts +179 -0
  625. package/src/Flow/Result.ts +284 -0
  626. package/src/Flow/Runtime.ts +339 -0
  627. package/src/Flow/TypeId.ts +9 -0
  628. package/src/Flow/index.ts +19 -0
  629. package/src/Flow/internal.ts +237 -0
  630. package/src/Flow/make.ts +299 -0
  631. package/src/FlowRuntime/CancelRequestFailed.ts +56 -0
  632. package/src/FlowRuntime/ExecutionIdentityConflict.ts +32 -0
  633. package/src/FlowRuntime/FlowCycleDetected.ts +41 -0
  634. package/src/FlowRuntime/FlowExecutionNotFound.ts +46 -0
  635. package/src/FlowRuntime/FlowInstance.ts +128 -0
  636. package/src/FlowRuntime/FlowRuntime.ts +339 -0
  637. package/src/FlowRuntime/NodeRecord.ts +185 -0
  638. package/src/FlowRuntime/WaitingAnnotation.ts +85 -0
  639. package/src/FlowRuntime/index.ts +17 -0
  640. package/src/Graph.ts +1865 -0
  641. package/src/HumanTask.ts +1021 -0
  642. package/src/Interpreter.ts +1635 -0
  643. package/src/Poll.ts +420 -0
  644. package/src/RetryPolicy.ts +550 -0
  645. package/src/Sleep.ts +296 -0
  646. package/src/Stall.ts +217 -0
  647. package/src/WaitFor.ts +228 -0
  648. package/src/index.ts +125 -0
  649. package/src/internal/BoundedJson.ts +109 -0
  650. package/src/internal/DeclarationSite.ts +214 -0
  651. package/src/internal/Declarations.ts +49 -0
  652. package/src/internal/ExecutionMiddleware.ts +29 -0
  653. package/src/internal/JsonSchemaSubset.ts +392 -0
  654. package/src/internal/OutcomeMarker.ts +19 -0
@@ -0,0 +1,1635 @@
1
+ /**
2
+ * The body interpreter: what a flow does when it runs.
3
+ *
4
+ * A `Flow` has no handler to register — its `body` IS the behavior, per
5
+ * `docs/concepts/flows-and-actions.md` — so something has to turn
6
+ * the graph that body describes into execution. That is this
7
+ * module. {@link layer} registers a flow with the runtime, and the
8
+ * handler it installs builds the graph with {@link module:Graph.build} and
9
+ * walks it: each node settles once, in dependency order, and the root's value
10
+ * is the flow's result.
11
+ *
12
+ * What each variant does is the run-time half of what the AST recorded. An
13
+ * `ActionCall` runs its declaration's implementation — looked up by tag in
14
+ * {@link module:Implementations.Implementations} before the walk starts — as
15
+ * the ordinary durable action `toLayer` built, so a node driven here takes
16
+ * the same attempt journal, retry policy, and tier as the same action called
17
+ * from a handler. Its invocation identity additionally folds this node's
18
+ * structural address, keeping concurrent graph sites replay-stable. A `Map`
19
+ * applies its deferred function to the real upstream value. A `Branch`
20
+ * evaluates its digested predicate on the real
21
+ * subject and settles ONLY the arm it took: the other arm is topology the plan
22
+ * shows and the run skipped, and it is reported as such rather than silently
23
+ * absent. An `All` joins its members by name, a `Succeed` yields its value, and
24
+ * an inline `FlowCall` was already flattened by graph building, so it settles
25
+ * with the body spliced beneath it. A `FlowCall` the author wrote as
26
+ * `.child(payload)` is the one node that is NOT flattened: it opens a real child
27
+ * execution under a deterministically derived id
28
+ * ({@link childExecutionId}), which is what gives it its own journal lineage and
29
+ * makes the parent's interruption and the child's suspension travel between
30
+ * them.
31
+ *
32
+ * Planned references are resolved the way the plan names them: a payload
33
+ * placeholder carries the `Ref` `{from, path}` its key material recorded, so
34
+ * `result.files` reads `files` off the settled value of the node that produced
35
+ * it.
36
+ *
37
+ * The walk is demand-driven from the root rather than a sweep over the node
38
+ * list, because dependency order puts BOTH branch arms before the branch that
39
+ * chooses between them, and executing an arm to discover it was not taken is
40
+ * exactly what static topology exists to avoid.
41
+ *
42
+ * @since 0.1.0
43
+ */
44
+
45
+ import { isRecord } from "@smthrs/canonical/Record"
46
+ import * as CapabilitySet from "@smthrs/capability/CapabilitySet"
47
+ import { DerivedKey, digest } from "@smthrs/keys"
48
+ import { isFatalDiagnostic } from "@smthrs/plan/GraphBuildError"
49
+ import * as KeyMaterial from "@smthrs/plan/KeyMaterial"
50
+ import * as Node from "@smthrs/plan/Node"
51
+ import * as Planned from "@smthrs/plan/Planned"
52
+ import * as StepKey from "@smthrs/plan/StepKey"
53
+ import * as Cause from "effect/Cause"
54
+ import * as Context from "effect/Context"
55
+ import type * as Crypto from "effect/Crypto"
56
+ import * as Deferred from "effect/Deferred"
57
+ import * as Effect from "effect/Effect"
58
+ import * as Exit from "effect/Exit"
59
+ import type * as Fiber from "effect/Fiber"
60
+ import * as Layer from "effect/Layer"
61
+ import * as Option from "effect/Option"
62
+ import * as Predicate from "effect/Predicate"
63
+ import * as Schema from "effect/Schema"
64
+ import * as Scope from "effect/Scope"
65
+ import { type Implementation, Implementations, layerImplementations } from "./Action/Implementations.ts"
66
+ import { DispatchReport, DispatchSite } from "./Action/StepIdentity.ts"
67
+ import * as DurableDeferred from "./DurableDeferred.ts"
68
+ import { attenuateCapabilities } from "./Flow/CapabilityCeiling.ts"
69
+ import type { Any as AnyFlow, AnyStructSchema, AnyWithProps, Flow } from "./Flow/Flow.ts"
70
+ import * as Outcome from "./Flow/Outcome.ts"
71
+ import { Handoff } from "./Flow/Result.ts"
72
+ import { suspend } from "./Flow/Runtime.ts"
73
+ import { TypeId as FlowTypeId } from "./Flow/TypeId.ts"
74
+ import { FlowInstance } from "./FlowRuntime/FlowInstance.ts"
75
+ import { FlowRuntime } from "./FlowRuntime/FlowRuntime.ts"
76
+ import type * as NodeRecord from "./FlowRuntime/NodeRecord.ts"
77
+ import { annotateWaiting } from "./FlowRuntime/WaitingAnnotation.ts"
78
+ import * as Graph from "./Graph.ts"
79
+ import * as HumanTask from "./HumanTask.ts"
80
+ import * as BoundedJson from "./internal/BoundedJson.ts"
81
+ import { ExecutionMiddleware } from "./internal/ExecutionMiddleware.ts"
82
+ import { OutcomeValueTypeId } from "./internal/OutcomeMarker.ts"
83
+ import * as Sleep from "./Sleep.ts"
84
+ import * as WaitFor from "./WaitFor.ts"
85
+
86
+ /**
87
+ * A graph the interpreter will not drive.
88
+ *
89
+ * Every code names something the run cannot recover from on its own: a graph
90
+ * whose topology is incomplete, an action with no implementation wired up, a
91
+ * call the interpreter does not execute, and a deferred function that did not
92
+ * survive serialization beside its AST. `missing_implementation_version`
93
+ * names a content-reusable action that lacks the canonical version contract;
94
+ * `implementation_version_mismatch` names a declaration and registry that
95
+ * disagree. Both are preflight failures, before action dispatch.
96
+ * `join_mismatch` names a race, any, or quorum whose journaled decision this
97
+ * plan's members can no longer reproduce. `plan_changed` refuses a durable
98
+ * resume whose rebuilt graph differs from its first recorded plan.
99
+ *
100
+ * @category errors
101
+ * @since 0.1.0
102
+ */
103
+ export class InterpreterError extends Schema.TaggedError<InterpreterError>()(
104
+ "@smthrs/flow/InterpreterError",
105
+ {
106
+ code: Schema.Literals([
107
+ "incomplete_graph",
108
+ "duplicate_node_id",
109
+ "unresolved_action",
110
+ "implementation_version_mismatch",
111
+ "missing_implementation_version",
112
+ "unresolved_reference",
113
+ "unsupported_call",
114
+ "missing_operation",
115
+ "node_record_too_large",
116
+ "plan_changed",
117
+ "join_mismatch"
118
+ ]),
119
+ flow: Schema.String,
120
+ node: Schema.String,
121
+ message: Schema.String
122
+ }
123
+ ) {}
124
+
125
+ /**
126
+ * What one interpretation produced: the root's value, every node that settled
127
+ * with the value it settled with, and the nodes the run never reached because
128
+ * a branch went the other way.
129
+ *
130
+ * @category models
131
+ * @since 0.1.0
132
+ */
133
+ export interface Interpretation {
134
+ readonly value: unknown
135
+ readonly settled: ReadonlyMap<string, unknown>
136
+ /** Typed failures observed before a catch recovered them. */
137
+ readonly failed: ReadonlyMap<string, unknown>
138
+ readonly skipped: ReadonlyArray<string>
139
+ }
140
+
141
+ /**
142
+ * The services a driven node needs: the runtime that executes an action, and
143
+ * the execution it is part of.
144
+ *
145
+ * @private
146
+ */
147
+ type Services = Crypto.Crypto | FlowRuntime | FlowInstance | Implementations
148
+
149
+ /** Reads one own data property without invoking an accessor or letting a proxy trap escape. */
150
+ const ownDataProperty = (value: unknown, key: PropertyKey): unknown => {
151
+ if (Object(value) !== value) return undefined
152
+ try {
153
+ const descriptor = Object.getOwnPropertyDescriptor(value, key)
154
+ return descriptor !== undefined && "value" in descriptor ? descriptor.value : undefined
155
+ } catch {
156
+ return undefined
157
+ }
158
+ }
159
+
160
+ /**
161
+ * The execution id a `.child()` boundary runs its child under.
162
+ *
163
+ * DECIDED: the id is DERIVED from the parent
164
+ * execution and the child node's structural address, not minted. That is what
165
+ * makes a boundary at-most-once under replay, exactly as
166
+ * `docs/concepts/trampoline-rounds.md` requires of a round handoff: a
167
+ * parent that is re-driven re-derives the same id, so the engine lands on the
168
+ * child execution that already exists instead of starting a second copy of it.
169
+ * The canonical tuple includes the callee and a canonical payload digest, so
170
+ * delimiter splicing and a changed invocation cannot alias an earlier child.
171
+ * SHA-256 uses the same injected derivation services as the repository's
172
+ * other durable identities. The versioned key prefix is dropped because the
173
+ * child-id wire format is the bare digest. `digest` owns the
174
+ * prefix knowledge, so this package never guesses at a stored-key format.
175
+ *
176
+ * @since 0.1.0
177
+ * @category constructors
178
+ */
179
+ export const childExecutionId = (
180
+ parentExecutionId: string,
181
+ nodeId: string,
182
+ calleeTag: string,
183
+ payload: unknown
184
+ ): Effect.Effect<string, never, Crypto.Crypto> =>
185
+ Effect.gen(function*() {
186
+ const payloadDigest = yield* Schema.decodeUnknownEffect(DerivedKey)(payload).pipe(Effect.orDie)
187
+ const tupleDigest = yield* Schema.decodeUnknownEffect(DerivedKey)([
188
+ parentExecutionId,
189
+ nodeId,
190
+ calleeTag,
191
+ payloadDigest
192
+ ]).pipe(Effect.orDie)
193
+ return digest(tupleDigest)
194
+ })
195
+
196
+ /**
197
+ * The largest encoded page of a recorded plan.
198
+ *
199
+ * A journal entry has a byte bound and a projection clips an oversized one, so
200
+ * a graph is PAGED rather than truncated: whatever does not fit in the plan
201
+ * record follows as appended subgraph pages. The budget is deliberately well
202
+ * under the 16 KiB gateway limit on graph events. The runtime measures the
203
+ * encoded journal envelope; absent that hook, the complete node record is
204
+ * measured in UTF-8. A node too large for one page is a typed preflight refusal.
205
+ *
206
+ * @since 1.0.0
207
+ * @category constants
208
+ */
209
+ export const maximumPageBytes = 12_000
210
+
211
+ /**
212
+ * How many distinct step key digests one node's settlement names.
213
+ *
214
+ * A journal entry has a byte bound and a digest is 64 characters, so the list
215
+ * is capped rather than left to grow with whatever a node dispatched. The
216
+ * cap is far above what any node reaches today — an action node drives one
217
+ * dispatch and keeps one step key however many times it is retried — and a
218
+ * node past it names the dispatches it started with rather than none.
219
+ *
220
+ * @since 1.0.0
221
+ * @category constants
222
+ */
223
+ export const maximumDispatchDigests = 16
224
+
225
+ /** The declared tag a node dispatches, if it dispatches one. @private */
226
+ const actionTag = (ast: Node.Ast): string | undefined =>
227
+ ast._tag === "ActionCall" ? ast.action : ast._tag === "FlowCall" ? ast.flow : undefined
228
+
229
+ /** What one node of a driven graph tells a monitor about itself. @private */
230
+ const summarize = (node: Graph.GraphNode): NodeRecord.NodeSummary => {
231
+ const action = actionTag(node.ast)
232
+ return {
233
+ id: node.id,
234
+ kind: node.kind,
235
+ dependsOn: KeyMaterial.dependencies(node.draft.material),
236
+ tier: node.draft.material.kind,
237
+ effects: node.draft.effects,
238
+ ...(action === undefined ? {} : { action }),
239
+ ...(node.declaredAt === undefined ? {} : { declaredAt: node.declaredAt })
240
+ }
241
+ }
242
+
243
+ /**
244
+ * The graph, as the pages a journal can hold.
245
+ *
246
+ * Nothing about one node has to fit one page. A node's summary is seated on a
247
+ * page with no dependencies on it, and then its dependency list and the edges
248
+ * that END on it are spread over as many following pages as they need: a
249
+ * continuation page re-seats the same summary and carries the next disjoint
250
+ * slice of `dependsOn`, so a reader that unions `dependsOn` per node id and
251
+ * concatenates edges reassembles exactly the graph that was built. That is
252
+ * what lets an input-driven fan-in — `Node.all` over a caller-sized list — be
253
+ * recorded at any width instead of refused.
254
+ *
255
+ * A node's summary is seated no later than the first page naming one of its
256
+ * dependencies or one of its incoming edges, so an assembled PREFIX of pages
257
+ * never names an edge whose destination is unknown.
258
+ *
259
+ * The typed refusal is left for the one thing paging cannot divide: a node
260
+ * whose summary with NO dependencies on it still exceeds the budget.
261
+ *
262
+ * @private
263
+ */
264
+ const planPages = (
265
+ flow: string,
266
+ graph: Graph.Graph,
267
+ measure: (record: NodeRecord.NodeRecord) => Effect.Effect<number, never, FlowInstance>
268
+ ): Effect.Effect<
269
+ ReadonlyArray<NodeRecord.PlanRecorded | NodeRecord.SubgraphAppended>,
270
+ InterpreterError,
271
+ FlowInstance | Crypto.Crypto
272
+ > =>
273
+ Effect.gen(function*() {
274
+ const identity = yield* StepKey.content({
275
+ body: { flow, nodes: Graph.drafts(graph), edges: Graph.edges(graph) },
276
+ inputs: {},
277
+ layers: [],
278
+ capabilities: {}
279
+ }).pipe(Effect.mapError((cause) =>
280
+ new InterpreterError({
281
+ code: "incomplete_graph",
282
+ flow,
283
+ node: "root",
284
+ message: `Cannot identify recorded plan: ${String(cause)}`
285
+ })
286
+ ))
287
+ const incoming = new Map<string, Array<NodeRecord.EdgeSummary>>()
288
+ let edgeCount = 0
289
+ for (const edge of Graph.edges(graph)) {
290
+ edgeCount += 1
291
+ const summary = { from: edge.from, to: edge.to, reason: edge.reason }
292
+ const existing = incoming.get(edge.to)
293
+ if (existing === undefined) incoming.set(edge.to, [summary])
294
+ else existing.push(summary)
295
+ }
296
+ type Page = { nodes: Array<NodeRecord.NodeSummary>; edges: Array<NodeRecord.EdgeSummary> }
297
+ const graphNodes = Graph.nodes(graph)
298
+ const pages: Array<Page> = []
299
+ let current: Page = { nodes: [], edges: [] }
300
+ const record = (
301
+ page: Page,
302
+ index: number,
303
+ count: number
304
+ ): NodeRecord.PlanRecorded | NodeRecord.SubgraphAppended => ({
305
+ ...(index === 0
306
+ ? { _tag: "PlanRecorded" as const, nodeCount: graphNodes.length, digest: identity }
307
+ : { _tag: "SubgraphAppended" as const }),
308
+ sourceId: `plan/0/${index}`,
309
+ flow,
310
+ generation: 0,
311
+ page: index,
312
+ pages: count,
313
+ ...page
314
+ })
315
+ // Every page is started by a node seat or by an edge, and a node is seated
316
+ // once plus at most once per dependency that starts a continuation page,
317
+ // so the page count is at most nodes + dependencies + edges. Reserving
318
+ // that many digits is what makes replacing the reservation with the final
319
+ // count shrink an encoded envelope or leave it alone, never grow it.
320
+ const reserved = graphNodes.reduce(
321
+ (total, node) => total + KeyMaterial.dependencies(node.draft.material).length,
322
+ graphNodes.length + edgeCount
323
+ )
324
+ const fits = (page: Page) =>
325
+ Effect.map(measure(record(page, pages.length, reserved)), (bytes) => bytes <= maximumPageBytes)
326
+ const flush = () => {
327
+ pages.push(current)
328
+ current = { nodes: [], edges: [] }
329
+ }
330
+ /**
331
+ * How many of the remaining items the page still holds.
332
+ *
333
+ * A page only grows as items are added to it, so the boundary is found by
334
+ * bisection: a thousand-way fan-in costs a logarithmic number of
335
+ * measurements per page rather than one per item. Taking none is the page
336
+ * as it stands, which was measured before anything was offered to it.
337
+ */
338
+ const admits = <T>(rest: ReadonlyArray<T>, grow: (take: number) => Page) =>
339
+ Effect.gen(function*() {
340
+ if (yield* fits(grow(rest.length))) return rest.length
341
+ let low = 0
342
+ let high = rest.length - 1
343
+ while (low < high) {
344
+ const mid = low + Math.ceil((high - low) / 2)
345
+ if (yield* fits(grow(mid))) low = mid
346
+ else high = mid - 1
347
+ }
348
+ return low
349
+ })
350
+ /**
351
+ * Puts every item somewhere, starting a page whenever the current one is
352
+ * full. `resume` prepares a freshly started page so what follows still
353
+ * says which node it belongs to, and a page that was just started and
354
+ * admits nothing is the end of what paging can do.
355
+ */
356
+ const spread = <T>(
357
+ items: ReadonlyArray<T>,
358
+ onto: (page: Page, taken: ReadonlyArray<T>) => Page,
359
+ resume: (page: Page) => Page,
360
+ refusal: () => InterpreterError
361
+ ): Effect.Effect<void, InterpreterError, FlowInstance> =>
362
+ Effect.gen(function*() {
363
+ let rest = items
364
+ let started = false
365
+ while (rest.length > 0) {
366
+ const take = yield* admits(rest, (count) => onto(current, rest.slice(0, count)))
367
+ if (take > 0) {
368
+ current = onto(current, rest.slice(0, take))
369
+ rest = rest.slice(take)
370
+ started = false
371
+ continue
372
+ }
373
+ if (started) return yield* refusal()
374
+ flush()
375
+ current = resume(current)
376
+ started = true
377
+ }
378
+ })
379
+ for (const node of graphNodes) {
380
+ const summary = summarize(node)
381
+ const arriving = incoming.get(node.id) ?? []
382
+ // The summary alone first. What a page must hold whole is the node
383
+ // itself, and that is the only thing left to refuse.
384
+ const bare: NodeRecord.NodeSummary = { ...summary, dependsOn: [] }
385
+ const seat = (page: Page): Page => ({ nodes: [...page.nodes, bare], edges: page.edges })
386
+ const tooLarge = () =>
387
+ new InterpreterError({
388
+ code: "node_record_too_large",
389
+ flow,
390
+ node: node.id,
391
+ message:
392
+ `Node "${node.id}" and its encoded envelope exceed ${maximumPageBytes} bytes with no dependencies on it`
393
+ })
394
+ if (!(yield* fits(seat(current)))) {
395
+ if (current.nodes.length > 0 || current.edges.length > 0) flush()
396
+ if (!(yield* fits(seat(current)))) return yield* tooLarge()
397
+ }
398
+ current = seat(current)
399
+ // Then its dependencies, onto the summary the page just seated, and then
400
+ // the edges that end on it. Either may run past this page onto the next.
401
+ yield* spread(
402
+ summary.dependsOn,
403
+ (page, taken) => {
404
+ const seated = page.nodes[page.nodes.length - 1]!
405
+ return {
406
+ nodes: [...page.nodes.slice(0, -1), { ...seated, dependsOn: [...seated.dependsOn, ...taken] }],
407
+ edges: page.edges
408
+ }
409
+ },
410
+ seat,
411
+ tooLarge
412
+ )
413
+ yield* spread(
414
+ arriving,
415
+ (page, taken) => ({ nodes: page.nodes, edges: [...page.edges, ...taken] }),
416
+ (page) => page,
417
+ tooLarge
418
+ )
419
+ }
420
+ pages.push(current)
421
+ return pages.map((page, index) => record(page, index, pages.length))
422
+ })
423
+
424
+ /**
425
+ * Interprets a flow body, or a bare node, against real values.
426
+ *
427
+ * The graph is built first and in full — planning is a pure function of the
428
+ * declarations and the payload, so the whole shape of the round is known before
429
+ * the first action runs — and then driven. The low-level default permits
430
+ * process-local callbacks; pass `callbackIdentity: "stable"` for reproducible
431
+ * callback identity. {@link layerWithImplementations} selects that policy by
432
+ * default.
433
+ *
434
+ * @since 0.1.0
435
+ * @category constructors
436
+ */
437
+ export const interpret = (
438
+ flowOrNode: Parameters<typeof Graph.build>[0],
439
+ payload?: unknown,
440
+ options: Graph.BuildOptions = {}
441
+ ): Effect.Effect<Interpretation, unknown, Services> => interpretWithPolicy(flowOrNode, payload, options)
442
+
443
+ /** Every interpreter requires versions before permitting content-key reuse. @private */
444
+ const interpretWithPolicy = (
445
+ flowOrNode: Parameters<typeof Graph.build>[0],
446
+ payload: unknown,
447
+ options: Graph.BuildOptions,
448
+ registerReachable?: (flow: AnyWithProps) => Effect.Effect<void>
449
+ ): Effect.Effect<Interpretation, unknown, Services> =>
450
+ Effect.gen(function*() {
451
+ const table = yield* Implementations
452
+ const name = "_tag" in flowOrNode ? flowOrNode._tag : "node"
453
+ const refuse = (
454
+ code: InterpreterError["code"],
455
+ node: string,
456
+ message: string
457
+ ): Effect.Effect<never, InterpreterError> => Effect.fail(new InterpreterError({ code, flow: name, node, message }))
458
+
459
+ const graph = yield* Effect.try({
460
+ try: () => Graph.build(flowOrNode, payload, options),
461
+ catch: (cause) => {
462
+ // Graph.build normalizes its own refusals to GraphBuildError, but the
463
+ // flow body runs inside this boundary and a body throws whatever it
464
+ // throws. Reading `node` and `message` off the value without checking
465
+ // built an InterpreterError its own schema rejects, and the
466
+ // constructor's "Schema validation failed" then replaced the reason
467
+ // the author needed. All three fields are read through own data
468
+ // descriptors and narrowed here so a plain Error from a body arrives
469
+ // as a typed failure that still says why. A body that threw produced no
470
+ // topology, so it reports `incomplete_graph`.
471
+ const refusalNode = ownDataProperty(cause, "node")
472
+ const refusalCode = ownDataProperty(cause, "code")
473
+ const refusalMessage = ownDataProperty(cause, "message")
474
+ const node = typeof refusalNode === "string" ? refusalNode : ""
475
+ const reported = typeof refusalMessage === "string" ? refusalMessage : ""
476
+ return new InterpreterError({
477
+ code: refusalCode === "duplicate_node" ? "duplicate_node_id" : "incomplete_graph",
478
+ flow: name,
479
+ node,
480
+ message: reported === ""
481
+ ? `building the graph of flow ${name} threw ${
482
+ typeof cause === "string" ? cause : BoundedJson.render(cause, 512)
483
+ }`
484
+ : reported
485
+ })
486
+ }
487
+ })
488
+
489
+ const graphNodes = Graph.nodes(graph)
490
+ const byId = new Map(graphNodes.map((node) => [node.id, node]))
491
+
492
+ // Advisory diagnostics describe a graph that is exactly what will run, so
493
+ // they are reported by `Graph.diagnostics` and do not stop a run. A fatal
494
+ // one means the topology is not what the author wrote.
495
+ const fatal = graph.diagnostics.find(isFatalDiagnostic)
496
+ if (fatal !== undefined) {
497
+ return yield* refuse(
498
+ "incomplete_graph",
499
+ fatal.node,
500
+ `Graph of "${name}" is missing topology and cannot be driven: ${fatal.message}`
501
+ )
502
+ }
503
+
504
+ /**
505
+ * The node records this walk writes, if the runtime keeps any.
506
+ *
507
+ * A runtime without the seam costs a comparison per node and nothing else:
508
+ * the record is built by the thunk only when something will hold it.
509
+ */
510
+ const runtime = yield* FlowRuntime
511
+ const recordNode = runtime.recordNode
512
+ const record = recordNode === undefined
513
+ ? (_: () => NodeRecord.NodeRecord) => Effect.void as Effect.Effect<void, never, FlowInstance>
514
+ : (make: () => NodeRecord.NodeRecord) => recordNode(make())
515
+ // The whole graph, before the first node runs. Planning is pure, so this
516
+ // is the complete declared ceiling of the round rather than a prefix of
517
+ // what happened to run, and a monitor can draw the round it is watching
518
+ // from the first record it receives.
519
+ if (recordNode !== undefined) {
520
+ const measure = runtime.nodeRecordBytes ??
521
+ ((record: NodeRecord.NodeRecord) => Effect.succeed(new TextEncoder().encode(JSON.stringify(record)).byteLength))
522
+ // Validate every page before writing the first or dispatching any action.
523
+ const pages = yield* planPages(name, graph, measure)
524
+ yield* Effect.forEach(pages, (page) => recordNode(page), { discard: true })
525
+ }
526
+
527
+ // Everything the walk needs that the built graph can be asked for before it
528
+ // runs, is asked for here — so no such refusal can surface halfway through a
529
+ // body with the actions ahead of it already committed.
530
+ //
531
+ // A reference out of the graph is the first: a round may be PLANNED against
532
+ // a node an earlier generation settled — `Plan.append` exists for exactly
533
+ // that — but one interpretation settles one graph, so there is nothing to
534
+ // read it from. A missing implementation is the second, and it is a wiring
535
+ // error rather than a run-time contingency: every action the graph names
536
+ // is resolved up front, including the ones only an untaken branch arm would
537
+ // have reached, because the plan is the declared ceiling of what may run.
538
+ // Deferred operations must likewise survive beside every AST, even when
539
+ // the walk would skip their branch or catch arm.
540
+ const implementations = new Map<string, Implementation>()
541
+ const handoffDeclarations = new Map<string, AnyFlow>()
542
+ const childDeclarations = new Map<string, AnyWithProps>()
543
+ for (const node of graphNodes) {
544
+ const ast = node.ast
545
+ if (ast._tag === "Map" && Node.mapper(ast) === undefined) {
546
+ return yield* refuse("missing_operation", node.id, `Map at "${node.id}" lost its mapper.`)
547
+ }
548
+ if (ast._tag === "Branch" && Node.predicate(ast) === undefined) {
549
+ return yield* refuse("missing_operation", node.id, `Branch at "${node.id}" lost its predicate.`)
550
+ }
551
+ if (ast._tag === "Catch" && ast.filter !== undefined && Node.catchFilter(ast) === undefined) {
552
+ return yield* refuse("missing_operation", node.id, `Catch at "${node.id}" lost its schema filter.`)
553
+ }
554
+ for (const dependency of KeyMaterial.dependencies(node.draft.material)) {
555
+ if (byId.has(dependency)) continue
556
+ return yield* refuse(
557
+ "unresolved_reference",
558
+ node.id,
559
+ `Node "${node.id}" reads "${dependency}", which this graph does not hold.`
560
+ )
561
+ }
562
+ }
563
+ // Diagnose lost operations before action declarations, regardless of graph order.
564
+ for (const node of graphNodes) {
565
+ if (node.ast._tag === "FlowCall" && node.ast.mode === "handoff") {
566
+ const declaration = Node.declaration(node.ast)
567
+ if (!Predicate.hasProperty(declaration, FlowTypeId)) {
568
+ return yield* refuse(
569
+ "unsupported_call",
570
+ node.id,
571
+ `Handoff to flow "${node.ast.flow}" at "${node.id}" lost its declaration. ` +
572
+ "Build and interpret the authored node in the same process so its payload can be encoded."
573
+ )
574
+ }
575
+ handoffDeclarations.set(node.id, declaration as unknown as AnyFlow)
576
+ if (registerReachable !== undefined) yield* registerReachable(declaration as unknown as AnyWithProps)
577
+ continue
578
+ }
579
+ if (node.ast._tag === "FlowCall" && node.ast.mode === "boundary") {
580
+ // A boundary is one node here and a real execution underneath, so what
581
+ // it needs resolved up front is the callee itself: the declaration is
582
+ // what `execute` is called on, and losing it is a wiring error rather
583
+ // than a run-time contingency, exactly like an unimplemented action.
584
+ const declaration = Node.declaration(node.ast)
585
+ if (!Predicate.hasProperty(declaration, FlowTypeId)) {
586
+ return yield* refuse(
587
+ "unsupported_call",
588
+ node.id,
589
+ `Child boundary to flow "${node.ast.flow}" at "${node.id}" lost its declaration. ` +
590
+ "Build and interpret the authored node in the same process so the child can be executed."
591
+ )
592
+ }
593
+ childDeclarations.set(node.id, declaration as unknown as AnyWithProps)
594
+ if (registerReachable !== undefined) yield* registerReachable(declaration as unknown as AnyWithProps)
595
+ continue
596
+ }
597
+ if (node.ast._tag !== "ActionCall") continue
598
+ const implementation = yield* table.get(node.ast.action)
599
+ if (Option.isNone(implementation)) {
600
+ return yield* refuse(
601
+ "unresolved_action",
602
+ node.id,
603
+ `Action "${node.ast.action}" has no implementation. ` +
604
+ `Provide ONE Action.layerImplementations under both ${node.ast.action}.toLayer(execute) and ` +
605
+ "this interpreter layer: an implementation files itself with the table that is in scope " +
606
+ "while IT is built, so a table merged beside it, or a second one built above it, is not the " +
607
+ "table this driver reads."
608
+ )
609
+ }
610
+ const declaration = Node.declaration(node.ast)
611
+ const declaredVersion = ownDataProperty(declaration, "implementationVersion")
612
+ const tier = ownDataProperty(declaration, "tier")
613
+ if (tier === undefined) {
614
+ return yield* refuse(
615
+ "incomplete_graph",
616
+ node.id,
617
+ `Action "${node.ast.action}" lost its declaration, so its implementation identity cannot be checked. ` +
618
+ "Build the canonical composition from the authored action nodes."
619
+ )
620
+ }
621
+ if (
622
+ tier === "sealed" && ownDataProperty(declaration, "idempotencyKey") !== undefined &&
623
+ declaredVersion === undefined
624
+ ) {
625
+ return yield* refuse(
626
+ "missing_implementation_version",
627
+ node.id,
628
+ `Sealed action "${node.ast.action}" declares an idempotency key and can reuse recorded content. ` +
629
+ "Declare implementationVersion on Action.make and attest the same version in toLayer."
630
+ )
631
+ }
632
+ if (declaredVersion !== implementation.value.implementationVersion) {
633
+ return yield* refuse(
634
+ "implementation_version_mismatch",
635
+ node.id,
636
+ `Action "${node.ast.action}" does not match its registered implementationVersion. ` +
637
+ "Register the declared version and start a newly planned execution when its semantics change."
638
+ )
639
+ }
640
+ implementations.set(node.ast.action, implementation.value)
641
+ }
642
+
643
+ // Graph.build also accepts authored references. Unlike Plan.compile, this
644
+ // entry point has not yet checked them for cycles: a self-reference would
645
+ // join its own in-flight deferred forever. Validate the entire dependency
646
+ // graph before dispatch, including branch arms the execution might skip.
647
+ const remaining = new Map<string, number>()
648
+ const dependents = new Map<string, Array<string>>()
649
+ const ready: Array<string> = []
650
+ for (const node of graphNodes) {
651
+ const dependencies = KeyMaterial.dependencies(node.draft.material)
652
+ remaining.set(node.id, dependencies.length)
653
+ if (dependencies.length === 0) ready.push(node.id)
654
+ for (const dependency of dependencies) {
655
+ const consumers = dependents.get(dependency)
656
+ if (consumers === undefined) dependents.set(dependency, [node.id])
657
+ else consumers.push(node.id)
658
+ }
659
+ }
660
+ for (let index = 0; index < ready.length; index++) {
661
+ for (const consumer of dependents.get(ready[index]!) ?? []) {
662
+ const count = remaining.get(consumer)! - 1
663
+ remaining.set(consumer, count)
664
+ if (count === 0) ready.push(consumer)
665
+ }
666
+ }
667
+ if (ready.length !== graphNodes.length) {
668
+ const blocked = graphNodes.find((node) => remaining.get(node.id)! > 0)!
669
+ return yield* refuse(
670
+ "incomplete_graph",
671
+ blocked.id,
672
+ `Graph of "${name}" contains a dependency cycle blocking "${blocked.id}". ` +
673
+ "Use an acyclic reference, a child boundary, or a trampoline handoff."
674
+ )
675
+ }
676
+ // The children a node settles with, in the order graph building recorded
677
+ // them: `first` then the arms of a branch, `first` then the continuation of
678
+ // a sequence, the members of a combination, the spliced body of an inline
679
+ // call. Payload references are NOT here — they are named by the key
680
+ // material, and settled from it.
681
+ const sources = new Map<string, Array<string>>()
682
+ for (const edge of Graph.edges(graph)) {
683
+ if (edge.reason !== "value") continue
684
+ sources.set(edge.to, [...sources.get(edge.to) ?? [], edge.from])
685
+ }
686
+
687
+ const settled = new Map<string, unknown>()
688
+ const failed = new Map<string, unknown>()
689
+
690
+ /**
691
+ * Projects a settled value along the property path a `Ref` recorded —
692
+ * through `StepKey.project`, the one projection semantics for the value
693
+ * channel. The scheduler digests exactly what this returns; resolving a
694
+ * `Ref` any other way here would let two executions that key identically
695
+ * consume different values.
696
+ */
697
+ const project = StepKey.project
698
+
699
+ /**
700
+ * Replaces every placeholder in a hydrated payload with what it stands for.
701
+ *
702
+ * The record is rebuilt with a null prototype and `defineProperty`, for the
703
+ * reason {@link module:Graph.build}'s cloners are: `output[key] = …` on an
704
+ * object literal routes `__proto__` through `Object.prototype`'s accessor,
705
+ * which drops an own `__proto__` field carrying a primitive and reparents
706
+ * the clone when it carries an object. A graph hydrates that field as data,
707
+ * so the value handed to an action has to keep it as data too.
708
+ */
709
+ const resolve = (value: unknown): unknown => {
710
+ const reference = Planned.reference(value)
711
+ if (reference !== undefined) {
712
+ return project(
713
+ settled.has(reference.node) ? settled.get(reference.node) : failed.get(reference.node),
714
+ reference.path
715
+ )
716
+ }
717
+ if (Array.isArray(value)) return value.map((item) => resolve(item))
718
+ if (!isRecord(value)) return value
719
+ const output: Record<string, unknown> = Object.create(null) as Record<string, unknown>
720
+ for (const key of Object.keys(value)) {
721
+ Object.defineProperty(output, key, {
722
+ configurable: true,
723
+ enumerable: true,
724
+ value: resolve(value[key]),
725
+ writable: true
726
+ })
727
+ }
728
+ const outcome = Object.getOwnPropertyDescriptor(value, OutcomeValueTypeId)
729
+ if (outcome !== undefined && "value" in outcome) {
730
+ Object.defineProperty(output, OutcomeValueTypeId, {
731
+ configurable: false,
732
+ enumerable: false,
733
+ value: outcome.value,
734
+ writable: false
735
+ })
736
+ }
737
+ return output
738
+ }
739
+
740
+ /**
741
+ * The settlement currently in flight for a node, for as long as it is.
742
+ *
743
+ * `settled.has(id)` alone is not a memo once the walk runs concurrently: it
744
+ * answers "has this node FINISHED", and two demands that arrive while the
745
+ * node is still running both see `false` and both execute it. A diamond
746
+ * would then run its shared node twice, which for an action with effects is
747
+ * a correctness bug rather than a wasted cycle.
748
+ *
749
+ * The check-and-register below is the whole reason this is a `suspend` and
750
+ * not a generator: the callback runs synchronously, so no other fiber can
751
+ * observe the gap between the lookup and the insert.
752
+ *
753
+ * Every computation runs on a fiber forked into `nodes`, the
754
+ * interpretation's own scope, and every demand — the first included — is a
755
+ * join on the node's deferred. Ownership by the interpretation rather than
756
+ * by whichever demand arrived first is what makes a ref-diamond sound: a
757
+ * failing `All` interrupts its member JOINS, never a shared node's
758
+ * execution, so recovery arms above can still join the shared work; and
759
+ * when the walk itself ends, closing the scope interrupts whatever is
760
+ * still running, so an orphaned execution cannot outlive the
761
+ * interpretation or write a phantom success into `settled` afterwards
762
+ * (Skyframe's and `MemoMap`'s ownership model).
763
+ */
764
+ const inFlight = new Map<string, Deferred.Deferred<unknown, unknown>>()
765
+ const nodes = yield* Scope.make()
766
+ /**
767
+ * The execution fiber of every node still running, so a join that settled
768
+ * early can interrupt the work of the members it no longer waits for.
769
+ */
770
+ const executions = new Map<string, Fiber.Fiber<unknown, unknown>>()
771
+ /**
772
+ * The child execution id each `.child()` node opened. Interrupting the
773
+ * node's fiber only stops its join: a losing child is cancelled through
774
+ * the runtime, the same request a caller's cancellation makes.
775
+ */
776
+ const childExecutions = new Map<string, string>()
777
+
778
+ /**
779
+ * The nodes this walk has already settled a record for.
780
+ *
781
+ * Everything else is what the walk never reached, which is exactly the
782
+ * `skipped` set reported below — an untaken branch arm, or a dependent of
783
+ * something that failed.
784
+ */
785
+ const reported = new Set<string>()
786
+
787
+ const settleNode = (id: string): Effect.Effect<unknown, unknown, Services> =>
788
+ Effect.suspend(() => {
789
+ if (settled.has(id)) return Effect.succeed(settled.get(id))
790
+ const waiting = inFlight.get(id)
791
+ // A later demand joins the execution rather than starting one.
792
+ if (waiting !== undefined) return Deferred.await(waiting)
793
+ const deferred = Deferred.makeUnsafe<unknown, unknown>()
794
+ inFlight.set(id, deferred)
795
+ const node = byId.get(id)!
796
+ const action = actionTag(node.ast)
797
+ // What this node's dispatches did, reported by the engine underneath
798
+ // it: a node whose dispatches were ALL served from durable records
799
+ // rebuilt nothing, and says so. The digests name those dispatches, so
800
+ // the attempt rows the engine wrote under them belong to THIS node,
801
+ // and the highest attempt reported is the node's real attempt count.
802
+ // A `Set` keeps the digests unrepeated in report order: one dispatch
803
+ // retried three times keeps one step key and reports it three times.
804
+ const dispatches = { executed: 0, replayed: 0, attempts: 1, digests: new Set<string>() }
805
+ const settleRecord =
806
+ (outcome: NodeRecord.NodeOutcome, settlement: { readonly value: unknown }) => (): NodeRecord.NodeRecord => ({
807
+ _tag: "NodeSettled",
808
+ sourceId: `node/${id}/1/settled`,
809
+ nodeId: id,
810
+ outcome,
811
+ attempts: dispatches.attempts,
812
+ stepKeyDigests: [...dispatches.digests],
813
+ value: settlement.value,
814
+ ...(action === undefined ? {} : { action })
815
+ })
816
+ const execution = record(() => ({
817
+ _tag: "NodeScheduled",
818
+ sourceId: `node/${id}/1`,
819
+ nodeId: id,
820
+ kind: node.kind,
821
+ attempt: 1,
822
+ ...(action === undefined ? {} : { action })
823
+ })).pipe(
824
+ Effect.andThen(
825
+ compute(node).pipe(
826
+ Effect.provideService(
827
+ DispatchReport,
828
+ DispatchReport.of({
829
+ dispatched: (dispatch) =>
830
+ Effect.sync(() => {
831
+ if (dispatch.outcome === "executed") dispatches.executed = dispatches.executed + 1
832
+ else dispatches.replayed = dispatches.replayed + 1
833
+ // Bounded where it is COLLECTED, not where it is
834
+ // written: a cap applied at the end still holds every
835
+ // digest in memory until then, and the point of the cap
836
+ // is that one node cannot grow without bound.
837
+ if (
838
+ dispatch.stepKeyDigest !== undefined &&
839
+ dispatches.digests.size < maximumDispatchDigests
840
+ ) {
841
+ dispatches.digests.add(dispatch.stepKeyDigest)
842
+ }
843
+ if (dispatch.attempt !== undefined && dispatch.attempt > dispatches.attempts) {
844
+ dispatches.attempts = dispatch.attempt
845
+ }
846
+ })
847
+ })
848
+ )
849
+ )
850
+ ),
851
+ Effect.tap((value) =>
852
+ Effect.sync(() => {
853
+ settled.set(id, value)
854
+ reported.add(id)
855
+ }).pipe(
856
+ Effect.andThen(record(
857
+ settleRecord(dispatches.executed === 0 && dispatches.replayed > 0 ? "clean" : "built", { value })
858
+ ))
859
+ )
860
+ ),
861
+ Effect.tapError((error) =>
862
+ Effect.sync(() => {
863
+ failed.set(id, error)
864
+ reported.add(id)
865
+ }).pipe(Effect.andThen(record(settleRecord("failed", { value: error }))))
866
+ ),
867
+ Effect.onExit((exit) => {
868
+ // An interrupted execution is evicted, not memoized: current
869
+ // joiners observe the interruption, but a LATER demand — a
870
+ // recovery arm, a re-driven walk — re-executes the node instead
871
+ // of replaying an interrupt that says nothing about it.
872
+ if (Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)) inFlight.delete(id)
873
+ executions.delete(id)
874
+ return Deferred.done(deferred, exit)
875
+ })
876
+ )
877
+ // `startImmediately` keeps the first demand's latency identical to the
878
+ // old inline execution: the node runs synchronously until its first
879
+ // real suspension, so a body that parks does so before the driver's
880
+ // next tick — the same observable cadence a sequential walk had.
881
+ return Effect.forkIn(
882
+ Effect.withFiber((fiber) => {
883
+ executions.set(id, fiber)
884
+ return execution
885
+ }),
886
+ nodes,
887
+ { startImmediately: true }
888
+ ).pipe(Effect.andThen(Deferred.await(deferred)))
889
+ })
890
+
891
+ /**
892
+ * The nodes that read each node's value through their key material: the
893
+ * payload references and barriers a structural subtree does not show.
894
+ */
895
+ const readers = new Map<string, Array<string>>()
896
+ for (const node of graphNodes) {
897
+ for (const dependency of KeyMaterial.dependencies(node.draft.material)) {
898
+ readers.set(dependency, [...readers.get(dependency) ?? [], node.id])
899
+ }
900
+ }
901
+
902
+ /**
903
+ * The work of the members a join no longer waits for: their running node
904
+ * fibers and the child executions those nodes opened.
905
+ *
906
+ * A member's structural subtree is its own, so its running executions are
907
+ * interrupted; nothing else demands them again. A node something outside that
908
+ * subtree also reads is shared work the interpretation owns — the same
909
+ * ownership an `All` failure respects — and keeps running, together with
910
+ * everything inside the subtree it depends on.
911
+ */
912
+ // Graph building gives every node exactly one structural parent and reads
913
+ // each child through that parent's key material, so the walk below never
914
+ // reaches a node twice and every node in a subtree has a reader.
915
+ const losingWork = (joinId: string, roots: ReadonlyArray<string>) => {
916
+ const subtree = new Set<string>()
917
+ const pending = [...roots]
918
+ while (pending.length > 0) {
919
+ const id = pending.pop()!
920
+ subtree.add(id)
921
+ pending.push(...sources.get(id) ?? [])
922
+ }
923
+ const kept = new Set<string>()
924
+ const shared = [...subtree].filter((id) =>
925
+ readers.get(id)!.some((reader) => reader !== joinId && !subtree.has(reader))
926
+ )
927
+ while (shared.length > 0) {
928
+ const id = shared.pop()!
929
+ if (kept.has(id) || !subtree.has(id)) continue
930
+ kept.add(id)
931
+ shared.push(...sources.get(id) ?? [], ...KeyMaterial.dependencies(byId.get(id)!.draft.material))
932
+ }
933
+ const fibers: Array<Fiber.Fiber<unknown, unknown>> = []
934
+ const childRuns: Array<JoinChild> = []
935
+ for (const id of subtree) {
936
+ if (kept.has(id)) continue
937
+ const fiber = executions.get(id)
938
+ if (fiber === undefined) continue
939
+ fibers.push(fiber)
940
+ const executionId = childExecutions.get(id)
941
+ if (executionId !== undefined) childRuns.push({ node: id, executionId })
942
+ }
943
+ return { fibers, childRuns }
944
+ }
945
+
946
+ /**
947
+ * Cancels the child executions a join's losers opened, through the
948
+ * runtime: interrupting a node's fiber only stops its join, so a losing
949
+ * child is cancelled with the same request a caller's cancellation makes.
950
+ */
951
+ const cancelChildren = (childRuns: ReadonlyArray<JoinChild>): Effect.Effect<void, never, Services> =>
952
+ Effect.forEach(
953
+ childRuns,
954
+ ({ executionId, node }) =>
955
+ Effect.orDie(runtime.interrupt(childDeclarations.get(node)! as AnyFlow, executionId)),
956
+ { discard: true }
957
+ )
958
+
959
+ /**
960
+ * Settles a `Race` node: `race`, `any`, or `quorum`.
961
+ *
962
+ * The members that decided are journaled, in the order they decided,
963
+ * under a durable deferred named for the node before any loser is
964
+ * interrupted. A resumed walk that finds the record settles exactly those
965
+ * members — whose own dispatches replay from their durable records — and
966
+ * never demands the others, so it reaches the same verdict without
967
+ * running a loser again. A defect or an interpreter refusal in a member is
968
+ * not a decision: it propagates unjournaled, as it would from an `All`.
969
+ *
970
+ * The same record names the child executions the losers had opened, so a
971
+ * process that dies between the journal write and their cancellation does
972
+ * not orphan them: the resumed walk never demands a loser, and it cancels
973
+ * those children from the record instead (#2892). Cancellation is a
974
+ * first-writer-wins request, so repeating it on every replay is harmless.
975
+ */
976
+ const join = (
977
+ node: Graph.GraphNode,
978
+ ast: Extract<Node.Ast, { readonly _tag: "Race" }>,
979
+ children: ReadonlyArray<string>
980
+ ): Effect.Effect<unknown, unknown, Services> =>
981
+ Effect.gen(function*() {
982
+ const members = Object.keys(ast.nodes)
983
+ const journal = DurableDeferred.make(`join/${node.id}`, { success: JoinDecision })
984
+ const instance = yield* FlowInstance
985
+ // A join is not a wait: reading its record must not consume the
986
+ // waiting classification a parked sibling declared.
987
+ const waiting = instance.waiting
988
+ const recorded = yield* runtime.deferredResult(journal)
989
+ instance.waiting = waiting
990
+ if (Option.isSome(recorded) && Exit.isSuccess(recorded.value)) {
991
+ const record = recorded.value.value
992
+ const decided = "members" in record ? record.members : record
993
+ const opened = "members" in record ? record.children : []
994
+ const indices = decided.map((member) => members.indexOf(member))
995
+ if (indices.some((index) => index < 0) || new Set(indices).size !== indices.length) {
996
+ return yield* refuse(
997
+ "join_mismatch",
998
+ node.id,
999
+ `Join at "${node.id}" journaled members ${JSON.stringify(decided)}, which it no longer holds.`
1000
+ )
1001
+ }
1002
+ const lost = opened.find((child) => !childDeclarations.has(child.node))
1003
+ if (lost !== undefined) {
1004
+ return yield* refuse(
1005
+ "join_mismatch",
1006
+ node.id,
1007
+ `Join at "${node.id}" journaled a losing child at "${lost.node}", which the plan no longer holds.`
1008
+ )
1009
+ }
1010
+ yield* cancelChildren(opened)
1011
+ // The losers are never demanded, so nothing of theirs starts again.
1012
+ const exits = yield* Effect.forEach(indices, (index) => Effect.exit(settleNode(children[index]!)), {
1013
+ concurrency: "unbounded"
1014
+ })
1015
+ // The verdict must close on the LAST journaled member, exactly as it
1016
+ // did live: one that closes earlier, or never, is a different join.
1017
+ const outcomes: Array<Decision> = []
1018
+ for (let position = 0; position < exits.length; position++) {
1019
+ const exit = exits[position]!
1020
+ if (classify(exit) !== "decisive") return yield* exit
1021
+ outcomes.push({ member: members[indices[position]!]!, exit })
1022
+ const verdict = decide(ast, members.length, outcomes)
1023
+ if (verdict !== undefined && position === exits.length - 1) return yield* verdict
1024
+ if (verdict !== undefined) break
1025
+ }
1026
+ return yield* refuse(
1027
+ "join_mismatch",
1028
+ node.id,
1029
+ `Join at "${node.id}" replayed members ${JSON.stringify(decided)} without reaching its verdict.`
1030
+ )
1031
+ }
1032
+
1033
+ const outcomes: Array<Decision> = []
1034
+ let verdict: Exit.Exit<unknown, unknown> | undefined
1035
+ let journaled = false
1036
+ const over = Deferred.makeUnsafe<void>()
1037
+ const watch = (index: number) =>
1038
+ Effect.exit(settleNode(children[index]!)).pipe(
1039
+ Effect.flatMap((exit) =>
1040
+ Effect.sync(() => {
1041
+ if (verdict !== undefined) return
1042
+ const kind = classify(exit)
1043
+ // An interrupted member is still undecided: it parked, or a
1044
+ // join elsewhere cancelled shared work. The others may still
1045
+ // decide without it.
1046
+ if (kind === "pending") return
1047
+ if (kind === "fatal") {
1048
+ verdict = exit
1049
+ } else {
1050
+ outcomes.push({ member: members[index]!, exit })
1051
+ verdict = decide(ast, members.length, outcomes)
1052
+ journaled = verdict !== undefined
1053
+ }
1054
+ if (verdict !== undefined) Deferred.doneUnsafe(over, Exit.void)
1055
+ })
1056
+ )
1057
+ )
1058
+ yield* Effect.raceFirst(
1059
+ Effect.forEach(children, (_, index) => watch(index), { concurrency: "unbounded", discard: true }),
1060
+ Deferred.await(over)
1061
+ )
1062
+ // Every member ended interrupted: the join cannot decide in this
1063
+ // round, and the walk parks or ends with it.
1064
+ if (verdict === undefined) return yield* Effect.interrupt
1065
+ const decidedMembers = outcomes.map((outcome) => outcome.member)
1066
+ yield* Effect.uninterruptible(
1067
+ Effect.gen(function*() {
1068
+ const { childRuns, fibers } = losingWork(
1069
+ node.id,
1070
+ children.filter((_, index) => !decidedMembers.includes(members[index]!))
1071
+ )
1072
+ if (journaled) {
1073
+ yield* runtime.deferredDone(journal, {
1074
+ flowName: instance.flow._tag,
1075
+ executionId: instance.executionId,
1076
+ deferredName: journal.name,
1077
+ exit: Exit.succeed({ members: decidedMembers, children: childRuns })
1078
+ })
1079
+ }
1080
+ // The interruption is requested, not awaited: a loser's finalizers
1081
+ // may wait on work that only settles after the join returns, and
1082
+ // the node scope awaits every fiber when the walk ends anyway.
1083
+ yield* Effect.withFiber((current) =>
1084
+ Effect.sync(() => {
1085
+ for (const fiber of fibers) fiber.interruptUnsafe(current.id)
1086
+ })
1087
+ )
1088
+ yield* cancelChildren(childRuns)
1089
+ })
1090
+ )
1091
+ return yield* verdict
1092
+ })
1093
+
1094
+ const compute: (node: Graph.GraphNode) => Effect.Effect<unknown, unknown, Services> = Effect.fnUntraced(
1095
+ function*(node: Graph.GraphNode) {
1096
+ const children = sources.get(node.id) ?? []
1097
+ const ast = node.ast
1098
+ if (ast._tag === "AndThen" && ast.next !== undefined) {
1099
+ // An explicit sequence is a success boundary for the WHOLE next
1100
+ // subtree. Joining first and then concurrently lets a nested All,
1101
+ // Map, or inline call start effects before its prerequisite settles.
1102
+ // bindPlanned is distinct: it declares data dependencies and can
1103
+ // expose independent work while the producer is still running.
1104
+ yield* settleNode(children[0]!)
1105
+ return yield* settleNode(children[1]!)
1106
+ }
1107
+ if (ast._tag === "Branch") {
1108
+ // The predicate decides on the REAL value, and only the arm it chose
1109
+ // is settled. Both arms are still in the plan; the untaken one is
1110
+ // reported as skipped.
1111
+ const decide = Node.predicate(ast)!
1112
+ const subject = yield* settleNode(children[0]!)
1113
+ return yield* settleNode(decide(subject) ? children[1]! : children[2]!)
1114
+ }
1115
+ if (ast._tag === "Catch") {
1116
+ // DECIDED: catch observes only typed
1117
+ // failures from ordinary protected execution. Interpreter refusals
1118
+ // propagate unchanged. Effect defects and
1119
+ // compensation failures remain outside this interpreter's error
1120
+ // channel, so recovery cannot conceal a broken invariant or weaken
1121
+ // withRollback's compensation guarantees.
1122
+ const protectedId = children[0]!
1123
+ return yield* Effect.matchEffect(settleNode(protectedId), {
1124
+ onFailure: (error) => {
1125
+ if (Schema.is(InterpreterError)(error)) return Effect.fail(error)
1126
+ const filter = Node.catchFilter(ast)
1127
+ if (filter !== undefined && !Schema.is(filter)(error)) {
1128
+ return Effect.fail(error)
1129
+ }
1130
+ // The failure arm's planned subject resolves through the
1131
+ // protected node id. Filing the typed failure as that settlement
1132
+ // also prevents its static failure edge from re-running the
1133
+ // protected graph while the recovery arm is driven.
1134
+ failed.set(protectedId, error)
1135
+ return settleNode(children[1]!)
1136
+ },
1137
+ onSuccess: Effect.succeed
1138
+ })
1139
+ }
1140
+ if (ast._tag === "Race") {
1141
+ // A join must not wait for every member, so its members are left
1142
+ // out of the dependency barrier below; its other dependencies — the
1143
+ // references and barriers it reads — still settle first.
1144
+ const members = new Set(children)
1145
+ yield* Effect.forEach(
1146
+ KeyMaterial.dependencies(node.draft.material).filter((dependency) =>
1147
+ !members.has(dependency) && !failed.has(dependency)
1148
+ ),
1149
+ settleNode,
1150
+ { concurrency: "unbounded", discard: true }
1151
+ )
1152
+ return yield* join(node, ast, children)
1153
+ }
1154
+ // Dependency order, from the key material: the same `Ref` and `Pending`
1155
+ // inputs the plan turns into edges, settled before the node that reads
1156
+ // them — and settled CONCURRENTLY, because independent dependencies are
1157
+ // exactly what the scheduler admits under separate permits. Two
1158
+ // execution surfaces disagreeing about concurrency is a correctness
1159
+ // hazard, not just a latency one.
1160
+ yield* Effect.forEach(
1161
+ KeyMaterial.dependencies(node.draft.material).filter((dependency) => !failed.has(dependency)),
1162
+ settleNode,
1163
+ { concurrency: "unbounded", discard: true }
1164
+ )
1165
+ switch (ast._tag) {
1166
+ case "ActionCall":
1167
+ // Resolved by the pre-pass above, which refuses the whole graph
1168
+ // when an action it names has no implementation. The structural
1169
+ // node address is durable dispatch identity, scoped to this one
1170
+ // implementation call; it never comes from fiber arrival order.
1171
+ return yield* implementations.get(ast.action)!.action(resolve(node.payload)).pipe(
1172
+ Effect.provideService(DispatchSite, node.id),
1173
+ attenuateCapabilities(node.capabilityCeilings)
1174
+ )
1175
+ case "Succeed":
1176
+ return resolve(node.payload)
1177
+ case "Fail":
1178
+ // The typed error channel, from a constant the plan already holds.
1179
+ // An enclosing catch observes it exactly as it observes an action's
1180
+ // failure, and an uncaught one is the body's typed failure.
1181
+ return yield* Effect.fail(resolve(node.payload))
1182
+ case "Map": {
1183
+ const transform = Node.mapper(ast)!
1184
+ return transform(yield* settleNode(children[0]!))
1185
+ }
1186
+ case "All": {
1187
+ // `All` is a combination, so its members settle concurrently — the
1188
+ // same semantics `PlanScheduler` gives the same graph. Fail-fast
1189
+ // matches `Effect.forEach`'s default and the scheduler's halt
1190
+ // rule — with the ownership caveat: what a failure interrupts is
1191
+ // each sibling's JOIN, not the node execution itself, which the
1192
+ // interpretation owns and interrupts when the walk ends. A node
1193
+ // whose execution was interrupted is evicted from the memo and
1194
+ // records nothing, so it reports as skipped rather than as a
1195
+ // phantom success.
1196
+ const members = Object.keys(ast.nodes)
1197
+ const values = yield* Effect.forEach(members, (_, index) => settleNode(children[index]!), {
1198
+ concurrency: "unbounded"
1199
+ })
1200
+ const joined: Record<string, unknown> = Object.create(null) as Record<string, unknown>
1201
+ for (let index = 0; index < members.length; index++) {
1202
+ Object.defineProperty(joined, members[index]!, {
1203
+ configurable: true,
1204
+ enumerable: true,
1205
+ value: values[index],
1206
+ writable: true
1207
+ })
1208
+ }
1209
+ return joined
1210
+ }
1211
+ case "AndThen":
1212
+ return yield* settleNode(children[1]!)
1213
+ case "FlowCall": {
1214
+ if (ast.mode === "handoff") {
1215
+ const declaration = handoffDeclarations.get(node.id)!
1216
+ // DECIDED: a handoff target's schema
1217
+ // services come from the context registration captured. A
1218
+ // body's type cannot enumerate declarations hidden in its
1219
+ // topology, so erase only that dynamic service parameter after
1220
+ // resolving the concrete declaration here.
1221
+ const payload = yield* Effect.flatMap(
1222
+ Effect.orDie(declaration.payloadSchema.makeEffect(resolve(node.payload) as never)),
1223
+ (decoded) =>
1224
+ Effect.orDie(
1225
+ Schema.encodeEffect(Schema.toCodecJson(declaration.payloadSchema))(decoded)
1226
+ )
1227
+ ) as unknown as Effect.Effect<unknown, never, Services>
1228
+ const ceiling = (yield* attenuateCapabilities(node.capabilityCeilings)(CapabilitySet.current)).groups
1229
+ const outcome = {
1230
+ _tag: "To",
1231
+ flow: ast.flow,
1232
+ payload,
1233
+ ...(ceiling.length === 0 ? {} : { capabilityCeilings: ceiling })
1234
+ } satisfies Outcome.To<unknown>
1235
+ Object.defineProperty(outcome, OutcomeValueTypeId, {
1236
+ configurable: false,
1237
+ enumerable: false,
1238
+ value: "To",
1239
+ writable: false
1240
+ })
1241
+ return outcome
1242
+ }
1243
+ if (ast.mode === "boundary") {
1244
+ // The real boundary of `docs/guides/run-a-child-flow.md`: an
1245
+ // ordinary child execution, opened through the same `execute` a
1246
+ // handler would call. The parent's `FlowInstance` is in scope, so
1247
+ // the engine records the lineage edge, interrupts the child with
1248
+ // the parent, and turns a suspended child into a suspended
1249
+ // parent — genuine nesting, not a spliced imitation of it. It is
1250
+ // deliberately NOT `Effect.scoped`: the interrupt link the engine
1251
+ // installs is a finalizer of the PARENT's scope, and closing a
1252
+ // scope of our own here would run it the moment the child
1253
+ // settled. The same dynamic schema-service erasure the handoff
1254
+ // above records applies to the cast: a body's type cannot
1255
+ // enumerate the declarations hidden in its topology.
1256
+ const declaration = childDeclarations.get(node.id)!
1257
+ const instance = yield* FlowInstance
1258
+ const childPayload = resolve(node.payload)
1259
+ const executionId = yield* (childExecutionId(
1260
+ instance.executionId,
1261
+ node.id,
1262
+ declaration._tag,
1263
+ childPayload
1264
+ ) as unknown as Effect.Effect<string, never, Services>)
1265
+ childExecutions.set(node.id, executionId)
1266
+ return yield* (declaration.execute(childPayload, {
1267
+ executionId
1268
+ }) as Effect.Effect<unknown, unknown, Services>).pipe(attenuateCapabilities(node.capabilityCeilings))
1269
+ }
1270
+ const spliced = children[0]
1271
+ if (spliced === undefined) {
1272
+ return yield* refuse(
1273
+ "unsupported_call",
1274
+ node.id,
1275
+ `Flow "${ast.flow}" is called at "${node.id}" as a leaf, which this interpreter does not drive. ` +
1276
+ "An inline .call() is spliced into the graph with the callee's body and driven with it, and " +
1277
+ "only a call that lost its declaration has no body to splice. Build and interpret the authored " +
1278
+ `node in the same process, or call it as ${ast.flow}.child(payload) to run it as its own execution.`
1279
+ )
1280
+ }
1281
+ return yield* settleNode(spliced)
1282
+ }
1283
+ }
1284
+ }
1285
+ )
1286
+
1287
+ // Closing the node scope on every exit — value, failure, or the
1288
+ // interpretation's own interruption — is the fail-fast half of the
1289
+ // ownership model: whatever is still running when the walk ends is
1290
+ // interrupted before the interpretation reports, so no execution
1291
+ // outlives it.
1292
+ /**
1293
+ * Everything the walk never reached, recorded once the walk is over.
1294
+ *
1295
+ * Only a walk that ENDED reports this. An interrupted one is a run that
1296
+ * parked or was cancelled, and its nodes are not skipped — the resumed
1297
+ * walk settles them, and recording them as skipped first would leave the
1298
+ * journal claiming a node never ran that later did. A typed failure is
1299
+ * terminal for the round, so its stranded nodes are skipped exactly as a
1300
+ * completed walk's untaken branch arm is.
1301
+ */
1302
+ const recordSkipped = (exit: Exit.Exit<unknown, unknown>) =>
1303
+ Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause) ? Effect.void : Effect.forEach(
1304
+ graphNodes.filter((node) => !reported.has(node.id)),
1305
+ (node) => {
1306
+ const action = actionTag(node.ast)
1307
+ return record(() => ({
1308
+ _tag: "NodeSettled",
1309
+ sourceId: `node/${node.id}/1/settled`,
1310
+ nodeId: node.id,
1311
+ outcome: "skipped",
1312
+ attempts: 0,
1313
+ // A node the walk never reached dispatched nothing, so it claims
1314
+ // no step key and there is no value it settled with.
1315
+ stepKeyDigests: [],
1316
+ ...(action === undefined ? {} : { action })
1317
+ }))
1318
+ },
1319
+ { discard: true }
1320
+ )
1321
+
1322
+ const value = yield* settleNode(options.root ?? "root").pipe(
1323
+ Effect.onExit((exit) => Scope.close(nodes, exit)),
1324
+ Effect.onExit(recordSkipped)
1325
+ )
1326
+ return {
1327
+ value,
1328
+ settled,
1329
+ failed,
1330
+ skipped: Graph.nodes(graph).filter((node) => !settled.has(node.id) && !failed.has(node.id)).map((node) => node.id)
1331
+ }
1332
+ })
1333
+
1334
+ /** A child execution a losing join member opened. @private */
1335
+ const JoinChild = Schema.Struct({ node: Schema.String, executionId: Schema.String })
1336
+ type JoinChild = typeof JoinChild.Type
1337
+
1338
+ /**
1339
+ * The members a join decided on, in the order they decided, and the child
1340
+ * executions its losers had opened. A bare member list is the record written
1341
+ * before losing children were journaled; it names none, and still replays.
1342
+ *
1343
+ * @private
1344
+ */
1345
+ const JoinDecision = Schema.Union([
1346
+ Schema.Struct({ members: Schema.Array(Schema.String), children: Schema.Array(JoinChild) }),
1347
+ Schema.Array(Schema.String)
1348
+ ])
1349
+
1350
+ /** One member's decisive settlement. @private */
1351
+ interface Decision {
1352
+ readonly member: string
1353
+ readonly exit: Exit.Exit<unknown, unknown>
1354
+ }
1355
+
1356
+ /**
1357
+ * How a member's settlement counts toward a join: a value or a typed failure
1358
+ * decides, an interruption is still undecided, and a defect or interpreter
1359
+ * refusal is fatal to the join.
1360
+ *
1361
+ * @private
1362
+ */
1363
+ const classify = (exit: Exit.Exit<unknown, unknown>): "decisive" | "pending" | "fatal" => {
1364
+ if (Exit.isSuccess(exit)) return "decisive"
1365
+ if (Cause.hasInterruptsOnly(exit.cause)) return "pending"
1366
+ const reasons = exit.cause.reasons
1367
+ const typed = reasons.every((reason) => reason._tag !== "Die") &&
1368
+ reasons.some((reason) => reason._tag === "Fail") &&
1369
+ !reasons.some((reason) => reason._tag === "Fail" && Schema.is(InterpreterError)(reason.error))
1370
+ return typed ? "decisive" : "fatal"
1371
+ }
1372
+
1373
+ /**
1374
+ * A join's verdict over the members that decided, in the order they decided,
1375
+ * or `undefined` while it is still open.
1376
+ *
1377
+ * `race` takes the first settlement. `any` and `quorum` succeed once `count`
1378
+ * members succeed, and fail with the failure that leaves too few members able
1379
+ * to: for `any`, that is the last one.
1380
+ *
1381
+ * @private
1382
+ */
1383
+ const decide = (
1384
+ ast: Extract<Node.Ast, { readonly _tag: "Race" }>,
1385
+ width: number,
1386
+ outcomes: ReadonlyArray<Decision>
1387
+ ): Exit.Exit<unknown, unknown> | undefined => {
1388
+ if (ast.mode === "race") return outcomes[0]?.exit
1389
+ const successes = outcomes.filter((outcome) => Exit.isSuccess(outcome.exit))
1390
+ if (successes.length >= ast.count) {
1391
+ if (ast.mode === "any") return successes[0]!.exit
1392
+ const joined: Record<string, unknown> = Object.create(null) as Record<string, unknown>
1393
+ for (const success of successes) {
1394
+ Object.defineProperty(joined, success.member, {
1395
+ configurable: true,
1396
+ enumerable: true,
1397
+ value: (success.exit as Exit.Success<unknown>).value,
1398
+ writable: true
1399
+ })
1400
+ }
1401
+ return Exit.succeed(joined)
1402
+ }
1403
+ if (outcomes.length - successes.length > width - ast.count) return outcomes[outcomes.length - 1]!.exit
1404
+ return undefined
1405
+ }
1406
+
1407
+ /**
1408
+ * Turns a body's root value into the settlement the engine acts on.
1409
+ *
1410
+ * Three of them exist, and only three
1411
+ * (`docs/concepts/trampoline-rounds.md`): `done(value)` is the answer, so the
1412
+ * value passes straight through; `Next.to(payload)` is the next round, recorded in the instance's
1413
+ * handoff slot so `Flow.intoResult` answers `Flow.Handoff`; and `park(reason)`
1414
+ * is a durable suspension, declared through the ordinary waiting vocabulary so
1415
+ * a durable driver parks the run under the flow's own reason and wake token
1416
+ * rather than the derived `timer`/`event` default. Anything else is an ordinary
1417
+ * value and is the answer as it stands, which is what keeps a body that never
1418
+ * heard of the trampoline working unchanged.
1419
+ *
1420
+ * @private
1421
+ */
1422
+ const settleOutcome = (value: unknown): Effect.Effect<unknown, never, FlowInstance> => {
1423
+ if (!Outcome.isOutcome(value)) return Effect.succeed(value)
1424
+ switch (value._tag) {
1425
+ case "Done":
1426
+ return Effect.succeed(value.value)
1427
+ case "To":
1428
+ return Effect.gen(function*() {
1429
+ const instance = yield* FlowInstance
1430
+ const current = yield* CapabilitySet.current
1431
+ instance.handoff = new Handoff({
1432
+ flow: value.flow,
1433
+ payload: value.payload,
1434
+ capabilityCeilings: [...current.groups, ...(value.capabilityCeilings ?? [])]
1435
+ })
1436
+ })
1437
+ case "Park":
1438
+ return Effect.andThen(
1439
+ annotateWaiting(value.reason),
1440
+ Effect.flatMap(FlowInstance, suspend)
1441
+ )
1442
+ }
1443
+ }
1444
+
1445
+ /**
1446
+ * Registers a flow with the runtime, driven by its body.
1447
+ *
1448
+ * This is the only way a flow's behavior reaches the runtime, and the reason a
1449
+ * flow has no `toLayer`: a flow has one behavior and it is the body, so there
1450
+ * is no second, opaque one to attach. An Action is what carries an
1451
+ * implementation, and it attaches that implementation with its own `toLayer`.
1452
+ * Compose this beside the action implementation layers the body calls, over the
1453
+ * {@link module:Implementations.layerImplementations} table they file
1454
+ * themselves in — the table goes UNDER them, because filing happens while an
1455
+ * implementation layer is built:
1456
+ *
1457
+ * ```ts
1458
+ * Layer.mergeAll(Read.toLayer(read), Write.toLayer(write), Interpreter.layer(Pipeline)).pipe(
1459
+ * Layer.provideMerge(Action.layerImplementations)
1460
+ * )
1461
+ * ```
1462
+ *
1463
+ * It accepts a flow whatever its requirement channel says, and requires only
1464
+ * the table. Registering a body is not running one: the handler installed here
1465
+ * resolves each call by tag when the run reaches it, which is the same thing a
1466
+ * driver does for a plan read back out of a journal. `Flow.execute` is where
1467
+ * the requirements are asked for.
1468
+ *
1469
+ * This low-level registration keeps the `process-local` callback policy for
1470
+ * compatibility. Select `callbackIdentity: "stable"`, or use
1471
+ * {@link layerWithImplementations}, to refuse unstable callbacks before
1472
+ * dispatch. Without that selection it makes no reproducible-plan guarantee.
1473
+ *
1474
+ * @since 0.1.0
1475
+ * @category layers
1476
+ */
1477
+ export const layer = <
1478
+ Tag extends string,
1479
+ Payload extends AnyStructSchema,
1480
+ Success extends Schema.Top,
1481
+ Error extends Schema.Top
1482
+ >(
1483
+ flow: Flow<Tag, Payload, Success, Error, any>,
1484
+ options: Graph.BuildOptions = {}
1485
+ ) => makeLayer(flow, options).pipe(Layer.provide(systemActions))
1486
+
1487
+ // These layers only register handlers at construction. Crypto is supplied by
1488
+ // the executing interpreter (Services), not required to register a flow.
1489
+ const systemHandlers = Layer.mergeAll(HumanTask.layer, Sleep.layer, WaitFor.layer) as Layer.Layer<
1490
+ never,
1491
+ never,
1492
+ FlowRuntime
1493
+ >
1494
+
1495
+ // Independent module/refresh layer builds can share one implementation table.
1496
+ // Memoize the built-ins by that owner, with Layer retaining each live scope's
1497
+ // reference, rather than registering a second handler or overriding the first.
1498
+ const systemMemoMaps = new WeakMap<Implementations["Service"], Layer.MemoMap>()
1499
+ const systemActions = Layer.fromBuild((currentMemoMap, scope) =>
1500
+ Effect.flatMap(Implementations, (table) => {
1501
+ let memoMap = systemMemoMaps.get(table)
1502
+ if (memoMap === undefined) {
1503
+ memoMap = currentMemoMap
1504
+ systemMemoMaps.set(table, memoMap)
1505
+ }
1506
+ return Layer.buildWithMemoMap(systemHandlers, memoMap, scope)
1507
+ })
1508
+ )
1509
+
1510
+ /** Registers the interpreter and reachable child flows under one policy. @private */
1511
+ const makeLayer = <
1512
+ Tag extends string,
1513
+ Payload extends AnyStructSchema,
1514
+ Success extends Schema.Top,
1515
+ Error extends Schema.Top
1516
+ >(
1517
+ flow: Flow<Tag, Payload, Success, Error, any>,
1518
+ options: Graph.BuildOptions,
1519
+ reachable?: Set<AnyWithProps>,
1520
+ ifAbsent = false
1521
+ ): Layer.Layer<
1522
+ never,
1523
+ never,
1524
+ | FlowRuntime
1525
+ | Implementations
1526
+ | Payload["DecodingServices"]
1527
+ | Payload["EncodingServices"]
1528
+ | Success["DecodingServices"]
1529
+ | Success["EncodingServices"]
1530
+ | Error["DecodingServices"]
1531
+ | Error["EncodingServices"]
1532
+ > =>
1533
+ Layer.effectDiscard(Effect.gen(function*() {
1534
+ const runtime = yield* FlowRuntime
1535
+ const registered = reachable ?? new Set<AnyWithProps>()
1536
+ if (registered.has(flow)) return
1537
+ // Lazy registration can be skipped by an independent live owner. Recheck
1538
+ // on later executions so releasing that owner permits fresh admission.
1539
+ if (!ifAbsent) registered.add(flow)
1540
+ const scope = yield* Effect.scope
1541
+ const context = yield* Effect.context<Services | Scope.Scope>()
1542
+ const implementations = Context.get(context, Implementations)
1543
+ // Keep discovered declarations in the host registration scope, rather
1544
+ // than the executing parent's scope: handoffs outlive that parent.
1545
+ // Dynamic declarations erase their codec services, just like execute;
1546
+ // those services still come from the registering host's context.
1547
+ const registerReachable = (declaration: AnyWithProps): Effect.Effect<void> =>
1548
+ Layer.buildWithScope(
1549
+ makeLayer(
1550
+ declaration as Flow<string, AnyStructSchema, Schema.Top, Schema.Top, any>,
1551
+ options,
1552
+ registered,
1553
+ true
1554
+ ),
1555
+ scope
1556
+ ).pipe(
1557
+ Effect.provide(context),
1558
+ Effect.asVoid
1559
+ ) as Effect.Effect<void>
1560
+ const middleware = Context.get(flow.annotations, ExecutionMiddleware)
1561
+ yield* runtime.register(
1562
+ flow,
1563
+ ((payload: Payload["Type"]) => {
1564
+ const body = Effect.flatMap(
1565
+ interpretWithPolicy(flow, payload, options, registerReachable),
1566
+ (interpretation) => settleOutcome(interpretation.value)
1567
+ )
1568
+ // Registration owns action resolution. A caller may supply a different
1569
+ // table when entering this flow as a child; retain dynamic execution
1570
+ // services while restoring this flow's registered implementations.
1571
+ return (middleware === undefined ? body : middleware.wrap(payload, body)).pipe(
1572
+ Effect.provideService(Implementations, implementations)
1573
+ )
1574
+ }) as (payload: Payload["Type"], executionId: string) => Effect.Effect<
1575
+ Success["Type"],
1576
+ Error["Type"],
1577
+ Crypto.Crypto | Implementations
1578
+ >,
1579
+ { ifAbsent }
1580
+ )
1581
+ }))
1582
+
1583
+ /**
1584
+ * Composes one flow and its implementation layers around one isolated registry.
1585
+ *
1586
+ * The implementation layer must provide every action requirement carried by the
1587
+ * flow. Callback identity defaults to `stable`: raw callbacks are refused before
1588
+ * dispatch, with a diagnostic naming the callback and `Node.capture`. Captures
1589
+ * must cover semantic configuration and implementation versions, including
1590
+ * behavior reached through imports. Explicit `callbackIdentity: "process-local"`
1591
+ * is available for local experimentation, without a reproducible-plan claim.
1592
+ * Sealed actions with an idempotency key must declare `implementationVersion`
1593
+ * and register that exact version. This rule is independent of callback policy:
1594
+ * those declarations can reuse recorded content even if their code changes.
1595
+ * Keyless sealed actions use invocation identity; compensable and irreversible
1596
+ * actions do not use content-key caching. Their versions remain optional.
1597
+ * Versions must cover semantic handler and service changes; callback stability
1598
+ * cannot establish that declaration's completeness.
1599
+ *
1600
+ * The interpreter validates all named graph actions before dispatching any
1601
+ * of them. Conflicting registrations fail while the layer builds; use an explicit
1602
+ * `toLayer(handler, { override: true })` only for intentional scoped substitution.
1603
+ *
1604
+ * @category layers
1605
+ * @since 0.1.0
1606
+ */
1607
+ export const layerWithImplementations = <
1608
+ Tag extends string,
1609
+ Payload extends AnyStructSchema,
1610
+ Success extends Schema.Top,
1611
+ Error extends Schema.Top,
1612
+ Requires,
1613
+ Provided,
1614
+ E,
1615
+ R
1616
+ >(
1617
+ flow: Flow<Tag, Payload, Success, Error, Requires>,
1618
+ implementations:
1619
+ & Layer.Layer<Provided, E, R>
1620
+ & (
1621
+ [NoInfer<Requires>] extends [Provided] ? unknown
1622
+ : { readonly missingActionImplementations: Exclude<Requires, Provided> }
1623
+ ),
1624
+ options: Graph.BuildOptions = {}
1625
+ ) =>
1626
+ Layer.merge(
1627
+ implementations,
1628
+ makeLayer(flow, { ...options, callbackIdentity: options.callbackIdentity ?? "stable" }).pipe(
1629
+ Layer.provide(systemActions)
1630
+ )
1631
+ )
1632
+ .pipe(
1633
+ Layer.provide(layerImplementations),
1634
+ Layer.fresh
1635
+ )