@smthrs/engine-store 0.0.0-stage → 1.0.0-rc.3

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 (787) hide show
  1. package/CHANGELOG.md +247 -0
  2. package/LICENSE +21 -0
  3. package/README.md +146 -2
  4. package/dist/cjs/ArtifactGc.d.ts +235 -0
  5. package/dist/cjs/ArtifactGc.d.ts.map +1 -0
  6. package/dist/cjs/ArtifactGc.js +183 -0
  7. package/dist/cjs/ArtifactGc.js.map +7 -0
  8. package/dist/cjs/ArtifactSync.d.ts +164 -0
  9. package/dist/cjs/ArtifactSync.d.ts.map +1 -0
  10. package/dist/cjs/ArtifactSync.js +144 -0
  11. package/dist/cjs/ArtifactSync.js.map +7 -0
  12. package/dist/cjs/CacheSync.d.ts +112 -0
  13. package/dist/cjs/CacheSync.d.ts.map +1 -0
  14. package/dist/cjs/CacheSync.js +64 -0
  15. package/dist/cjs/CacheSync.js.map +7 -0
  16. package/dist/cjs/DeferredClockFold.d.ts +354 -0
  17. package/dist/cjs/DeferredClockFold.d.ts.map +1 -0
  18. package/dist/cjs/DeferredClockFold.js +335 -0
  19. package/dist/cjs/DeferredClockFold.js.map +7 -0
  20. package/dist/cjs/DisasterRecovery.d.ts +431 -0
  21. package/dist/cjs/DisasterRecovery.d.ts.map +1 -0
  22. package/dist/cjs/DisasterRecovery.js +453 -0
  23. package/dist/cjs/DisasterRecovery.js.map +7 -0
  24. package/dist/cjs/DurableEngineState.d.ts +776 -0
  25. package/dist/cjs/DurableEngineState.d.ts.map +1 -0
  26. package/dist/cjs/DurableEngineState.js +1536 -0
  27. package/dist/cjs/DurableEngineState.js.map +7 -0
  28. package/dist/cjs/EngineStore.d.ts +158 -0
  29. package/dist/cjs/EngineStore.d.ts.map +1 -0
  30. package/dist/cjs/EngineStore.js +398 -0
  31. package/dist/cjs/EngineStore.js.map +7 -0
  32. package/dist/cjs/EngineStoreMetrics.d.ts +276 -0
  33. package/dist/cjs/EngineStoreMetrics.d.ts.map +1 -0
  34. package/dist/cjs/EngineStoreMetrics.js +135 -0
  35. package/dist/cjs/EngineStoreMetrics.js.map +7 -0
  36. package/dist/cjs/Errors.d.ts +22 -0
  37. package/dist/cjs/Errors.d.ts.map +1 -0
  38. package/dist/cjs/Errors.js +39 -0
  39. package/dist/cjs/Errors.js.map +7 -0
  40. package/dist/cjs/EventTypes.d.ts +27 -0
  41. package/dist/cjs/EventTypes.d.ts.map +1 -0
  42. package/dist/cjs/EventTypes.js +37 -0
  43. package/dist/cjs/EventTypes.js.map +7 -0
  44. package/dist/cjs/ExecutionFacts.d.ts +59 -0
  45. package/dist/cjs/ExecutionFacts.d.ts.map +1 -0
  46. package/dist/cjs/ExecutionFacts.js +123 -0
  47. package/dist/cjs/ExecutionFacts.js.map +7 -0
  48. package/dist/cjs/ExecutionSnapshot.d.ts +117 -0
  49. package/dist/cjs/ExecutionSnapshot.d.ts.map +1 -0
  50. package/dist/cjs/ExecutionSnapshot.js +111 -0
  51. package/dist/cjs/ExecutionSnapshot.js.map +7 -0
  52. package/dist/cjs/Inconsistency.d.ts +154 -0
  53. package/dist/cjs/Inconsistency.d.ts.map +1 -0
  54. package/dist/cjs/Inconsistency.js +135 -0
  55. package/dist/cjs/Inconsistency.js.map +7 -0
  56. package/dist/cjs/Migrations.d.ts +56 -0
  57. package/dist/cjs/Migrations.d.ts.map +1 -0
  58. package/dist/cjs/Migrations.js +78 -0
  59. package/dist/cjs/Migrations.js.map +7 -0
  60. package/dist/cjs/OwnerIdentity.d.ts +91 -0
  61. package/dist/cjs/OwnerIdentity.d.ts.map +1 -0
  62. package/dist/cjs/OwnerIdentity.js +65 -0
  63. package/dist/cjs/OwnerIdentity.js.map +7 -0
  64. package/dist/cjs/PlanInputStore.d.ts +110 -0
  65. package/dist/cjs/PlanInputStore.d.ts.map +1 -0
  66. package/dist/cjs/PlanInputStore.js +224 -0
  67. package/dist/cjs/PlanInputStore.js.map +7 -0
  68. package/dist/cjs/PlanMergeStore.d.ts +113 -0
  69. package/dist/cjs/PlanMergeStore.d.ts.map +1 -0
  70. package/dist/cjs/PlanMergeStore.js +255 -0
  71. package/dist/cjs/PlanMergeStore.js.map +7 -0
  72. package/dist/cjs/PlanScheduler.d.ts +363 -0
  73. package/dist/cjs/PlanScheduler.d.ts.map +1 -0
  74. package/dist/cjs/PlanScheduler.js +1090 -0
  75. package/dist/cjs/PlanScheduler.js.map +7 -0
  76. package/dist/cjs/Reconciliation.d.ts +175 -0
  77. package/dist/cjs/Reconciliation.d.ts.map +1 -0
  78. package/dist/cjs/Reconciliation.js +119 -0
  79. package/dist/cjs/Reconciliation.js.map +7 -0
  80. package/dist/cjs/ReplayOnly.d.ts +91 -0
  81. package/dist/cjs/ReplayOnly.d.ts.map +1 -0
  82. package/dist/cjs/ReplayOnly.js +52 -0
  83. package/dist/cjs/ReplayOnly.js.map +7 -0
  84. package/dist/cjs/Retention.d.ts +113 -0
  85. package/dist/cjs/Retention.d.ts.map +1 -0
  86. package/dist/cjs/Retention.js +99 -0
  87. package/dist/cjs/Retention.js.map +7 -0
  88. package/dist/cjs/RunCatalogRead.d.ts +17 -0
  89. package/dist/cjs/RunCatalogRead.d.ts.map +1 -0
  90. package/dist/cjs/RunCatalogRead.js +33 -0
  91. package/dist/cjs/RunCatalogRead.js.map +7 -0
  92. package/dist/cjs/RunChangeFeed.d.ts +75 -0
  93. package/dist/cjs/RunChangeFeed.d.ts.map +1 -0
  94. package/dist/cjs/RunChangeFeed.js +96 -0
  95. package/dist/cjs/RunChangeFeed.js.map +7 -0
  96. package/dist/cjs/RunState.d.ts +116 -0
  97. package/dist/cjs/RunState.d.ts.map +1 -0
  98. package/dist/cjs/RunState.js +76 -0
  99. package/dist/cjs/RunState.js.map +7 -0
  100. package/dist/cjs/Selection.d.ts +495 -0
  101. package/dist/cjs/Selection.d.ts.map +1 -0
  102. package/dist/cjs/Selection.js +288 -0
  103. package/dist/cjs/Selection.js.map +7 -0
  104. package/dist/cjs/SelectionStore.d.ts +141 -0
  105. package/dist/cjs/SelectionStore.d.ts.map +1 -0
  106. package/dist/cjs/SelectionStore.js +229 -0
  107. package/dist/cjs/SelectionStore.js.map +7 -0
  108. package/dist/cjs/StepBoundary.d.ts +501 -0
  109. package/dist/cjs/StepBoundary.d.ts.map +1 -0
  110. package/dist/cjs/StepBoundary.js +619 -0
  111. package/dist/cjs/StepBoundary.js.map +7 -0
  112. package/dist/cjs/StepCacheFold.d.ts +148 -0
  113. package/dist/cjs/StepCacheFold.d.ts.map +1 -0
  114. package/dist/cjs/StepCacheFold.js +131 -0
  115. package/dist/cjs/StepCacheFold.js.map +7 -0
  116. package/dist/cjs/StepSandbox.d.ts +78 -0
  117. package/dist/cjs/StepSandbox.d.ts.map +1 -0
  118. package/dist/cjs/StepSandbox.js +75 -0
  119. package/dist/cjs/StepSandbox.js.map +7 -0
  120. package/dist/cjs/WakeBus.d.ts +116 -0
  121. package/dist/cjs/WakeBus.d.ts.map +1 -0
  122. package/dist/cjs/WakeBus.js +89 -0
  123. package/dist/cjs/WakeBus.js.map +7 -0
  124. package/dist/cjs/WorkspaceSandbox.d.ts +619 -0
  125. package/dist/cjs/WorkspaceSandbox.d.ts.map +1 -0
  126. package/dist/cjs/WorkspaceSandbox.js +711 -0
  127. package/dist/cjs/WorkspaceSandbox.js.map +7 -0
  128. package/dist/cjs/index.d.ts +157 -0
  129. package/dist/cjs/index.d.ts.map +1 -0
  130. package/dist/cjs/index.js +95 -0
  131. package/dist/cjs/index.js.map +7 -0
  132. package/dist/cjs/internal/ActionPersistence.d.ts +276 -0
  133. package/dist/cjs/internal/ActionPersistence.d.ts.map +1 -0
  134. package/dist/cjs/internal/ActionPersistence.js +1395 -0
  135. package/dist/cjs/internal/ActionPersistence.js.map +7 -0
  136. package/dist/cjs/internal/ArtifactRoots.d.ts +38 -0
  137. package/dist/cjs/internal/ArtifactRoots.d.ts.map +1 -0
  138. package/dist/cjs/internal/ArtifactRoots.js +89 -0
  139. package/dist/cjs/internal/ArtifactRoots.js.map +7 -0
  140. package/dist/cjs/internal/AttemptAdmission.d.ts +53 -0
  141. package/dist/cjs/internal/AttemptAdmission.d.ts.map +1 -0
  142. package/dist/cjs/internal/AttemptAdmission.js +52 -0
  143. package/dist/cjs/internal/AttemptAdmission.js.map +7 -0
  144. package/dist/cjs/internal/AttemptLifecycle.d.ts +245 -0
  145. package/dist/cjs/internal/AttemptLifecycle.d.ts.map +1 -0
  146. package/dist/cjs/internal/AttemptLifecycle.js +112 -0
  147. package/dist/cjs/internal/AttemptLifecycle.js.map +7 -0
  148. package/dist/cjs/internal/AttemptProbe.d.ts +53 -0
  149. package/dist/cjs/internal/AttemptProbe.d.ts.map +1 -0
  150. package/dist/cjs/internal/AttemptProbe.js +66 -0
  151. package/dist/cjs/internal/AttemptProbe.js.map +7 -0
  152. package/dist/cjs/internal/CacheAdmission.d.ts +79 -0
  153. package/dist/cjs/internal/CacheAdmission.d.ts.map +1 -0
  154. package/dist/cjs/internal/CacheAdmission.js +71 -0
  155. package/dist/cjs/internal/CacheAdmission.js.map +7 -0
  156. package/dist/cjs/internal/CacheAgeHistory.d.ts +27 -0
  157. package/dist/cjs/internal/CacheAgeHistory.d.ts.map +1 -0
  158. package/dist/cjs/internal/CacheAgeHistory.js +61 -0
  159. package/dist/cjs/internal/CacheAgeHistory.js.map +7 -0
  160. package/dist/cjs/internal/CacheAgeVerdicts.d.ts +15 -0
  161. package/dist/cjs/internal/CacheAgeVerdicts.d.ts.map +1 -0
  162. package/dist/cjs/internal/CacheAgeVerdicts.js +73 -0
  163. package/dist/cjs/internal/CacheAgeVerdicts.js.map +7 -0
  164. package/dist/cjs/internal/CacheOutputPolicy.d.ts +24 -0
  165. package/dist/cjs/internal/CacheOutputPolicy.d.ts.map +1 -0
  166. package/dist/cjs/internal/CacheOutputPolicy.js +75 -0
  167. package/dist/cjs/internal/CacheOutputPolicy.js.map +7 -0
  168. package/dist/cjs/internal/CachePublication.d.ts +97 -0
  169. package/dist/cjs/internal/CachePublication.d.ts.map +1 -0
  170. package/dist/cjs/internal/CachePublication.js +80 -0
  171. package/dist/cjs/internal/CachePublication.js.map +7 -0
  172. package/dist/cjs/internal/CallFacts.d.ts +15 -0
  173. package/dist/cjs/internal/CallFacts.d.ts.map +1 -0
  174. package/dist/cjs/internal/CallFacts.js +97 -0
  175. package/dist/cjs/internal/CallFacts.js.map +7 -0
  176. package/dist/cjs/internal/CopiedRecord.d.ts +61 -0
  177. package/dist/cjs/internal/CopiedRecord.d.ts.map +1 -0
  178. package/dist/cjs/internal/CopiedRecord.js +134 -0
  179. package/dist/cjs/internal/CopiedRecord.js.map +7 -0
  180. package/dist/cjs/internal/DeferredPersistence.d.ts +108 -0
  181. package/dist/cjs/internal/DeferredPersistence.d.ts.map +1 -0
  182. package/dist/cjs/internal/DeferredPersistence.js +270 -0
  183. package/dist/cjs/internal/DeferredPersistence.js.map +7 -0
  184. package/dist/cjs/internal/EffectRecords.d.ts +92 -0
  185. package/dist/cjs/internal/EffectRecords.d.ts.map +1 -0
  186. package/dist/cjs/internal/EffectRecords.js +63 -0
  187. package/dist/cjs/internal/EffectRecords.js.map +7 -0
  188. package/dist/cjs/internal/EngineJj.d.ts +30 -0
  189. package/dist/cjs/internal/EngineJj.d.ts.map +1 -0
  190. package/dist/cjs/internal/EngineJj.js +42 -0
  191. package/dist/cjs/internal/EngineJj.js.map +7 -0
  192. package/dist/cjs/internal/EngineStateSchema.d.ts +68 -0
  193. package/dist/cjs/internal/EngineStateSchema.d.ts.map +1 -0
  194. package/dist/cjs/internal/EngineStateSchema.js +58 -0
  195. package/dist/cjs/internal/EngineStateSchema.js.map +7 -0
  196. package/dist/cjs/internal/ExecutionSnapshotRead.d.ts +162 -0
  197. package/dist/cjs/internal/ExecutionSnapshotRead.d.ts.map +1 -0
  198. package/dist/cjs/internal/ExecutionSnapshotRead.js +199 -0
  199. package/dist/cjs/internal/ExecutionSnapshotRead.js.map +7 -0
  200. package/dist/cjs/internal/ExecutionSnapshotRelations.d.ts +39 -0
  201. package/dist/cjs/internal/ExecutionSnapshotRelations.d.ts.map +1 -0
  202. package/dist/cjs/internal/ExecutionSnapshotRelations.js +105 -0
  203. package/dist/cjs/internal/ExecutionSnapshotRelations.js.map +7 -0
  204. package/dist/cjs/internal/ExitEncoding.d.ts +180 -0
  205. package/dist/cjs/internal/ExitEncoding.d.ts.map +1 -0
  206. package/dist/cjs/internal/ExitEncoding.js +236 -0
  207. package/dist/cjs/internal/ExitEncoding.js.map +7 -0
  208. package/dist/cjs/internal/FileBoundarySnapshot.d.ts +14 -0
  209. package/dist/cjs/internal/FileBoundarySnapshot.d.ts.map +1 -0
  210. package/dist/cjs/internal/FileBoundarySnapshot.js +51 -0
  211. package/dist/cjs/internal/FileBoundarySnapshot.js.map +7 -0
  212. package/dist/cjs/internal/FileEnumeration.d.ts +127 -0
  213. package/dist/cjs/internal/FileEnumeration.d.ts.map +1 -0
  214. package/dist/cjs/internal/FileEnumeration.js +194 -0
  215. package/dist/cjs/internal/FileEnumeration.js.map +7 -0
  216. package/dist/cjs/internal/HostReflection.d.ts +80 -0
  217. package/dist/cjs/internal/HostReflection.d.ts.map +1 -0
  218. package/dist/cjs/internal/HostReflection.js +43 -0
  219. package/dist/cjs/internal/HostReflection.js.map +7 -0
  220. package/dist/cjs/internal/JournalRecords.d.ts +325 -0
  221. package/dist/cjs/internal/JournalRecords.d.ts.map +1 -0
  222. package/dist/cjs/internal/JournalRecords.js +122 -0
  223. package/dist/cjs/internal/JournalRecords.js.map +7 -0
  224. package/dist/cjs/internal/NodeJournal.d.ts +157 -0
  225. package/dist/cjs/internal/NodeJournal.d.ts.map +1 -0
  226. package/dist/cjs/internal/NodeJournal.js +192 -0
  227. package/dist/cjs/internal/NodeJournal.js.map +7 -0
  228. package/dist/cjs/internal/Ordering.d.ts +14 -0
  229. package/dist/cjs/internal/Ordering.d.ts.map +1 -0
  230. package/dist/cjs/internal/Ordering.js +25 -0
  231. package/dist/cjs/internal/Ordering.js.map +7 -0
  232. package/dist/cjs/internal/PlanInputs.d.ts +14 -0
  233. package/dist/cjs/internal/PlanInputs.d.ts.map +1 -0
  234. package/dist/cjs/internal/PlanInputs.js +78 -0
  235. package/dist/cjs/internal/PlanInputs.js.map +7 -0
  236. package/dist/cjs/internal/PlanMerges.d.ts +178 -0
  237. package/dist/cjs/internal/PlanMerges.d.ts.map +1 -0
  238. package/dist/cjs/internal/PlanMerges.js +125 -0
  239. package/dist/cjs/internal/PlanMerges.js.map +7 -0
  240. package/dist/cjs/internal/ProvenanceSlot.d.ts +30 -0
  241. package/dist/cjs/internal/ProvenanceSlot.d.ts.map +1 -0
  242. package/dist/cjs/internal/ProvenanceSlot.js +53 -0
  243. package/dist/cjs/internal/ProvenanceSlot.js.map +7 -0
  244. package/dist/cjs/internal/ResultEnvelope.d.ts +88 -0
  245. package/dist/cjs/internal/ResultEnvelope.d.ts.map +1 -0
  246. package/dist/cjs/internal/ResultEnvelope.js +90 -0
  247. package/dist/cjs/internal/ResultEnvelope.js.map +7 -0
  248. package/dist/cjs/internal/RetentionOps.d.ts +414 -0
  249. package/dist/cjs/internal/RetentionOps.d.ts.map +1 -0
  250. package/dist/cjs/internal/RetentionOps.js +354 -0
  251. package/dist/cjs/internal/RetentionOps.js.map +7 -0
  252. package/dist/cjs/internal/RunCatalogOps.d.ts +118 -0
  253. package/dist/cjs/internal/RunCatalogOps.d.ts.map +1 -0
  254. package/dist/cjs/internal/RunCatalogOps.js +85 -0
  255. package/dist/cjs/internal/RunCatalogOps.js.map +7 -0
  256. package/dist/cjs/internal/RunCoordinator.d.ts +78 -0
  257. package/dist/cjs/internal/RunCoordinator.d.ts.map +1 -0
  258. package/dist/cjs/internal/RunCoordinator.js +123 -0
  259. package/dist/cjs/internal/RunCoordinator.js.map +7 -0
  260. package/dist/cjs/internal/RunDriver.d.ts +192 -0
  261. package/dist/cjs/internal/RunDriver.d.ts.map +1 -0
  262. package/dist/cjs/internal/RunDriver.js +1584 -0
  263. package/dist/cjs/internal/RunDriver.js.map +7 -0
  264. package/dist/cjs/internal/RunLifecycle.d.ts +22 -0
  265. package/dist/cjs/internal/RunLifecycle.d.ts.map +1 -0
  266. package/dist/cjs/internal/RunLifecycle.js +63 -0
  267. package/dist/cjs/internal/RunLifecycle.js.map +7 -0
  268. package/dist/cjs/internal/RunListing.d.ts +78 -0
  269. package/dist/cjs/internal/RunListing.d.ts.map +1 -0
  270. package/dist/cjs/internal/RunListing.js +171 -0
  271. package/dist/cjs/internal/RunListing.js.map +7 -0
  272. package/dist/cjs/internal/RunListingError.d.ts +35 -0
  273. package/dist/cjs/internal/RunListingError.d.ts.map +1 -0
  274. package/dist/cjs/internal/RunListingError.js +47 -0
  275. package/dist/cjs/internal/RunListingError.js.map +7 -0
  276. package/dist/cjs/internal/RuntimeGraph.d.ts +73 -0
  277. package/dist/cjs/internal/RuntimeGraph.d.ts.map +1 -0
  278. package/dist/cjs/internal/RuntimeGraph.js +180 -0
  279. package/dist/cjs/internal/RuntimeGraph.js.map +7 -0
  280. package/dist/cjs/internal/SandboxedExecution.d.ts +80 -0
  281. package/dist/cjs/internal/SandboxedExecution.d.ts.map +1 -0
  282. package/dist/cjs/internal/SandboxedExecution.js +104 -0
  283. package/dist/cjs/internal/SandboxedExecution.js.map +7 -0
  284. package/dist/cjs/internal/StateTransaction.d.ts +44 -0
  285. package/dist/cjs/internal/StateTransaction.d.ts.map +1 -0
  286. package/dist/cjs/internal/StateTransaction.js +52 -0
  287. package/dist/cjs/internal/StateTransaction.js.map +7 -0
  288. package/dist/cjs/internal/StepFacts.d.ts +14 -0
  289. package/dist/cjs/internal/StepFacts.d.ts.map +1 -0
  290. package/dist/cjs/internal/StepFacts.js +58 -0
  291. package/dist/cjs/internal/StepFacts.js.map +7 -0
  292. package/dist/cjs/internal/TypedEvents.d.ts +14 -0
  293. package/dist/cjs/internal/TypedEvents.d.ts.map +1 -0
  294. package/dist/cjs/internal/TypedEvents.js +37 -0
  295. package/dist/cjs/internal/TypedEvents.js.map +7 -0
  296. package/dist/cjs/migrations/0001_initial.d.ts +17 -0
  297. package/dist/cjs/migrations/0001_initial.d.ts.map +1 -0
  298. package/dist/cjs/migrations/0001_initial.js +61 -0
  299. package/dist/cjs/migrations/0001_initial.js.map +7 -0
  300. package/dist/cjs/migrations/0002_selection_store.d.ts +15 -0
  301. package/dist/cjs/migrations/0002_selection_store.d.ts.map +1 -0
  302. package/dist/cjs/migrations/0002_selection_store.js +56 -0
  303. package/dist/cjs/migrations/0002_selection_store.js.map +7 -0
  304. package/dist/cjs/migrations/0003_plan_inputs.d.ts +13 -0
  305. package/dist/cjs/migrations/0003_plan_inputs.d.ts.map +1 -0
  306. package/dist/cjs/migrations/0003_plan_inputs.js +90 -0
  307. package/dist/cjs/migrations/0003_plan_inputs.js.map +7 -0
  308. package/dist/cjs/migrations/0004_plan_environment.d.ts +14 -0
  309. package/dist/cjs/migrations/0004_plan_environment.d.ts.map +1 -0
  310. package/dist/cjs/migrations/0004_plan_environment.js +56 -0
  311. package/dist/cjs/migrations/0004_plan_environment.js.map +7 -0
  312. package/dist/cjs/migrations/0005_plan_merges.d.ts +14 -0
  313. package/dist/cjs/migrations/0005_plan_merges.d.ts.map +1 -0
  314. package/dist/cjs/migrations/0005_plan_merges.js +82 -0
  315. package/dist/cjs/migrations/0005_plan_merges.js.map +7 -0
  316. package/dist/cjs/migrations/0006_execution_listing.d.ts +15 -0
  317. package/dist/cjs/migrations/0006_execution_listing.d.ts.map +1 -0
  318. package/dist/cjs/migrations/0006_execution_listing.js +101 -0
  319. package/dist/cjs/migrations/0006_execution_listing.js.map +7 -0
  320. package/dist/cjs/migrations/0007_run_parent_sequence.d.ts +15 -0
  321. package/dist/cjs/migrations/0007_run_parent_sequence.d.ts.map +1 -0
  322. package/dist/cjs/migrations/0007_run_parent_sequence.js +40 -0
  323. package/dist/cjs/migrations/0007_run_parent_sequence.js.map +7 -0
  324. package/dist/cjs/migrations/0008_deferred_consumption.d.ts +16 -0
  325. package/dist/cjs/migrations/0008_deferred_consumption.d.ts.map +1 -0
  326. package/dist/cjs/migrations/0008_deferred_consumption.js +45 -0
  327. package/dist/cjs/migrations/0008_deferred_consumption.js.map +7 -0
  328. package/dist/cjs/migrations/0009_deferred_clock_facts.d.ts +24 -0
  329. package/dist/cjs/migrations/0009_deferred_clock_facts.d.ts.map +1 -0
  330. package/dist/cjs/migrations/0009_deferred_clock_facts.js +141 -0
  331. package/dist/cjs/migrations/0009_deferred_clock_facts.js.map +7 -0
  332. package/dist/cjs/migrations/0010_pending_cancellation.d.ts +15 -0
  333. package/dist/cjs/migrations/0010_pending_cancellation.d.ts.map +1 -0
  334. package/dist/cjs/migrations/0010_pending_cancellation.js +41 -0
  335. package/dist/cjs/migrations/0010_pending_cancellation.js.map +7 -0
  336. package/dist/cjs/package.json +1 -0
  337. package/dist/cjs/test/TestStores.d.ts +89 -0
  338. package/dist/cjs/test/TestStores.d.ts.map +1 -0
  339. package/dist/cjs/test/TestStores.js +98 -0
  340. package/dist/cjs/test/TestStores.js.map +7 -0
  341. package/dist/esm/ArtifactGc.d.ts +235 -0
  342. package/dist/esm/ArtifactGc.d.ts.map +1 -0
  343. package/dist/esm/ArtifactGc.js +268 -0
  344. package/dist/esm/ArtifactGc.js.map +1 -0
  345. package/dist/esm/ArtifactSync.d.ts +164 -0
  346. package/dist/esm/ArtifactSync.d.ts.map +1 -0
  347. package/dist/esm/ArtifactSync.js +176 -0
  348. package/dist/esm/ArtifactSync.js.map +1 -0
  349. package/dist/esm/CacheSync.d.ts +112 -0
  350. package/dist/esm/CacheSync.d.ts.map +1 -0
  351. package/dist/esm/CacheSync.js +94 -0
  352. package/dist/esm/CacheSync.js.map +1 -0
  353. package/dist/esm/DeferredClockFold.d.ts +354 -0
  354. package/dist/esm/DeferredClockFold.d.ts.map +1 -0
  355. package/dist/esm/DeferredClockFold.js +472 -0
  356. package/dist/esm/DeferredClockFold.js.map +1 -0
  357. package/dist/esm/DisasterRecovery.d.ts +431 -0
  358. package/dist/esm/DisasterRecovery.d.ts.map +1 -0
  359. package/dist/esm/DisasterRecovery.js +523 -0
  360. package/dist/esm/DisasterRecovery.js.map +1 -0
  361. package/dist/esm/DurableEngineState.d.ts +776 -0
  362. package/dist/esm/DurableEngineState.d.ts.map +1 -0
  363. package/dist/esm/DurableEngineState.js +1648 -0
  364. package/dist/esm/DurableEngineState.js.map +1 -0
  365. package/dist/esm/EngineStore.d.ts +158 -0
  366. package/dist/esm/EngineStore.d.ts.map +1 -0
  367. package/dist/esm/EngineStore.js +403 -0
  368. package/dist/esm/EngineStore.js.map +1 -0
  369. package/dist/esm/EngineStoreMetrics.d.ts +276 -0
  370. package/dist/esm/EngineStoreMetrics.d.ts.map +1 -0
  371. package/dist/esm/EngineStoreMetrics.js +260 -0
  372. package/dist/esm/EngineStoreMetrics.js.map +1 -0
  373. package/dist/esm/Errors.d.ts +22 -0
  374. package/dist/esm/Errors.d.ts.map +1 -0
  375. package/dist/esm/Errors.js +22 -0
  376. package/dist/esm/Errors.js.map +1 -0
  377. package/dist/esm/EventTypes.d.ts +27 -0
  378. package/dist/esm/EventTypes.d.ts.map +1 -0
  379. package/dist/esm/EventTypes.js +27 -0
  380. package/dist/esm/EventTypes.js.map +1 -0
  381. package/dist/esm/ExecutionFacts.d.ts +59 -0
  382. package/dist/esm/ExecutionFacts.d.ts.map +1 -0
  383. package/dist/esm/ExecutionFacts.js +96 -0
  384. package/dist/esm/ExecutionFacts.js.map +1 -0
  385. package/dist/esm/ExecutionSnapshot.d.ts +117 -0
  386. package/dist/esm/ExecutionSnapshot.d.ts.map +1 -0
  387. package/dist/esm/ExecutionSnapshot.js +95 -0
  388. package/dist/esm/ExecutionSnapshot.js.map +1 -0
  389. package/dist/esm/Inconsistency.d.ts +154 -0
  390. package/dist/esm/Inconsistency.d.ts.map +1 -0
  391. package/dist/esm/Inconsistency.js +133 -0
  392. package/dist/esm/Inconsistency.js.map +1 -0
  393. package/dist/esm/Migrations.d.ts +56 -0
  394. package/dist/esm/Migrations.d.ts.map +1 -0
  395. package/dist/esm/Migrations.js +91 -0
  396. package/dist/esm/Migrations.js.map +1 -0
  397. package/dist/esm/OwnerIdentity.d.ts +91 -0
  398. package/dist/esm/OwnerIdentity.d.ts.map +1 -0
  399. package/dist/esm/OwnerIdentity.js +99 -0
  400. package/dist/esm/OwnerIdentity.js.map +1 -0
  401. package/dist/esm/PlanInputStore.d.ts +110 -0
  402. package/dist/esm/PlanInputStore.d.ts.map +1 -0
  403. package/dist/esm/PlanInputStore.js +207 -0
  404. package/dist/esm/PlanInputStore.js.map +1 -0
  405. package/dist/esm/PlanMergeStore.d.ts +113 -0
  406. package/dist/esm/PlanMergeStore.d.ts.map +1 -0
  407. package/dist/esm/PlanMergeStore.js +244 -0
  408. package/dist/esm/PlanMergeStore.js.map +1 -0
  409. package/dist/esm/PlanScheduler.d.ts +363 -0
  410. package/dist/esm/PlanScheduler.d.ts.map +1 -0
  411. package/dist/esm/PlanScheduler.js +1321 -0
  412. package/dist/esm/PlanScheduler.js.map +1 -0
  413. package/dist/esm/Reconciliation.d.ts +175 -0
  414. package/dist/esm/Reconciliation.d.ts.map +1 -0
  415. package/dist/esm/Reconciliation.js +175 -0
  416. package/dist/esm/Reconciliation.js.map +1 -0
  417. package/dist/esm/ReplayOnly.d.ts +91 -0
  418. package/dist/esm/ReplayOnly.d.ts.map +1 -0
  419. package/dist/esm/ReplayOnly.js +59 -0
  420. package/dist/esm/ReplayOnly.js.map +1 -0
  421. package/dist/esm/Retention.d.ts +113 -0
  422. package/dist/esm/Retention.d.ts.map +1 -0
  423. package/dist/esm/Retention.js +139 -0
  424. package/dist/esm/Retention.js.map +1 -0
  425. package/dist/esm/RunCatalogRead.d.ts +17 -0
  426. package/dist/esm/RunCatalogRead.d.ts.map +1 -0
  427. package/dist/esm/RunCatalogRead.js +17 -0
  428. package/dist/esm/RunCatalogRead.js.map +1 -0
  429. package/dist/esm/RunChangeFeed.d.ts +75 -0
  430. package/dist/esm/RunChangeFeed.d.ts.map +1 -0
  431. package/dist/esm/RunChangeFeed.js +81 -0
  432. package/dist/esm/RunChangeFeed.js.map +1 -0
  433. package/dist/esm/RunState.d.ts +116 -0
  434. package/dist/esm/RunState.d.ts.map +1 -0
  435. package/dist/esm/RunState.js +109 -0
  436. package/dist/esm/RunState.js.map +1 -0
  437. package/dist/esm/Selection.d.ts +495 -0
  438. package/dist/esm/Selection.d.ts.map +1 -0
  439. package/dist/esm/Selection.js +482 -0
  440. package/dist/esm/Selection.js.map +1 -0
  441. package/dist/esm/SelectionStore.d.ts +141 -0
  442. package/dist/esm/SelectionStore.d.ts.map +1 -0
  443. package/dist/esm/SelectionStore.js +215 -0
  444. package/dist/esm/SelectionStore.js.map +1 -0
  445. package/dist/esm/StepBoundary.d.ts +501 -0
  446. package/dist/esm/StepBoundary.d.ts.map +1 -0
  447. package/dist/esm/StepBoundary.js +919 -0
  448. package/dist/esm/StepBoundary.js.map +1 -0
  449. package/dist/esm/StepCacheFold.d.ts +148 -0
  450. package/dist/esm/StepCacheFold.d.ts.map +1 -0
  451. package/dist/esm/StepCacheFold.js +201 -0
  452. package/dist/esm/StepCacheFold.js.map +1 -0
  453. package/dist/esm/StepSandbox.d.ts +78 -0
  454. package/dist/esm/StepSandbox.d.ts.map +1 -0
  455. package/dist/esm/StepSandbox.js +70 -0
  456. package/dist/esm/StepSandbox.js.map +1 -0
  457. package/dist/esm/WakeBus.d.ts +116 -0
  458. package/dist/esm/WakeBus.d.ts.map +1 -0
  459. package/dist/esm/WakeBus.js +121 -0
  460. package/dist/esm/WakeBus.js.map +1 -0
  461. package/dist/esm/WorkspaceSandbox.d.ts +619 -0
  462. package/dist/esm/WorkspaceSandbox.d.ts.map +1 -0
  463. package/dist/esm/WorkspaceSandbox.js +985 -0
  464. package/dist/esm/WorkspaceSandbox.js.map +1 -0
  465. package/dist/esm/index.d.ts +157 -0
  466. package/dist/esm/index.d.ts.map +1 -0
  467. package/dist/esm/index.js +157 -0
  468. package/dist/esm/index.js.map +1 -0
  469. package/dist/esm/internal/ActionPersistence.d.ts +276 -0
  470. package/dist/esm/internal/ActionPersistence.d.ts.map +1 -0
  471. package/dist/esm/internal/ActionPersistence.js +2306 -0
  472. package/dist/esm/internal/ActionPersistence.js.map +1 -0
  473. package/dist/esm/internal/ArtifactRoots.d.ts +38 -0
  474. package/dist/esm/internal/ArtifactRoots.d.ts.map +1 -0
  475. package/dist/esm/internal/ArtifactRoots.js +76 -0
  476. package/dist/esm/internal/ArtifactRoots.js.map +1 -0
  477. package/dist/esm/internal/AttemptAdmission.d.ts +53 -0
  478. package/dist/esm/internal/AttemptAdmission.d.ts.map +1 -0
  479. package/dist/esm/internal/AttemptAdmission.js +52 -0
  480. package/dist/esm/internal/AttemptAdmission.js.map +1 -0
  481. package/dist/esm/internal/AttemptLifecycle.d.ts +245 -0
  482. package/dist/esm/internal/AttemptLifecycle.d.ts.map +1 -0
  483. package/dist/esm/internal/AttemptLifecycle.js +104 -0
  484. package/dist/esm/internal/AttemptLifecycle.js.map +1 -0
  485. package/dist/esm/internal/AttemptProbe.d.ts +53 -0
  486. package/dist/esm/internal/AttemptProbe.d.ts.map +1 -0
  487. package/dist/esm/internal/AttemptProbe.js +83 -0
  488. package/dist/esm/internal/AttemptProbe.js.map +1 -0
  489. package/dist/esm/internal/CacheAdmission.d.ts +79 -0
  490. package/dist/esm/internal/CacheAdmission.d.ts.map +1 -0
  491. package/dist/esm/internal/CacheAdmission.js +72 -0
  492. package/dist/esm/internal/CacheAdmission.js.map +1 -0
  493. package/dist/esm/internal/CacheAgeHistory.d.ts +27 -0
  494. package/dist/esm/internal/CacheAgeHistory.d.ts.map +1 -0
  495. package/dist/esm/internal/CacheAgeHistory.js +49 -0
  496. package/dist/esm/internal/CacheAgeHistory.js.map +1 -0
  497. package/dist/esm/internal/CacheAgeVerdicts.d.ts +15 -0
  498. package/dist/esm/internal/CacheAgeVerdicts.d.ts.map +1 -0
  499. package/dist/esm/internal/CacheAgeVerdicts.js +54 -0
  500. package/dist/esm/internal/CacheAgeVerdicts.js.map +1 -0
  501. package/dist/esm/internal/CacheOutputPolicy.d.ts +24 -0
  502. package/dist/esm/internal/CacheOutputPolicy.d.ts.map +1 -0
  503. package/dist/esm/internal/CacheOutputPolicy.js +73 -0
  504. package/dist/esm/internal/CacheOutputPolicy.js.map +1 -0
  505. package/dist/esm/internal/CachePublication.d.ts +97 -0
  506. package/dist/esm/internal/CachePublication.d.ts.map +1 -0
  507. package/dist/esm/internal/CachePublication.js +116 -0
  508. package/dist/esm/internal/CachePublication.js.map +1 -0
  509. package/dist/esm/internal/CallFacts.d.ts +15 -0
  510. package/dist/esm/internal/CallFacts.d.ts.map +1 -0
  511. package/dist/esm/internal/CallFacts.js +83 -0
  512. package/dist/esm/internal/CallFacts.js.map +1 -0
  513. package/dist/esm/internal/CopiedRecord.d.ts +61 -0
  514. package/dist/esm/internal/CopiedRecord.d.ts.map +1 -0
  515. package/dist/esm/internal/CopiedRecord.js +165 -0
  516. package/dist/esm/internal/CopiedRecord.js.map +1 -0
  517. package/dist/esm/internal/DeferredPersistence.d.ts +108 -0
  518. package/dist/esm/internal/DeferredPersistence.d.ts.map +1 -0
  519. package/dist/esm/internal/DeferredPersistence.js +240 -0
  520. package/dist/esm/internal/DeferredPersistence.js.map +1 -0
  521. package/dist/esm/internal/EffectRecords.d.ts +92 -0
  522. package/dist/esm/internal/EffectRecords.d.ts.map +1 -0
  523. package/dist/esm/internal/EffectRecords.js +94 -0
  524. package/dist/esm/internal/EffectRecords.js.map +1 -0
  525. package/dist/esm/internal/EngineJj.d.ts +30 -0
  526. package/dist/esm/internal/EngineJj.d.ts.map +1 -0
  527. package/dist/esm/internal/EngineJj.js +29 -0
  528. package/dist/esm/internal/EngineJj.js.map +1 -0
  529. package/dist/esm/internal/EngineStateSchema.d.ts +68 -0
  530. package/dist/esm/internal/EngineStateSchema.d.ts.map +1 -0
  531. package/dist/esm/internal/EngineStateSchema.js +53 -0
  532. package/dist/esm/internal/EngineStateSchema.js.map +1 -0
  533. package/dist/esm/internal/ExecutionSnapshotRead.d.ts +162 -0
  534. package/dist/esm/internal/ExecutionSnapshotRead.d.ts.map +1 -0
  535. package/dist/esm/internal/ExecutionSnapshotRead.js +218 -0
  536. package/dist/esm/internal/ExecutionSnapshotRead.js.map +1 -0
  537. package/dist/esm/internal/ExecutionSnapshotRelations.d.ts +39 -0
  538. package/dist/esm/internal/ExecutionSnapshotRelations.d.ts.map +1 -0
  539. package/dist/esm/internal/ExecutionSnapshotRelations.js +81 -0
  540. package/dist/esm/internal/ExecutionSnapshotRelations.js.map +1 -0
  541. package/dist/esm/internal/ExitEncoding.d.ts +180 -0
  542. package/dist/esm/internal/ExitEncoding.d.ts.map +1 -0
  543. package/dist/esm/internal/ExitEncoding.js +361 -0
  544. package/dist/esm/internal/ExitEncoding.js.map +1 -0
  545. package/dist/esm/internal/FileBoundarySnapshot.d.ts +14 -0
  546. package/dist/esm/internal/FileBoundarySnapshot.d.ts.map +1 -0
  547. package/dist/esm/internal/FileBoundarySnapshot.js +29 -0
  548. package/dist/esm/internal/FileBoundarySnapshot.js.map +1 -0
  549. package/dist/esm/internal/FileEnumeration.d.ts +127 -0
  550. package/dist/esm/internal/FileEnumeration.d.ts.map +1 -0
  551. package/dist/esm/internal/FileEnumeration.js +236 -0
  552. package/dist/esm/internal/FileEnumeration.js.map +1 -0
  553. package/dist/esm/internal/HostReflection.d.ts +80 -0
  554. package/dist/esm/internal/HostReflection.d.ts.map +1 -0
  555. package/dist/esm/internal/HostReflection.js +98 -0
  556. package/dist/esm/internal/HostReflection.js.map +1 -0
  557. package/dist/esm/internal/JournalRecords.d.ts +325 -0
  558. package/dist/esm/internal/JournalRecords.d.ts.map +1 -0
  559. package/dist/esm/internal/JournalRecords.js +306 -0
  560. package/dist/esm/internal/JournalRecords.js.map +1 -0
  561. package/dist/esm/internal/NodeJournal.d.ts +157 -0
  562. package/dist/esm/internal/NodeJournal.d.ts.map +1 -0
  563. package/dist/esm/internal/NodeJournal.js +298 -0
  564. package/dist/esm/internal/NodeJournal.js.map +1 -0
  565. package/dist/esm/internal/Ordering.d.ts +14 -0
  566. package/dist/esm/internal/Ordering.d.ts.map +1 -0
  567. package/dist/esm/internal/Ordering.js +14 -0
  568. package/dist/esm/internal/Ordering.js.map +1 -0
  569. package/dist/esm/internal/PlanInputs.d.ts +14 -0
  570. package/dist/esm/internal/PlanInputs.d.ts.map +1 -0
  571. package/dist/esm/internal/PlanInputs.js +57 -0
  572. package/dist/esm/internal/PlanInputs.js.map +1 -0
  573. package/dist/esm/internal/PlanMerges.d.ts +178 -0
  574. package/dist/esm/internal/PlanMerges.d.ts.map +1 -0
  575. package/dist/esm/internal/PlanMerges.js +113 -0
  576. package/dist/esm/internal/PlanMerges.js.map +1 -0
  577. package/dist/esm/internal/ProvenanceSlot.d.ts +30 -0
  578. package/dist/esm/internal/ProvenanceSlot.d.ts.map +1 -0
  579. package/dist/esm/internal/ProvenanceSlot.js +39 -0
  580. package/dist/esm/internal/ProvenanceSlot.js.map +1 -0
  581. package/dist/esm/internal/ResultEnvelope.d.ts +88 -0
  582. package/dist/esm/internal/ResultEnvelope.d.ts.map +1 -0
  583. package/dist/esm/internal/ResultEnvelope.js +83 -0
  584. package/dist/esm/internal/ResultEnvelope.js.map +1 -0
  585. package/dist/esm/internal/RetentionOps.d.ts +414 -0
  586. package/dist/esm/internal/RetentionOps.d.ts.map +1 -0
  587. package/dist/esm/internal/RetentionOps.js +576 -0
  588. package/dist/esm/internal/RetentionOps.js.map +1 -0
  589. package/dist/esm/internal/RunCatalogOps.d.ts +118 -0
  590. package/dist/esm/internal/RunCatalogOps.d.ts.map +1 -0
  591. package/dist/esm/internal/RunCatalogOps.js +120 -0
  592. package/dist/esm/internal/RunCatalogOps.js.map +1 -0
  593. package/dist/esm/internal/RunCoordinator.d.ts +78 -0
  594. package/dist/esm/internal/RunCoordinator.d.ts.map +1 -0
  595. package/dist/esm/internal/RunCoordinator.js +115 -0
  596. package/dist/esm/internal/RunCoordinator.js.map +1 -0
  597. package/dist/esm/internal/RunDriver.d.ts +192 -0
  598. package/dist/esm/internal/RunDriver.d.ts.map +1 -0
  599. package/dist/esm/internal/RunDriver.js +2334 -0
  600. package/dist/esm/internal/RunDriver.js.map +1 -0
  601. package/dist/esm/internal/RunLifecycle.d.ts +22 -0
  602. package/dist/esm/internal/RunLifecycle.d.ts.map +1 -0
  603. package/dist/esm/internal/RunLifecycle.js +49 -0
  604. package/dist/esm/internal/RunLifecycle.js.map +1 -0
  605. package/dist/esm/internal/RunListing.d.ts +78 -0
  606. package/dist/esm/internal/RunListing.d.ts.map +1 -0
  607. package/dist/esm/internal/RunListing.js +146 -0
  608. package/dist/esm/internal/RunListing.js.map +1 -0
  609. package/dist/esm/internal/RunListingError.d.ts +35 -0
  610. package/dist/esm/internal/RunListingError.d.ts.map +1 -0
  611. package/dist/esm/internal/RunListingError.js +27 -0
  612. package/dist/esm/internal/RunListingError.js.map +1 -0
  613. package/dist/esm/internal/RuntimeGraph.d.ts +73 -0
  614. package/dist/esm/internal/RuntimeGraph.d.ts.map +1 -0
  615. package/dist/esm/internal/RuntimeGraph.js +182 -0
  616. package/dist/esm/internal/RuntimeGraph.js.map +1 -0
  617. package/dist/esm/internal/SandboxedExecution.d.ts +80 -0
  618. package/dist/esm/internal/SandboxedExecution.d.ts.map +1 -0
  619. package/dist/esm/internal/SandboxedExecution.js +107 -0
  620. package/dist/esm/internal/SandboxedExecution.js.map +1 -0
  621. package/dist/esm/internal/StateTransaction.d.ts +44 -0
  622. package/dist/esm/internal/StateTransaction.d.ts.map +1 -0
  623. package/dist/esm/internal/StateTransaction.js +54 -0
  624. package/dist/esm/internal/StateTransaction.js.map +1 -0
  625. package/dist/esm/internal/StepFacts.d.ts +14 -0
  626. package/dist/esm/internal/StepFacts.d.ts.map +1 -0
  627. package/dist/esm/internal/StepFacts.js +35 -0
  628. package/dist/esm/internal/StepFacts.js.map +1 -0
  629. package/dist/esm/internal/TypedEvents.d.ts +14 -0
  630. package/dist/esm/internal/TypedEvents.d.ts.map +1 -0
  631. package/dist/esm/internal/TypedEvents.js +14 -0
  632. package/dist/esm/internal/TypedEvents.js.map +1 -0
  633. package/dist/esm/migrations/0001_initial.d.ts +17 -0
  634. package/dist/esm/migrations/0001_initial.d.ts.map +1 -0
  635. package/dist/esm/migrations/0001_initial.js +41 -0
  636. package/dist/esm/migrations/0001_initial.js.map +1 -0
  637. package/dist/esm/migrations/0002_selection_store.d.ts +15 -0
  638. package/dist/esm/migrations/0002_selection_store.d.ts.map +1 -0
  639. package/dist/esm/migrations/0002_selection_store.js +34 -0
  640. package/dist/esm/migrations/0002_selection_store.js.map +1 -0
  641. package/dist/esm/migrations/0003_plan_inputs.d.ts +13 -0
  642. package/dist/esm/migrations/0003_plan_inputs.d.ts.map +1 -0
  643. package/dist/esm/migrations/0003_plan_inputs.js +70 -0
  644. package/dist/esm/migrations/0003_plan_inputs.js.map +1 -0
  645. package/dist/esm/migrations/0004_plan_environment.d.ts +14 -0
  646. package/dist/esm/migrations/0004_plan_environment.d.ts.map +1 -0
  647. package/dist/esm/migrations/0004_plan_environment.js +33 -0
  648. package/dist/esm/migrations/0004_plan_environment.js.map +1 -0
  649. package/dist/esm/migrations/0005_plan_merges.d.ts +14 -0
  650. package/dist/esm/migrations/0005_plan_merges.d.ts.map +1 -0
  651. package/dist/esm/migrations/0005_plan_merges.js +59 -0
  652. package/dist/esm/migrations/0005_plan_merges.js.map +1 -0
  653. package/dist/esm/migrations/0006_execution_listing.d.ts +15 -0
  654. package/dist/esm/migrations/0006_execution_listing.d.ts.map +1 -0
  655. package/dist/esm/migrations/0006_execution_listing.js +81 -0
  656. package/dist/esm/migrations/0006_execution_listing.js.map +1 -0
  657. package/dist/esm/migrations/0007_run_parent_sequence.d.ts +15 -0
  658. package/dist/esm/migrations/0007_run_parent_sequence.d.ts.map +1 -0
  659. package/dist/esm/migrations/0007_run_parent_sequence.js +18 -0
  660. package/dist/esm/migrations/0007_run_parent_sequence.js.map +1 -0
  661. package/dist/esm/migrations/0008_deferred_consumption.d.ts +16 -0
  662. package/dist/esm/migrations/0008_deferred_consumption.d.ts.map +1 -0
  663. package/dist/esm/migrations/0008_deferred_consumption.js +24 -0
  664. package/dist/esm/migrations/0008_deferred_consumption.js.map +1 -0
  665. package/dist/esm/migrations/0009_deferred_clock_facts.d.ts +24 -0
  666. package/dist/esm/migrations/0009_deferred_clock_facts.d.ts.map +1 -0
  667. package/dist/esm/migrations/0009_deferred_clock_facts.js +127 -0
  668. package/dist/esm/migrations/0009_deferred_clock_facts.js.map +1 -0
  669. package/dist/esm/migrations/0010_pending_cancellation.d.ts +15 -0
  670. package/dist/esm/migrations/0010_pending_cancellation.d.ts.map +1 -0
  671. package/dist/esm/migrations/0010_pending_cancellation.js +19 -0
  672. package/dist/esm/migrations/0010_pending_cancellation.js.map +1 -0
  673. package/dist/esm/test/TestStores.d.ts +89 -0
  674. package/dist/esm/test/TestStores.d.ts.map +1 -0
  675. package/dist/esm/test/TestStores.js +103 -0
  676. package/dist/esm/test/TestStores.js.map +1 -0
  677. package/docs/README.md +113 -0
  678. package/docs/api.md +1494 -0
  679. package/docs/concepts/attempts-and-replay.md +149 -0
  680. package/docs/concepts/cache-admission.md +265 -0
  681. package/docs/concepts/call-facts.md +86 -0
  682. package/docs/concepts/durable-waits.md +175 -0
  683. package/docs/concepts/execution-facts.md +90 -0
  684. package/docs/concepts/ownership-and-fencing.md +128 -0
  685. package/docs/concepts/step-boundaries.md +148 -0
  686. package/docs/concepts/workspace-transactions.md +199 -0
  687. package/docs/event-types.md +14 -0
  688. package/docs/guides/back-up-and-restore.md +164 -0
  689. package/docs/guides/collect-unreferenced-artifacts.md +128 -0
  690. package/docs/guides/compose-a-durable-engine.md +146 -0
  691. package/docs/guides/coordinate-two-processes.md +120 -0
  692. package/docs/guides/defer-work-with-selection.md +171 -0
  693. package/docs/guides/delete-old-run-history.md +154 -0
  694. package/docs/guides/drive-a-plan.md +314 -0
  695. package/docs/guides/observe-engine-metrics.md +98 -0
  696. package/docs/guides/observe-executions.md +187 -0
  697. package/docs/guides/reclaim-runs-from-a-dead-host.md +133 -0
  698. package/docs/guides/share-a-cache-across-machines.md +134 -0
  699. package/docs/guides/testing.md +123 -0
  700. package/docs/installation.md +102 -0
  701. package/docs/quickstart.md +215 -0
  702. package/docs/troubleshooting.md +347 -0
  703. package/package.json +438 -3
  704. package/src/ArtifactGc.ts +408 -0
  705. package/src/ArtifactSync.ts +278 -0
  706. package/src/CacheSync.ts +131 -0
  707. package/src/DeferredClockFold.ts +586 -0
  708. package/src/DisasterRecovery.ts +888 -0
  709. package/src/DurableEngineState.ts +2656 -0
  710. package/src/EngineStore.ts +656 -0
  711. package/src/EngineStoreMetrics.ts +331 -0
  712. package/src/Errors.ts +28 -0
  713. package/src/EventTypes.ts +27 -0
  714. package/src/ExecutionFacts.ts +125 -0
  715. package/src/ExecutionSnapshot.ts +186 -0
  716. package/src/Inconsistency.ts +253 -0
  717. package/src/Migrations.ts +95 -0
  718. package/src/OwnerIdentity.ts +125 -0
  719. package/src/PlanInputStore.ts +292 -0
  720. package/src/PlanMergeStore.ts +330 -0
  721. package/src/PlanScheduler.ts +1806 -0
  722. package/src/Reconciliation.ts +224 -0
  723. package/src/ReplayOnly.ts +95 -0
  724. package/src/Retention.ts +214 -0
  725. package/src/RunCatalogRead.ts +27 -0
  726. package/src/RunChangeFeed.ts +125 -0
  727. package/src/RunState.ts +137 -0
  728. package/src/Selection.ts +683 -0
  729. package/src/SelectionStore.ts +353 -0
  730. package/src/StepBoundary.ts +1165 -0
  731. package/src/StepCacheFold.ts +244 -0
  732. package/src/StepSandbox.ts +110 -0
  733. package/src/WakeBus.ts +167 -0
  734. package/src/WorkspaceSandbox.ts +1604 -0
  735. package/src/index.ts +185 -0
  736. package/src/internal/ActionPersistence.ts +2687 -0
  737. package/src/internal/ArtifactRoots.ts +92 -0
  738. package/src/internal/AttemptAdmission.ts +76 -0
  739. package/src/internal/AttemptLifecycle.ts +163 -0
  740. package/src/internal/AttemptProbe.ts +90 -0
  741. package/src/internal/CacheAdmission.ts +133 -0
  742. package/src/internal/CacheAgeHistory.ts +58 -0
  743. package/src/internal/CacheAgeVerdicts.ts +61 -0
  744. package/src/internal/CacheOutputPolicy.ts +82 -0
  745. package/src/internal/CachePublication.ts +153 -0
  746. package/src/internal/CallFacts.ts +87 -0
  747. package/src/internal/CopiedRecord.ts +201 -0
  748. package/src/internal/DeferredPersistence.ts +459 -0
  749. package/src/internal/EffectRecords.ts +136 -0
  750. package/src/internal/EngineJj.ts +30 -0
  751. package/src/internal/EngineStateSchema.ts +92 -0
  752. package/src/internal/ExecutionSnapshotRead.ts +298 -0
  753. package/src/internal/ExecutionSnapshotRelations.ts +121 -0
  754. package/src/internal/ExitEncoding.ts +453 -0
  755. package/src/internal/FileBoundarySnapshot.ts +39 -0
  756. package/src/internal/FileEnumeration.ts +334 -0
  757. package/src/internal/HostReflection.ts +128 -0
  758. package/src/internal/JournalRecords.ts +396 -0
  759. package/src/internal/NodeJournal.ts +340 -0
  760. package/src/internal/Ordering.ts +14 -0
  761. package/src/internal/PlanInputs.ts +62 -0
  762. package/src/internal/PlanMerges.ts +123 -0
  763. package/src/internal/ProvenanceSlot.ts +52 -0
  764. package/src/internal/ResultEnvelope.ts +118 -0
  765. package/src/internal/RetentionOps.ts +865 -0
  766. package/src/internal/RunCatalogOps.ts +174 -0
  767. package/src/internal/RunCoordinator.ts +215 -0
  768. package/src/internal/RunDriver.ts +3152 -0
  769. package/src/internal/RunLifecycle.ts +53 -0
  770. package/src/internal/RunListing.ts +210 -0
  771. package/src/internal/RunListingError.ts +37 -0
  772. package/src/internal/RuntimeGraph.ts +253 -0
  773. package/src/internal/SandboxedExecution.ts +165 -0
  774. package/src/internal/StateTransaction.ts +71 -0
  775. package/src/internal/StepFacts.ts +36 -0
  776. package/src/internal/TypedEvents.ts +29 -0
  777. package/src/migrations/0001_initial.ts +51 -0
  778. package/src/migrations/0002_selection_store.ts +38 -0
  779. package/src/migrations/0003_plan_inputs.ts +75 -0
  780. package/src/migrations/0004_plan_environment.ts +34 -0
  781. package/src/migrations/0005_plan_merges.ts +60 -0
  782. package/src/migrations/0006_execution_listing.ts +86 -0
  783. package/src/migrations/0007_run_parent_sequence.ts +19 -0
  784. package/src/migrations/0008_deferred_consumption.ts +27 -0
  785. package/src/migrations/0009_deferred_clock_facts.ts +163 -0
  786. package/src/migrations/0010_pending_cancellation.ts +20 -0
  787. package/src/test/TestStores.ts +145 -0
