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

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 +201 -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 +1586 -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 +300 -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 +3154 -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,1604 @@
1
+ /**
2
+ * Functional workspace transactions for action execution.
3
+ *
4
+ * An action body normally mutates the host tree in place, which leaves the
5
+ * engine two bad options: trust the declaration, or diff the whole repository
6
+ * after the fact. This module takes the third one. The body runs against an
7
+ * **isolated workspace** seeded with exactly its declared read set; its writes
8
+ * are a value — a whole-tree diff — that reaches the host only through an
9
+ * explicit {@link Service.materialize} step with preconditions and rollback.
10
+ *
11
+ * Two consequences follow, and they are the reason this exists:
12
+ *
13
+ * - **Whole-tree write observation is structural.** The transaction *is* the
14
+ * tree, so "did this body write outside its declared write set" is a map
15
+ * comparison, not an inference. `StepBoundary`'s filesystem layer can only
16
+ * re-measure paths it was told about, which is why it never claims
17
+ * `wholeTreeWritesVerified` and why nothing it settled could enter the
18
+ * cross-run cache.
19
+ * - **The host is untouched until copy-back.** An execution whose observations
20
+ * contradict its declaration is {@link Invalidated}, and an invalidated
21
+ * result exposes provenance and violations *only* — never the candidate
22
+ * output, files, or queued effects.
23
+ *
24
+ * This is a **deterministic transaction model, not a security boundary** — the
25
+ * same caveat the proof of concept carried. A body that reaches the host
26
+ * through a service this module does not seed (a spawned native process, an
27
+ * undecorated socket) is outside the transaction. Actually denying that
28
+ * ambient access requires a machine boundary such as the providers in
29
+ * `packages/smithers/flows/sandbox/docs/api.md`. The one host
30
+ * write this module *does* perform is confined, however: copy-back resolves
31
+ * symlinks before it lands a byte and refuses any target whose canonical
32
+ * location escapes the workspace root.
33
+ *
34
+ * Settled bundles reach the host without a human review gate. Governing
35
+ * design: `docs/concepts/workspace-transactions.md`.
36
+ *
37
+ * @since 0.1.0
38
+ */
39
+
40
+ import * as ArtifactStore from "@smthrs/artifacts/ArtifactStore"
41
+ import * as FileLease from "@smthrs/artifacts/FileLease"
42
+ import { Sha256 } from "@smthrs/crypto"
43
+ import type { FileBoundary } from "@smthrs/flow/FileBoundary"
44
+ import * as KernelFileSystem from "@smthrs/kernel/FileSystem"
45
+ import { Workspace as KernelWorkspace } from "@smthrs/kernel/Workspace"
46
+ import { DerivedKey } from "@smthrs/keys"
47
+ import * as FileSet from "@smthrs/plan/FileSet"
48
+ import * as Cause from "effect/Cause"
49
+ import * as Context from "effect/Context"
50
+ import type * as Crypto from "effect/Crypto"
51
+ import * as Effect from "effect/Effect"
52
+ import * as Exit from "effect/Exit"
53
+ import * as FileSystem from "effect/FileSystem"
54
+ import * as Layer from "effect/Layer"
55
+ import * as Metric from "effect/Metric"
56
+ import * as Path from "effect/Path"
57
+ import * as PlatformError from "effect/PlatformError"
58
+ import * as RcMap from "effect/RcMap"
59
+ import * as Ref from "effect/Ref"
60
+ import * as Result from "effect/Result"
61
+ import * as Schema from "effect/Schema"
62
+ import * as Scope from "effect/Scope"
63
+ import * as Semaphore from "effect/Semaphore"
64
+ import * as EngineStoreMetrics from "./EngineStoreMetrics.ts"
65
+ import * as FileBoundarySnapshot from "./internal/FileBoundarySnapshot.ts"
66
+ import * as FileEnumeration from "./internal/FileEnumeration.ts"
67
+ import { compareText } from "./internal/Ordering.ts"
68
+
69
+ /**
70
+ * A protocol-neutral resource named in execution provenance.
71
+ *
72
+ * `kind` leaves room for later database, object-store, or network resources;
73
+ * this implementation records filesystem resources with `kind: "file"`.
74
+ *
75
+ * @category models
76
+ * @since 0.1.0
77
+ */
78
+ export interface Resource {
79
+ readonly kind: string
80
+ readonly id: string
81
+ }
82
+
83
+ /**
84
+ * An input value actually consulted by an execution.
85
+ *
86
+ * @category models
87
+ * @since 0.1.0
88
+ */
89
+ export interface InputObservation {
90
+ readonly resource: Resource
91
+ readonly digest: string
92
+ }
93
+
94
+ /**
95
+ * An output value actually produced by an execution.
96
+ *
97
+ * @category models
98
+ * @since 0.1.0
99
+ */
100
+ export interface OutputObservation {
101
+ readonly resource: Resource
102
+ readonly operation: "write" | "remove"
103
+ readonly digest: string | undefined
104
+ }
105
+
106
+ /**
107
+ * Observed data dependencies and products of one isolated execution.
108
+ *
109
+ * @category models
110
+ * @since 0.1.0
111
+ */
112
+ export interface Provenance {
113
+ readonly baseRevision: string
114
+ readonly inputs: ReadonlyArray<InputObservation>
115
+ readonly outputs: ReadonlyArray<OutputObservation>
116
+ }
117
+
118
+ /**
119
+ * One functional filesystem transformation.
120
+ *
121
+ * `beforeDigest` is a materialization precondition, never a hint: copy-back is
122
+ * a compare-and-set against it. `afterDigest` is absent for a removal;
123
+ * otherwise it addresses the file's complete post-state. `after` carries those
124
+ * bytes inline when they fit the host's inline bound, and is absent when they
125
+ * were spilled to the content-addressed artifact store under `afterDigest`.
126
+ *
127
+ * @category models
128
+ * @since 0.1.0
129
+ */
130
+ export interface FileChange {
131
+ readonly path: string
132
+ readonly beforeDigest: string | undefined
133
+ readonly afterDigest: string | undefined
134
+ readonly after?: Uint8Array | undefined
135
+ }
136
+
137
+ /**
138
+ * An external effect requested by an execution but deliberately **not**
139
+ * dispatched inside its speculative transaction.
140
+ *
141
+ * A sandbox that dispatched these would have already sent the message when its
142
+ * result turned out to be invalid, or would send it twice on a conflict retry.
143
+ * Delivery and idempotency belong to a dispatch stage that runs after
144
+ * copy-back settles; materializing file changes dispatches nothing.
145
+ *
146
+ * @category models
147
+ * @since 0.1.0
148
+ */
149
+ export interface QueuedEffect {
150
+ readonly protocol: string
151
+ readonly idempotencyKey: string
152
+ readonly payload: unknown
153
+ }
154
+
155
+ /**
156
+ * The generic functional result of one isolated execution.
157
+ *
158
+ * `output` is the body's ordinary return value and stays schema-typed — the
159
+ * engine persists it as the action's recorded result, so it must be
160
+ * JSON-encodable. It is deliberately not bytes: file products travel in
161
+ * {@link FileChange}, which is content-addressed and can spill to the artifact
162
+ * store, and folding them into the output would defeat both.
163
+ *
164
+ * @category models
165
+ * @since 0.1.0
166
+ */
167
+ export interface WorkflowResult<out Output = unknown> {
168
+ readonly output: Output
169
+ readonly files: ReadonlyArray<FileChange>
170
+ readonly provenance: Provenance
171
+ readonly effects: ReadonlyArray<QueuedEffect>
172
+ }
173
+
174
+ /**
175
+ * A filesystem and effect-outbox view scoped to one workspace transaction.
176
+ *
177
+ * A body receives this service only while executing inside
178
+ * {@link Service.execute}; every call mutates the isolated transaction and
179
+ * never the host. It sits beside — and over the same transaction as — the
180
+ * re-rooted Effect `FileSystem` the sandbox also seeds, so a body written
181
+ * against either surface is isolated.
182
+ *
183
+ * @category services
184
+ * @since 0.1.0
185
+ */
186
+ export interface Workspace {
187
+ readonly readFile: (path: string) => Effect.Effect<Uint8Array, WorkspaceError>
188
+ readonly writeFile: (path: string, content: Uint8Array) => Effect.Effect<void, WorkspaceError>
189
+ readonly removeFile: (path: string) => Effect.Effect<void, WorkspaceError>
190
+ readonly queueEffect: (effect: QueuedEffect) => Effect.Effect<void>
191
+ }
192
+
193
+ /**
194
+ * Context service made available only inside a workspace transaction.
195
+ *
196
+ * @category services
197
+ * @since 0.1.0
198
+ */
199
+ export const Workspace: Context.Service<Workspace, Workspace> = Context.Service<Workspace>(
200
+ "@smthrs/engine-store/WorkspaceSandbox/Workspace"
201
+ )
202
+
203
+ /**
204
+ * The declaration and body submitted to a workspace sandbox.
205
+ *
206
+ * `descriptor` is the same {@link FileBoundary} the step key was derived from
207
+ * and `StepBoundary` prepares against — an optimistic prediction the sandbox
208
+ * validates against what the body actually did. The proof of concept carried
209
+ * `Effects.Declaration` plus `KeyMaterial` here; on this side the declaration
210
+ * vocabulary is `FileBoundary` and step identity is already the engine's, so
211
+ * `cacheKey` is only an opaque memo address the caller may omit.
212
+ *
213
+ * @category models
214
+ * @since 0.1.0
215
+ */
216
+ export interface Execution<out Output, out Error> {
217
+ readonly descriptor: FileBoundary
218
+ readonly cacheKey?: string | undefined
219
+ readonly workflow: Effect.Effect<Output, Error, Workspace | FileSystem.FileSystem>
220
+ }
221
+
222
+ /**
223
+ * Why observed execution contradicted its declaration.
224
+ *
225
+ * @category models
226
+ * @since 0.1.0
227
+ */
228
+ export interface DeclarationViolation {
229
+ readonly kind: "undeclared-read" | "undeclared-write"
230
+ readonly resource: Resource
231
+ }
232
+
233
+ /**
234
+ * Memo outcome of an execution.
235
+ *
236
+ * The memo is a **run-local accelerator**, not the cross-run cache: the engine
237
+ * owns that through `CacheStore` and the step key. `disabled` is what a caller
238
+ * that supplied no `cacheKey` gets, and is the engine's ordinary case.
239
+ *
240
+ * A `hit` hands back the same {@link WorkflowResult} the miss produced rather
241
+ * than a copy of it — the proof of concept deep-cloned, but cloning the output
242
+ * needs `structuredClone`, and this port's output is not constrained to
243
+ * `Schema.Json`. The transaction itself is still isolated (its reads and writes
244
+ * copy bytes in both directions); what a caller must not do is mutate a
245
+ * returned result in place and then ask for it again.
246
+ *
247
+ * @category models
248
+ * @since 0.1.0
249
+ */
250
+ export type CacheOutcome =
251
+ | { readonly status: "disabled" }
252
+ | { readonly status: "miss"; readonly key: string }
253
+ | { readonly status: "hit"; readonly key: string }
254
+
255
+ /**
256
+ * An execution whose observations matched its declaration.
257
+ *
258
+ * Only this variant is accepted by {@link Service.materialize}, so invalidated
259
+ * speculative changes cannot be applied by accident.
260
+ *
261
+ * @category models
262
+ * @since 0.1.0
263
+ */
264
+ export interface Accepted<out Output = unknown> {
265
+ readonly _tag: "Accepted"
266
+ readonly result: WorkflowResult<Output>
267
+ readonly cache: CacheOutcome
268
+ /**
269
+ * Deviations the declaration did not predict. Empty in hard mode — that
270
+ * execution is {@link Invalidated} instead — and, in expected mode, the
271
+ * whole-tree evidence behind the deviation the engine journals.
272
+ *
273
+ * The proof of concept had no expected mode and so had no field here. It is
274
+ * carried on the result rather than recomputed by the caller because only
275
+ * the sandbox knows the root the declaration and the observations have to be
276
+ * compared in.
277
+ */
278
+ readonly violations: ReadonlyArray<DeclarationViolation>
279
+ }
280
+
281
+ /**
282
+ * An execution discarded because its observations contradicted its
283
+ * declaration.
284
+ *
285
+ * The candidate output, file changes, and queued effects are intentionally
286
+ * absent from this shape — there is no accessor that leaks them. Provenance
287
+ * and violations remain so the engine can journal an honest reason and, in
288
+ * expected mode, re-plan.
289
+ *
290
+ * @category models
291
+ * @since 0.1.0
292
+ */
293
+ export interface Invalidated {
294
+ readonly _tag: "Invalidated"
295
+ readonly provenance: Provenance
296
+ readonly violations: ReadonlyArray<DeclarationViolation>
297
+ }
298
+
299
+ /**
300
+ * The result of validating one speculative execution.
301
+ *
302
+ * @category models
303
+ * @since 0.1.0
304
+ */
305
+ export type ExecutionResult<Output = unknown> = Accepted<Output> | Invalidated
306
+
307
+ /**
308
+ * Stable workspace sandbox failure codes.
309
+ *
310
+ * @category schemas
311
+ * @since 0.1.0
312
+ */
313
+ export const WorkspaceErrorCode = Schema.Literals([
314
+ "invalid_path",
315
+ "not_found",
316
+ "host_unavailable",
317
+ "path_escapes_workspace",
318
+ "commit_lock_timeout"
319
+ ])
320
+
321
+ /**
322
+ * Stable workspace sandbox failure codes.
323
+ *
324
+ * @category models
325
+ * @since 0.1.0
326
+ */
327
+ export type WorkspaceErrorCode = typeof WorkspaceErrorCode.Type
328
+
329
+ /**
330
+ * A body could not execute inside — or its result could not be moved through —
331
+ * an isolated workspace.
332
+ *
333
+ * `cause` carries the refusing host or artifact-store failure whole, so a
334
+ * debugger sees the original error instead of a flattened message
335
+ * (`PlatformError`'s own convention).
336
+ *
337
+ * @category errors
338
+ * @since 0.1.0
339
+ */
340
+ export class WorkspaceError extends Schema.TaggedError<WorkspaceError>()(
341
+ "@smthrs/engine-store/WorkspaceError",
342
+ {
343
+ code: WorkspaceErrorCode,
344
+ message: Schema.String,
345
+ cause: Schema.optional(Schema.Defect())
346
+ }
347
+ ) {}
348
+
349
+ /**
350
+ * Copy-back would overwrite host state that changed since the transaction's
351
+ * base snapshot was taken.
352
+ *
353
+ * The engine answers this by retrying the attempt from a fresh base a bounded
354
+ * number of times. This local retry is rebase-shaped. The worktree-lane
355
+ * surface was removed; this error does not recreate it.
356
+ *
357
+ * @category errors
358
+ * @since 0.1.0
359
+ */
360
+ export class MaterializationConflict extends Schema.TaggedError<MaterializationConflict>()(
361
+ "@smthrs/engine-store/MaterializationConflict",
362
+ {
363
+ paths: Schema.Array(
364
+ Schema.NonEmptyString.check(Schema.isMaxLength(4_096))
365
+ ).check(Schema.isMaxLength(1_024)),
366
+ message: Schema.NonEmptyString.check(Schema.isMaxLength(16_384))
367
+ }
368
+ ) {}
369
+
370
+ /**
371
+ * Whether a failure is a materialization conflict in live or durable form.
372
+ * Persisted failures retain the schema tag but not the class prototype, so
373
+ * replay classification must recognize both representations.
374
+ *
375
+ * @category guards
376
+ * @since 0.1.0
377
+ */
378
+ export const isMaterializationConflict = (error: unknown): boolean => {
379
+ if (typeof error !== "object" || error === null) return false
380
+ try {
381
+ if (error instanceof MaterializationConflict) return true
382
+ const own = (name: string): unknown => {
383
+ const descriptor = Object.getOwnPropertyDescriptor(error, name)
384
+ return descriptor !== undefined && descriptor.enumerable && "value" in descriptor
385
+ ? descriptor.value
386
+ : undefined
387
+ }
388
+ const paths = own("paths")
389
+ const message = own("message")
390
+ if (
391
+ own("_tag") !== MaterializationConflict.identifier ||
392
+ typeof message !== "string" ||
393
+ message.length === 0 ||
394
+ message.length > 16_384 ||
395
+ !Array.isArray(paths) ||
396
+ Object.getPrototypeOf(paths) !== Array.prototype ||
397
+ paths.length > 1_024
398
+ ) return false
399
+ for (let index = 0; index < paths.length; index++) {
400
+ const descriptor = Object.getOwnPropertyDescriptor(paths, String(index))
401
+ if (
402
+ descriptor === undefined ||
403
+ !("value" in descriptor) ||
404
+ !descriptor.enumerable ||
405
+ typeof descriptor.value !== "string" ||
406
+ descriptor.value.length === 0 ||
407
+ descriptor.value.length > 4_096
408
+ ) return false
409
+ }
410
+ if (
411
+ Reflect.ownKeys(paths).some((key) =>
412
+ key !== "length" &&
413
+ (typeof key !== "string" || !/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= paths.length)
414
+ )
415
+ ) return false
416
+ return Reflect.ownKeys(error).every((key) => key === "_tag" || key === "paths" || key === "message")
417
+ } catch {
418
+ return false
419
+ }
420
+ }
421
+
422
+ /**
423
+ * The two-phase workspace transaction service.
424
+ *
425
+ * `execute` is speculative and never touches the host. `materialize` is the
426
+ * only host write. See its filesystem failure limits below.
427
+ *
428
+ * @category models
429
+ * @since 0.1.0
430
+ */
431
+ export interface Service {
432
+ readonly execute: <Output, Error>(
433
+ execution: Execution<Output, Error>
434
+ ) => Effect.Effect<ExecutionResult<Output>, Error | WorkspaceError, Crypto.Crypto>
435
+ /**
436
+ * Checks all copy-back preconditions before applying any file change.
437
+ * The filesystem host serializes cooperating commits through confinement,
438
+ * preflight, apply, and rollback. On apply failure it attempts restoration
439
+ * from an in-memory pre-image journal; this is not crash-atomic. A failed
440
+ * rollback returns a compound cause containing both the apply failure and
441
+ * a `WorkspaceError` carrying the rollback cause. The caller must reconcile
442
+ * host files after a crash or failed rollback before resuming work.
443
+ *
444
+ * Filesystem coordination reserves `.smithers-workspace-lock` under the
445
+ * root, and copy-back refuses the `.flows` engine state directory plus any
446
+ * `FileSystemOptions.reservedPaths`, whatever the write set or mode. The
447
+ * lock is an `@smthrs/artifacts/FileLease`: a killed holder's lock is
448
+ * reclaimed once its heartbeat is 60 seconds old, and a commit that cannot
449
+ * take it within 2 minutes fails with `commit_lock_timeout` before changing
450
+ * anything. Writers that ignore the lock are outside this guarantee.
451
+ */
452
+ readonly materialize: <Output>(
453
+ accepted: Accepted<Output>
454
+ ) => Effect.Effect<void, MaterializationConflict | WorkspaceError, Crypto.Crypto>
455
+ }
456
+
457
+ /**
458
+ * Context service for the selected workspace sandbox implementation.
459
+ *
460
+ * @category services
461
+ * @since 0.1.0
462
+ */
463
+ export const WorkspaceSandbox: Context.Service<Service, Service> = Context.Service<Service>(
464
+ "@smthrs/engine-store/WorkspaceSandbox"
465
+ )
466
+
467
+ /**
468
+ * Constructs a workspace sandbox from an implementation.
469
+ *
470
+ * @category constructors
471
+ * @since 0.1.0
472
+ */
473
+ export const make = (service: Service): Service => WorkspaceSandbox.of(service)
474
+
475
+ /**
476
+ * Provides a workspace sandbox implementation.
477
+ *
478
+ * @category layers
479
+ * @since 0.1.0
480
+ */
481
+ export const layer = (service: Service): Layer.Layer<Service> => Layer.succeed(WorkspaceSandbox, make(service))
482
+
483
+ /**
484
+ * Delivery for the effects a transaction queued but refused to send.
485
+ *
486
+ * The engine runs this **after** copy-back settles, never inside the
487
+ * transaction: an effect dispatched speculatively has already reached the
488
+ * world when its execution turns out to be invalid, and has reached it twice
489
+ * when copy-back loses a race and the body re-runs. Deduplication is by
490
+ * {@link QueuedEffect.idempotencyKey}, which is the same key
491
+ * `docs/concepts/workspace-transactions.md` requires before an irreversible
492
+ * effect may be retried at all.
493
+ *
494
+ * @category services
495
+ * @since 0.1.0
496
+ */
497
+ export interface Dispatcher {
498
+ readonly dispatch: (effect: QueuedEffect) => Effect.Effect<void>
499
+ }
500
+
501
+ /**
502
+ * Context service for the queued-effect dispatch stage. Optional: with no
503
+ * dispatcher provided the engine journals what a transaction queued and sends
504
+ * nothing.
505
+ *
506
+ * @category services
507
+ * @since 0.1.0
508
+ */
509
+ export const EffectDispatcher: Context.Service<Dispatcher, Dispatcher> = Context.Service<Dispatcher>(
510
+ "@smthrs/engine-store/WorkspaceSandbox/EffectDispatcher"
511
+ )
512
+
513
+ /**
514
+ * Provides a queued-effect dispatch stage.
515
+ *
516
+ * @category layers
517
+ * @since 0.1.0
518
+ */
519
+ export const layerDispatcher = (dispatcher: Dispatcher): Layer.Layer<Dispatcher> =>
520
+ Layer.succeed(EffectDispatcher, EffectDispatcher.of(dispatcher))
521
+
522
+ // -----------------------------------------------------------------------------
523
+ // path vocabulary
524
+ // -----------------------------------------------------------------------------
525
+
526
+ const decoder = new TextDecoder()
527
+ const encoder = new TextEncoder()
528
+
529
+ const invalidPath = (path: string): WorkspaceError =>
530
+ new WorkspaceError({
531
+ code: "invalid_path",
532
+ message: `Workspace paths must be relative paths inside the workspace without '..': ${path}`
533
+ })
534
+
535
+ /**
536
+ * Normalizes a caller path to a workspace-relative key.
537
+ *
538
+ * Absolute paths are accepted only under `root`, which is how a body that
539
+ * resolved `workspace.root` itself still lands inside the transaction; every
540
+ * other absolute path, and anything traversing upward, is refused rather than
541
+ * silently confined.
542
+ */
543
+ const normalizePath = (root: string, path: string, allowRoot = false): Result.Result<string, WorkspaceError> => {
544
+ const slashed = path.replaceAll("\\", "/")
545
+ const slashedRoot = root.replaceAll("\\", "/")
546
+ const rooted = slashed === slashedRoot
547
+ ? ""
548
+ : slashedRoot !== "" && slashed.startsWith(`${slashedRoot}/`)
549
+ ? slashed.slice(slashedRoot.length + 1)
550
+ : slashed
551
+ const parts = rooted.split("/")
552
+ if (rooted.startsWith("/") || parts.some((part) => part === "..")) return Result.fail(invalidPath(path))
553
+ const compact = parts.filter((part) => part !== "" && part !== ".").join("/")
554
+ return compact === "" && !allowRoot ? Result.fail(invalidPath(path)) : Result.succeed(compact)
555
+ }
556
+
557
+ /**
558
+ * Whether a declared write-set entry covers an observed path. A plain string
559
+ * is a literal path, even when it contains `*` or `**`, exactly as
560
+ * `StepBoundary` captures and `FileSet.overlaps` compares it; wildcard
561
+ * declarations are written as a Glob.
562
+ */
563
+ const covers = (entry: FileSet.Entry, path: string): boolean =>
564
+ typeof entry === "string"
565
+ ? entry === path
566
+ : entry._tag === "TreeArtifact"
567
+ ? path === entry.path || path.startsWith(`${entry.path}/`)
568
+ : FileSet.matchesGlob(entry, path)
569
+
570
+ const resource = (path: string): Resource => ({ kind: "file", id: path })
571
+
572
+ /**
573
+ * Restates a declaration in the transaction's own path vocabulary.
574
+ *
575
+ * A caller may declare `out/result.txt` or the absolute path the same file has
576
+ * on the host — the kernel-decorated `FileSystem` accepts both, so the
577
+ * declaration does too. Observed paths are always workspace-relative, so the
578
+ * declaration has to be brought to the same footing before anything is
579
+ * compared against it, or an absolute declaration would cover nothing and
580
+ * every write would read as undeclared. An entry that cannot be named inside
581
+ * the workspace is kept verbatim: it covers nothing, which is the honest
582
+ * answer for a declaration pointing outside the tree.
583
+ */
584
+ const workspaceRelative = (root: string, descriptor: FileBoundary): FileBoundary => {
585
+ const relative = (path: string): string => {
586
+ const normalized = normalizePath(root, path)
587
+ return Result.isFailure(normalized) ? path : normalized.success
588
+ }
589
+ return {
590
+ readSet: descriptor.readSet.map((entry) =>
591
+ FileSet.isGlob(entry)
592
+ ? {
593
+ ...entry,
594
+ include: [relative(entry.include[0]), ...entry.include.slice(1).map(relative)],
595
+ ...(entry.exclude === undefined ? {} : { exclude: entry.exclude.map(relative) })
596
+ }
597
+ : { ...entry, path: relative(entry.path) }
598
+ ),
599
+ writeSet: descriptor.writeSet.map((entry) =>
600
+ typeof entry === "string"
601
+ ? relative(entry)
602
+ : entry._tag === "TreeArtifact"
603
+ ? { ...entry, path: relative(entry.path) }
604
+ : {
605
+ ...entry,
606
+ include: [relative(entry.include[0]), ...entry.include.slice(1).map(relative)],
607
+ ...(entry.exclude === undefined ? {} : { exclude: entry.exclude.map(relative) })
608
+ }
609
+ ),
610
+ ...(descriptor.removes === undefined ? {} : { removes: descriptor.removes.map(relative) }),
611
+ boundaryMode: descriptor.boundaryMode
612
+ }
613
+ }
614
+
615
+ const digestOf = (bytes: Uint8Array): Effect.Effect<string, never, Crypto.Crypto> =>
616
+ Schema.decodeUnknownEffect(Sha256)(bytes).pipe(Effect.orDie)
617
+
618
+ const parentDirectory = (path: string): string | undefined => {
619
+ const index = path.lastIndexOf("/")
620
+ return index <= 0 ? undefined : path.slice(0, index)
621
+ }
622
+
623
+ // -----------------------------------------------------------------------------
624
+ // the host seam
625
+ // -----------------------------------------------------------------------------
626
+
627
+ /**
628
+ * Everything a workspace sandbox needs from the world it isolates bodies from.
629
+ *
630
+ * Splitting it out is what lets one transaction implementation serve both an
631
+ * in-memory host (tests, browsers, and the conformance suite) and a real
632
+ * filesystem host: the transaction, the diff, the violation check, and the
633
+ * provenance are identical, and only *where the base comes from* and *where
634
+ * copy-back lands* differ.
635
+ *
636
+ * @category models
637
+ * @since 0.1.0
638
+ */
639
+ export interface Host {
640
+ /**
641
+ * The transaction's base. A host that can seed exactly the declared read set
642
+ * does so — a body then cannot read an undeclared file because it is not
643
+ * there, which is the strong tier of `Effect Taxonomy.md`. A host that hands
644
+ * back a wider tree gets the same guarantees post hoc, through the
645
+ * undeclared-read check.
646
+ */
647
+ readonly snapshot: (
648
+ descriptor: FileBoundary
649
+ ) => Effect.Effect<ReadonlyMap<string, Uint8Array>, WorkspaceError, Crypto.Crypto>
650
+ /**
651
+ * The host's current digest for a path the transaction never observed, or
652
+ * `undefined` when the host holds nothing there.
653
+ *
654
+ * A host that seeds only the declared read set does not know what else is on
655
+ * disk, and "absent from the seed" is emphatically **not** "absent from the
656
+ * host": a body writing a declared output it never declared as a read is the
657
+ * ordinary case, and that file usually already exists from a previous run.
658
+ * Treating the seed as the whole world made every such copy-back a
659
+ * {@link MaterializationConflict} the engine could only rebase into the same
660
+ * refusal, and made an identical rewrite look like a change.
661
+ *
662
+ * The digest this returns is therefore the change's `beforeDigest`. A blind
663
+ * write cannot carry a compare-and-set over the body's whole execution — the
664
+ * body never read the prior content, so there is nothing it could have
665
+ * depended on — but copy-back still refuses if the path moves again between
666
+ * the diff and the commit.
667
+ */
668
+ readonly baseline: (
669
+ path: string
670
+ ) => Effect.Effect<string | undefined, WorkspaceError, Crypto.Crypto>
671
+ /**
672
+ * Decides whether a produced file's bytes travel inline in its
673
+ * {@link FileChange} or by content address. Returning `undefined` means the
674
+ * bytes were retained under the change's `afterDigest` and must be fetched
675
+ * back at copy-back.
676
+ */
677
+ readonly retain: (
678
+ bytes: Uint8Array
679
+ ) => Effect.Effect<Uint8Array | undefined, WorkspaceError, Crypto.Crypto>
680
+ /**
681
+ * Applies the diff as a compare-and-set on every `beforeDigest`. Hosts must
682
+ * serialize preflight through apply and rollback with competing commits.
683
+ * Check every precondition before changing files; on apply failure attempt
684
+ * to restore every touched path and preserve both causes if rollback fails.
685
+ * Crash recovery and host reconciliation remain the caller's responsibility.
686
+ */
687
+ readonly commit: (
688
+ changes: ReadonlyArray<FileChange>
689
+ ) => Effect.Effect<void, MaterializationConflict | WorkspaceError, Crypto.Crypto>
690
+ /** The absolute root absolute host paths are interpreted against. */
691
+ readonly root: string
692
+ }
693
+
694
+ // -----------------------------------------------------------------------------
695
+ // the transaction
696
+ // -----------------------------------------------------------------------------
697
+
698
+ /**
699
+ * Reads are recorded as bytes and hashed once, after the body returns.
700
+ * Digesting inside `readFile` would put `Crypto` in the error-free signature
701
+ * Effect's own `FileSystem` fixes, and a body must see the tag's real shape.
702
+ */
703
+ interface Trace {
704
+ readonly inputs: Array<{ readonly path: string; readonly content: Uint8Array }>
705
+ readonly attemptedReads: Array<{ readonly path: string; readonly produced: boolean }>
706
+ readonly effects: Array<QueuedEffect>
707
+ }
708
+
709
+ const notFound = (path: string): WorkspaceError =>
710
+ new WorkspaceError({ code: "not_found", message: `Workspace file does not exist: ${path}` })
711
+
712
+ /**
713
+ * The transaction surfaces: the {@link Workspace} tag and a re-rooted Effect
714
+ * `FileSystem`, both over one path-keyed map.
715
+ *
716
+ * The `FileSystem` is built from `makeNoop` on purpose. Only the operations
717
+ * listed here are meaningful over a functional map, and a body reaching for
718
+ * one that is not — a temp file, a watch, a stream — gets the platform's own
719
+ * refusal rather than a plausible lie. Widening the surface is a deliberate
720
+ * change to this list, never a silent default.
721
+ */
722
+ const transaction = (base: ReadonlyMap<string, Uint8Array>, trace: Trace, root: string) => {
723
+ const files = new Map(base)
724
+ const produced = new Set<string>()
725
+ const resolvePath = (path: string, allowRoot = false): Effect.Effect<string, WorkspaceError> => {
726
+ const normalized = normalizePath(root, path, allowRoot)
727
+ return Result.isFailure(normalized) ? Effect.fail(normalized.failure) : Effect.succeed(normalized.success)
728
+ }
729
+ const readBytes = Effect.fn("WorkspaceSandbox.read")(function*(path: string) {
730
+ const key = yield* resolvePath(path)
731
+ trace.attemptedReads.push({ path: key, produced: produced.has(key) })
732
+ const content = files.get(key)
733
+ if (content === undefined) return yield* Effect.fail(notFound(key))
734
+ trace.inputs.push({ path: key, content })
735
+ return content.slice()
736
+ })
737
+ const writeBytes = Effect.fn("WorkspaceSandbox.write")(function*(path: string, content: Uint8Array) {
738
+ const key = yield* resolvePath(path)
739
+ produced.add(key)
740
+ files.set(key, content.slice())
741
+ })
742
+ const removePath = Effect.fn("WorkspaceSandbox.remove")(function*(path: string) {
743
+ const key = yield* resolvePath(path)
744
+ produced.add(key)
745
+ files.delete(key)
746
+ })
747
+ const workspace = Workspace.of({
748
+ readFile: (path) => readBytes(path),
749
+ writeFile: (path, content) => writeBytes(path, content),
750
+ removeFile: (path) => removePath(path),
751
+ queueEffect: (effect) => Effect.sync(() => trace.effects.push(effect))
752
+ })
753
+ const refuse = (method: string, path: string) => (error: WorkspaceError) =>
754
+ error.code === "not_found"
755
+ ? PlatformError.systemError({
756
+ _tag: "NotFound",
757
+ module: "FileSystem",
758
+ method,
759
+ description: error.message,
760
+ pathOrDescriptor: path
761
+ })
762
+ : PlatformError.badArgument({ module: "FileSystem", method, description: error.message })
763
+ const fileSystem = FileSystem.makeNoop({
764
+ // `exists` and `readDirectory` trace every PRESENCE they reveal: under a
765
+ // whole-tree-seeding host the map holds undeclared world state, and a
766
+ // probe that observes it is a read the key never folded. Absence stays
767
+ // untraced — under the declared-set host an undeclared path is absent by
768
+ // construction, the Bazel forest's own deterministic answer.
769
+ exists: (path) =>
770
+ resolvePath(path, true).pipe(
771
+ Effect.map((key) => {
772
+ if (key === "") return true
773
+ if (files.has(key)) {
774
+ trace.attemptedReads.push({ path: key, produced: produced.has(key) })
775
+ return true
776
+ }
777
+ const prefix = `${key}/`
778
+ let present = false
779
+ for (const candidate of files.keys()) {
780
+ if (!candidate.startsWith(prefix)) continue
781
+ trace.attemptedReads.push({ path: candidate, produced: produced.has(candidate) })
782
+ present = true
783
+ }
784
+ return present
785
+ }),
786
+ // A path the transaction cannot even name does not exist in it; that
787
+ // is an answer, not a host failure.
788
+ Effect.catch(() => Effect.succeed(false))
789
+ ),
790
+ readFile: (path) => readBytes(path).pipe(Effect.mapError(refuse("readFile", path))),
791
+ readFileString: (path) =>
792
+ readBytes(path).pipe(
793
+ Effect.map((bytes) => decoder.decode(bytes)),
794
+ Effect.mapError(refuse("readFileString", path))
795
+ ),
796
+ writeFile: (path, data) => writeBytes(path, data).pipe(Effect.mapError(refuse("writeFile", path))),
797
+ writeFileString: (path, data) =>
798
+ writeBytes(path, encoder.encode(data)).pipe(Effect.mapError(refuse("writeFileString", path))),
799
+ remove: (path) => removePath(path).pipe(Effect.mapError(refuse("remove", path))),
800
+ // Directories are implicit in a path-keyed map: creating one is a
801
+ // successful no-op so a body that mirrors ordinary filesystem discipline
802
+ // works unchanged, and listing one is a prefix scan.
803
+ makeDirectory: () => Effect.void,
804
+ readDirectory: (path) =>
805
+ resolvePath(path, true).pipe(
806
+ Effect.map((key) => {
807
+ const prefix = key === "" ? "" : `${key}/`
808
+ const entries = new Set<string>()
809
+ for (const candidate of files.keys()) {
810
+ if (!candidate.startsWith(prefix)) continue
811
+ // A listing reveals each file it names, so each is a traced read.
812
+ trace.attemptedReads.push({ path: candidate, produced: produced.has(candidate) })
813
+ entries.add(candidate.slice(prefix.length).split("/")[0]!)
814
+ }
815
+ return [...entries].sort(compareText)
816
+ }),
817
+ Effect.mapError(refuse("readDirectory", path))
818
+ )
819
+ })
820
+ return { files, workspace, fileSystem }
821
+ }
822
+
823
+ const changes = Effect.fn("WorkspaceSandbox.changes")(function*(
824
+ base: ReadonlyMap<string, Uint8Array>,
825
+ current: ReadonlyMap<string, Uint8Array>,
826
+ host: Host
827
+ ) {
828
+ const paths = [...new Set([...base.keys(), ...current.keys()])].sort(compareText)
829
+ const result: Array<FileChange> = []
830
+ for (const path of paths) {
831
+ const before = base.get(path)
832
+ const after = current.get(path)
833
+ // A path the transaction never observed is not a path the host lacks: ask
834
+ // the host what is there before claiming the change creates it.
835
+ const beforeDigest = before === undefined ? yield* host.baseline(path) : yield* digestOf(before)
836
+ const afterDigest = after === undefined ? undefined : yield* digestOf(after)
837
+ if (beforeDigest === afterDigest) continue
838
+ result.push({
839
+ path,
840
+ beforeDigest,
841
+ afterDigest,
842
+ ...(after === undefined ? {} : { after: yield* host.retain(after) })
843
+ })
844
+ }
845
+ return result
846
+ })
847
+
848
+ /**
849
+ * The compare-and-set every copy-back runs before it writes a single byte,
850
+ * capturing each target's pre-image while it looks.
851
+ *
852
+ * Both hosts share it, so "all-or-nothing" cannot drift between the in-memory
853
+ * conformance implementation and the filesystem one: a bundle whose base moved
854
+ * is refused whole, and the tree is left exactly as it was found. The captured
855
+ * pre-images are the rollback journal the filesystem host restores from when a
856
+ * mid-apply refusal would otherwise strand a half-written tree.
857
+ */
858
+ const preflight = Effect.fn("WorkspaceSandbox.preflight")(function*(
859
+ changes: ReadonlyArray<FileChange>,
860
+ current: (path: string) => Effect.Effect<Uint8Array | undefined, WorkspaceError, Crypto.Crypto>
861
+ ) {
862
+ const conflicting: Array<string> = []
863
+ const before = new Map<string, Uint8Array | undefined>()
864
+ for (const change of changes) {
865
+ const content = yield* current(change.path)
866
+ before.set(change.path, content)
867
+ const digest = content === undefined ? undefined : yield* digestOf(content)
868
+ conflicting.push(...(digest === change.beforeDigest ? [] : [change.path]))
869
+ }
870
+ const conflict = conflicting.length === 0 ? undefined : new MaterializationConflict({
871
+ paths: conflicting,
872
+ message: "Workspace state changed after the transaction's base snapshot was taken"
873
+ })
874
+ return { conflict, before }
875
+ })
876
+
877
+ const revisionOf = Effect.fn("WorkspaceSandbox.revision")(function*(base: ReadonlyMap<string, Uint8Array>) {
878
+ const entries: Array<{ readonly path: string; readonly digest: string }> = []
879
+ for (const [path, content] of [...base].sort((left, right) => compareText(left[0], right[0]))) {
880
+ entries.push({ path, digest: yield* digestOf(content) })
881
+ }
882
+ return yield* Schema.decodeUnknownEffect(DerivedKey)({ kind: "workspace-revision", entries }).pipe(Effect.orDie)
883
+ })
884
+
885
+ /**
886
+ * Everything the declaration failed to predict, deduplicated.
887
+ *
888
+ * Reads and writes are checked against the declaration's exact paths and patterns; writes
889
+ * against the declared write set's patterns. A read of a file the body itself
890
+ * produced is not undeclared — the transaction, not the host, supplied it.
891
+ *
892
+ * @category accessors
893
+ * @since 0.1.0
894
+ */
895
+ export const violations = (
896
+ descriptor: FileBoundary,
897
+ base: ReadonlyMap<string, Uint8Array>,
898
+ provenance: Provenance
899
+ ): ReadonlyArray<DeclarationViolation> => {
900
+ const declaredReads = descriptor.readSet
901
+ const observed: Array<DeclarationViolation> = [
902
+ ...provenance.inputs
903
+ .filter((input) =>
904
+ base.has(input.resource.id) &&
905
+ !declaredReads.some((entry) =>
906
+ FileSet.isGlob(entry) ? FileSet.matchesGlob(entry, input.resource.id) : entry.path === input.resource.id
907
+ )
908
+ )
909
+ .map((input): DeclarationViolation => ({ kind: "undeclared-read", resource: input.resource })),
910
+ ...provenance.outputs
911
+ .filter((output) =>
912
+ ![...descriptor.writeSet, ...descriptor.removes ?? []].some((entry) => covers(entry, output.resource.id))
913
+ )
914
+ .map((output): DeclarationViolation => ({ kind: "undeclared-write", resource: output.resource }))
915
+ ]
916
+ return [
917
+ ...new Map(observed.map((violation) => [`${violation.kind}:${violation.resource.id}`, violation])).values()
918
+ ]
919
+ }
920
+
921
+ /**
922
+ * Builds a workspace sandbox over a {@link Host}.
923
+ *
924
+ * @category constructors
925
+ * @since 0.1.0
926
+ */
927
+ export const makeHosted = (host: Host): Service => {
928
+ const memo = new Map<
929
+ string,
930
+ { readonly result: WorkflowResult<never>; readonly violations: ReadonlyArray<DeclarationViolation> }
931
+ >()
932
+ /**
933
+ * The speculative half, untraced: the `execute` span below wraps it, so
934
+ * the transaction body reports under one span with its metrics.
935
+ */
936
+ const executeBody = Effect.fnUntraced(function*<Output, Error>(
937
+ execution: Execution<Output, Error>
938
+ ) {
939
+ const boundary = FileBoundarySnapshot.make(execution.descriptor)
940
+ const key = execution.cacheKey
941
+ if (key !== undefined) {
942
+ const hit = memo.get(key)
943
+ if (hit !== undefined) {
944
+ return {
945
+ _tag: "Accepted" as const,
946
+ result: hit.result as unknown as WorkflowResult<Output>,
947
+ violations: hit.violations,
948
+ cache: { status: "hit" as const, key }
949
+ }
950
+ }
951
+ }
952
+ const captured = yield* host.snapshot(boundary)
953
+ // The host owns its returned buffers. Preserve the instant represented by
954
+ // snapshot() even if that host or another fiber mutates them while the
955
+ // workflow is suspended.
956
+ const base = new Map([...captured].map(([path, content]) => [path, content.slice()]))
957
+ const trace: Trace = { inputs: [], attemptedReads: [], effects: [] }
958
+ const isolated = transaction(base, trace, host.root)
959
+ const outcome = yield* execution.workflow.pipe(
960
+ Effect.provideService(Workspace, isolated.workspace),
961
+ Effect.provideService(FileSystem.FileSystem, isolated.fileSystem),
962
+ Effect.exit
963
+ )
964
+ const inputs: Array<InputObservation> = []
965
+ for (const input of trace.inputs) {
966
+ inputs.push({ resource: resource(input.path), digest: yield* digestOf(input.content) })
967
+ }
968
+ const descriptor = workspaceRelative(host.root, boundary)
969
+ const undeclaredReads = trace.attemptedReads.filter((attempt) =>
970
+ !attempt.produced &&
971
+ !descriptor.readSet.some((entry) =>
972
+ FileSet.isGlob(entry) ? FileSet.matchesGlob(entry, attempt.path) : entry.path === attempt.path
973
+ )
974
+ )
975
+ if (Exit.isFailure(outcome)) {
976
+ // Hard mode only, mirroring the success path below: in expected mode
977
+ // an undeclared read is a deviation the engine journals, and a body's
978
+ // own typed failure must surface as itself — converting it into an
979
+ // isolation verdict would both harden a soft boundary and mask the
980
+ // error the retry policy classifies on.
981
+ if (undeclaredReads.length > 0 && boundary.boundaryMode === "hard") {
982
+ return {
983
+ _tag: "Invalidated" as const,
984
+ provenance: {
985
+ baseRevision: yield* revisionOf(base),
986
+ inputs,
987
+ outputs: []
988
+ },
989
+ violations: [
990
+ ...new Map(undeclaredReads.map((attempt) => [attempt.path, {
991
+ kind: "undeclared-read" as const,
992
+ resource: resource(attempt.path)
993
+ }])).values()
994
+ ]
995
+ }
996
+ }
997
+ return yield* Effect.failCause(outcome.cause)
998
+ }
999
+ const output = outcome.value
1000
+ const files = yield* changes(base, isolated.files, host)
1001
+ const provenance: Provenance = {
1002
+ baseRevision: yield* revisionOf(base),
1003
+ inputs,
1004
+ outputs: files.map((change) => ({
1005
+ resource: resource(change.path),
1006
+ operation: change.afterDigest === undefined ? "remove" as const : "write" as const,
1007
+ digest: change.afterDigest
1008
+ }))
1009
+ }
1010
+ const invalid = [
1011
+ ...new Map([
1012
+ ...violations(descriptor, base, provenance),
1013
+ ...undeclaredReads.map((attempt) => ({
1014
+ kind: "undeclared-read" as const,
1015
+ resource: resource(attempt.path)
1016
+ }))
1017
+ ].map((violation) => [`${violation.kind}:${violation.resource.id}`, violation])).values()
1018
+ ]
1019
+ // Hard mode discards; expected mode admits the result and leaves the
1020
+ // deviation for the engine to journal and reconcile
1021
+ // (`Effect Taxonomy.md`, "Expected sets — the soft mode"). Either way
1022
+ // the host has not been touched.
1023
+ if (invalid.length > 0 && boundary.boundaryMode === "hard") {
1024
+ return { _tag: "Invalidated" as const, provenance, violations: invalid }
1025
+ }
1026
+ const result: WorkflowResult<Output> = { output, files, provenance, effects: trace.effects }
1027
+ if (key !== undefined) {
1028
+ memo.set(key, { result: result as unknown as WorkflowResult<never>, violations: invalid })
1029
+ }
1030
+ return {
1031
+ _tag: "Accepted" as const,
1032
+ result,
1033
+ violations: invalid,
1034
+ cache: key === undefined ? { status: "disabled" as const } : { status: "miss" as const, key }
1035
+ }
1036
+ })
1037
+ return make({
1038
+ execute: Effect.fn("WorkspaceSandbox.execute")(
1039
+ function*<Output, Error>(
1040
+ execution: Execution<Output, Error>
1041
+ ) {
1042
+ yield* Effect.annotateCurrentSpan({ boundaryMode: execution.descriptor.boundaryMode })
1043
+ return yield* executeBody(execution)
1044
+ },
1045
+ (effect) =>
1046
+ EngineStoreMetrics.observe({
1047
+ timer: EngineStoreMetrics.sandboxExecutionDuration,
1048
+ counter: EngineStoreMetrics.sandboxExecution
1049
+ })(effect)
1050
+ ),
1051
+ materialize: Effect.fn("WorkspaceSandbox.materialize")(
1052
+ function*<Output>(accepted: Accepted<Output>) {
1053
+ yield* Effect.annotateCurrentSpan({ changes: accepted.result.files.length })
1054
+ // Nothing to copy back needs no commit: a body that changed nothing
1055
+ // (a sealed read) must not need write authority over the root.
1056
+ if (accepted.result.files.length === 0) return
1057
+ yield* host.commit(accepted.result.files)
1058
+ },
1059
+ (effect) =>
1060
+ EngineStoreMetrics.observe({
1061
+ timer: EngineStoreMetrics.materializationDuration,
1062
+ counter: EngineStoreMetrics.materialization
1063
+ })(effect)
1064
+ )
1065
+ })
1066
+ }
1067
+
1068
+ // -----------------------------------------------------------------------------
1069
+ // the in-memory host
1070
+ // -----------------------------------------------------------------------------
1071
+
1072
+ /**
1073
+ * Initial file contents accepted by {@link makeMemory}.
1074
+ *
1075
+ * @category models
1076
+ * @since 0.1.0
1077
+ */
1078
+ export type InitialFiles = Readonly<Record<string, string | Uint8Array>>
1079
+
1080
+ /**
1081
+ * A host file snapshot exposed by the deterministic in-memory implementation.
1082
+ *
1083
+ * @category models
1084
+ * @since 0.1.0
1085
+ */
1086
+ export interface HostFile {
1087
+ readonly path: string
1088
+ readonly content: Uint8Array
1089
+ }
1090
+
1091
+ /**
1092
+ * The deterministic in-memory sandbox plus read-only test observations.
1093
+ *
1094
+ * @category models
1095
+ * @since 0.1.0
1096
+ */
1097
+ export interface MemorySandbox {
1098
+ readonly service: Service
1099
+ readonly files: Effect.Effect<ReadonlyArray<HostFile>>
1100
+ }
1101
+
1102
+ /**
1103
+ * Creates a deterministic in-memory workspace sandbox.
1104
+ *
1105
+ * The returned `files` effect observes *host* state, not a running
1106
+ * transaction: it changes only through `service.materialize`. This host hands
1107
+ * the transaction its whole tree rather than seeding the declared read set, so
1108
+ * an undeclared read is observable — which is exactly what makes it the
1109
+ * conformance implementation for the bare tier of `Effect Taxonomy.md`.
1110
+ *
1111
+ * @category constructors
1112
+ * @since 0.1.0
1113
+ */
1114
+ export const makeMemory = (
1115
+ initialFiles: InitialFiles = {}
1116
+ ): Effect.Effect<MemorySandbox, WorkspaceError, Crypto.Crypto> =>
1117
+ Effect.gen(function*() {
1118
+ const entries: Array<readonly [string, Uint8Array]> = []
1119
+ for (const [path, content] of Object.entries(initialFiles)) {
1120
+ const normalized = normalizePath("", path)
1121
+ if (Result.isFailure(normalized)) return yield* Effect.fail(normalized.failure)
1122
+ entries.push([normalized.success, typeof content === "string" ? encoder.encode(content) : content.slice()])
1123
+ }
1124
+ const host = yield* Ref.make<ReadonlyMap<string, Uint8Array>>(new Map(entries))
1125
+ const service = makeHosted({
1126
+ root: "",
1127
+ snapshot: () => Ref.get(host),
1128
+ // This host seeds the transaction with the WHOLE tree, so a path the
1129
+ // transaction did not observe is one the host genuinely did not hold:
1130
+ // `undefined` is the honest snapshot-time answer, and a file that
1131
+ // appeared underneath the transaction is caught by copy-back's
1132
+ // compare-and-set rather than clobbered.
1133
+ baseline: () => Effect.succeed(undefined),
1134
+ retain: (bytes) => Effect.succeed(bytes.slice()),
1135
+ commit: Effect.fn("WorkspaceSandbox.commit")(function*(changes) {
1136
+ yield* Effect.annotateCurrentSpan({ changes: changes.length })
1137
+ const current = yield* Ref.get(host)
1138
+ const { conflict } = yield* preflight(changes, (path) => Effect.succeed(current.get(path)))
1139
+ if (conflict !== undefined) {
1140
+ yield* Metric.update(EngineStoreMetrics.materializationConflicts, 1)
1141
+ return yield* Effect.fail(conflict)
1142
+ }
1143
+ const next = new Map(current)
1144
+ for (const change of changes) {
1145
+ if (change.afterDigest === undefined) next.delete(change.path)
1146
+ else next.set(change.path, change.after!.slice())
1147
+ }
1148
+ yield* Ref.set(host, next)
1149
+ })
1150
+ })
1151
+ return {
1152
+ service,
1153
+ files: Ref.get(host).pipe(
1154
+ Effect.map((current) =>
1155
+ [...current]
1156
+ .map(([path, content]) => ({ path, content }))
1157
+ .sort((left, right) => compareText(left.path, right.path))
1158
+ )
1159
+ )
1160
+ }
1161
+ })
1162
+
1163
+ // -----------------------------------------------------------------------------
1164
+ // the filesystem host
1165
+ // -----------------------------------------------------------------------------
1166
+
1167
+ /**
1168
+ * Filesystem workspace sandbox options.
1169
+ *
1170
+ * @category models
1171
+ * @since 0.1.0
1172
+ */
1173
+ export interface FileSystemOptions {
1174
+ /**
1175
+ * The largest produced file (in bytes) carried inline in its
1176
+ * {@link FileChange}. Anything larger is retained in the content-addressed
1177
+ * artifact store and fetched back at copy-back. Defaults to 1 MiB, matching
1178
+ * `StepBoundary`'s evidence bound.
1179
+ */
1180
+ readonly maxInlineBytes?: number | undefined
1181
+ /**
1182
+ * Root-relative paths copy-back refuses to write or remove, in addition to
1183
+ * the always-reserved `.smithers-workspace-lock` and `.flows` directories.
1184
+ * Name the engine database, its `-wal`/`-shm` siblings, and the artifact
1185
+ * directory here when they live under the root outside `.flows`. A
1186
+ * reserved directory also reserves every path beneath it.
1187
+ */
1188
+ readonly reservedPaths?: ReadonlyArray<string> | undefined
1189
+ }
1190
+
1191
+ // Shared across separately constructed sandboxes. Entries live only while a
1192
+ // commit is running or waiting, so short-lived workspace roots do not leak.
1193
+ const CommitLocks = Context.Service<RcMap.RcMap<string, Semaphore.Semaphore>>(
1194
+ "@smthrs/engine-store/WorkspaceSandbox/CommitLocks"
1195
+ )
1196
+ const locksLayer = Layer.effect(CommitLocks, RcMap.make({ lookup: (_root: string) => Semaphore.make(1) }))
1197
+ // The memo map shares the layer across active operations, including operations
1198
+ // from different store instances. Their scopes release the final owner.
1199
+ const lockOwners = Layer.makeMemoMapUnsafe()
1200
+
1201
+ const coordinateCommit = <A, E, R>(root: string, write: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
1202
+ Effect.scoped(Effect.gen(function*() {
1203
+ const services = yield* Layer.buildWithMemoMap(locksLayer, lockOwners, yield* Scope.Scope)
1204
+ const lock = yield* RcMap.get(Context.get(services, CommitLocks), root)
1205
+ return yield* lock.withPermit(write)
1206
+ }))
1207
+
1208
+ const commitLockName = ".smithers-workspace-lock"
1209
+ // The conventional engine state directory: the SQLite store, its WAL/SHM
1210
+ // siblings, and the artifact objects. A step body must never replace them.
1211
+ const engineStateName = ".flows"
1212
+
1213
+ const defaultMaxInlineBytes = 1024 * 1024
1214
+
1215
+ const hostFailure = (cause: unknown): WorkspaceError =>
1216
+ new WorkspaceError({
1217
+ code: "host_unavailable",
1218
+ message: "the host filesystem could not serve the workspace transaction",
1219
+ cause
1220
+ })
1221
+
1222
+ const artifactFailure = (cause: { readonly message: string }): WorkspaceError =>
1223
+ new WorkspaceError({
1224
+ code: "host_unavailable",
1225
+ message: `the artifact store could not serve the workspace transaction: ${cause.message}`,
1226
+ cause
1227
+ })
1228
+
1229
+ /**
1230
+ * The errno names the descriptor-relative host reports when it refuses to
1231
+ * resolve a path inside its pinned root: a symlink at the final component
1232
+ * (`ELOOP`); a symlink at an intermediate component (`ELOOP` on Linux,
1233
+ * `ENOTDIR` on Darwin, which also covers a file standing where a directory
1234
+ * must be); or `..` traversal, a path outside the root, a hard-linked file, or
1235
+ * a root whose identity changed (`EPERM`). Each means the change does not
1236
+ * name a workspace entry reachable without following a link.
1237
+ */
1238
+ const confinementCodes: ReadonlySet<unknown> = new Set(["ELOOP", "ENOTDIR", "EPERM"])
1239
+
1240
+ const refusedByConfinement = (error: PlatformError.PlatformError): boolean =>
1241
+ error.reason._tag !== "BadArgument" &&
1242
+ typeof error.reason.cause === "object" && error.reason.cause !== null &&
1243
+ confinementCodes.has((error.reason.cause as { readonly code?: unknown }).code)
1244
+
1245
+ /** Maps one copy-back host failure onto the sandbox's typed error codes. */
1246
+ const copyBackFailure = (path: string) => (error: PlatformError.PlatformError): WorkspaceError =>
1247
+ refusedByConfinement(error)
1248
+ ? new WorkspaceError({
1249
+ code: "path_escapes_workspace",
1250
+ message:
1251
+ `materializing ${path} was refused: it does not name a workspace entry reachable without following a link (${error.message})`,
1252
+ cause: error
1253
+ })
1254
+ : hostFailure(error)
1255
+
1256
+ /**
1257
+ * Maps a commit-lock failure: a missed acquisition deadline is
1258
+ * `commit_lock_timeout`, so a caller can tell "another writer holds the root"
1259
+ * from "the host refused".
1260
+ */
1261
+ const lockFailure = (cause: unknown): WorkspaceError =>
1262
+ Cause.isTimeoutError(cause)
1263
+ ? new WorkspaceError({
1264
+ code: "commit_lock_timeout",
1265
+ message:
1266
+ `another writer held ${commitLockName} for the whole acquisition deadline; a lock whose owner stopped is reclaimed once its heartbeat is ${FileLease.defaultStaleAfterMs} ms old`,
1267
+ cause
1268
+ })
1269
+ // `FileLease` hands every other refusal over as the host's `PlatformError`.
1270
+ : copyBackFailure(commitLockName)(cause as PlatformError.PlatformError)
1271
+
1272
+ // Over-approximates the spellings a case-insensitive, normalization-insensitive
1273
+ // volume equates: `.FLOWS`, `.flow\u017f`, and `.\ufb02ows` all open `.flows` on
1274
+ // default APFS. Folding more than a volume does only refuses more names.
1275
+ const foldPath = (path: string): string => path.normalize("NFKC").toUpperCase().toLowerCase().normalize("NFKC")
1276
+
1277
+ // NTFS with 8.3 name creation on (the system volume default) also opens a
1278
+ // long name through its generated short name: `.flows` through `FLOWS~1`, or
1279
+ // `FL3A2B~1` once the numbered tails collide. Linux reaches such a volume
1280
+ // through WSL's `/mnt/c` and macOS through SMB, so the shape is refused on
1281
+ // every host. The pattern is broader than the generator: any `~` and digit
1282
+ // tail on a base of at most eight characters, with an extension of at most
1283
+ // three.
1284
+ const shortNameAlias = /^[^.~]{0,7}~\d{1,7}(\.[^.]{0,3})?$/u
1285
+ const isShortNameAlias = (segment: string): boolean => {
1286
+ // `split` always yields a first element, possibly empty.
1287
+ const base = segment.split(".")[0]!
1288
+ return base.length <= 8 && shortNameAlias.test(segment)
1289
+ }
1290
+
1291
+ /**
1292
+ * Builds the filesystem-backed workspace sandbox.
1293
+ *
1294
+ * **Copy-in, not overlay.** The transaction is seeded with exactly the
1295
+ * declared read set and nothing else
1296
+ * (`docs/concepts/workspace-transactions.md`). Bazel uses the same strategy, which
1297
+ * is why whole-tree write observation is structural here. Re-rooting a
1298
+ * body onto a bare host tree instead would leave every undeclared file
1299
+ * readable and reduce enforcement to the taxonomy's weakest, post-hoc
1300
+ * detection-by-diff tier; and the OS-level overlay that would fix that is not
1301
+ * reachable through Effect's `FileSystem` tag, so it could not run in a
1302
+ * browser. Seeding costs a copy of the declared reads and buys both.
1303
+ *
1304
+ * **Copy-back is confined and journaled.** Every copy-back host call (the
1305
+ * lock, preflight, apply, and rollback) goes through
1306
+ * `@smthrs/kernel/FileSystem`'s `confined` view: one descriptor-relative,
1307
+ * no-follow request against the workspace root pinned when the commit starts.
1308
+ * A symlink anywhere on a change's path, a hard-linked file, or a root
1309
+ * replaced mid-commit is refused with `path_escapes_workspace`, never
1310
+ * followed, and no check-then-write window exists for a concurrent process to
1311
+ * swap a link into. A copy-back through a symlink that stays inside the root
1312
+ * is refused too: the body writes the referent path itself. `fs` must
1313
+ * therefore carry the kernel's descriptor-relative executor (Node and Bun
1314
+ * hosts use `@smthrs/platform-node`'s `AtomicFileSystem`) or a
1315
+ * whole-filesystem isolation attestation (`withIsolatedFileSystem`, for an
1316
+ * in-memory volume); a plain path-based host is refused at copy-back with
1317
+ * `host_unavailable`. Snapshot reads use `fs` as given. A root-keyed semaphore and
1318
+ * the `.smithers-workspace-lock` lease file (`@smthrs/artifacts/FileLease`)
1319
+ * serialize cooperating callers, including separate processes. Preconditions run before file
1320
+ * changes, and the apply loop keeps each target's pre-image for in-process
1321
+ * rollback. A crash or rollback failure can leave partial changes; see
1322
+ * {@link Service.materialize} for the caller's reconciliation obligations.
1323
+ * Hosts must implement exclusive `wx` file creation, `stat` with an mtime,
1324
+ * `utimes`, `rename`, and `remove` for the reserved lock. Copy-back
1325
+ * also refuses the `.flows` engine state directory and any
1326
+ * {@link FileSystemOptions.reservedPaths}, so no write set or boundary mode
1327
+ * lets a step body replace the engine database or its artifact blobs.
1328
+ *
1329
+ * @category constructors
1330
+ * @since 0.1.0
1331
+ */
1332
+ export const makeFileSystem = (
1333
+ fs: FileSystem.FileSystem,
1334
+ platformPath: Path.Path,
1335
+ artifacts: ArtifactStore.Service,
1336
+ workspaceRoot: string,
1337
+ options: FileSystemOptions = {}
1338
+ ): Service => {
1339
+ const maxInlineBytes = options.maxInlineBytes ?? defaultMaxInlineBytes
1340
+ const root = workspaceRoot.replaceAll(/\/+$/g, "")
1341
+ const reserved = [
1342
+ commitLockName,
1343
+ engineStateName,
1344
+ ...(options.reservedPaths ?? []).map((path) => path.replace(/^(\.\/)+/, "").replaceAll(/\/+$/g, ""))
1345
+ ].filter((path) => path !== "" && path !== ".").map((name) => ({ name, segments: foldPath(name).split("/") }))
1346
+ // Why the root-relative `path` is refused, if it targets or lies beneath a
1347
+ // reserved entry. The confined host refuses every symlink on a path, so the
1348
+ // remaining aliases are spellings the volume itself equates: case,
1349
+ // compatibility, and normalization variants on APFS and NTFS, and NTFS 8.3
1350
+ // short names. Comparing folded segments, and refusing a short-name shape
1351
+ // wherever a reserved segment stands, refuses them on every volume,
1352
+ // case-sensitive ones included.
1353
+ const reservedAt = (path: string): string | undefined => {
1354
+ const segments = foldPath(path).split("/")
1355
+ const within = reserved.filter((entry) => entry.segments.length <= segments.length)
1356
+ const exact = within.find((entry) => entry.segments.every((segment, index) => segments[index] === segment))
1357
+ if (exact !== undefined) return `the workspace path ${exact.name} is reserved`
1358
+ const alias = within.find((entry) =>
1359
+ // `within` holds only entries no longer than the path, so every index is present.
1360
+ entry.segments.every((segment, index) => segments[index] === segment || isShortNameAlias(segments[index]!))
1361
+ )
1362
+ if (alias === undefined) return undefined
1363
+ const spelling = path.split("/").slice(0, alias.segments.length).join("/")
1364
+ return `the workspace path ${spelling} may be a short-name alias of a reserved path`
1365
+ }
1366
+ const hostPath = (path: string) => root === "" ? path : `${root}/${path}`
1367
+ // One host call, not two: the read reports an absent path itself. On the
1368
+ // confined host
1369
+ // the `exists` probe that used to precede every read was a second CPython
1370
+ // fork (`@smthrs/platform-node/AtomicFileSystem`) for an answer the read
1371
+ // was about to give.
1372
+ const readFrom = (
1373
+ host: FileSystem.FileSystem,
1374
+ path: string,
1375
+ onError: (error: PlatformError.PlatformError) => WorkspaceError
1376
+ ) =>
1377
+ host.readFile(path).pipe(
1378
+ Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)),
1379
+ Effect.mapError(onError)
1380
+ )
1381
+ const readIfPresent = Effect.fn("WorkspaceSandbox.readIfPresent")(function*(path: string) {
1382
+ return yield* readFrom(fs, path, hostFailure)
1383
+ })
1384
+ const canonicalRoot = fs.realPath(root === "" ? "." : root).pipe(
1385
+ Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)),
1386
+ Effect.mapError(hostFailure)
1387
+ )
1388
+ const withCommitLock = <E, R>(
1389
+ changes: ReadonlyArray<FileChange>,
1390
+ effect: (host: FileSystem.FileSystem) => Effect.Effect<void, E, R>
1391
+ ) =>
1392
+ Effect.gen(function*() {
1393
+ for (const change of changes) {
1394
+ const hit = reservedAt(change.path)
1395
+ if (hit !== undefined) return yield* Effect.fail(hostFailure(hit))
1396
+ }
1397
+ yield* fs.makeDirectory(root === "" ? "." : root, { recursive: true }).pipe(Effect.mapError(hostFailure))
1398
+ // Pinned once per commit, after the root exists: every lock, preflight,
1399
+ // apply, and rollback call below resolves against this root identity.
1400
+ const host = yield* KernelFileSystem.confined(fs, root).pipe(
1401
+ Effect.provideService(Path.Path, platformPath),
1402
+ Effect.mapError(hostFailure)
1403
+ )
1404
+ const canonical = yield* canonicalRoot
1405
+ // The confined view refuses `utimes`, which the lease heartbeat needs.
1406
+ // The heartbeat touches the lock only after a confined, no-follow read
1407
+ // found this call's own token there, so the one path-based call it
1408
+ // makes can at worst refresh an mtime.
1409
+ const leaseHost: FileSystem.FileSystem = { ...host, utimes: fs.utimes }
1410
+ return yield* coordinateCommit(
1411
+ canonical ?? root,
1412
+ FileLease.withLease(leaseHost, hostPath(commitLockName), effect(host), lockFailure, {
1413
+ label: "Workspace commit lock",
1414
+ annotations: { root: canonical ?? root }
1415
+ })
1416
+ )
1417
+ })
1418
+ const commitWith = Effect.fn("WorkspaceSandbox.commit")(function*(
1419
+ host: FileSystem.FileSystem,
1420
+ changes: ReadonlyArray<FileChange>
1421
+ ) {
1422
+ yield* Effect.annotateCurrentSpan({ changes: changes.length })
1423
+ // The advisory lock and process semaphore cover preflight, apply, and
1424
+ // rollback. Preconditions run only after both are acquired.
1425
+ const { before, conflict } = yield* preflight(
1426
+ changes,
1427
+ (path) => readFrom(host, hostPath(path), copyBackFailure(path))
1428
+ )
1429
+ if (conflict !== undefined) {
1430
+ yield* Metric.update(EngineStoreMetrics.materializationConflicts, 1)
1431
+ return yield* Effect.fail(conflict)
1432
+ }
1433
+ const resolved = new Map<string, Uint8Array>()
1434
+ for (const change of changes) {
1435
+ if (change.afterDigest === undefined) continue
1436
+ const bytes = change.after ?? (yield* artifacts.get(`${change.afterDigest}`).pipe(
1437
+ Effect.mapError((error) =>
1438
+ error._tag === "@smthrs/artifacts/ArtifactStoreError"
1439
+ ? artifactFailure(error)
1440
+ : new WorkspaceError({
1441
+ code: "not_found",
1442
+ message: `the retained bytes for ${change.path} are unavailable: ${error.message}`,
1443
+ cause: error
1444
+ })
1445
+ )
1446
+ ))
1447
+ resolved.set(change.path, bytes)
1448
+ }
1449
+ const applied: Array<FileChange> = []
1450
+ const apply = Effect.gen(function*() {
1451
+ for (const change of changes) {
1452
+ // Journal the change before touching its target: a write that
1453
+ // fails halfway may still have mutated the file it was writing.
1454
+ applied.push(change)
1455
+ const target = hostPath(change.path)
1456
+ if (change.afterDigest === undefined) {
1457
+ yield* host.remove(target).pipe(Effect.mapError(copyBackFailure(change.path)))
1458
+ continue
1459
+ }
1460
+ const parent = parentDirectory(target)
1461
+ if (parent !== undefined) {
1462
+ yield* host.makeDirectory(parent, { recursive: true }).pipe(
1463
+ Effect.mapError(copyBackFailure(change.path))
1464
+ )
1465
+ }
1466
+ yield* host.writeFile(target, resolved.get(change.path)!).pipe(
1467
+ Effect.mapError(copyBackFailure(change.path))
1468
+ )
1469
+ }
1470
+ })
1471
+ const rollback = Effect.gen(function*() {
1472
+ for (const change of [...applied].reverse()) {
1473
+ const target = hostPath(change.path)
1474
+ const previous = before.get(change.path)
1475
+ if (previous === undefined) {
1476
+ yield* host.remove(target, { force: true }).pipe(Effect.mapError(copyBackFailure(change.path)))
1477
+ } else {
1478
+ yield* host.writeFile(target, previous).pipe(Effect.mapError(copyBackFailure(change.path)))
1479
+ }
1480
+ }
1481
+ // Keep empty parent directories. Effect's portable FileSystem has no
1482
+ // atomic, non-recursive rmdir; recursively deleting a directory after
1483
+ // a separate emptiness check could erase a concurrent writer's file.
1484
+ })
1485
+ yield* apply.pipe(
1486
+ Effect.catchCause((cause) =>
1487
+ Effect.gen(function*() {
1488
+ const restored = yield* Effect.exit(rollback)
1489
+ if (Exit.isSuccess(restored)) return yield* Effect.failCause(cause)
1490
+ const reasons = restored.cause.reasons
1491
+ .filter(Cause.isFailReason)
1492
+ .map((reason) => reason.error.message)
1493
+ // Both failures travel: the apply cause that opened the window and
1494
+ // the rollback cause that could not close it, composed the way
1495
+ // `acquireUseRelease` composes a failed use with a failed release.
1496
+ return yield* Effect.failCause(
1497
+ Cause.combine(
1498
+ cause,
1499
+ Cause.fail(
1500
+ new WorkspaceError({
1501
+ code: "host_unavailable",
1502
+ message: `copy-back failed mid-apply and rollback could not restore the workspace: ${
1503
+ reasons.join("; ")
1504
+ }`,
1505
+ cause: restored.cause
1506
+ })
1507
+ )
1508
+ )
1509
+ )
1510
+ })
1511
+ ),
1512
+ // Interruption inside the apply window is a mid-sequence abort the
1513
+ // journal exists to prevent; the window is bounded local work, so it
1514
+ // closes before the fiber answers the interrupt.
1515
+ Effect.uninterruptible
1516
+ )
1517
+ })
1518
+ return makeHosted({
1519
+ root,
1520
+ snapshot: Effect.fn("WorkspaceSandbox.snapshot")(function*(descriptor) {
1521
+ yield* Effect.annotateCurrentSpan({
1522
+ reads: descriptor.readSet.length,
1523
+ boundaryMode: descriptor.boundaryMode
1524
+ })
1525
+ const base = new Map<string, Uint8Array>()
1526
+ for (const entry of descriptor.readSet) {
1527
+ // Through `FileEnumeration`, never the host `glob`: host results skip
1528
+ // dotfiles, so a declared read glob covering one would seed a sandbox
1529
+ // that silently hides it from the body.
1530
+ const paths = FileSet.isGlob(entry)
1531
+ ? yield* FileEnumeration.expandGlob(fs, entry, {
1532
+ resolve: (path) => path === "" ? (root === "" ? "." : root) : hostPath(path)
1533
+ }).pipe(Effect.mapError(hostFailure))
1534
+ : [entry.path]
1535
+ for (const path of paths) {
1536
+ const normalized = normalizePath(root, path)
1537
+ if (Result.isFailure(normalized)) return yield* Effect.fail(normalized.failure)
1538
+ const content = yield* readIfPresent(hostPath(normalized.success))
1539
+ // A declared read that does not exist is seeded as absent, not as an
1540
+ // error: `StepBoundary.prepare` already records the mismatch as the
1541
+ // evidence that refuses the cache hit.
1542
+ if (content !== undefined) base.set(normalized.success, content)
1543
+ }
1544
+ }
1545
+ for (const path of descriptor.removes ?? []) {
1546
+ const normalized = normalizePath(root, path)
1547
+ /* v8 ignore next -- `FileBoundary.removes` is `FileSet.Pattern`-checked, so a decoded boundary cannot carry an upward or absolute removal; the branch guards hand-built descriptors */
1548
+ if (Result.isFailure(normalized)) return yield* Effect.fail(normalized.failure)
1549
+ const content = yield* readIfPresent(hostPath(normalized.success))
1550
+ if (content !== undefined) base.set(normalized.success, content)
1551
+ }
1552
+ return base
1553
+ }),
1554
+ baseline: Effect.fn("WorkspaceSandbox.baseline")(function*(path) {
1555
+ const content = yield* readIfPresent(hostPath(path))
1556
+ return content === undefined ? undefined : yield* digestOf(content)
1557
+ }),
1558
+ // The bytes now live at the change's `afterDigest`; the change itself
1559
+ // carries only the reference, exactly as oversized `StepBoundary`
1560
+ // evidence does.
1561
+ retain: (bytes) =>
1562
+ bytes.length <= maxInlineBytes
1563
+ ? Effect.succeed(bytes)
1564
+ : artifacts.put(bytes).pipe(Effect.mapError(artifactFailure), Effect.as(undefined)),
1565
+ commit: (changes) => withCommitLock(changes, (host) => commitWith(host, changes))
1566
+ })
1567
+ }
1568
+
1569
+ /**
1570
+ * Provides the filesystem-backed workspace sandbox.
1571
+ *
1572
+ * Host access arrives through Effect's `FileSystem` tag — the same tag the
1573
+ * capability kernel decorates in place — and blob retention through
1574
+ * `@smthrs/artifacts`, so the same sandbox runs over a purely local store or a
1575
+ * local-plus-shared composition without knowing which it got. The workspace
1576
+ * root arrives through the kernel's `Workspace` service, so absolute paths a
1577
+ * body resolved for itself still land inside the transaction.
1578
+ *
1579
+ * The layer refuses to build over a host that {@link makeFileSystem} could
1580
+ * not confine (a plain path-based `FileSystem`), failing with
1581
+ * `host_unavailable`, so a composition that could never copy back safely is
1582
+ * rejected at startup rather than at its first commit.
1583
+ *
1584
+ * @category layers
1585
+ * @since 0.1.0
1586
+ */
1587
+ export const layerFileSystem = (
1588
+ options: FileSystemOptions = {}
1589
+ ): Layer.Layer<
1590
+ Service,
1591
+ WorkspaceError,
1592
+ FileSystem.FileSystem | Path.Path | ArtifactStore.ArtifactStore | KernelWorkspace
1593
+ > =>
1594
+ Layer.effect(
1595
+ WorkspaceSandbox,
1596
+ Effect.gen(function*() {
1597
+ const fs = yield* FileSystem.FileSystem
1598
+ const platformPath = yield* Path.Path
1599
+ const artifacts = yield* ArtifactStore.ArtifactStore
1600
+ const workspace = yield* KernelWorkspace
1601
+ yield* KernelFileSystem.requireConfinable(fs, workspace.root).pipe(Effect.mapError(hostFailure))
1602
+ return makeFileSystem(fs, platformPath, artifacts, workspace.root, options)
1603
+ })
1604
+ )