@@ -0,0 +1,1806 @@
1
+ /**
2
+ * Drives a persisted plan: the node scheduler.
3
+ *
4
+ * `@smthrs/plan` makes the plan a value; this makes it run. The scheduler
5
+ * walks the persisted graph, dispatches ready nodes through the same
6
+ * `internal/ActionPersistence` seam every action uses — so the shared step
7
+ * cache, the workspace sandbox's execute→materialize transaction, attempt
8
+ * rows, and the fenced journal all apply unchanged — and records an
9
+ * evaluation outcome per node.
10
+ *
11
+ * ## What it is, and what it is not
12
+ *
13
+ * Skyframe's `AbstractParallelEvaluator` is the prior art for the state
14
+ * machine (`CHECK_DEPENDENCIES → VERIFIED_CLEAN | NEEDS_REBUILDING →
15
+ * REBUILDING → DONE`) and `EvaluationProgressReceiver` for the outcome
16
+ * vocabulary. Two deliberate deviations:
17
+ *
18
+ * 1. **No cache-invalidating node visitor.** Content addressing governs
19
+ * reuse. Private reverse indexes propagate settled outcomes and readiness,
20
+ * never dirty bits. Dispatch-time content measurement still decides
21
+ * whether a recorded result can serve the node.
22
+ * 2. **Completion-driven, without Skyframe restarts.** Skyframe atomically
23
+ * signals reverse dependencies when one node settles. Our dependencies are
24
+ * declared in the plan before anything runs, so one coordinator can make
25
+ * the equivalent decision from the plan, settled state, and scheduling
26
+ * permits: every completion is reconciled and immediately opens another
27
+ * admission pass. Giving up the wavefront barrier also gives up its
28
+ * trivially serialized reconciliation; draining and applying the durable
29
+ * deviation journal after each settlement, before reevaluating downstream
30
+ * readiness, restores that ordering without holding unrelated ready cones.
31
+ * The coordinator is the sole writer of scheduling state, which keeps every
32
+ * admission decision deterministic without the `DeterministicHelper` the
33
+ * vault rejected.
34
+ *
35
+ * ## The three limits
36
+ *
37
+ * The same number must never
38
+ * stand in for graph width, scheduler admission, and provider capacity. This
39
+ * module owns the middle one only: `concurrency.steps` is the leaf-execution
40
+ * cap, and `concurrency.agents` is the subset cap an agent node consumes in
41
+ * addition. Structural width is whatever the plan says; seats belong to
42
+ * `Agent Adapters` and are not modelled here.
43
+ *
44
+ * Under contention, ready work is ordered by effective priority — declared
45
+ * `priority` plus one point per capacity-constrained admission pass. A ready
46
+ * node gains a point exactly when a pass considers it but cannot give it the
47
+ * required permits. Aging is what lets priority change latency without
48
+ * permitting starvation; equal effective priorities preserve deterministic
49
+ * plan order.
50
+ *
51
+ * ## Observing the world exactly once
52
+ *
53
+ * The torn-run rule: never re-observe the
54
+ * world mid-run. Reads with no preceding writer are **source** inputs; their
55
+ * digests and glob membership are durably recorded before dispatch and restored
56
+ * on reopening. Preceding producers' outputs are measured after settlement —
57
+ * observing our own output, not the world. Later and self-writes are not source
58
+ * producers. New generations retain prior pins and record new observations.
59
+ *
60
+ * @since 0.1.0
61
+ */
62
+
63
+ import { Sha256 } from "@smthrs/crypto"
64
+ import { FlowEngine } from "@smthrs/engine"
65
+ import type { FileBoundary } from "@smthrs/flow/FileBoundary"
66
+ import { Journal, type JournalEvent } from "@smthrs/journal"
67
+ import type { Jj } from "@smthrs/kernel"
68
+ import { Plan, Scheduling, StepKey } from "@smthrs/plan"
69
+ import { PlanStore } from "@smthrs/plan-store"
70
+ import * as FileSet from "@smthrs/plan/FileSet"
71
+ import { AttemptStore, type Ownership, type RunStore } from "@smthrs/run-store"
72
+ import type { CacheStore } from "@smthrs/step-cache"
73
+ import * as Cause from "effect/Cause"
74
+ import * as Clock from "effect/Clock"
75
+ import * as Context from "effect/Context"
76
+ import type * as Crypto from "effect/Crypto"
77
+ import * as Effect from "effect/Effect"
78
+ import * as Exit from "effect/Exit"
79
+ import * as FileSystem from "effect/FileSystem"
80
+ import * as Layer from "effect/Layer"
81
+ import * as Metric from "effect/Metric"
82
+ import * as Option from "effect/Option"
83
+ import * as Queue from "effect/Queue"
84
+ import * as Ref from "effect/Ref"
85
+ import * as Result from "effect/Result"
86
+ import * as Schema from "effect/Schema"
87
+ import * as EngineStoreMetrics from "./EngineStoreMetrics.ts"
88
+ import * as ActionPersistence from "./internal/ActionPersistence.ts"
89
+ import * as AttemptAdmission from "./internal/AttemptAdmission.ts"
90
+ import * as CacheAgeVerdicts from "./internal/CacheAgeVerdicts.ts"
91
+ import * as FileEnumeration from "./internal/FileEnumeration.ts"
92
+ import * as JournalRecords from "./internal/JournalRecords.ts"
93
+ import { compareText } from "./internal/Ordering.ts"
94
+ import * as PlanInputs from "./internal/PlanInputs.ts"
95
+ import * as PlanMerges from "./internal/PlanMerges.ts"
96
+ import * as RuntimeGraph from "./internal/RuntimeGraph.ts"
97
+ import * as PlanInputStore from "./PlanInputStore.ts"
98
+ import * as PlanMergeStore from "./PlanMergeStore.ts"
99
+ import * as Reconciliation from "./Reconciliation.ts"
100
+ import * as ReplayOnly from "./ReplayOnly.ts"
101
+ import * as Selection from "./Selection.ts"
102
+ import * as StepBoundary from "./StepBoundary.ts"
103
+ import * as WorkspaceSandbox from "./WorkspaceSandbox.ts"
104
+
105
+ /**
106
+ * The evaluation outcomes: Skyframe's `EvaluationState` adapted to a
107
+ * content-addressed store, plus the selection debt.
108
+ *
109
+ * - `built` — the node executed (`SUCCESS_VERSION_CHANGED`).
110
+ * - `clean` — a recorded result served it and no executor ran. This includes
111
+ * same-run durable attempt replay for every tier, and shared-cache hits for
112
+ * eligible sealed work (`SUCCESS_VERSION_UNCHANGED`). It does not assert
113
+ * cross-run cache eligibility.
114
+ * - `failed` — the node executed and failed, or reconciliation failed it.
115
+ * - `skipped` — its cone prevented dispatch, or `stop-merge` stopped a
116
+ * conflicted attempt in favour of a merge node. The latter has nonzero
117
+ * attempts; skipped does not imply that its executor never ran. Skyframe
118
+ * splits failure by version instead; a content-addressed store never serves
119
+ * a failure from cache, so that split has no meaning here and this one does.
120
+ * - `deferred`: a selection guess postponed this sink
121
+ * (`docs/guides/defer-work-with-selection.md`). It never dispatched,
122
+ * wrote no cache row, and is journaled as a debt for a later guess-free
123
+ * pass. Distinct from `skipped` — the work was runnable, a guess postponed
124
+ * it — and never reported as passed.
125
+ *
126
+ * @since 0.1.0
127
+ * @category models
128
+ */
129
+ export type Outcome = "built" | "clean" | "failed" | "skipped" | "deferred"
130
+
131
+ /**
132
+ * One material `Ref` input, resolved: the settled output of `from` projected
133
+ * along `path` — for sealed work, the value whose digest the dispatch key folds.
134
+ *
135
+ * @since 0.1.0
136
+ * @category models
137
+ */
138
+ export interface ResolvedInput {
139
+ readonly from: string
140
+ readonly path: ReadonlyArray<string>
141
+ readonly value: unknown
142
+ }
143
+
144
+ /**
145
+ * What the scheduler hands a node's executor.
146
+ *
147
+ * @since 0.1.0
148
+ * @category models
149
+ */
150
+ export interface NodeInput {
151
+ readonly node: Plan.PlanNode
152
+ readonly attempt: number
153
+ /** The measured boundary; folded into sealed keys, diagnostic context for other tiers. */
154
+ readonly boundary: FileBoundary
155
+ /**
156
+ * The node's material `Ref` inputs, resolved through the same projection the
157
+ * dispatch key digests for sealed work. Ordering (`Pending`) dependencies
158
+ * and unprojected sibling fields are deliberately absent. This preserves
159
+ * the consumed-data contract for every tier and prevents a sealed cache hit
160
+ * from serving an execution that consumed different data.
161
+ */
162
+ readonly inputs: ReadonlyArray<ResolvedInput>
163
+ }
164
+
165
+ /**
166
+ * The DI seam that turns a plan node into work.
167
+ *
168
+ * The scheduler owns identity, admission, caching, and journaling; it
169
+ * deliberately owns nothing about what a node *means*. A flow runtime, a test,
170
+ * or a remote host each supply their own executor.
171
+ *
172
+ * @since 0.1.0
173
+ * @category models
174
+ */
175
+ export interface Executor {
176
+ readonly execute: (input: NodeInput) => Effect.Effect<unknown, unknown>
177
+ }
178
+
179
+ /**
180
+ * Service tag for the node executor.
181
+ *
182
+ * @since 0.1.0
183
+ * @category services
184
+ */
185
+ export class NodeExecutor extends Context.Service<NodeExecutor, Executor>()("@smthrs/engine-store/NodeExecutor") {}
186
+
187
+ /**
188
+ * Provides a plain {@link Executor} value as the {@link NodeExecutor} service.
189
+ *
190
+ * This is the whole wiring story for the scheduler's one open seam: a flow
191
+ * runtime, a test double, or a remote dispatcher each become a layer by
192
+ * passing themselves here.
193
+ *
194
+ * @since 0.1.0
195
+ * @category layers
196
+ */
197
+ export const layerExecutor = (executor: Executor): Layer.Layer<NodeExecutor> => Layer.succeed(NodeExecutor, executor)
198
+
199
+ /**
200
+ * How one node ended.
201
+ *
202
+ * @since 0.1.0
203
+ * @category models
204
+ */
205
+ export interface Settlement {
206
+ readonly nodeId: string
207
+ /** The plan-time key: a pure function of declarations. */
208
+ readonly planKey: string
209
+ /** The execution key: content-derived for sealed work; run/plan/node/declaration-scoped for other tiers. */
210
+ readonly dispatchKey: string
211
+ readonly outcome: Outcome
212
+ readonly attempts: number
213
+ readonly rebases: number
214
+ }
215
+
216
+ /**
217
+ * What a run of a plan produced.
218
+ *
219
+ * @since 0.1.0
220
+ * @category models
221
+ */
222
+ export interface Report {
223
+ readonly planId: string
224
+ readonly digest: string
225
+ readonly settlements: ReadonlyArray<Settlement>
226
+ readonly results: Readonly<Record<string, unknown>>
227
+ readonly verdicts: ReadonlyArray<{ readonly nodeId: string; readonly verdict: Reconciliation.Verdict }>
228
+ /** Merge nodes a `stop-merge` conflict appended to the plan. */
229
+ readonly appended: ReadonlyArray<string>
230
+ }
231
+
232
+ /**
233
+ * Scheduler construction options.
234
+ *
235
+ * @since 0.1.0
236
+ * @category models
237
+ */
238
+ export interface Options {
239
+ readonly runId: string
240
+ readonly owner: Ownership.OwnerId
241
+ readonly sourceId: string
242
+ /**
243
+ * Engine-resolved identity, copied at scheduler construction and durably
244
+ * bound before dispatch. A changed identity cannot resume the same run.
245
+ * Omitting it preserves existing action keys but is itself a distinct binding.
246
+ */
247
+ readonly environment?: StepKey.EnvironmentIdentity | undefined
248
+ /**
249
+ * The admission caps. Both default to unbounded, because a cap the caller
250
+ * did not declare is not the scheduler's to invent — `aspects.ts` owns the
251
+ * policy and narrows it. Explicit caps must be positive safe integers;
252
+ * zero is refused rather than silently clamped or allowed to deadlock.
253
+ */
254
+ readonly concurrency?: Scheduling.Concurrency | undefined
255
+ /**
256
+ * How many times a `delay-rebase` node may re-measure and re-key against a
257
+ * newly recorded base before reconciliation is asked. Bounded on purpose: an
258
+ * unbounded rebase loop is a livelock with good manners.
259
+ */
260
+ readonly rebaseLimit?: number | undefined
261
+ /**
262
+ * Probabilistic selection input for this run (`Selection`). Every field is
263
+ * optional because every default is inert: no changed paths and no beliefs
264
+ * admit everything, so a caller that never opted in runs exactly as today.
265
+ * `full: true` is the run-level override — every verdict is treated as
266
+ * `Admit` and the override is journaled, the way `--fresh` ignores the
267
+ * cache.
268
+ */
269
+ readonly selection?: {
270
+ /** Changed paths the belief edges are matched against. */
271
+ readonly changed?: ReadonlyArray<string> | undefined
272
+ /** The belief snapshot pinned before planning. */
273
+ readonly beliefs?: Selection.BeliefSnapshot | undefined
274
+ /** Deferral policy; `deferBelow` defaults to zero, which defers nothing. */
275
+ readonly policy?: Selection.Policy | undefined
276
+ /** Force full selection: treat every verdict as `Admit`, journaled. */
277
+ readonly full?: boolean | undefined
278
+ } | undefined
279
+ }
280
+
281
+ /**
282
+ * Everything driving a plan needs from the composition.
283
+ *
284
+ * @since 0.1.0
285
+ * @category models
286
+ */
287
+ export type Requirements =
288
+ | PlanMergeStore.PlanMergeStore
289
+ | AttemptStore.AttemptStore
290
+ | CacheStore.CacheStore
291
+ | Crypto.Crypto
292
+ | Jj.Jj
293
+ | Journal.Journal
294
+ | NodeExecutor
295
+ | PlanStore.PlanStore
296
+ | PlanInputStore.PlanInputStore
297
+ | RunStore.RunStore
298
+ | StepBoundary.Service
299
+
300
+ /**
301
+ * A refusal the scheduler itself raises. A node's own failure is an outcome,
302
+ * not one of these — the run continues and the report says `failed`.
303
+ *
304
+ * @since 0.1.0
305
+ * @category errors
306
+ */
307
+ export class SchedulerError extends Schema.TaggedError<SchedulerError>()("@smthrs/engine-store/SchedulerError", {
308
+ code: Schema.Literals([
309
+ "invalid_plan",
310
+ "boundary_unavailable",
311
+ "key_uncomputable",
312
+ "elaboration_failed",
313
+ "store_failed"
314
+ ]),
315
+ message: Schema.String,
316
+ cause: Schema.optional(Schema.Unknown),
317
+ /** Failures while settling siblings after the primary scheduler failure. */
318
+ cleanupErrors: Schema.optional(Schema.Array(Schema.Unknown))
319
+ }) {}
320
+
321
+ /**
322
+ * Plan-driving operations.
323
+ *
324
+ * @since 0.1.0
325
+ * @category models
326
+ */
327
+ export interface Service {
328
+ /** Persists generation 0 and journals `plan-recorded`. */
329
+ readonly record: (
330
+ plan: Plan.Plan
331
+ ) => Effect.Effect<PlanStore.RecordResult, SchedulerError, PlanStore.PlanStore | Journal.Journal | Crypto.Crypto>
332
+ /** Persists the newest generation and journals `subgraph-appended`. */
333
+ readonly append: (
334
+ plan: Plan.Plan
335
+ ) => Effect.Effect<void, SchedulerError, PlanStore.PlanStore | Journal.Journal | Crypto.Crypto>
336
+ /** Drives the plan to completion and reports every node's outcome. */
337
+ readonly run: (plan: Plan.Plan) => Effect.Effect<Report, SchedulerError, Requirements>
338
+ }
339
+
340
+ /**
341
+ * Service tag for the plan scheduler.
342
+ *
343
+ * @since 0.1.0
344
+ * @category services
345
+ */
346
+ export class PlanScheduler extends Context.Service<PlanScheduler, Service>()("@smthrs/engine-store/PlanScheduler") {}
347
+
348
+ /**
349
+ * The declared digest handed to `StepBoundary.prepare` when the scheduler is
350
+ * *asking* what a path holds rather than asserting it. Prepare measures every
351
+ * declared read regardless of what the declaration claims, so any placeholder
352
+ * works; a self-describing one can never be mistaken for a real digest.
353
+ *
354
+ * @private
355
+ */
356
+ /**
357
+ * The tag a plan node dispatches, when its material names one.
358
+ *
359
+ * A material body is opaque to the compiler by design, so this reads it
360
+ * defensively and answers nothing rather than guessing: a merge node, a body
361
+ * an elaborator shaped differently, and a node whose tag is not a string all
362
+ * carry no tag at all. @private
363
+ */
364
+ const actionOf = (node: Plan.PlanNode): string | undefined => {
365
+ const body = node.material.body
366
+ if (typeof body !== "object" || body === null) return undefined
367
+ const fields = body as Record<string, unknown>
368
+ const named = typeof fields["action"] === "string" ? fields["action"] : fields["flow"]
369
+ return typeof named === "string" && named !== "" ? named : undefined
370
+ }
371
+
372
+ /**
373
+ * One node of a plan, as a journal record carries it.
374
+ *
375
+ * The same shape the interpreter writes, so a reader folds both executors
376
+ * with one decoder. `effects` and `key` are the plan's own; there is no
377
+ * declaration site here, because a plan node is compiled from a draft and the
378
+ * draft's author frame is not something the compiler keeps. @private
379
+ */
380
+ const nodeSummary = (node: Plan.PlanNode) => {
381
+ const action = actionOf(node)
382
+ return {
383
+ id: node.id,
384
+ kind: node.kind,
385
+ dependsOn: node.dependsOn,
386
+ tier: node.material.kind,
387
+ key: node.key,
388
+ generation: node.generation,
389
+ ...(action === undefined ? {} : { action })
390
+ }
391
+ }
392
+
393
+ const unmeasured = "unmeasured"
394
+
395
+ /** @private */
396
+ const isConflict = (cause: Cause.Cause<unknown>): boolean =>
397
+ cause.reasons.some((reason) => Cause.isFailReason(reason) && WorkspaceSandbox.isMaterializationConflict(reason.error))
398
+
399
+ /** @private */
400
+ const DeviationPayload = Schema.Struct({
401
+ stepKeyDigest: Schema.NonEmptyString,
402
+ attempt: Schema.Int,
403
+ paths: Schema.Array(Schema.String),
404
+ diffIdentity: Schema.String
405
+ })
406
+
407
+ const decodeDeviation = Schema.decodeUnknownOption(DeviationPayload)
408
+
409
+ const digestOf = Schema.decodeUnknownEffect(Sha256)
410
+
411
+ /** @private */
412
+ interface DispatchStart {
413
+ readonly dispatchKey: string
414
+ readonly attempts: number
415
+ readonly rebases: number
416
+ readonly waited: number
417
+ readonly results: ReadonlyMap<string, unknown>
418
+ readonly inputs: ReadonlyArray<ResolvedInput>
419
+ }
420
+
421
+ /** @private */
422
+ interface ObservedRead {
423
+ readonly entry: FileSet.ReadEntry
424
+ /** Source members observed when this declaration entered the run. */
425
+ readonly sourcePaths: ReadonlyArray<string>
426
+ }
427
+
428
+ /** @private */
429
+ interface DispatchProgress {
430
+ readonly dispatchKey: string
431
+ readonly attempts: number
432
+ readonly rebases: number
433
+ }
434
+
435
+ /** @private */
436
+ interface CompletedDispatch extends DispatchProgress {
437
+ readonly outcome: "built" | "clean"
438
+ readonly result: unknown
439
+ }
440
+
441
+ /** @private */
442
+ interface FailedDispatch extends DispatchProgress {
443
+ readonly outcome: "failed"
444
+ }
445
+
446
+ /** @private */
447
+ interface ConflictedDispatch extends DispatchProgress {
448
+ readonly outcome: "conflicted"
449
+ readonly strategy: Plan.RuntimeStrategy
450
+ }
451
+
452
+ /** @private */
453
+ type Dispatched = CompletedDispatch | FailedDispatch | ConflictedDispatch
454
+
455
+ /** @private */
456
+ interface DispatchSettlementEvent {
457
+ readonly _tag: "DispatchSettled"
458
+ readonly node: Plan.PlanNode
459
+ readonly exit: Exit.Exit<Dispatched, SchedulerError>
460
+ }
461
+
462
+ /** @private */
463
+ type CoordinatorEvent =
464
+ | { readonly _tag: "AttemptKeyed"; readonly nodeId: string; readonly digest: string }
465
+ | DispatchSettlementEvent
466
+
467
+ const storeFailure = (message: string) => (cause: unknown) =>
468
+ new SchedulerError({ code: "store_failed", message, cause })
469
+
470
+ const nonNegativeSafeInteger = (name: string, value: number): number => {
471
+ if (!Number.isSafeInteger(value) || value < 0) {
472
+ throw new RangeError(`${name} must be a non-negative safe integer`)
473
+ }
474
+ return value
475
+ }
476
+
477
+ /**
478
+ * Constructs a scheduler bound to one run.
479
+ *
480
+ * @since 0.1.0
481
+ * @category constructors
482
+ */
483
+ export const make = (options: Options): Service => {
484
+ const admission = AttemptAdmission.makeUnsafe()
485
+ const cacheAgeVerdict = CacheAgeVerdicts.make(options.runId)
486
+ const rebaseLimit = nonNegativeSafeInteger("rebaseLimit", options.rebaseLimit ?? 3)
487
+ const scheduling = Scheduling.make(options.concurrency)
488
+ // Capture before the first async boundary. Callers can retain and mutate
489
+ // their options, but cannot change this scheduler's identity after admission.
490
+ const capturedEnvironment = Result.try(() =>
491
+ Schema.decodeUnknownSync(Schema.UndefinedOr(StepKey.EnvironmentIdentity))(
492
+ options.environment,
493
+ { onExcessProperty: "error" }
494
+ )
495
+ )
496
+
497
+ // Every scheduler record addresses the run's root lineage, so a frame can
498
+ // reach it (`apps/site/src/content/docs/docs/concepts/time-travel.mdx`).
499
+ const lineageId = FlowEngine.Lineage.root(options.runId)
500
+ const source = (suffix: string) => ({ runId: options.runId, sourceId: `${options.sourceId}/${suffix}`, lineageId })
501
+
502
+ /**
503
+ * Scheduler records take the journal's durable, owner-fenced channel: a plan
504
+ * digest binds an approval, so it may never ride the lossy queue. A zombie
505
+ * that lost the run self-interrupts on `fence_lost`, exactly as
506
+ * `internal/ActionPersistence` does — a scheduler that kept dispatching
507
+ * after losing ownership is the failure mode fencing exists to prevent.
508
+ */
509
+ const emit = (record: JournalEvent.Input) =>
510
+ Effect.flatMap(Journal.Journal, (journal) => journal.emitDurable(record, options.owner)).pipe(
511
+ Effect.catch((error) =>
512
+ error.code === "fence_lost"
513
+ ? Effect.interrupt
514
+ : Effect.fail(storeFailure("the scheduler could not journal a record")(error))
515
+ )
516
+ )
517
+
518
+ const atomically = <A, E, R>(message: string, effect: Effect.Effect<A, E, R>) =>
519
+ Effect.flatMap(Journal.Journal, (journal) => journal.transact(effect)).pipe(
520
+ Effect.mapError((error) => error instanceof SchedulerError ? error : storeFailure(message)(error))
521
+ )
522
+
523
+ const verifyPlan = (candidate: Plan.Plan) =>
524
+ Plan.verify(candidate).pipe(
525
+ Effect.mapError((cause) =>
526
+ new SchedulerError({ code: "invalid_plan", message: "the scheduler requires a verified plan", cause })
527
+ )
528
+ )
529
+
530
+ const record: Service["record"] = Effect.fn("PlanScheduler.record")((candidate) =>
531
+ Effect.gen(function*() {
532
+ const plan = yield* verifyPlan(candidate)
533
+ yield* Effect.annotateCurrentSpan({
534
+ runId: options.runId,
535
+ planId: plan.planId,
536
+ digest: plan.digest,
537
+ generation: plan.generation
538
+ })
539
+ const store = yield* PlanStore.PlanStore
540
+ const now = yield* Clock.currentTimeMillis.pipe(Effect.map(Math.floor))
541
+ const outcome = yield* atomically(
542
+ "could not atomically record the plan",
543
+ Effect.gen(function*() {
544
+ const recorded = yield* store.record(plan, now).pipe(
545
+ Effect.mapError(storeFailure("could not record the plan"))
546
+ )
547
+ yield* emit(JournalRecords.planRecorded(source(`plan/${plan.planId}`), {
548
+ planId: plan.planId,
549
+ flow: plan.flow,
550
+ digest: plan.digest,
551
+ baseDigest: plan.baseDigest,
552
+ generation: plan.generation,
553
+ nodes: plan.nodes.length,
554
+ outcome: recorded._tag,
555
+ // The graph itself, beside the count it has always carried. A
556
+ // reader that only knows how MANY nodes ran cannot show which
557
+ // ones, and the count stays because the CLI's run progress reads
558
+ // it as a number.
559
+ graph: { nodes: plan.nodes.map(nodeSummary) }
560
+ }))
561
+ return recorded
562
+ })
563
+ )
564
+ yield* Effect.annotateCurrentSpan({ outcome: outcome._tag })
565
+ return outcome
566
+ })
567
+ )
568
+
569
+ const append: Service["append"] = Effect.fn("PlanScheduler.append")((candidate) =>
570
+ Effect.gen(function*() {
571
+ const plan = yield* verifyPlan(candidate)
572
+ yield* Effect.annotateCurrentSpan({
573
+ runId: options.runId,
574
+ planId: plan.planId,
575
+ digest: plan.digest,
576
+ generation: plan.generation
577
+ })
578
+ const store = yield* PlanStore.PlanStore
579
+ yield* atomically(
580
+ "could not atomically append the subgraph",
581
+ Effect.gen(function*() {
582
+ yield* store.append(plan).pipe(Effect.mapError(storeFailure("could not append the subgraph")))
583
+ yield* emit(JournalRecords.subgraphAppended(source(`plan/${plan.planId}/${plan.generation}`), {
584
+ planId: plan.planId,
585
+ digest: plan.digest,
586
+ baseDigest: plan.baseDigest,
587
+ generation: plan.generation,
588
+ nodeIds: Plan.generationNodes(plan).map((node) => node.id),
589
+ graph: { nodes: Plan.generationNodes(plan).map(nodeSummary) }
590
+ }))
591
+ })
592
+ )
593
+ yield* Effect.annotateCurrentSpan({ outcome: "appended" })
594
+ })
595
+ )
596
+
597
+ const run: Service["run"] = Effect.fn("PlanScheduler.run")((candidate) =>
598
+ Effect.gen(function*() {
599
+ if (Result.isFailure(capturedEnvironment)) {
600
+ return yield* Effect.fail(
601
+ new SchedulerError({
602
+ code: "key_uncomputable",
603
+ message: "the scheduler requires a valid execution environment identity"
604
+ })
605
+ )
606
+ }
607
+ const environment = capturedEnvironment.success
608
+ const environmentDigest = yield* StepKey.environmentIdentity(environment).pipe(Effect.mapError((cause) =>
609
+ new SchedulerError({ code: "key_uncomputable", message: "could not bind the execution environment", cause })
610
+ ))
611
+ const initial = yield* verifyPlan(candidate)
612
+ yield* Effect.annotateCurrentSpan({
613
+ runId: options.runId,
614
+ planId: initial.planId,
615
+ digest: initial.digest,
616
+ nodes: initial.nodes.length
617
+ })
618
+ const boundaries = yield* StepBoundary.StepBoundary
619
+ const fileSystem = yield* Effect.serviceOption(FileSystem.FileSystem)
620
+ const executor = yield* NodeExecutor
621
+ const journal = yield* Journal.Journal
622
+ const inputStore = yield* PlanInputStore.PlanInputStore
623
+ const mergeStore = yield* PlanMergeStore.PlanMergeStore
624
+ const mergeIdentity = {
625
+ runId: options.runId,
626
+ planId: initial.planId,
627
+ baseDigest: initial.baseDigest,
628
+ environmentDigest
629
+ }
630
+ const mergeFailure = (cause: PlanMergeStore.PlanMergeError) =>
631
+ cause.code === "fence_lost"
632
+ ? Effect.interrupt :
633
+ Effect.fail(storeFailure("could not recover or persist the plan's merge decisions")(cause))
634
+ const recoveredMerges = yield* mergeStore.list(mergeIdentity, options.owner).pipe(Effect.catch(mergeFailure))
635
+ const reconciler = Option.getOrElse(
636
+ yield* Effect.serviceOption(Reconciliation.Reconciliation),
637
+ Reconciliation.makeDefault
638
+ )
639
+ const selector = Option.getOrElse(
640
+ yield* Effect.serviceOption(Selection.Selection),
641
+ Selection.makeNoop
642
+ )
643
+ // A verifier composes the scheduler replay-only, as it does the engine:
644
+ // a recorded node replays and a node that would run its body stops.
645
+ const replayOnly = Option.getOrUndefined(yield* Effect.serviceOption(ReplayOnly.ReplayOnly))
646
+ yield* Effect.logDebug("scheduler run started", {
647
+ digest: initial.digest,
648
+ nodes: initial.nodes.length
649
+ })
650
+
651
+ // Only a coordinator failure makes interrupted siblings terminal here.
652
+ // Parking, external cancellation and fence loss retain their established
653
+ // recovery semantics. The flag is set before the child scope closes.
654
+ let abortedForFailure = false
655
+ const cleanupErrors: Array<unknown> = []
656
+ let plan = yield* PlanMerges.recover(initial, recoveredMerges).pipe(Effect.catch(mergeFailure))
657
+ const graph = RuntimeGraph.make(plan.nodes)
658
+ const states = graph.states
659
+ const results = new Map<string, unknown>()
660
+ const digestMemo = StepKey.makeDigestMemo()
661
+ const verdicts: Array<{ nodeId: string; verdict: Reconciliation.Verdict }> = []
662
+ const appended: Array<string> = []
663
+ const digestToNode = new Map<string, Array<string>>()
664
+ const deviationSignatures = new Map<string, string>()
665
+ const writers = new Map<string, string>()
666
+ const writerEntries: Array<{ readonly entry: FileSet.Entry; readonly nodeId: string }> = []
667
+ const nodesById = new Map<string, Plan.PlanNode>()
668
+ let cursor: number | undefined = undefined
669
+ const events = yield* Queue.unbounded<CoordinatorEvent>()
670
+
671
+ const stateOf = (node: Plan.PlanNode): RuntimeGraph.NodeState =>
672
+ states.get(node.id)!
673
+
674
+ /**
675
+ * Reconciliation's first declared owner of each path. Read version
676
+ * selection is different: only a reader's predecessors produce its
677
+ * inputs, never the reader itself or an explicitly later writer.
678
+ */
679
+ const indexWriters = (nodes: ReadonlyArray<Plan.PlanNode>) => {
680
+ for (const node of nodes) {
681
+ nodesById.set(node.id, node)
682
+ for (const entry of [...FileSet.expand(node.effects.writes), ...node.effects.removes ?? []]) {
683
+ writerEntries.push({ entry, nodeId: node.id })
684
+ if (typeof entry === "string" && !writers.has(entry)) {
685
+ writers.set(entry, node.id)
686
+ }
687
+ }
688
+ }
689
+ }
690
+ indexWriters(plan.nodes)
691
+
692
+ const writerFor = (path: string): string | undefined =>
693
+ writers.get(path) ?? writerEntries.find(({ entry }) => FileSet.overlaps(entry, path))?.nodeId
694
+
695
+ // One bounded traversal per observation/dispatch, not an eager matrix
696
+ // of every node's transitive ancestors. Shared ancestors are visited
697
+ // once, and the temporary set is released after this reader's work.
698
+ const writerScopesBefore = (node: Plan.PlanNode): ReadonlyArray<FileSet.Entry> => {
699
+ const scopes: Array<FileSet.Entry> = []
700
+ graph.visitAncestors(node, (predecessor) => {
701
+ scopes.push(...FileSet.expand(predecessor.effects.writes), ...predecessor.effects.removes ?? [])
702
+ })
703
+ return scopes
704
+ }
705
+
706
+ const expandReads = (entries: ReadonlyArray<FileSet.ReadEntry>, what: string) =>
707
+ Effect.gen(function*() {
708
+ const expanded: Array<{ readonly entry: FileSet.ReadEntry; readonly paths: ReadonlyArray<string> }> = []
709
+ for (const entry of entries) {
710
+ if (typeof entry === "string") {
711
+ expanded.push({ entry, paths: [entry] })
712
+ continue
713
+ }
714
+ if (Option.isNone(fileSystem)) {
715
+ return yield* Effect.fail(
716
+ new SchedulerError({
717
+ code: "boundary_unavailable",
718
+ message: `the host cannot expand ${what} without a FileSystem`
719
+ })
720
+ )
721
+ }
722
+ // Through `FileEnumeration`, never the host `glob`: host results
723
+ // are absolute under the kernel FileSystem and skip dotfiles, so
724
+ // a workspace-relative pattern silently expanded to nothing.
725
+ const matches = yield* FileEnumeration.expandGlob(fileSystem.value, entry).pipe(
726
+ Effect.mapError((cause) =>
727
+ new SchedulerError({
728
+ code: "boundary_unavailable",
729
+ message: `the host could not expand ${what}`,
730
+ cause
731
+ })
732
+ )
733
+ )
734
+ expanded.push({ entry, paths: matches })
735
+ }
736
+ return expanded
737
+ })
738
+
739
+ const prepare = (paths: ReadonlyArray<string>, what: string) =>
740
+ boundaries.prepare({
741
+ readSet: [...new Set(paths)].sort().map((path) => ({ path, digest: unmeasured })),
742
+ writeSet: [],
743
+ boundaryMode: "hard"
744
+ }).pipe(
745
+ Effect.mapError((cause) =>
746
+ new SchedulerError({
747
+ code: "boundary_unavailable",
748
+ message: `the host could not measure ${what}`,
749
+ cause
750
+ })
751
+ )
752
+ )
753
+
754
+ const expandProducedMatches = (
755
+ glob: FileSet.Glob,
756
+ writerScopes: ReadonlyArray<FileSet.Entry>,
757
+ what: string
758
+ ) =>
759
+ Effect.gen(function*() {
760
+ const scopes = writerScopes.filter((entry) => FileSet.overlaps(glob, entry))
761
+ if (scopes.length === 0) return []
762
+ if (Option.isNone(fileSystem)) {
763
+ return yield* Effect.fail(
764
+ new SchedulerError({
765
+ code: "boundary_unavailable",
766
+ message: `the host cannot expand ${what} without a FileSystem`
767
+ })
768
+ )
769
+ }
770
+ const paths = new Set<string>()
771
+ for (const entry of scopes) {
772
+ if (typeof entry === "string") {
773
+ // One host call: `stat` reports both whether the producer's
774
+ // output is there yet and what kind of thing it is, so the
775
+ // `exists` probe that used to precede it only doubled the cost.
776
+ // On the confined host each call is one CPython fork
777
+ // (`@smthrs/platform-node/AtomicFileSystem`).
778
+ const info = yield* fileSystem.value.stat(entry).pipe(
779
+ Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)),
780
+ Effect.mapError((cause) =>
781
+ new SchedulerError({
782
+ code: "boundary_unavailable",
783
+ message: `the host could not expand ${what}`,
784
+ cause
785
+ })
786
+ )
787
+ )
788
+ // Exact writer declarations name files; directory outputs use
789
+ // `TreeArtifact`, and only files are members of a read glob. A
790
+ // producer that has not written yet is simply not a candidate.
791
+ if (info?.type === "File") paths.add(entry)
792
+ continue
793
+ }
794
+ const matches = FileSet.isGlob(entry)
795
+ ? yield* FileEnumeration.expandGlob(fileSystem.value, entry).pipe(
796
+ Effect.mapError((cause) =>
797
+ new SchedulerError({
798
+ code: "boundary_unavailable",
799
+ message: `the host could not expand ${what}`,
800
+ cause
801
+ })
802
+ )
803
+ )
804
+ : yield* FileEnumeration.filesUnder(fileSystem.value, entry.path).pipe(
805
+ Effect.mapError((cause) =>
806
+ new SchedulerError({
807
+ code: "boundary_unavailable",
808
+ message: `the host could not expand ${what}`,
809
+ cause
810
+ })
811
+ )
812
+ )
813
+ for (const path of matches) {
814
+ if (FileSet.matchesGlob(glob, path)) paths.add(path)
815
+ }
816
+ }
817
+ return [...paths].sort()
818
+ })
819
+
820
+ // A read declaration is observed only when its node enters this run:
821
+ // generation zero here, or one newly appended generation below. Paths
822
+ // already pinned by an earlier declaration reuse that digest; measuring
823
+ // them again would tear the run. A new source path is measured now,
824
+ // which is its first observation in this run.
825
+ const pinned = new Map<string, string>()
826
+ const observedReads = new Map<string, ReadonlyArray<ObservedRead>>()
827
+ const inputFailure = (cause: PlanInputStore.PlanInputError) =>
828
+ cause.code === "fence_lost"
829
+ ? Effect.interrupt
830
+ : Effect.fail(storeFailure("could not recover or persist the plan's source observations")(cause))
831
+ const observeReads = (
832
+ nodes: ReadonlyArray<Plan.PlanNode>,
833
+ generation: number,
834
+ what: string,
835
+ commit?: (
836
+ snapshot: PlanInputStore.Snapshot
837
+ ) => Effect.Effect<
838
+ PlanInputStore.Snapshot,
839
+ SchedulerError,
840
+ PlanStore.PlanStore | Journal.Journal | Crypto.Crypto
841
+ >
842
+ ) =>
843
+ Effect.gen(function*() {
844
+ const address = {
845
+ runId: options.runId,
846
+ planId: initial.planId,
847
+ baseDigest: initial.baseDigest,
848
+ environmentDigest,
849
+ generation
850
+ }
851
+ const stored = yield* inputStore.get(address, options.owner).pipe(Effect.catch(inputFailure))
852
+ let snapshot: PlanInputStore.Snapshot
853
+ if (Option.isSome(stored)) {
854
+ snapshot = stored.value
855
+ } else {
856
+ const unpinned = new Set<string>()
857
+ const observedNodes: Array<PlanInputStore.Snapshot["nodes"][number]> = []
858
+ for (const node of nodes) {
859
+ const observed: Array<ObservedRead> = []
860
+ const reads = FileSet.expandReads(node.effects.reads)
861
+ const writerScopes = reads.length === 0 ? [] : writerScopesBefore(node)
862
+ for (const { entry, paths } of yield* expandReads(reads, what)) {
863
+ const sourcePaths = paths.filter((path) => !writerScopes.some((scope) => FileSet.overlaps(scope, path)))
864
+ for (const path of sourcePaths) {
865
+ if (!pinned.has(path)) unpinned.add(path)
866
+ }
867
+ observed.push({ entry, sourcePaths })
868
+ }
869
+ observedNodes.push({ id: node.id, key: node.key, reads: observed })
870
+ }
871
+ const pins = unpinned.size === 0 ? [] : (yield* prepare([...unpinned], what)).readSnapshot
872
+ const measured: PlanInputStore.Snapshot = {
873
+ version: 1,
874
+ generation,
875
+ nodes: observedNodes,
876
+ pins
877
+ }
878
+ yield* PlanInputs.validate(measured, nodes, pinned, writerScopesBefore).pipe(Effect.catch(inputFailure))
879
+ snapshot = commit === undefined
880
+ ? yield* inputStore.record(address, measured, options.owner).pipe(Effect.catch(inputFailure))
881
+ : measured
882
+ }
883
+ if (commit !== undefined) snapshot = yield* commit(snapshot)
884
+ // Do not publish speculative measurements. A competing observation
885
+ // wins durably, and every recovered declaration must still describe
886
+ // this verified plan before it can influence an execution key.
887
+ const nextPins = yield* PlanInputs.validate(snapshot, nodes, pinned, writerScopesBefore).pipe(
888
+ Effect.catch(inputFailure)
889
+ )
890
+ for (const node of snapshot.nodes) observedReads.set(node.id, node.reads)
891
+ for (const [path, digest] of nextPins) pinned.set(path, digest)
892
+ })
893
+
894
+ // Recover each generation before any dispatch. Existing observations
895
+ // never enumerate or measure the host again, even after process restart.
896
+ for (let generation = 0; generation <= plan.generation; generation++) {
897
+ yield* observeReads(
898
+ plan.nodes.filter((node) => node.generation === generation),
899
+ generation,
900
+ "the plan's source inputs"
901
+ )
902
+ }
903
+ // A merge intent is authoritative scheduler state, not a failed-action
904
+ // cache entry or a convention inferred from an arbitrary node's body.
905
+ for (const { intent } of recoveredMerges) {
906
+ const state = stateOf(nodesById.get(intent.nodeId)!)
907
+ graph.stage(intent.nodeId, "skipped")
908
+ state.dispatchKey = intent.dispatchKey
909
+ state.attempts = intent.attempts
910
+ state.rebases = intent.rebases
911
+ }
912
+ graph.reconcile()
913
+
914
+ const measure = (node: Plan.PlanNode) =>
915
+ Effect.gen(function*() {
916
+ const measured = new Map<string, string>()
917
+ const produced = new Set<string>()
918
+ const observed = observedReads.get(node.id)!
919
+ const writerScopes = observed.length === 0 ? [] : writerScopesBefore(node)
920
+ for (const read of observed) {
921
+ if (typeof read.entry === "string") {
922
+ if (read.sourcePaths.length === 0) produced.add(read.entry)
923
+ else measured.set(read.entry, pinned.get(read.entry)!)
924
+ continue
925
+ }
926
+ for (const path of read.sourcePaths) measured.set(path, pinned.get(path)!)
927
+ // A glob's source membership is frozen above. Dispatch may only
928
+ // add paths derived from preceding writer scopes. Neither an
929
+ // unproduced arrival nor a future writer widens the source world.
930
+ for (const path of yield* expandProducedMatches(read.entry, writerScopes, `the inputs of ${node.id}`)) {
931
+ produced.add(path)
932
+ }
933
+ }
934
+ if (produced.size > 0) {
935
+ // Produced paths are our own outputs, so measuring them once their
936
+ // producer has settled is not re-observing the world.
937
+ const prepared = yield* prepare([...produced], `the inputs of ${node.id}`)
938
+ for (const entry of prepared.readSnapshot) measured.set(entry.path, entry.digest)
939
+ }
940
+ return {
941
+ readSet: [...measured].sort(([left], [right]) => compareText(left, right)).map(([path, digest]) => ({
942
+ path,
943
+ digest
944
+ })),
945
+ writeSet: FileSet.expand(node.effects.writes),
946
+ // Declared removals travel with the boundary: they are what makes
947
+ // an absent declared output legitimate rather than a defect.
948
+ ...(node.effects.removes === undefined ? {} : { removes: node.effects.removes }),
949
+ boundaryMode: node.effects.boundaryMode
950
+ } satisfies FileBoundary
951
+ })
952
+
953
+ /**
954
+ * Non-sealed work uses a stable run-local structural scope: it replays
955
+ * durable attempts but never reuses an effect from another invocation.
956
+ * For sealed work:
957
+ *
958
+ * The key a dispatch is recorded under: the node's own declaration
959
+ * folded with the CONTENT of everything it consumes — the digests the
960
+ * host just measured for its files, and the digest of each upstream
961
+ * node's settled output value.
962
+ *
963
+ * Not the plan key. That key folds every upstream key transitively, so
964
+ * an edit anywhere upstream re-ran everything below it even when the
965
+ * edited node's output value was byte-identical. Bazel's
966
+ * `ActionCacheChecker` stops invalidation at unchanged content, and
967
+ * `StepKey.dispatchIdentity` is how this does.
968
+ *
969
+ * The plan key alone could never have been it either: two runs whose
970
+ * input files differ declare the same graph, and serving one the other's
971
+ * result is exactly the staleness the boundary exists to prevent.
972
+ */
973
+ const dispatchKeyFor = (
974
+ node: Plan.PlanNode,
975
+ boundary: FileBoundary,
976
+ settledResults: ReadonlyMap<string, unknown>
977
+ ) =>
978
+ (node.material.kind === "sealed" ?
979
+ StepKey.dispatchIdentity({
980
+ material: node.material,
981
+ results: Object.fromEntries(
982
+ node.material.inputs.flatMap((input) =>
983
+ input._tag === "Ref" ? [[input.from, settledResults.get(input.from)] as const] : []
984
+ )
985
+ ),
986
+ digestMemo,
987
+ environment,
988
+ hermetic: {
989
+ ...boundary,
990
+ readSet: StepBoundary.exactReads(boundary)
991
+ }
992
+ }) :
993
+ StepKey.ordinal({
994
+ runId: options.runId,
995
+ // Stable structural scope, never admission order. The declaration
996
+ // fingerprint prevents a changed plan from reusing an old attempt.
997
+ parentScope: JSON.stringify(["plan-node/v1", initial.planId, node.id, node.key]),
998
+ ordinal: 0,
999
+ tier: node.material.kind
1000
+ })).pipe(
1001
+ /* v8 ignore next 3 -- verified plans have serializable material, admission resolves every dependency, and successful upstream values already crossed durable serialization; key derivation still exposes its honest failure channel */
1002
+ Effect.mapError((cause) =>
1003
+ new SchedulerError({ code: "key_uncomputable", message: `could not key ${node.id}`, cause })
1004
+ )
1005
+ )
1006
+
1007
+ const runtimeStrategyOf = (node: Plan.PlanNode): Plan.RuntimeStrategy =>
1008
+ node.conflicts.some((conflict) => conflict.runtime === "stop-merge") ? "stop-merge" : node.runtime
1009
+
1010
+ const markSettled = (node: Plan.PlanNode, outcome: Outcome): void => {
1011
+ graph.stage(node.id, outcome)
1012
+ }
1013
+
1014
+ const emitSettlement = (node: Plan.PlanNode, sourceSeq?: number) =>
1015
+ Effect.gen(function*() {
1016
+ const state = stateOf(node)
1017
+ const action = actionOf(node)
1018
+ yield* emit(JournalRecords.nodeSettled({ ...source(`node/${node.id}/settled`), sourceSeq }, {
1019
+ planId: plan.planId,
1020
+ nodeId: node.id,
1021
+ planKey: node.key,
1022
+ dispatchKey: state.dispatchKey,
1023
+ outcome: state.outcome,
1024
+ attempts: state.attempts,
1025
+ rebases: state.rebases,
1026
+ ...(action === undefined ? {} : { action })
1027
+ }))
1028
+ yield* Metric.update(EngineStoreMetrics.node[state.outcome], 1)
1029
+ })
1030
+
1031
+ const settle = (node: Plan.PlanNode, outcome: Outcome) => {
1032
+ markSettled(node, outcome)
1033
+ graph.reconcile()
1034
+ return emitSettlement(node)
1035
+ }
1036
+
1037
+ const applyVerdict = (nodeId: string, verdict: Reconciliation.Verdict, trigger: string) =>
1038
+ Effect.gen(function*() {
1039
+ verdicts.push({ nodeId, verdict })
1040
+ yield* emit(JournalRecords.nodeReconciled(source(`node/${nodeId}/reconciled/${verdicts.length}`), {
1041
+ planId: plan.planId,
1042
+ nodeId,
1043
+ trigger,
1044
+ verdict
1045
+ }))
1046
+ if (verdict._tag === "Fail") {
1047
+ // The scheduler chooses the node it asks about, so a verdict always
1048
+ // names one it is tracking.
1049
+ graph.stage(nodeId, "failed")
1050
+ results.delete(nodeId)
1051
+ return
1052
+ }
1053
+ if (verdict._tag === "Reorder") {
1054
+ // Re-plan with the discovered dependency made explicit. It binds
1055
+ // work that has not dispatched yet — the deviating node already
1056
+ // ran, and rewriting history is what append-only forbids.
1057
+ // Persisting the edge is the caller's next elaboration; the
1058
+ // verdict is journaled so it can be.
1059
+ graph.reorder(nodeId, verdict.dependsOn)
1060
+ }
1061
+ // `FactorOut` needs no graph surgery: two identical extracted steps
1062
+ // collapse to one key by themselves, so the second is a `clean`.
1063
+ })
1064
+
1065
+ const dispatch: (
1066
+ node: Plan.PlanNode,
1067
+ start: DispatchStart
1068
+ ) => Effect.Effect<Dispatched, SchedulerError, Requirements> = Effect.fn(
1069
+ "PlanScheduler.dispatch"
1070
+ )(function*(node: Plan.PlanNode, start: DispatchStart) {
1071
+ return yield* Effect.gen(function*() {
1072
+ yield* Effect.annotateCurrentSpan({
1073
+ runId: options.runId,
1074
+ planId: plan.planId,
1075
+ nodeId: node.id,
1076
+ kind: node.kind,
1077
+ planKey: node.key
1078
+ })
1079
+ // Attempt bookkeeping is local to this fiber. The coordinator owns
1080
+ // the durable scheduler state and applies this progress only when it
1081
+ // processes the terminal event.
1082
+ let dispatchKey = start.dispatchKey
1083
+ let attempts = start.attempts
1084
+ let rebases = start.rebases
1085
+ while (true) {
1086
+ const boundary = yield* measure(node)
1087
+ const measuredKey = yield* dispatchKeyFor(node, boundary, start.results)
1088
+ if (dispatchKey !== "" && dispatchKey !== measuredKey) {
1089
+ yield* emit(JournalRecords.nodeInvalidated(source(`node/${node.id}/${attempts}/invalidated`), {
1090
+ planId: plan.planId,
1091
+ nodeId: node.id,
1092
+ planKey: node.key,
1093
+ from: dispatchKey,
1094
+ to: measuredKey,
1095
+ reason: "measured-inputs-changed"
1096
+ }))
1097
+ }
1098
+ dispatchKey = measuredKey
1099
+ attempts = attempts + 1
1100
+ yield* Effect.annotateCurrentSpan({ dispatchKey, attempt: attempts })
1101
+ const action = actionOf(node)
1102
+ yield* emit(JournalRecords.nodeScheduled(source(`node/${node.id}/${attempts}`), {
1103
+ planId: plan.planId,
1104
+ nodeId: node.id,
1105
+ kind: node.kind,
1106
+ planKey: node.key,
1107
+ dispatchKey,
1108
+ attempt: attempts,
1109
+ priority: node.priority,
1110
+ waited: start.waited,
1111
+ ...(action === undefined ? {} : { action })
1112
+ }))
1113
+ const dispatchDigest = yield* Effect.orDie(digestOf(dispatchKey))
1114
+ yield* Queue.offer(events, { _tag: "AttemptKeyed", nodeId: node.id, digest: dispatchDigest })
1115
+ const ran = yield* Ref.make(false)
1116
+ const exit = yield* ActionPersistence.make({
1117
+ runId: options.runId,
1118
+ owner: options.owner,
1119
+ admission,
1120
+ cacheAgeVerdict,
1121
+ replayOnly,
1122
+ sourceId: `${options.sourceId}/node/${node.id}`,
1123
+ execute: () =>
1124
+ Ref.set(ran, true).pipe(
1125
+ Effect.andThen(executor.execute({ node, attempt: attempts, boundary, inputs: start.inputs }))
1126
+ )
1127
+ })({
1128
+ action: {},
1129
+ attempt: attempts,
1130
+ key: dispatchKey,
1131
+ tier: node.material.kind,
1132
+ nondeterministic: node.material.nondeterministic,
1133
+ metadata: boundary
1134
+ }).pipe(
1135
+ Effect.onInterrupt(() =>
1136
+ abortedForFailure
1137
+ ? atomically(
1138
+ "could not settle a sibling aborted by scheduler failure",
1139
+ Effect.gen(function*() {
1140
+ const store = yield* AttemptStore.AttemptStore
1141
+ const finishedAtMs = yield* Clock.currentTimeMillis.pipe(Effect.map(Math.floor))
1142
+ const identity = { runId: options.runId, stepKeyDigest: dispatchDigest, attempt: attempts }
1143
+ const finished = yield* store.finish({
1144
+ ...identity,
1145
+ state: "failed",
1146
+ finishedAtMs,
1147
+ error: { code: "scheduler_aborted", message: "A scheduler failure interrupted this attempt" }
1148
+ }, options.owner)
1149
+ // A terminal attempt or a replacement owner already decides
1150
+ // the row. Never overwrite it or emit a contradictory fact.
1151
+ if (finished._tag === "Finished") {
1152
+ yield* emit(JournalRecords.attemptFinished(source(`node/${node.id}/${attempts}/aborted`), {
1153
+ ...identity,
1154
+ state: "failed",
1155
+ reason: "scheduler_aborted"
1156
+ }))
1157
+ }
1158
+ })
1159
+ ).pipe(Effect.catch((cause) =>
1160
+ Effect.sync(() => {
1161
+ cleanupErrors.push(cause)
1162
+ })
1163
+ ))
1164
+ : Effect.void
1165
+ ),
1166
+ Effect.exit
1167
+ )
1168
+ if (Exit.isSuccess(exit)) {
1169
+ return {
1170
+ outcome: (yield* Ref.get(ran)) ? "built" : "clean",
1171
+ result: exit.value,
1172
+ dispatchKey,
1173
+ attempts,
1174
+ rebases
1175
+ } as const
1176
+ }
1177
+ // Losing a fence self-interrupts `ActionPersistence`. An
1178
+ // interrupted dispatch is the run losing ownership, never a node
1179
+ // failure that the scheduler may report and continue past.
1180
+ if (Cause.hasInterrupts(exit.cause)) return yield* Effect.interrupt
1181
+ // The generic executor supplies no irreversible idempotency or
1182
+ // reconciliation contract. A conflict is not proof the effect
1183
+ // never happened; neither a retry nor a freshly keyed merge may
1184
+ // blindly execute more work after that uncertain result.
1185
+ if (!isConflict(exit.cause) || node.material.kind === "irreversible") {
1186
+ return { outcome: "failed", dispatchKey, attempts, rebases } as const
1187
+ }
1188
+ const strategy = runtimeStrategyOf(node)
1189
+ if (strategy === "delay-rebase" && rebases < rebaseLimit) {
1190
+ // Hold the dependents — they are not ready while this node is in
1191
+ // flight — and re-execute against the newly recorded base. The
1192
+ // next turn of the loop re-measures, which re-keys, which is a
1193
+ // new attempt rather than a retry of the old identity.
1194
+ rebases = rebases + 1
1195
+ continue
1196
+ }
1197
+ return { outcome: "conflicted", strategy, dispatchKey, attempts, rebases } as const
1198
+ }
1199
+ }).pipe(
1200
+ Effect.tap((result) => Effect.annotateCurrentSpan({ outcome: result.outcome }))
1201
+ )
1202
+ })
1203
+
1204
+ /**
1205
+ * `stop-merge`: the losing action is stopped and both lanes are routed
1206
+ * through an explicit merge node appended to the SAME plan, as an
1207
+ * ordinary elaboration. The merge critical section uses this module's
1208
+ * restart-or-fail landing contract, so the merge node carries no rebase
1209
+ * budget of its own: it lands, or the run has a failed node. There is no
1210
+ * worktree-lane surface; that design was removed.
1211
+ */
1212
+ const appendMerge = (intent: PlanMergeStore.Intent) =>
1213
+ Effect.gen(function*() {
1214
+ const node = yield* PlanMerges.validateIntent(plan, intent).pipe(Effect.catch(mergeFailure))
1215
+ const winners = intent.peers.filter((id) => results.has(id))
1216
+ const mergeId = PlanMerges.allocateId(plan, node.id)
1217
+ const parentDigest = plan.digest
1218
+ const grown = yield* Plan.append(plan, [PlanMerges.draft(node, mergeId, winners)]).pipe(
1219
+ Effect.mapError((cause) =>
1220
+ new SchedulerError({
1221
+ code: "elaboration_failed",
1222
+ message: `could not append the merge node for ${node.id}`,
1223
+ cause
1224
+ })
1225
+ )
1226
+ )
1227
+ // `appendMerge` copies the stopped node's effects byte-for-byte, so
1228
+ // re-indexing adds no new writer coverage and cannot turn an
1229
+ // already-pinned source path into a producer. The new generation's
1230
+ // read declarations have not themselves been observed, however:
1231
+ // expand them once now and pin only source paths this run has never
1232
+ // measured. Existing pins are always reused.
1233
+ yield* observeReads(
1234
+ Plan.generationNodes(grown),
1235
+ grown.generation,
1236
+ `generation ${grown.generation}'s source inputs`,
1237
+ (snapshot) =>
1238
+ atomically(
1239
+ "could not atomically commit the merge extension",
1240
+ Effect.gen(function*() {
1241
+ const recorded = yield* inputStore.record(
1242
+ { ...mergeIdentity, generation: grown.generation },
1243
+ snapshot,
1244
+ options.owner
1245
+ )
1246
+ .pipe(Effect.catch(inputFailure))
1247
+ yield* append(grown)
1248
+ yield* mergeStore.complete(mergeIdentity, intent.nodeId, {
1249
+ version: 1,
1250
+ generation: grown.generation,
1251
+ parentDigest,
1252
+ planDigest: grown.digest,
1253
+ mergeId,
1254
+ mergeKey: Plan.generationNodes(grown)[0]!.key,
1255
+ winners
1256
+ }, options.owner).pipe(Effect.catch(mergeFailure))
1257
+ return recorded
1258
+ })
1259
+ )
1260
+ )
1261
+ // No asynchronous boundary may expose only part of this admission.
1262
+ // Frozen generations preserve the indexed prefix and its state.
1263
+ const suffix = Plan.generationNodes(grown)
1264
+ graph.append(suffix)
1265
+ plan = grown
1266
+ indexWriters(suffix)
1267
+ appended.push(mergeId)
1268
+ })
1269
+
1270
+ const pendingSettlements: Array<DispatchSettlementEvent> = []
1271
+ const registerAttempt = (event: Extract<CoordinatorEvent, { readonly _tag: "AttemptKeyed" }>) => {
1272
+ const dispatchedUnder = digestToNode.get(event.digest)
1273
+ if (dispatchedUnder === undefined) {
1274
+ digestToNode.set(event.digest, [event.nodeId])
1275
+ } else if (!dispatchedUnder.includes(event.nodeId)) {
1276
+ digestToNode.set(event.digest, [...dispatchedUnder, event.nodeId])
1277
+ }
1278
+ }
1279
+ /**
1280
+ * Absorbs every report already offered without processing another node's
1281
+ * settlement. Attempt-key reports update attribution state immediately;
1282
+ * terminal reports stay ordered in `pendingSettlements` for the one-at-a-
1283
+ * time coordinator. A deviation can appear in a journal page only after
1284
+ * its attempt-key report was offered, so calling this after each page
1285
+ * closes the journal/queue observation race without a shared mutable map.
1286
+ */
1287
+ const drainReportedEvents = Effect.gen(function*() {
1288
+ while (true) {
1289
+ const next = yield* Queue.poll(events)
1290
+ if (Option.isNone(next)) return
1291
+ if (next.value._tag === "AttemptKeyed") registerAttempt(next.value)
1292
+ else pendingSettlements.push(next.value)
1293
+ }
1294
+ })
1295
+
1296
+ const nextSettlement = Effect.gen(function*() {
1297
+ const pending = pendingSettlements.shift()
1298
+ if (pending !== undefined) return pending
1299
+ while (true) {
1300
+ const event = yield* Queue.take(events)
1301
+ if (event._tag === "DispatchSettled") return event
1302
+ registerAttempt(event)
1303
+ }
1304
+ })
1305
+
1306
+ /**
1307
+ * The first consumer `flows.engine.expected-set-deviation` has ever had.
1308
+ * The events are read from the journal rather than from the attempt row
1309
+ * on purpose: a deviation is a durable, replayable fact, and consuming
1310
+ * the fact keeps this seam usable by anything else that reads the
1311
+ * journal.
1312
+ *
1313
+ * Two properties the drain owes the reconciler, both of them about not
1314
+ * letting arrival order decide a verdict:
1315
+ *
1316
+ * 1. **Attribute the whole page before judging any of it.** Deviating on
1317
+ * the same paths is a *symmetric* fact — two steps that both ran
1318
+ * `npm install` — so registering each signature as the entry was
1319
+ * judged answered the same situation two different ways: the first
1320
+ * node saw no peer and was failed, and only the second was factored
1321
+ * out. Every deviation on the page is attributed first, so both sides
1322
+ * of a pair see each other.
1323
+ * 2. **Drain the journal, not one page of it.** Concurrent dispatches can
1324
+ * journal more records than one page holds, and the final completion
1325
+ * is the one that matters: nothing follows it to pick up what a single
1326
+ * page left behind, so a deviation past the cursor would never reach
1327
+ * the seam.
1328
+ */
1329
+ const pendingDeviations: Array<{
1330
+ readonly nodeId: string
1331
+ readonly signature: string
1332
+ readonly payload: typeof DeviationPayload.Type
1333
+ }> = []
1334
+ const drainDeviations = Effect.gen(function*() {
1335
+ let hasMore = true
1336
+ while (hasMore) {
1337
+ const page = yield* journal.entries({
1338
+ runId: options.runId as JournalEvent.RunId,
1339
+ ...(cursor === undefined ? {} : { after: cursor as JournalEvent.Seq }),
1340
+ limit: 512
1341
+ }).pipe(Effect.mapError(storeFailure("could not read the run's journal")))
1342
+ // Every deviation in this page causally follows its attempt-key
1343
+ // report. Absorb those reports now, after the journal read, so even
1344
+ // an in-flight sibling can be attributed without dispatch fibers
1345
+ // mutating coordinator state.
1346
+ yield* drainReportedEvents
1347
+ const attributed: Array<{ nodeId: string; signature: string; payload: typeof DeviationPayload.Type }> = []
1348
+ for (const entry of page.entries) {
1349
+ cursor = entry.seq
1350
+ if (entry.eventType !== "flows.engine.expected-set-deviation") continue
1351
+ const payload = decodeDeviation(entry.payload)
1352
+ if (Option.isNone(payload)) continue
1353
+ const nodeIds = digestToNode.get(payload.value.stepKeyDigest)
1354
+ if (nodeIds === undefined) continue
1355
+ // The attempt belongs to an executing node by construction. If
1356
+ // its built settlement is absent from current bookkeeping — the
1357
+ // record arrived first, or is being durably replayed into an
1358
+ // all-clean invocation — preserve first dispatch order.
1359
+ const nodeId = nodeIds.find((candidate) => states.get(candidate)!.outcome === "built") ?? nodeIds[0]!
1360
+ const signature = JSON.stringify([
1361
+ "expected-set-deviation/v1",
1362
+ [...new Set(payload.value.paths)].sort(compareText)
1363
+ ])
1364
+ deviationSignatures.set(nodeId, signature)
1365
+ attributed.push({ nodeId, signature, payload: payload.value })
1366
+ }
1367
+ pendingDeviations.push(...attributed)
1368
+ for (let index = 0; index < pendingDeviations.length;) {
1369
+ const { nodeId, payload, signature } = pendingDeviations[index]!
1370
+ // A concurrent dispatch can durably journal its deviation before
1371
+ // its terminal event reaches the head of the coordinator queue.
1372
+ // Its signature participates in symmetric attribution now, but
1373
+ // its verdict waits until that node's settlement is processed.
1374
+ if (states.get(nodeId)?.status !== "settled") {
1375
+ index = index + 1
1376
+ continue
1377
+ }
1378
+ const alsoDeviatedBy = [...deviationSignatures.entries()]
1379
+ .filter(([other, otherSignature]) => other !== nodeId && otherSignature === signature)
1380
+ .map(([other]) => other)
1381
+ // Null prototype: a reconciler's lookup of a path named `toString` must miss.
1382
+ const declaredBy: Record<string, string> = Object.assign(
1383
+ Object.create(null),
1384
+ Object.fromEntries(
1385
+ payload.paths.flatMap((path) => {
1386
+ const owner = writerFor(path)
1387
+ return owner === undefined ? [] : [[path, owner] as const]
1388
+ })
1389
+ )
1390
+ )
1391
+ const verdict = yield* reconciler.onDeviation({
1392
+ nodeId,
1393
+ keyDigest: payload.stepKeyDigest,
1394
+ attempt: payload.attempt,
1395
+ paths: payload.paths,
1396
+ diffIdentity: payload.diffIdentity,
1397
+ declaredBy,
1398
+ alsoDeviatedBy
1399
+ })
1400
+ yield* applyVerdict(nodeId, verdict, "deviation")
1401
+ pendingDeviations.splice(index, 1)
1402
+ }
1403
+ hasMore = page.hasMore
1404
+ }
1405
+ })
1406
+
1407
+ /**
1408
+ * The selection consult, once per run, against the initial plan and the
1409
+ * pinned belief snapshot (`docs/guides/defer-work-with-selection.md`). Only sinks,
1410
+ * nodes nothing in the plan depends on,
1411
+ * are offered, because deferring a node something consumes would block
1412
+ * or corrupt its consumers. A verdict may postpone or propose, never
1413
+ * remove: `Admit` is a no-op, a `Defer` marks the sink for the loop
1414
+ * below, a `Propose` is journaled and nothing more (v1 never
1415
+ * auto-appends), and a Defer naming a non-sink — or a Propose naming
1416
+ * anything the plan already accounts for, its flow included — is
1417
+ * ignored and journaled as an inconsistency observation, against the
1418
+ * same `present` set the layer was shown. The `full` override skips
1419
+ * the consult entirely: nothing is deferred and nothing is proposed
1420
+ * under it, and the override record is what makes that visible.
1421
+ * Elaboration cannot
1422
+ * invalidate the marks: an appended merge node depends only on nodes
1423
+ * that produced results, and a deferred node never executes, so a
1424
+ * deferred sink can never gain a dependent mid-run.
1425
+ */
1426
+ const deferrals = new Map<string, { edge: Selection.SuspectedEdge; likelihood: number }>()
1427
+ if (options.selection?.full === true) {
1428
+ yield* emit(JournalRecords.selectionOverridden(source("selection/override"), {
1429
+ planId: plan.planId,
1430
+ mode: "full"
1431
+ }))
1432
+ } else {
1433
+ const dependedOn = new Set(plan.nodes.flatMap((node) => node.dependsOn))
1434
+ const sinks = plan.nodes.filter((node) => !dependedOn.has(node.id))
1435
+ const present = new Set([plan.flow, ...plan.nodes.map((node) => node.id)])
1436
+ const sinkIds = new Set(sinks.map((node) => node.id))
1437
+ const selected = yield* selector.select({
1438
+ changed: options.selection?.changed ?? [],
1439
+ sinks: sinks.map((node) => ({ nodeId: node.id, planKey: node.key })),
1440
+ present: [...present],
1441
+ beliefs: options.selection?.beliefs ?? { pinnedAtMs: 0, edges: [] },
1442
+ policy: options.selection?.policy ?? { deferBelow: 0 }
1443
+ })
1444
+ for (let index = 0; index < selected.length; index++) {
1445
+ const { nodeId, verdict } = selected[index]!
1446
+ if (verdict._tag === "Admit") continue
1447
+ if (verdict._tag === "Defer" && sinkIds.has(nodeId)) {
1448
+ deferrals.set(nodeId, { edge: verdict.edge, likelihood: verdict.likelihood })
1449
+ continue
1450
+ }
1451
+ if (verdict._tag === "Propose" && !present.has(nodeId)) {
1452
+ yield* emit(JournalRecords.selectionProposed(source(`selection/proposed/${index}`), {
1453
+ planId: plan.planId,
1454
+ flow: verdict.flow,
1455
+ edge: verdict.edge,
1456
+ confidence: verdict.confidence
1457
+ }))
1458
+ continue
1459
+ }
1460
+ yield* emit(JournalRecords.selectionInconsistent(source(`selection/inconsistent/${index}`), {
1461
+ planId: plan.planId,
1462
+ nodeId,
1463
+ verdict: verdict._tag,
1464
+ reason: verdict._tag === "Defer" ? "not-a-deferrable-sink" : "proposes-a-present-node"
1465
+ }))
1466
+ }
1467
+ }
1468
+
1469
+ const passiveSettlements = Effect.gen(function*() {
1470
+ while (true) {
1471
+ const blocked = graph.blocked()
1472
+ // Halt, not continue-on-failure: a dependent of failed work never
1473
+ // dispatches. Repeating this pass makes the skip cascade settle in
1474
+ // dependency order before any newly exposed work is admitted.
1475
+ for (const node of blocked) yield* settle(node, "skipped")
1476
+ if (blocked.length > 0) continue
1477
+ // A deferral is a postponement, never a removal, and it only takes
1478
+ // effect on work that was genuinely runnable: a marked sink whose
1479
+ // cone failed settled `skipped` above instead, because work that
1480
+ // could not have run is not a debt. The debt record precedes the
1481
+ // settlement so `Selection.debt` reads them in cause-then-effect
1482
+ // order.
1483
+ const ready = graph.ready()
1484
+ const postponed = ready.filter((node) => deferrals.has(node.id))
1485
+ for (const node of postponed) {
1486
+ const debt = deferrals.get(node.id)!
1487
+ yield* emit(JournalRecords.selectionDeferred(source(`node/${node.id}/selection-deferred`), {
1488
+ planId: plan.planId,
1489
+ nodeId: node.id,
1490
+ planKey: node.key,
1491
+ edge: debt.edge,
1492
+ likelihood: debt.likelihood
1493
+ }))
1494
+ yield* settle(node, "deferred")
1495
+ }
1496
+ if (postponed.length > 0) continue
1497
+ return ready
1498
+ }
1499
+ })
1500
+
1501
+ const dispatchStart = (node: Plan.PlanNode): DispatchStart => {
1502
+ const state = stateOf(node)
1503
+ const settledResults = new Map<string, unknown>()
1504
+ const inputs: Array<ResolvedInput> = []
1505
+ for (const input of node.material.inputs) {
1506
+ if (input._tag !== "Ref") continue
1507
+ const value = results.get(input.from)
1508
+ settledResults.set(input.from, value)
1509
+ // Admission proved every dependency settled successfully; `settle`
1510
+ // records even an `undefined` result in the map, so every material
1511
+ // Ref has a value to project here.
1512
+ inputs.push({ from: input.from, path: input.path, value: StepKey.project(value, input.path) })
1513
+ }
1514
+ return {
1515
+ dispatchKey: state.dispatchKey,
1516
+ attempts: state.attempts,
1517
+ rebases: state.rebases,
1518
+ waited: state.waited,
1519
+ results: settledResults,
1520
+ inputs
1521
+ }
1522
+ }
1523
+
1524
+ let admissions = 0
1525
+ yield* Effect.scoped(
1526
+ Effect.gen(function*() {
1527
+ let inFlightSteps = 0
1528
+ let inFlightAgents = 0
1529
+ const pendingMerges: Array<PlanMergeStore.Intent> = recoveredMerges
1530
+ .filter((decision) => decision.completion === undefined).map((decision) => decision.intent)
1531
+
1532
+ const appendReadyMerges = Effect.gen(function*() {
1533
+ for (let index = 0; index < pendingMerges.length;) {
1534
+ const pending = pendingMerges[index]!
1535
+ if (pending.peers.some((peer) => states.get(peer)?.status !== "settled")) {
1536
+ index = index + 1
1537
+ continue
1538
+ }
1539
+ yield* appendMerge(pending)
1540
+ pendingMerges.splice(index, 1)
1541
+ }
1542
+ })
1543
+
1544
+ // Passive failure/selection propagation can settle the last peer of
1545
+ // a recovered intent without a dispatch event. Recheck merges until
1546
+ // neither propagation nor an append exposes more scheduling work.
1547
+ const advance = Effect.gen(function*() {
1548
+ while (true) {
1549
+ const ready = yield* passiveSettlements
1550
+ const before = plan.generation
1551
+ yield* appendReadyMerges
1552
+ if (plan.generation === before) return ready
1553
+ }
1554
+ })
1555
+
1556
+ const admit = (ready: ReadonlyArray<Plan.PlanNode>) =>
1557
+ Effect.gen(function*() {
1558
+ const decision = scheduling.admit(
1559
+ graph.candidates(ready),
1560
+ { steps: inFlightSteps, agents: inFlightAgents }
1561
+ )
1562
+ const admitted = decision.admitted.map(({ node }) => node)
1563
+ // Event-driven aging: this admission pass considered every ready
1564
+ // node, and only capacity can leave one behind.
1565
+ for (const { node, waited } of decision.deferred) {
1566
+ stateOf(node).waited = waited
1567
+ }
1568
+ if (admitted.length > 0) {
1569
+ admissions = admissions + 1
1570
+ yield* Effect.annotateCurrentSpan({ admissions })
1571
+ yield* Metric.update(EngineStoreMetrics.schedulerAdmissions, 1)
1572
+ yield* Effect.logDebug("scheduler admission launched", {
1573
+ admission: admissions,
1574
+ ready: ready.length,
1575
+ admitted: admitted.length,
1576
+ agents: decision.agents,
1577
+ inFlightSteps,
1578
+ inFlightAgents
1579
+ })
1580
+ }
1581
+ const launches = admitted.map((node) => {
1582
+ const start = dispatchStart(node)
1583
+ graph.start(node.id)
1584
+ inFlightSteps = inFlightSteps + 1
1585
+ if (node.kind === "agent") inFlightAgents = inFlightAgents + 1
1586
+ return { node, start }
1587
+ })
1588
+ for (const { node, start } of launches) {
1589
+ yield* dispatch(node, start).pipe(
1590
+ Effect.trackDuration(EngineStoreMetrics.schedulerDispatchDuration),
1591
+ Effect.exit,
1592
+ Effect.flatMap((exit) => Queue.offer(events, { _tag: "DispatchSettled", node, exit })),
1593
+ Effect.asVoid,
1594
+ Effect.forkScoped({ startImmediately: true })
1595
+ )
1596
+ }
1597
+ })
1598
+
1599
+ // Repair a settlement missing after a crash without appending the
1600
+ // same stopped decision again when its original record survived.
1601
+ for (const { intent } of recoveredMerges) yield* emitSettlement(nodesById.get(intent.nodeId)!, 0)
1602
+ yield* Effect.flatMap(advance, admit)
1603
+ while (graph.remaining > 0) {
1604
+ // Compiled plans are acyclic, inferred edges refuse cycle closure,
1605
+ // and discovered edges point only from undispatched nodes to the
1606
+ // already-settled node whose verdict added them, so pending work
1607
+ // with nothing in flight cannot have an empty ready set. If an
1608
+ // invariant breaks anyway, fail loudly: returning here would
1609
+ // report unsettled nodes with their initial outcome as success.
1610
+ /* v8 ignore next 7 -- unreachable while the invariants above hold */
1611
+ if (inFlightSteps === 0) {
1612
+ return yield* Effect.die(
1613
+ new Error(
1614
+ `PlanScheduler invariant broken: ${graph.remaining} nodes are pending and nothing is in flight`
1615
+ )
1616
+ )
1617
+ }
1618
+ const event = yield* nextSettlement
1619
+ if (Exit.isFailure(event.exit)) return yield* Effect.failCause(event.exit.cause)
1620
+
1621
+ const node = event.node
1622
+ const result = event.exit.value
1623
+ const state = stateOf(node)
1624
+ inFlightSteps = inFlightSteps - 1
1625
+ if (node.kind === "agent") inFlightAgents = inFlightAgents - 1
1626
+ state.dispatchKey = result.dispatchKey
1627
+ state.attempts = result.attempts
1628
+ state.rebases = result.rebases
1629
+
1630
+ switch (result.outcome) {
1631
+ case "built":
1632
+ case "clean":
1633
+ results.set(node.id, result.result)
1634
+ markSettled(node, result.outcome)
1635
+ break
1636
+ case "failed":
1637
+ markSettled(node, "failed")
1638
+ break
1639
+ case "conflicted": {
1640
+ if (result.strategy === "stop-merge") {
1641
+ const intent = yield* atomically(
1642
+ "could not persist the stopped-attempt decision",
1643
+ Effect.gen(function*() {
1644
+ const intent = yield* mergeStore.intend(mergeIdentity, {
1645
+ version: 1,
1646
+ nodeId: node.id,
1647
+ nodeKey: node.key,
1648
+ dispatchKey: state.dispatchKey,
1649
+ attempts: state.attempts,
1650
+ rebases: state.rebases,
1651
+ peers: node.conflicts.map((conflict) => conflict.with)
1652
+ .filter((peer) => states.get(peer)?.status === "running" || results.has(peer))
1653
+ }, options.owner).pipe(Effect.catch(mergeFailure))
1654
+ yield* PlanMerges.validateIntent(plan, intent).pipe(Effect.catch(mergeFailure))
1655
+ return intent
1656
+ })
1657
+ )
1658
+ markSettled(node, "skipped")
1659
+ // The former dispatch barrier guaranteed that every
1660
+ // conflicting sibling admitted beside this node had reported
1661
+ // its result before `appendMerge` selected winners. Preserve
1662
+ // that semantic boundary only for those already-running
1663
+ // peers; unrelated work never holds the merge cone.
1664
+ state.dispatchKey = intent.dispatchKey
1665
+ state.attempts = intent.attempts
1666
+ state.rebases = intent.rebases
1667
+ pendingMerges.push(intent)
1668
+ break
1669
+ }
1670
+ const verdict = yield* reconciler.onConflict({
1671
+ nodeId: node.id,
1672
+ keyDigest: state.dispatchKey,
1673
+ attempt: state.attempts,
1674
+ rebases: state.rebases,
1675
+ strategy: result.strategy,
1676
+ conflictsWith: node.conflicts.map((conflict) => conflict.with)
1677
+ })
1678
+ markSettled(node, "failed")
1679
+ yield* applyVerdict(node.id, verdict, "materialization-conflict")
1680
+ break
1681
+ }
1682
+ }
1683
+
1684
+ yield* appendReadyMerges
1685
+
1686
+ // The dispatch durably journalled every deviation before its event.
1687
+ // Apply those verdicts before consulting downstream readiness.
1688
+ yield* drainDeviations
1689
+ // A dispatch outcome is provisional until its deviations have been
1690
+ // reconciled. Persist exactly one terminal fact, after a Fail
1691
+ // verdict has had the chance to replace built with failed.
1692
+ yield* emitSettlement(node)
1693
+ graph.reconcile()
1694
+ // Process terminal events already observed by the journal drain
1695
+ // before opening more permits. This preserves fail-fast behavior for
1696
+ // an already-failed sibling while still admitting immediately when
1697
+ // unrelated work is genuinely still in flight.
1698
+ const ready = yield* advance
1699
+ if (pendingSettlements.length === 0) yield* admit(ready)
1700
+ }
1701
+ }).pipe(Effect.onError((cause) =>
1702
+ Effect.sync(() => {
1703
+ abortedForFailure = cause.reasons.some((reason) => reason._tag === "Fail")
1704
+ })
1705
+ ))
1706
+ ).pipe(Effect.catch((error) =>
1707
+ Effect.fail(
1708
+ cleanupErrors.length === 0 ? error : new SchedulerError({
1709
+ code: error.code,
1710
+ message: error.message,
1711
+ cause: error.cause,
1712
+ cleanupErrors
1713
+ })
1714
+ )
1715
+ ))
1716
+
1717
+ const settlements = plan.nodes.map((node) => {
1718
+ const state = stateOf(node)
1719
+ return {
1720
+ nodeId: node.id,
1721
+ planKey: node.key,
1722
+ dispatchKey: state.dispatchKey,
1723
+ outcome: state.outcome,
1724
+ attempts: state.attempts,
1725
+ rebases: state.rebases
1726
+ }
1727
+ })
1728
+ yield* Effect.logDebug("scheduler run completed", {
1729
+ admissions,
1730
+ built: settlements.filter((settlement) => settlement.outcome === "built").length,
1731
+ clean: settlements.filter((settlement) => settlement.outcome === "clean").length,
1732
+ failed: settlements.filter((settlement) => settlement.outcome === "failed").length,
1733
+ skipped: settlements.filter((settlement) => settlement.outcome === "skipped").length,
1734
+ deferred: settlements.filter((settlement) => settlement.outcome === "deferred").length
1735
+ })
1736
+ return {
1737
+ planId: plan.planId,
1738
+ digest: plan.digest,
1739
+ settlements,
1740
+ results: Object.fromEntries(results),
1741
+ verdicts,
1742
+ appended
1743
+ }
1744
+ }).pipe(
1745
+ Effect.annotateSpans({ runId: options.runId }),
1746
+ Effect.annotateLogs({ runId: options.runId }),
1747
+ Effect.onExit((exit) => Effect.annotateCurrentSpan({ outcome: EngineStoreMetrics.exitTag(exit).toLowerCase() }))
1748
+ )
1749
+ )
1750
+
1751
+ return { record, append, run }
1752
+ }
1753
+
1754
+ /**
1755
+ * Provides a scheduler bound to one run.
1756
+ *
1757
+ * @since 0.1.0
1758
+ * @category layers
1759
+ */
1760
+ export const layer = (options: Options): Layer.Layer<PlanScheduler> => Layer.succeed(PlanScheduler, make(options))
1761
+
1762
+ /**
1763
+ * What {@link recertify} returns: the repaying run's id, its report, and the
1764
+ * deferring run's remaining debt after the repayment is counted.
1765
+ *
1766
+ * @since 0.1.0
1767
+ * @category models
1768
+ */
1769
+ export interface RecertifyResult {
1770
+ readonly runId: string
1771
+ readonly report: Report
1772
+ readonly remaining: ReadonlyArray<Selection.DebtEntry>
1773
+ }
1774
+
1775
+ /**
1776
+ * The recertification driver: re-drives a compiled plan under a fresh,
1777
+ * caller-supplied run with the `full` selection override — guess-free by
1778
+ * construction — then reports the deferring run's remaining debt via
1779
+ * `Selection.debt(deferringRunId, { repaidBy: [options.runId] })`.
1780
+ *
1781
+ * The design draft placed this beside the other Selection helpers; it lives
1782
+ * here because it constructs a scheduler, and `Selection` must stay
1783
+ * type-only toward this module to keep the dependency graph acyclic. The
1784
+ * deferring run's journal is never written — repayment is a read-side join.
1785
+ * Scheduling the cadence (nightly, per-merge) stays a product concern; this
1786
+ * is only the primitive one pass runs.
1787
+ *
1788
+ * @since 0.1.0
1789
+ * @category combinators
1790
+ */
1791
+ export const recertify = Effect.fn("PlanScheduler.recertify")(
1792
+ (input: {
1793
+ readonly plan: Plan.Plan
1794
+ readonly deferringRunId: string
1795
+ readonly options: Options
1796
+ }) =>
1797
+ Effect.gen(function*() {
1798
+ const scheduler = make({
1799
+ ...input.options,
1800
+ selection: { ...input.options.selection, full: true }
1801
+ })
1802
+ const report = yield* scheduler.run(input.plan)
1803
+ const remaining = yield* Selection.debt(input.deferringRunId, { repaidBy: [input.options.runId] })
1804
+ return { runId: input.options.runId, report, remaining } satisfies RecertifyResult
1805
+ })
1806
+ )