@smthrs/control 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 (418) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/LICENSE +21 -0
  3. package/README.md +168 -2
  4. package/dist/cjs/ApprovalAuthority.d.ts +73 -0
  5. package/dist/cjs/ApprovalAuthority.d.ts.map +1 -0
  6. package/dist/cjs/ApprovalAuthority.js +62 -0
  7. package/dist/cjs/ApprovalAuthority.js.map +7 -0
  8. package/dist/cjs/Cancellation.d.ts +107 -0
  9. package/dist/cjs/Cancellation.d.ts.map +1 -0
  10. package/dist/cjs/Cancellation.js +72 -0
  11. package/dist/cjs/Cancellation.js.map +7 -0
  12. package/dist/cjs/Channels.d.ts +170 -0
  13. package/dist/cjs/Channels.d.ts.map +1 -0
  14. package/dist/cjs/Channels.js +278 -0
  15. package/dist/cjs/Channels.js.map +7 -0
  16. package/dist/cjs/Control.d.ts +202 -0
  17. package/dist/cjs/Control.d.ts.map +1 -0
  18. package/dist/cjs/Control.js +47 -0
  19. package/dist/cjs/Control.js.map +7 -0
  20. package/dist/cjs/ControlClient.d.ts +52 -0
  21. package/dist/cjs/ControlClient.d.ts.map +1 -0
  22. package/dist/cjs/ControlClient.js +191 -0
  23. package/dist/cjs/ControlClient.js.map +7 -0
  24. package/dist/cjs/ControlError.d.ts +318 -0
  25. package/dist/cjs/ControlError.d.ts.map +1 -0
  26. package/dist/cjs/ControlError.js +249 -0
  27. package/dist/cjs/ControlError.js.map +7 -0
  28. package/dist/cjs/ControlExecutor.d.ts +372 -0
  29. package/dist/cjs/ControlExecutor.d.ts.map +1 -0
  30. package/dist/cjs/ControlExecutor.js +123 -0
  31. package/dist/cjs/ControlExecutor.js.map +7 -0
  32. package/dist/cjs/ControlFacts.d.ts +454 -0
  33. package/dist/cjs/ControlFacts.d.ts.map +1 -0
  34. package/dist/cjs/ControlFacts.js +261 -0
  35. package/dist/cjs/ControlFacts.js.map +7 -0
  36. package/dist/cjs/ControlLive.d.ts +23 -0
  37. package/dist/cjs/ControlLive.d.ts.map +1 -0
  38. package/dist/cjs/ControlLive.js +1293 -0
  39. package/dist/cjs/ControlLive.js.map +7 -0
  40. package/dist/cjs/ControlRpcs.d.ts +1204 -0
  41. package/dist/cjs/ControlRpcs.d.ts.map +1 -0
  42. package/dist/cjs/ControlRpcs.js +247 -0
  43. package/dist/cjs/ControlRpcs.js.map +7 -0
  44. package/dist/cjs/ControlRuntime.d.ts +635 -0
  45. package/dist/cjs/ControlRuntime.d.ts.map +1 -0
  46. package/dist/cjs/ControlRuntime.js +744 -0
  47. package/dist/cjs/ControlRuntime.js.map +7 -0
  48. package/dist/cjs/ControlSchema.d.ts +2642 -0
  49. package/dist/cjs/ControlSchema.d.ts.map +1 -0
  50. package/dist/cjs/ControlSchema.js +634 -0
  51. package/dist/cjs/ControlSchema.js.map +7 -0
  52. package/dist/cjs/ControlServer.d.ts +51 -0
  53. package/dist/cjs/ControlServer.d.ts.map +1 -0
  54. package/dist/cjs/ControlServer.js +121 -0
  55. package/dist/cjs/ControlServer.js.map +7 -0
  56. package/dist/cjs/Credential.d.ts +136 -0
  57. package/dist/cjs/Credential.d.ts.map +1 -0
  58. package/dist/cjs/Credential.js +168 -0
  59. package/dist/cjs/Credential.js.map +7 -0
  60. package/dist/cjs/CredentialCipher.d.ts +90 -0
  61. package/dist/cjs/CredentialCipher.d.ts.map +1 -0
  62. package/dist/cjs/CredentialCipher.js +45 -0
  63. package/dist/cjs/CredentialCipher.js.map +7 -0
  64. package/dist/cjs/CredentialStore.d.ts +97 -0
  65. package/dist/cjs/CredentialStore.d.ts.map +1 -0
  66. package/dist/cjs/CredentialStore.js +81 -0
  67. package/dist/cjs/CredentialStore.js.map +7 -0
  68. package/dist/cjs/DispatchReader.d.ts +112 -0
  69. package/dist/cjs/DispatchReader.d.ts.map +1 -0
  70. package/dist/cjs/DispatchReader.js +45 -0
  71. package/dist/cjs/DispatchReader.js.map +7 -0
  72. package/dist/cjs/Health.d.ts +333 -0
  73. package/dist/cjs/Health.d.ts.map +1 -0
  74. package/dist/cjs/Health.js +311 -0
  75. package/dist/cjs/Health.js.map +7 -0
  76. package/dist/cjs/JevSessionChecker.d.ts +57 -0
  77. package/dist/cjs/JevSessionChecker.d.ts.map +1 -0
  78. package/dist/cjs/JevSessionChecker.js +113 -0
  79. package/dist/cjs/JevSessionChecker.js.map +7 -0
  80. package/dist/cjs/Lineage.d.ts +131 -0
  81. package/dist/cjs/Lineage.d.ts.map +1 -0
  82. package/dist/cjs/Lineage.js +81 -0
  83. package/dist/cjs/Lineage.js.map +7 -0
  84. package/dist/cjs/Migrations.d.ts +34 -0
  85. package/dist/cjs/Migrations.d.ts.map +1 -0
  86. package/dist/cjs/Migrations.js +60 -0
  87. package/dist/cjs/Migrations.js.map +7 -0
  88. package/dist/cjs/Monitor.d.ts +282 -0
  89. package/dist/cjs/Monitor.d.ts.map +1 -0
  90. package/dist/cjs/Monitor.js +283 -0
  91. package/dist/cjs/Monitor.js.map +7 -0
  92. package/dist/cjs/ScopedToken.d.ts +193 -0
  93. package/dist/cjs/ScopedToken.d.ts.map +1 -0
  94. package/dist/cjs/ScopedToken.js +135 -0
  95. package/dist/cjs/ScopedToken.js.map +7 -0
  96. package/dist/cjs/SqlControlRuntime.d.ts +161 -0
  97. package/dist/cjs/SqlControlRuntime.d.ts.map +1 -0
  98. package/dist/cjs/SqlControlRuntime.js +1522 -0
  99. package/dist/cjs/SqlControlRuntime.js.map +7 -0
  100. package/dist/cjs/SqlCredentialStore.d.ts +43 -0
  101. package/dist/cjs/SqlCredentialStore.d.ts.map +1 -0
  102. package/dist/cjs/SqlCredentialStore.js +113 -0
  103. package/dist/cjs/SqlCredentialStore.js.map +7 -0
  104. package/dist/cjs/Steering.d.ts +69 -0
  105. package/dist/cjs/Steering.d.ts.map +1 -0
  106. package/dist/cjs/Steering.js +49 -0
  107. package/dist/cjs/Steering.js.map +7 -0
  108. package/dist/cjs/SystemFlows.d.ts +223 -0
  109. package/dist/cjs/SystemFlows.d.ts.map +1 -0
  110. package/dist/cjs/SystemFlows.js +195 -0
  111. package/dist/cjs/SystemFlows.js.map +7 -0
  112. package/dist/cjs/WebCryptoCipher.d.ts +49 -0
  113. package/dist/cjs/WebCryptoCipher.d.ts.map +1 -0
  114. package/dist/cjs/WebCryptoCipher.js +129 -0
  115. package/dist/cjs/WebCryptoCipher.js.map +7 -0
  116. package/dist/cjs/WebhookChannel.d.ts +113 -0
  117. package/dist/cjs/WebhookChannel.d.ts.map +1 -0
  118. package/dist/cjs/WebhookChannel.js +98 -0
  119. package/dist/cjs/WebhookChannel.js.map +7 -0
  120. package/dist/cjs/index.d.ts +160 -0
  121. package/dist/cjs/index.d.ts.map +1 -0
  122. package/dist/cjs/index.js +91 -0
  123. package/dist/cjs/index.js.map +7 -0
  124. package/dist/cjs/internal/MutationBoundary.d.ts +27 -0
  125. package/dist/cjs/internal/MutationBoundary.d.ts.map +1 -0
  126. package/dist/cjs/internal/MutationBoundary.js +50 -0
  127. package/dist/cjs/internal/MutationBoundary.js.map +7 -0
  128. package/dist/cjs/internal/activeFibers.d.ts +12 -0
  129. package/dist/cjs/internal/activeFibers.d.ts.map +1 -0
  130. package/dist/cjs/internal/activeFibers.js +30 -0
  131. package/dist/cjs/internal/activeFibers.js.map +7 -0
  132. package/dist/cjs/internal/issues.d.ts +28 -0
  133. package/dist/cjs/internal/issues.d.ts.map +1 -0
  134. package/dist/cjs/internal/issues.js +34 -0
  135. package/dist/cjs/internal/issues.js.map +7 -0
  136. package/dist/cjs/internal/planning.d.ts +347 -0
  137. package/dist/cjs/internal/planning.d.ts.map +1 -0
  138. package/dist/cjs/internal/planning.js +137 -0
  139. package/dist/cjs/internal/planning.js.map +7 -0
  140. package/dist/cjs/internal/sqlSchemaErrors.d.ts +18 -0
  141. package/dist/cjs/internal/sqlSchemaErrors.d.ts.map +1 -0
  142. package/dist/cjs/internal/sqlSchemaErrors.js +40 -0
  143. package/dist/cjs/internal/sqlSchemaErrors.js.map +7 -0
  144. package/dist/cjs/migrations/0001_control_tables.d.ts +18 -0
  145. package/dist/cjs/migrations/0001_control_tables.d.ts.map +1 -0
  146. package/dist/cjs/migrations/0001_control_tables.js +115 -0
  147. package/dist/cjs/migrations/0001_control_tables.js.map +7 -0
  148. package/dist/cjs/migrations/0002_run_keys.d.ts +15 -0
  149. package/dist/cjs/migrations/0002_run_keys.d.ts.map +1 -0
  150. package/dist/cjs/migrations/0002_run_keys.js +44 -0
  151. package/dist/cjs/migrations/0002_run_keys.js.map +7 -0
  152. package/dist/cjs/migrations/0003_signal_commands.d.ts +13 -0
  153. package/dist/cjs/migrations/0003_signal_commands.d.ts.map +1 -0
  154. package/dist/cjs/migrations/0003_signal_commands.js +50 -0
  155. package/dist/cjs/migrations/0003_signal_commands.js.map +7 -0
  156. package/dist/cjs/migrations/0004_approval_decisions.d.ts +13 -0
  157. package/dist/cjs/migrations/0004_approval_decisions.d.ts.map +1 -0
  158. package/dist/cjs/migrations/0004_approval_decisions.js +45 -0
  159. package/dist/cjs/migrations/0004_approval_decisions.js.map +7 -0
  160. package/dist/cjs/migrations/0005_signal_principals.d.ts +18 -0
  161. package/dist/cjs/migrations/0005_signal_principals.d.ts.map +1 -0
  162. package/dist/cjs/migrations/0005_signal_principals.js +45 -0
  163. package/dist/cjs/migrations/0005_signal_principals.js.map +7 -0
  164. package/dist/cjs/migrations/0006_run_principals.d.ts +18 -0
  165. package/dist/cjs/migrations/0006_run_principals.d.ts.map +1 -0
  166. package/dist/cjs/migrations/0006_run_principals.js +49 -0
  167. package/dist/cjs/migrations/0006_run_principals.js.map +7 -0
  168. package/dist/cjs/migrations/0007_resume_consent.d.ts +18 -0
  169. package/dist/cjs/migrations/0007_resume_consent.d.ts.map +1 -0
  170. package/dist/cjs/migrations/0007_resume_consent.js +46 -0
  171. package/dist/cjs/migrations/0007_resume_consent.js.map +7 -0
  172. package/dist/cjs/package.json +1 -0
  173. package/dist/cjs/test/TestControl.d.ts +19 -0
  174. package/dist/cjs/test/TestControl.d.ts.map +1 -0
  175. package/dist/cjs/test/TestControl.js +62 -0
  176. package/dist/cjs/test/TestControl.js.map +7 -0
  177. package/dist/esm/ApprovalAuthority.d.ts +73 -0
  178. package/dist/esm/ApprovalAuthority.d.ts.map +1 -0
  179. package/dist/esm/ApprovalAuthority.js +72 -0
  180. package/dist/esm/ApprovalAuthority.js.map +1 -0
  181. package/dist/esm/Cancellation.d.ts +107 -0
  182. package/dist/esm/Cancellation.d.ts.map +1 -0
  183. package/dist/esm/Cancellation.js +116 -0
  184. package/dist/esm/Cancellation.js.map +1 -0
  185. package/dist/esm/Channels.d.ts +170 -0
  186. package/dist/esm/Channels.d.ts.map +1 -0
  187. package/dist/esm/Channels.js +312 -0
  188. package/dist/esm/Channels.js.map +1 -0
  189. package/dist/esm/Control.d.ts +202 -0
  190. package/dist/esm/Control.d.ts.map +1 -0
  191. package/dist/esm/Control.js +42 -0
  192. package/dist/esm/Control.js.map +1 -0
  193. package/dist/esm/ControlClient.d.ts +52 -0
  194. package/dist/esm/ControlClient.d.ts.map +1 -0
  195. package/dist/esm/ControlClient.js +217 -0
  196. package/dist/esm/ControlClient.js.map +1 -0
  197. package/dist/esm/ControlError.d.ts +318 -0
  198. package/dist/esm/ControlError.d.ts.map +1 -0
  199. package/dist/esm/ControlError.js +359 -0
  200. package/dist/esm/ControlError.js.map +1 -0
  201. package/dist/esm/ControlExecutor.d.ts +372 -0
  202. package/dist/esm/ControlExecutor.d.ts.map +1 -0
  203. package/dist/esm/ControlExecutor.js +212 -0
  204. package/dist/esm/ControlExecutor.js.map +1 -0
  205. package/dist/esm/ControlFacts.d.ts +454 -0
  206. package/dist/esm/ControlFacts.d.ts.map +1 -0
  207. package/dist/esm/ControlFacts.js +324 -0
  208. package/dist/esm/ControlFacts.js.map +1 -0
  209. package/dist/esm/ControlLive.d.ts +23 -0
  210. package/dist/esm/ControlLive.d.ts.map +1 -0
  211. package/dist/esm/ControlLive.js +1589 -0
  212. package/dist/esm/ControlLive.js.map +1 -0
  213. package/dist/esm/ControlRpcs.d.ts +1204 -0
  214. package/dist/esm/ControlRpcs.d.ts.map +1 -0
  215. package/dist/esm/ControlRpcs.js +299 -0
  216. package/dist/esm/ControlRpcs.js.map +1 -0
  217. package/dist/esm/ControlRuntime.d.ts +635 -0
  218. package/dist/esm/ControlRuntime.d.ts.map +1 -0
  219. package/dist/esm/ControlRuntime.js +808 -0
  220. package/dist/esm/ControlRuntime.js.map +1 -0
  221. package/dist/esm/ControlSchema.d.ts +2642 -0
  222. package/dist/esm/ControlSchema.d.ts.map +1 -0
  223. package/dist/esm/ControlSchema.js +1030 -0
  224. package/dist/esm/ControlSchema.js.map +1 -0
  225. package/dist/esm/ControlServer.d.ts +51 -0
  226. package/dist/esm/ControlServer.d.ts.map +1 -0
  227. package/dist/esm/ControlServer.js +145 -0
  228. package/dist/esm/ControlServer.js.map +1 -0
  229. package/dist/esm/Credential.d.ts +136 -0
  230. package/dist/esm/Credential.d.ts.map +1 -0
  231. package/dist/esm/Credential.js +190 -0
  232. package/dist/esm/Credential.js.map +1 -0
  233. package/dist/esm/CredentialCipher.d.ts +90 -0
  234. package/dist/esm/CredentialCipher.d.ts.map +1 -0
  235. package/dist/esm/CredentialCipher.js +56 -0
  236. package/dist/esm/CredentialCipher.js.map +1 -0
  237. package/dist/esm/CredentialStore.d.ts +97 -0
  238. package/dist/esm/CredentialStore.d.ts.map +1 -0
  239. package/dist/esm/CredentialStore.js +101 -0
  240. package/dist/esm/CredentialStore.js.map +1 -0
  241. package/dist/esm/DispatchReader.d.ts +112 -0
  242. package/dist/esm/DispatchReader.d.ts.map +1 -0
  243. package/dist/esm/DispatchReader.js +76 -0
  244. package/dist/esm/DispatchReader.js.map +1 -0
  245. package/dist/esm/Health.d.ts +333 -0
  246. package/dist/esm/Health.d.ts.map +1 -0
  247. package/dist/esm/Health.js +400 -0
  248. package/dist/esm/Health.js.map +1 -0
  249. package/dist/esm/JevSessionChecker.d.ts +57 -0
  250. package/dist/esm/JevSessionChecker.d.ts.map +1 -0
  251. package/dist/esm/JevSessionChecker.js +108 -0
  252. package/dist/esm/JevSessionChecker.js.map +1 -0
  253. package/dist/esm/Lineage.d.ts +131 -0
  254. package/dist/esm/Lineage.d.ts.map +1 -0
  255. package/dist/esm/Lineage.js +174 -0
  256. package/dist/esm/Lineage.js.map +1 -0
  257. package/dist/esm/Migrations.d.ts +34 -0
  258. package/dist/esm/Migrations.d.ts.map +1 -0
  259. package/dist/esm/Migrations.js +53 -0
  260. package/dist/esm/Migrations.js.map +1 -0
  261. package/dist/esm/Monitor.d.ts +282 -0
  262. package/dist/esm/Monitor.d.ts.map +1 -0
  263. package/dist/esm/Monitor.js +415 -0
  264. package/dist/esm/Monitor.js.map +1 -0
  265. package/dist/esm/ScopedToken.d.ts +193 -0
  266. package/dist/esm/ScopedToken.d.ts.map +1 -0
  267. package/dist/esm/ScopedToken.js +224 -0
  268. package/dist/esm/ScopedToken.js.map +1 -0
  269. package/dist/esm/SqlControlRuntime.d.ts +161 -0
  270. package/dist/esm/SqlControlRuntime.d.ts.map +1 -0
  271. package/dist/esm/SqlControlRuntime.js +1756 -0
  272. package/dist/esm/SqlControlRuntime.js.map +1 -0
  273. package/dist/esm/SqlCredentialStore.d.ts +43 -0
  274. package/dist/esm/SqlCredentialStore.d.ts.map +1 -0
  275. package/dist/esm/SqlCredentialStore.js +97 -0
  276. package/dist/esm/SqlCredentialStore.js.map +1 -0
  277. package/dist/esm/Steering.d.ts +69 -0
  278. package/dist/esm/Steering.d.ts.map +1 -0
  279. package/dist/esm/Steering.js +89 -0
  280. package/dist/esm/Steering.js.map +1 -0
  281. package/dist/esm/SystemFlows.d.ts +223 -0
  282. package/dist/esm/SystemFlows.d.ts.map +1 -0
  283. package/dist/esm/SystemFlows.js +198 -0
  284. package/dist/esm/SystemFlows.js.map +1 -0
  285. package/dist/esm/WebCryptoCipher.d.ts +49 -0
  286. package/dist/esm/WebCryptoCipher.d.ts.map +1 -0
  287. package/dist/esm/WebCryptoCipher.js +123 -0
  288. package/dist/esm/WebCryptoCipher.js.map +1 -0
  289. package/dist/esm/WebhookChannel.d.ts +113 -0
  290. package/dist/esm/WebhookChannel.d.ts.map +1 -0
  291. package/dist/esm/WebhookChannel.js +109 -0
  292. package/dist/esm/WebhookChannel.js.map +1 -0
  293. package/dist/esm/index.d.ts +160 -0
  294. package/dist/esm/index.d.ts.map +1 -0
  295. package/dist/esm/index.js +160 -0
  296. package/dist/esm/index.js.map +1 -0
  297. package/dist/esm/internal/MutationBoundary.d.ts +27 -0
  298. package/dist/esm/internal/MutationBoundary.d.ts.map +1 -0
  299. package/dist/esm/internal/MutationBoundary.js +40 -0
  300. package/dist/esm/internal/MutationBoundary.js.map +1 -0
  301. package/dist/esm/internal/activeFibers.d.ts +12 -0
  302. package/dist/esm/internal/activeFibers.d.ts.map +1 -0
  303. package/dist/esm/internal/activeFibers.js +17 -0
  304. package/dist/esm/internal/activeFibers.js.map +1 -0
  305. package/dist/esm/internal/issues.d.ts +28 -0
  306. package/dist/esm/internal/issues.d.ts.map +1 -0
  307. package/dist/esm/internal/issues.js +35 -0
  308. package/dist/esm/internal/issues.js.map +1 -0
  309. package/dist/esm/internal/planning.d.ts +347 -0
  310. package/dist/esm/internal/planning.d.ts.map +1 -0
  311. package/dist/esm/internal/planning.js +199 -0
  312. package/dist/esm/internal/planning.js.map +1 -0
  313. package/dist/esm/internal/sqlSchemaErrors.d.ts +18 -0
  314. package/dist/esm/internal/sqlSchemaErrors.d.ts.map +1 -0
  315. package/dist/esm/internal/sqlSchemaErrors.js +38 -0
  316. package/dist/esm/internal/sqlSchemaErrors.js.map +1 -0
  317. package/dist/esm/migrations/0001_control_tables.d.ts +18 -0
  318. package/dist/esm/migrations/0001_control_tables.d.ts.map +1 -0
  319. package/dist/esm/migrations/0001_control_tables.js +96 -0
  320. package/dist/esm/migrations/0001_control_tables.js.map +1 -0
  321. package/dist/esm/migrations/0002_run_keys.d.ts +15 -0
  322. package/dist/esm/migrations/0002_run_keys.d.ts.map +1 -0
  323. package/dist/esm/migrations/0002_run_keys.js +22 -0
  324. package/dist/esm/migrations/0002_run_keys.js.map +1 -0
  325. package/dist/esm/migrations/0003_signal_commands.d.ts +13 -0
  326. package/dist/esm/migrations/0003_signal_commands.d.ts.map +1 -0
  327. package/dist/esm/migrations/0003_signal_commands.js +26 -0
  328. package/dist/esm/migrations/0003_signal_commands.js.map +1 -0
  329. package/dist/esm/migrations/0004_approval_decisions.d.ts +13 -0
  330. package/dist/esm/migrations/0004_approval_decisions.d.ts.map +1 -0
  331. package/dist/esm/migrations/0004_approval_decisions.js +25 -0
  332. package/dist/esm/migrations/0004_approval_decisions.js.map +1 -0
  333. package/dist/esm/migrations/0005_signal_principals.d.ts +18 -0
  334. package/dist/esm/migrations/0005_signal_principals.d.ts.map +1 -0
  335. package/dist/esm/migrations/0005_signal_principals.js +27 -0
  336. package/dist/esm/migrations/0005_signal_principals.js.map +1 -0
  337. package/dist/esm/migrations/0006_run_principals.d.ts +18 -0
  338. package/dist/esm/migrations/0006_run_principals.d.ts.map +1 -0
  339. package/dist/esm/migrations/0006_run_principals.js +30 -0
  340. package/dist/esm/migrations/0006_run_principals.js.map +1 -0
  341. package/dist/esm/migrations/0007_resume_consent.d.ts +18 -0
  342. package/dist/esm/migrations/0007_resume_consent.d.ts.map +1 -0
  343. package/dist/esm/migrations/0007_resume_consent.js +27 -0
  344. package/dist/esm/migrations/0007_resume_consent.js.map +1 -0
  345. package/dist/esm/test/TestControl.d.ts +19 -0
  346. package/dist/esm/test/TestControl.d.ts.map +1 -0
  347. package/dist/esm/test/TestControl.js +30 -0
  348. package/dist/esm/test/TestControl.js.map +1 -0
  349. package/docs/README.md +189 -0
  350. package/docs/api.md +982 -0
  351. package/docs/concepts/authority.md +109 -0
  352. package/docs/concepts/cancellation.md +129 -0
  353. package/docs/concepts/lineage.md +132 -0
  354. package/docs/concepts/ownership.md +139 -0
  355. package/docs/concepts/projections.md +203 -0
  356. package/docs/concepts/receipts.md +128 -0
  357. package/docs/guides/approvals.md +284 -0
  358. package/docs/guides/cancel-and-resume.md +162 -0
  359. package/docs/guides/durable-storage.md +147 -0
  360. package/docs/guides/implement-an-executor.md +173 -0
  361. package/docs/guides/ingest-a-webhook.md +177 -0
  362. package/docs/guides/list-runs.md +160 -0
  363. package/docs/guides/monitor-runs.md +176 -0
  364. package/docs/guides/observe-health.md +147 -0
  365. package/docs/guides/postgres-tests.md +7 -0
  366. package/docs/guides/serve-over-rpc.md +220 -0
  367. package/docs/guides/signal-a-run.md +53 -0
  368. package/docs/guides/steer-a-run.md +138 -0
  369. package/docs/guides/store-credentials.md +164 -0
  370. package/docs/guides/testing.md +139 -0
  371. package/docs/guides/watch-a-run.md +154 -0
  372. package/docs/installation.md +106 -0
  373. package/docs/quickstart.md +163 -0
  374. package/docs/troubleshooting.md +208 -0
  375. package/package.json +405 -3
  376. package/src/ApprovalAuthority.ts +114 -0
  377. package/src/Cancellation.ts +172 -0
  378. package/src/Channels.ts +493 -0
  379. package/src/Control.ts +337 -0
  380. package/src/ControlClient.ts +319 -0
  381. package/src/ControlError.ts +378 -0
  382. package/src/ControlExecutor.ts +490 -0
  383. package/src/ControlFacts.ts +383 -0
  384. package/src/ControlLive.ts +2127 -0
  385. package/src/ControlRpcs.ts +443 -0
  386. package/src/ControlRuntime.ts +1601 -0
  387. package/src/ControlSchema.ts +1380 -0
  388. package/src/ControlServer.ts +182 -0
  389. package/src/Credential.ts +310 -0
  390. package/src/CredentialCipher.ts +110 -0
  391. package/src/CredentialStore.ts +152 -0
  392. package/src/DispatchReader.ts +122 -0
  393. package/src/Health.ts +591 -0
  394. package/src/JevSessionChecker.ts +127 -0
  395. package/src/Lineage.ts +203 -0
  396. package/src/Migrations.ts +56 -0
  397. package/src/Monitor.ts +600 -0
  398. package/src/ScopedToken.ts +306 -0
  399. package/src/SqlControlRuntime.ts +2478 -0
  400. package/src/SqlCredentialStore.ts +148 -0
  401. package/src/Steering.ts +96 -0
  402. package/src/SystemFlows.ts +225 -0
  403. package/src/WebCryptoCipher.ts +169 -0
  404. package/src/WebhookChannel.ts +166 -0
  405. package/src/index.ts +188 -0
  406. package/src/internal/MutationBoundary.ts +46 -0
  407. package/src/internal/activeFibers.ts +22 -0
  408. package/src/internal/issues.ts +40 -0
  409. package/src/internal/planning.ts +262 -0
  410. package/src/internal/sqlSchemaErrors.ts +37 -0
  411. package/src/migrations/0001_control_tables.ts +99 -0
  412. package/src/migrations/0002_run_keys.ts +23 -0
  413. package/src/migrations/0003_signal_commands.ts +27 -0
  414. package/src/migrations/0004_approval_decisions.ts +25 -0
  415. package/src/migrations/0005_signal_principals.ts +27 -0
  416. package/src/migrations/0006_run_principals.ts +31 -0
  417. package/src/migrations/0007_resume_consent.ts +28 -0
  418. package/src/test/TestControl.ts +47 -0
@@ -0,0 +1,1293 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var ControlLive_exports = {};
30
+ __export(ControlLive_exports, {
31
+ layer: () => layer
32
+ });
33
+ module.exports = __toCommonJS(ControlLive_exports);
34
+ var Sha256 = __toESM(require("@smthrs/crypto/Sha256"), 1);
35
+ var Fault = __toESM(require("@smthrs/flow/Fault"), 1);
36
+ var import_journal = require("@smthrs/journal");
37
+ var import_notifications = require("@smthrs/notifications");
38
+ var SteerPayload = __toESM(require("@smthrs/notifications/SteerPayload"), 1);
39
+ var import_registry = require("@smthrs/registry");
40
+ var import_Descriptor = require("@smthrs/registry/Descriptor");
41
+ var import_effect = require("effect");
42
+ var Cancellation = __toESM(require("./Cancellation.js"), 1);
43
+ var import_Control = require("./Control.js");
44
+ var import_ControlError = require("./ControlError.js");
45
+ var import_ControlExecutor = require("./ControlExecutor.js");
46
+ var ControlFacts = __toESM(require("./ControlFacts.js"), 1);
47
+ var import_ControlRuntime = require("./ControlRuntime.js");
48
+ var import_ControlSchema = require("./ControlSchema.js");
49
+ var DispatchReader = __toESM(require("./DispatchReader.js"), 1);
50
+ var import_issues = require("./internal/issues.js");
51
+ var MutationBoundary = __toESM(require("./internal/MutationBoundary.js"), 1);
52
+ var import_planning = require("./internal/planning.js");
53
+ var Lineage = __toESM(require("./Lineage.js"), 1);
54
+ var Steering = __toESM(require("./Steering.js"), 1);
55
+ const sourceId = import_journal.JournalEvent.SourceId.make("/control");
56
+ const snapshotPageSize = 1024;
57
+ const snapshotPartitionConcurrency = 8;
58
+ const partitionPageSize = 100;
59
+ const unavailable = (feature) => new import_ControlError.Unavailable({ feature, ticket: "control-runtime-engine-integration" });
60
+ const watchReadFailed = (cause) => cause.code === "journal_closed" ? unavailable("watch") : new import_ControlError.PersistenceError({
61
+ operation: "watch",
62
+ message: `Reading the control journal failed (${cause.code})`,
63
+ cause
64
+ });
65
+ const accepted = (key, runId) => runId === void 0 ? { _tag: "Accepted", receiptId: key } : { _tag: "Accepted", receiptId: key, runId };
66
+ const terminal = (status) => status === "cancelled" || status === "completed" || status === "failed";
67
+ const live = (status) => status === "running" || status === "accepted";
68
+ const terminalOrAccepted = (key, run) => terminal(run.status) ? { _tag: "Terminal", runId: run.runId, status: run.status } : accepted(key, run.runId);
69
+ const withoutStampedPrincipal = (input) => {
70
+ if (input === null || typeof input !== "object") return input;
71
+ const { principal: _principal, ...rest } = input;
72
+ const message = rest["message"];
73
+ if (message === null || typeof message !== "object") return rest;
74
+ const { principal: _messagePrincipal, ...messageRest } = message;
75
+ return { ...rest, message: messageRest };
76
+ };
77
+ const fingerprint = (operation, principal, input) => `control-mutation:v2:${Sha256.digestSync((0, import_planning.canonical)({
78
+ operation,
79
+ actor: { id: principal.id, kind: principal.kind },
80
+ intent: withoutStampedPrincipal(input)
81
+ }))}`;
82
+ const json = (value) => JSON.parse(JSON.stringify(value));
83
+ const declaredTrigger = (decodedInput) => {
84
+ if (decodedInput === null || typeof decodedInput !== "object" || Array.isArray(decodedInput)) return void 0;
85
+ const held = decodedInput["trigger"];
86
+ return held !== null && typeof held === "object" && !Array.isArray(held) ? held : void 0;
87
+ };
88
+ const invalid = (issue) => new import_ControlError.InvalidInput({ issue });
89
+ const AttributedApprovalInput = import_effect.Schema.Struct({
90
+ ...import_ControlSchema.ApprovalInputSchema.fields,
91
+ principal: import_effect.Schema.optional(import_ControlSchema.Principal)
92
+ });
93
+ const AttributedReasonedMutationInput = import_effect.Schema.Struct({
94
+ ...import_ControlSchema.ReasonedMutationInputSchema.fields,
95
+ principal: import_effect.Schema.optional(import_ControlSchema.Principal)
96
+ });
97
+ const AttributedResumeInput = import_effect.Schema.Struct({
98
+ ...import_ControlSchema.ResumeInputSchema.fields,
99
+ principal: import_effect.Schema.optional(import_ControlSchema.Principal)
100
+ });
101
+ const AttributedRunInput = import_effect.Schema.Union([
102
+ import_effect.Schema.Struct({
103
+ ...import_ControlSchema.RunInputSchema.members[0].fields,
104
+ principal: import_effect.Schema.optional(import_ControlSchema.Principal),
105
+ reservedRunId: import_effect.Schema.optional(import_effect.Schema.NonEmptyString)
106
+ }),
107
+ import_effect.Schema.Struct({ ...import_ControlSchema.RunInputSchema.members[1].fields, principal: import_effect.Schema.optional(import_ControlSchema.Principal) })
108
+ ]);
109
+ const AttributedSignalInput = import_effect.Schema.Struct({
110
+ ...import_ControlSchema.SignalInputSchema.fields,
111
+ principal: import_effect.Schema.optional(import_ControlSchema.Principal)
112
+ });
113
+ const validIdempotencyKey = (value) => {
114
+ if (value.length === 0 || value.length > 1024 || value.includes("\0")) return false;
115
+ for (let index = 0; index < value.length; index++) {
116
+ const unit = value.charCodeAt(index);
117
+ if (unit >= 55296 && unit <= 56319) {
118
+ const low = value.charCodeAt(++index);
119
+ if (!(low >= 56320 && low <= 57343)) return false;
120
+ continue;
121
+ }
122
+ if (unit >= 56320 && unit <= 57343) return false;
123
+ }
124
+ return true;
125
+ };
126
+ const snapshotMutation = (operation, decode, input) => import_effect.Effect.suspend(() => {
127
+ const admitted = MutationBoundary.admit(input);
128
+ if (!admitted.ok) return import_effect.Effect.fail(invalid(`${operation}: ${admitted.complaint}`));
129
+ return decode(admitted.value).pipe(
130
+ import_effect.Effect.mapError((error) => invalid(`${operation}: invalid mutation at ${(0, import_issues.schemaIssuePath)(error)}`)),
131
+ import_effect.Effect.flatMap(
132
+ (snapshot) => validIdempotencyKey(snapshot.idempotencyKey) ? import_effect.Effect.succeed(snapshot) : import_effect.Effect.fail(invalid(`${operation}.idempotencyKey: must be 1 to 1024 well-formed characters`))
133
+ )
134
+ );
135
+ });
136
+ const snapshotApproval = (operation, input) => snapshotMutation(operation, import_effect.Schema.decodeUnknownEffect(AttributedApprovalInput), input);
137
+ const snapshotReasonedMutation = (operation, input) => snapshotMutation(operation, import_effect.Schema.decodeUnknownEffect(AttributedReasonedMutationInput), input);
138
+ const snapshotResume = (input) => snapshotMutation("resume", import_effect.Schema.decodeUnknownEffect(AttributedResumeInput), input);
139
+ const snapshotRun = (input) => snapshotMutation("run", import_effect.Schema.decodeUnknownEffect(AttributedRunInput), input);
140
+ const snapshotSignal = (input) => snapshotMutation("signal", import_effect.Schema.decodeUnknownEffect(AttributedSignalInput), input);
141
+ const snapshotSteer = (input) => snapshotMutation("steer", import_effect.Schema.decodeUnknownEffect(import_ControlSchema.SteerInputSchema), input);
142
+ const pageBounds = (cursor, limit) => {
143
+ if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 1 || limit > import_ControlSchema.maxPageSize)) {
144
+ return import_effect.Effect.fail(
145
+ invalid(`limit: must be an integer between 1 and ${import_ControlSchema.maxPageSize}, received ${String(limit)}`)
146
+ );
147
+ }
148
+ if (cursor === void 0) return import_effect.Effect.succeed({ start: 0, size: limit ?? import_ControlSchema.defaultPageSize });
149
+ const start = Number(cursor);
150
+ return Number.isSafeInteger(start) && start >= 0 ? import_effect.Effect.succeed({ start, size: limit ?? import_ControlSchema.defaultPageSize }) : import_effect.Effect.fail(invalid(`cursor: must be a cursor this listing returned, received ${JSON.stringify(cursor)}`));
151
+ };
152
+ const cursorNatural = import_effect.Schema.Number.check(
153
+ import_effect.Schema.isInt(),
154
+ import_effect.Schema.isGreaterThanOrEqualTo(0),
155
+ import_effect.Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER)
156
+ );
157
+ const runCursor = import_effect.Schema.fromJsonString(import_effect.Schema.Struct({
158
+ version: import_effect.Schema.Literal(1),
159
+ filters: import_effect.Schema.String,
160
+ source: import_effect.Schema.Union([import_effect.Schema.Literal(0), import_effect.Schema.Literal(1)]),
161
+ sequence: cursorNatural,
162
+ createdAt: cursorNatural,
163
+ runId: import_effect.Schema.NonEmptyString
164
+ }));
165
+ const page = (values, bounds) => {
166
+ const items = values.slice(bounds.start, bounds.start + bounds.size);
167
+ const next = bounds.start + items.length;
168
+ return next < values.length ? { items, nextCursor: String(next) } : { items };
169
+ };
170
+ const eventFromEntry = (entry) => ({
171
+ sequence: entry.seq,
172
+ kind: entry.eventType,
173
+ runId: entry.runId,
174
+ occurredAt: entry.emittedAtMs,
175
+ payload: entry.payload
176
+ });
177
+ const layer = import_effect.Layer.effect(
178
+ import_Control.Control,
179
+ import_effect.Effect.gen(function* () {
180
+ const runtime = yield* import_ControlRuntime.ControlRuntime;
181
+ const journal = yield* import_journal.Journal.Journal;
182
+ const notifications = yield* import_notifications.NotificationQueue.NotificationQueue;
183
+ const registry = yield* import_registry.Registry.Registry;
184
+ const executor = yield* import_effect.Effect.serviceOption(import_ControlExecutor.ControlExecutor);
185
+ const dispatch = import_effect.Option.getOrElse(
186
+ yield* import_effect.Effect.serviceOption(DispatchReader.DispatchReader),
187
+ DispatchReader.makeNone
188
+ );
189
+ const observing = import_effect.Option.isSome(executor) && executor.value.readExecution !== void 0;
190
+ const observe = (run) => import_effect.Effect.gen(function* () {
191
+ if (import_effect.Option.isNone(executor) || executor.value.readExecution === void 0) return run;
192
+ const observed = yield* executor.value.readExecution(run.runId);
193
+ if (observed._tag === "Missing") return { ...run, executionObservation: "missing" };
194
+ return {
195
+ ...run,
196
+ executionObservation: "observed",
197
+ executionView: observed.executionView,
198
+ status: observed.status,
199
+ waitingReason: observed.waitingReason,
200
+ parentRunId: observed.parentRunId,
201
+ lineageId: observed.lineageId,
202
+ roundOrdinal: observed.roundOrdinal,
203
+ // The executor's answer replaces this plane's copy here as it does
204
+ // for every other observed field. It has to: a control plane over
205
+ // its own coordination database cannot see the executions a flow
206
+ // spawned, so a nested `HumanTask` park is a fact only the executor
207
+ // holds. Where the two share one database the runtime computes the
208
+ // same rows itself and the observation reproduces them.
209
+ pendingWaits: observed.pendingWaits
210
+ };
211
+ });
212
+ const getRun = (runId) => runtime.getRun(runId).pipe(import_effect.Effect.flatMap(observe));
213
+ const refuseCodeDrift = (runId) => runtime.codeDrift(runId).pipe(import_effect.Effect.flatMap((drift) => drift === void 0 ? import_effect.Effect.void : import_effect.Effect.fail(drift)));
214
+ const withCodeDrift = (run) => terminal(run.status) ? import_effect.Effect.succeed(run) : runtime.codeDrift(run.runId).pipe(
215
+ import_effect.Effect.map(
216
+ (drift) => drift === void 0 ? run : {
217
+ ...run,
218
+ codeDrift: {
219
+ ...drift.recorded === void 0 ? {} : { recorded: drift.recorded },
220
+ ...drift.current === void 0 ? {} : { current: drift.current },
221
+ ...drift.recordedEngine === void 0 ? {} : { recordedEngine: drift.recordedEngine },
222
+ ...drift.currentEngine === void 0 ? {} : { currentEngine: drift.currentEngine }
223
+ }
224
+ }
225
+ ),
226
+ import_effect.Effect.orElseSucceed(() => run)
227
+ );
228
+ const mutationSemaphore = yield* import_effect.Semaphore.make(1);
229
+ const record = (runId, eventType, payload) => (
230
+ // Unfenced: the control plane mutates runs it does not own — that is
231
+ // the point of a control plane — so its event records are
232
+ // first-writer-wins admissions, not owner-fenced lifecycle writes.
233
+ journal.emitDurableUnfenced(
234
+ new import_journal.JournalEvent.Input({
235
+ runId: import_journal.JournalEvent.RunId.make(runId),
236
+ sourceId,
237
+ eventType,
238
+ payload: json(payload)
239
+ })
240
+ ).pipe(
241
+ import_effect.Effect.map((receipt) => receipt.seq),
242
+ import_effect.Effect.mapError(
243
+ (cause) => new import_ControlError.PersistenceError({
244
+ operation: eventType,
245
+ message: `Failed to persist ${eventType}`,
246
+ cause
247
+ })
248
+ )
249
+ )
250
+ );
251
+ const emit = (runId, eventType, payload) => import_effect.Effect.asVoid(record(runId, eventType, payload));
252
+ const settleUnlaunched = (runId, cause, fault) => import_effect.Effect.gen(function* () {
253
+ const fence = yield* runtime.claimFence(runId);
254
+ const run = yield* runtime.writeStatus(runId, fence, "failed");
255
+ yield* emit(
256
+ runId,
257
+ "control.run.failed",
258
+ json({ runId, status: "failed", cause: cause.slice(0, 4096), fault, ...ControlFacts.runFact(run) })
259
+ );
260
+ }).pipe(
261
+ journal.transact,
262
+ import_effect.Effect.catchCause(
263
+ (failure) => import_effect.Effect.annotateLogs(
264
+ import_effect.Effect.logWarning("A refused launch could not be settled"),
265
+ { runId, cause: import_effect.Cause.pretty(failure) }
266
+ )
267
+ )
268
+ );
269
+ const transact = (operation, effect) => mutationSemaphore.withPermits(1)(
270
+ journal.transact(effect).pipe(
271
+ import_effect.Effect.mapError(
272
+ (cause) => cause instanceof import_journal.Journal.JournalError ? new import_ControlError.PersistenceError({
273
+ operation: `${operation}.idempotency`,
274
+ message: `Failed to commit ${operation} and its idempotency receipt atomically`,
275
+ cause
276
+ }) : cause
277
+ )
278
+ )
279
+ );
280
+ const mutate = (operation, key, principal, mutationFingerprint, effect, replay = true, claimRunKey = false) => transact(
281
+ operation,
282
+ import_effect.Effect.gen(function* () {
283
+ const durableKey = (0, import_planning.mutationKey)(operation, key, principal);
284
+ const prior = yield* runtime.lookupMutation(durableKey, mutationFingerprint);
285
+ if (prior !== void 0 && (replay || prior._tag === "Conflict")) {
286
+ return prior._tag === "AlreadyApplied" ? { ...prior, receiptId: key } : prior;
287
+ }
288
+ const claim = claimRunKey ? yield* runtime.claimRunKey(durableKey, mutationFingerprint) : void 0;
289
+ if (claim?._tag === "Raced") return (0, import_planning.alreadyApplied)(key, claim.receipt);
290
+ return yield* import_effect.Effect.gen(function* () {
291
+ const receipt = yield* effect;
292
+ if (receipt._tag === "Parked" && claimRunKey) {
293
+ yield* runtime.releaseRunKey(durableKey);
294
+ } else if (receipt._tag !== "Parked" && prior === void 0) {
295
+ yield* runtime.recordMutation(durableKey, mutationFingerprint, receipt);
296
+ }
297
+ return receipt;
298
+ }).pipe(import_effect.Effect.onExit(
299
+ (exit) => claim?._tag === "Claimed" && import_effect.Exit.isFailure(exit) ? runtime.releaseRunKey(durableKey) : import_effect.Effect.void
300
+ ));
301
+ })
302
+ );
303
+ const takeUpResume = (runId, sequence) => import_effect.Option.isNone(executor) ? import_effect.Effect.void : executor.value.resumeRun({ runId }).pipe(
304
+ import_effect.Effect.flatMap((uptake) => uptake === "resuming" ? runtime.clearResume(runId, sequence) : import_effect.Effect.void)
305
+ );
306
+ const hand = (launch) => import_effect.Effect.gen(function* () {
307
+ const acceptance = import_effect.Option.isSome(executor) ? yield* executor.value.launch(launch).pipe(
308
+ import_effect.Effect.tapError((error) => settleUnlaunched(error.runId, error.message, Fault.of(error)))
309
+ ) : "pending";
310
+ yield* transact(
311
+ "run.acceptance",
312
+ import_effect.Effect.gen(function* () {
313
+ const current = yield* runtime.getRun(launch.run.runId);
314
+ if (current.status !== "accepted") return;
315
+ const fence = yield* runtime.claimFence(current.runId);
316
+ const run = acceptance === "accepted" ? yield* runtime.writeStatus(current.runId, fence, "running") : yield* runtime.releasePending(current.runId, fence);
317
+ yield* emit(
318
+ run.runId,
319
+ acceptance === "accepted" ? "control.run.running" : "control.run.pending",
320
+ {
321
+ runId: run.runId,
322
+ status: run.status,
323
+ ...ControlFacts.runFact(run)
324
+ }
325
+ );
326
+ })
327
+ );
328
+ });
329
+ const relaunchStranded = (runId) => import_effect.Effect.gen(function* () {
330
+ const current = yield* runtime.getRun(runId);
331
+ if (current.status !== "accepted" || current.ownerId === void 0) return;
332
+ const ours = yield* runtime.claimFence(runId).pipe(
333
+ import_effect.Effect.as(true),
334
+ import_effect.Effect.catchTag("/control/ClaimLost", () => import_effect.Effect.succeed(false))
335
+ );
336
+ if (ours) return;
337
+ yield* refuseCodeDrift(runId);
338
+ const claimed = yield* runtime.resume(runId, { scope: "launched" }).pipe(
339
+ import_effect.Effect.catchTag("/control/ClaimLost", () => import_effect.Effect.succeed(void 0))
340
+ );
341
+ if (claimed === void 0 || claimed.status !== "accepted" || claimed.planId === void 0) return;
342
+ yield* hand({ plan: yield* runtime.getPlan(claimed.planId), run: claimed });
343
+ }).pipe(
344
+ // The recorded receipt is the retry's answer either way; a relaunch
345
+ // that fails leaves the run where it was for the next retry.
346
+ import_effect.Effect.catch(
347
+ (failure) => import_effect.Effect.annotateLogs(
348
+ import_effect.Effect.logWarning("A stranded run could not be relaunched"),
349
+ { runId, cause: failure.message }
350
+ )
351
+ )
352
+ );
353
+ const decide = (decision, submitted) => import_effect.Effect.gen(function* () {
354
+ const input = yield* snapshotApproval(decision, submitted);
355
+ const principal = yield* runtime.stampPrincipal(input.principal);
356
+ return yield* decideAs(decision, input, principal).pipe(
357
+ // Every refusal leaves a durable audit record: who asked, for which
358
+ // target, under which scope. The journal row's time is when. It is
359
+ // written after the refused mutation rolled back, so it is the only
360
+ // row the refusal adds; a record that cannot be written fails the
361
+ // call rather than leaving an unaudited refusal.
362
+ import_effect.Effect.catchTag("/control/Unauthorized", (refusal) => emit(
363
+ input.target._tag === "Plan" ? `plan:${input.target.planId}` : input.target.runId,
364
+ "control.approval.refused",
365
+ json({ decision, principal, scope: input.scope, target: input.target })
366
+ ).pipe(import_effect.Effect.andThen(import_effect.Effect.fail(refusal))))
367
+ );
368
+ });
369
+ const decideAs = (decision, input, principal) => import_effect.Effect.gen(function* () {
370
+ yield* runtime.authorizeApproval({ principal, target: input.target, decision, scope: input.scope });
371
+ if (input.target._tag === "Node") {
372
+ const current = yield* getRun(input.target.runId).pipe(
373
+ import_effect.Effect.catchTag("/control/RunNotFound", () => import_effect.Effect.succeed(void 0))
374
+ );
375
+ if (current !== void 0 && terminal(current.status)) {
376
+ const settled = { _tag: "Terminal", runId: current.runId, status: current.status };
377
+ return settled;
378
+ }
379
+ }
380
+ let delegated;
381
+ const receipt = yield* mutate(
382
+ decision,
383
+ input.idempotencyKey,
384
+ principal,
385
+ fingerprint(decision, principal, input),
386
+ import_effect.Effect.gen(function* () {
387
+ if (input.target._tag === "Node") yield* refuseCodeDrift(input.target.runId);
388
+ const token = yield* runtime.lookupApproval(input.target);
389
+ yield* runtime.resolveApproval(token, decision, principal, input.scope);
390
+ if (decision === "approved") {
391
+ yield* runtime.installBulkGrant(token, input.target.envelope, input.scope);
392
+ }
393
+ yield* emit(
394
+ input.target._tag === "Plan" ? `plan:${input.target.planId}` : input.target.runId,
395
+ `control.approval.${decision}`,
396
+ json({
397
+ ...ControlFacts.approvalDecisionFact(token.tokenId, input.target),
398
+ target: input.target._tag,
399
+ scope: input.scope,
400
+ envelope: input.target.envelope,
401
+ principal
402
+ })
403
+ );
404
+ if (input.target._tag === "Plan") return accepted(input.idempotencyKey);
405
+ const runId = input.target.runId;
406
+ delegated = yield* runtime.requestResume(runId);
407
+ yield* emit(runId, "control.run.resumed", { runId });
408
+ return accepted(input.idempotencyKey, runId);
409
+ })
410
+ );
411
+ if (input.target._tag === "Node" && delegated !== void 0) {
412
+ yield* takeUpResume(input.target.runId, delegated);
413
+ }
414
+ return receipt;
415
+ });
416
+ const runMutation = (submitted) => import_effect.Effect.gen(function* () {
417
+ const input = yield* snapshotResume(submitted);
418
+ const settled = yield* getRun(input.runId);
419
+ if (terminal(settled.status)) {
420
+ return { _tag: "Terminal", runId: settled.runId, status: settled.status };
421
+ }
422
+ const principal = yield* runtime.stampPrincipal(input.principal);
423
+ return yield* mutate(
424
+ "resume",
425
+ input.idempotencyKey,
426
+ principal,
427
+ fingerprint("resume", principal, input),
428
+ import_effect.Effect.gen(function* () {
429
+ const current = yield* getRun(input.runId);
430
+ if (terminal(current.status)) {
431
+ return { _tag: "Terminal", runId: current.runId, status: current.status };
432
+ }
433
+ if (input.allowCodeDrift !== true) yield* refuseCodeDrift(input.runId);
434
+ let handedTo;
435
+ const claimed = yield* (input.allowCodeDrift === true ? runtime.resumeAdopting(input.runId, { scope: "launched" }) : runtime.resume(input.runId, { scope: "launched" })).pipe(
436
+ import_effect.Effect.catchTag("/control/ClaimLost", (error) => {
437
+ if (error.parkedBy !== void 0) {
438
+ handedTo = error.parkedBy;
439
+ return import_effect.Effect.succeed(void 0);
440
+ }
441
+ if (error.reason !== void 0) return import_effect.Effect.fail(error);
442
+ return runtime.getRun(input.runId).pipe(import_effect.Effect.flatMap(
443
+ (stored) => (
444
+ // A retained fork is parked in control and pending in the
445
+ // engine. Its accepted read overlay is not a live claim.
446
+ // Never replace that engine continuation with control state.
447
+ live(stored.status) || live(current.status) && current.executionView?.current.status !== "pending" ? import_effect.Effect.fail(new import_ControlError.ClaimLost({ runId: input.runId })) : import_effect.Effect.succeed(void 0)
448
+ )
449
+ ));
450
+ })
451
+ );
452
+ const sequence = yield* record(
453
+ input.runId,
454
+ "control.run.resume",
455
+ json({
456
+ runId: input.runId,
457
+ status: (claimed ?? current).status,
458
+ ...claimed === void 0 ? {} : ControlFacts.runFact(claimed),
459
+ principal,
460
+ ...input.reason === void 0 ? {} : { reason: input.reason },
461
+ ...handedTo === void 0 ? {} : { handedTo }
462
+ })
463
+ );
464
+ if (handedTo !== void 0) {
465
+ yield* runtime.requestResume(input.runId, { consent: sequence });
466
+ const handedOff = {
467
+ _tag: "Accepted",
468
+ receiptId: input.idempotencyKey,
469
+ runId: input.runId,
470
+ handedTo
471
+ };
472
+ return handedOff;
473
+ }
474
+ return claimed === void 0 ? accepted(input.idempotencyKey, input.runId) : terminalOrAccepted(input.idempotencyKey, claimed);
475
+ })
476
+ );
477
+ });
478
+ const executorRequestCancel = (runId) => import_effect.Option.isNone(executor) ? import_effect.Effect.succeed("unknown") : executor.value.requestCancel({ runId });
479
+ const executorSettleCancelledPark = (runId) => import_effect.Option.isNone(executor) ? import_effect.Effect.void : executor.value.settleCancelledPark({ runId });
480
+ const reconcileTerminal = (runId, status) => import_effect.Effect.gen(function* () {
481
+ const current = yield* runtime.getRun(runId);
482
+ if (terminal(current.status)) return;
483
+ const fence = yield* runtime.claimFence(runId).pipe(
484
+ // A parked coordination row has released its fence. Claim that row
485
+ // before copying the engine's terminal fact; a live peer still wins.
486
+ import_effect.Effect.catchTag("/control/ClaimLost", () => runtime.resume(runId).pipe(import_effect.Effect.andThen(runtime.claimFence(runId))))
487
+ );
488
+ const run = yield* runtime.writeStatus(runId, fence, status);
489
+ yield* emit(runId, `control.run.${status}`, json({ runId, status, ...ControlFacts.runFact(run) }));
490
+ }).pipe(
491
+ journal.transact,
492
+ import_effect.Effect.catchCause(
493
+ (failure) => import_effect.Effect.annotateLogs(
494
+ import_effect.Effect.logWarning("A settled engine row could not be reconciled onto the control row"),
495
+ { runId, status, cause: import_effect.Cause.pretty(failure) }
496
+ )
497
+ )
498
+ );
499
+ const wake = (run, messageId) => {
500
+ if (run.status !== "parked") return import_effect.Effect.void;
501
+ if (run.waitingReason !== "event" && run.waitingReason !== "released") return import_effect.Effect.void;
502
+ return refuseCodeDrift(run.runId).pipe(
503
+ import_effect.Effect.andThen(runtime.resume(run.runId, { scope: "launched" })),
504
+ import_effect.Effect.flatMap(
505
+ (resumed) => emit(
506
+ run.runId,
507
+ "control.steer.woke",
508
+ {
509
+ runId: run.runId,
510
+ messageId,
511
+ status: resumed.status,
512
+ ...ControlFacts.runFact(resumed)
513
+ }
514
+ )
515
+ ),
516
+ // A detached live host keeps its fence. Delegate the admitted event
517
+ // wake to it, just as a node decision does, instead of leaving an idle
518
+ // retained module with input it will never get a boundary to drain.
519
+ import_effect.Effect.catchTag(
520
+ "/control/ClaimLost",
521
+ (refusal) => refusal.parkedBy === void 0 ? import_effect.Effect.void : import_effect.Effect.gen(function* () {
522
+ const delegated = yield* runtime.requestResume(run.runId).pipe(
523
+ import_effect.Effect.catchTag("/control/InvalidInput", () => import_effect.Effect.succeed(void 0))
524
+ );
525
+ if (delegated !== void 0) yield* emit(run.runId, "control.run.resumed", { runId: run.runId });
526
+ })
527
+ ),
528
+ import_effect.Effect.catchTag("/control/RunNotFound", () => import_effect.Effect.void),
529
+ import_effect.Effect.catchTag(
530
+ "/control/CodeDrift",
531
+ (drift) => import_effect.Effect.annotateLogs(import_effect.Effect.logWarning("A steer did not wake a run whose code changed"), {
532
+ runId: run.runId,
533
+ cause: drift.message
534
+ })
535
+ )
536
+ );
537
+ };
538
+ const withSteering = (runs) => import_effect.Effect.forEach(runs, (run) => notifications.pending(run.runId).pipe(
539
+ import_effect.Effect.map((pending) => ({
540
+ ...run,
541
+ steering: {
542
+ pending: pending.filter((notification) => notification.delivery === "steer").length
543
+ }
544
+ })),
545
+ import_effect.Effect.catchTag("/notifications/NotificationError", () => import_effect.Effect.succeed(run)),
546
+ import_effect.Effect.mapError(
547
+ (cause) => new import_ControlError.PersistenceError({
548
+ operation: "control.list.steering",
549
+ message: `Failed to read pending steering for ${run.runId}`,
550
+ cause
551
+ })
552
+ )
553
+ ));
554
+ const triggerRuns = (triggerId) => import_effect.Effect.gen(function* () {
555
+ let start = 0;
556
+ while (true) {
557
+ const fires = yield* dispatch.fires({
558
+ _tag: "fires",
559
+ filters: { triggerId },
560
+ limit: import_ControlSchema.maxPageSize,
561
+ ...start === 0 ? {} : { cursor: String(start) }
562
+ });
563
+ if (fires.length <= start + import_ControlSchema.maxPageSize) {
564
+ return fires.flatMap((fire) => fire.triggerId === triggerId && fire.runId !== void 0 ? [fire.runId] : []);
565
+ }
566
+ start = Math.max(fires.length, start * 2);
567
+ }
568
+ });
569
+ const readerSees = (reader, runId) => runId.startsWith("plan:") ? import_effect.Effect.succeed(false) : runtime.getRun(runId).pipe(
570
+ import_effect.Effect.map((run) => (0, import_ControlRuntime.launchedByMatches)(run, reader)),
571
+ import_effect.Effect.catchTag("/control/RunNotFound", () => import_effect.Effect.succeed(false))
572
+ );
573
+ const confined = (submitted, mutation) => submitted.reader === void 0 ? mutation : import_effect.Effect.flatMap(
574
+ readerSees(submitted.reader, submitted.runId),
575
+ (visible) => visible ? mutation : import_effect.Effect.fail(new import_ControlError.RunNotFound({ runId: submitted.runId }))
576
+ );
577
+ const list = (request) => import_effect.Effect.gen(function* () {
578
+ if (request._tag === "executions") {
579
+ if (request.reader !== void 0 && !(yield* readerSees(request.reader, request.runId))) {
580
+ return yield* new import_ControlError.RunNotFound({ runId: request.runId });
581
+ }
582
+ yield* runtime.getRun(request.runId);
583
+ yield* import_effect.Schema.decodeUnknownEffect(import_ControlSchema.ListRequest)(request).pipe(
584
+ import_effect.Effect.mapError(() => invalid("executionIds: at most 200 nonempty execution IDs are admitted"))
585
+ );
586
+ const batch = import_effect.Option.isSome(executor) && executor.value.readExecutions !== void 0 ? yield* executor.value.readExecutions(request) : {
587
+ source: null,
588
+ revision: null,
589
+ snapshots: request.executionIds.map((executionId) => ({
590
+ _tag: "Unavailable",
591
+ executionId,
592
+ reason: "unsupported"
593
+ }))
594
+ };
595
+ return { _tag: "executions", source: batch.source, revision: batch.revision, items: batch.snapshots };
596
+ }
597
+ const bounds = yield* pageBounds(request._tag === "runs" ? void 0 : request.cursor, request.limit);
598
+ if (request._tag === "plans") {
599
+ if (request.reader !== void 0) return { _tag: "plans", items: [] };
600
+ const result = yield* runtime.queryPlans({
601
+ flowId: request.filters?.flowId,
602
+ decision: request.filters?.decision,
603
+ after: bounds.start,
604
+ limit: bounds.size
605
+ });
606
+ const items2 = result.plans.map((plan) => ({
607
+ card: plan.card,
608
+ // The JSON form the durable runtime stores, whichever runtime answers.
609
+ input: JSON.parse(JSON.stringify(plan.decodedInput ?? null)),
610
+ decision: plan.decision
611
+ }));
612
+ return result.next === void 0 ? { _tag: "plans", items: items2 } : { _tag: "plans", items: items2, nextCursor: String(result.next) };
613
+ }
614
+ if (request._tag === "flows") {
615
+ const [registered, warnings] = yield* import_effect.Effect.all([registry.list(), registry.warnings()]);
616
+ const available = registered.length > 0 ? registered.map((descriptor) => ({
617
+ flowId: descriptor.name,
618
+ description: descriptor.description,
619
+ ...(0, import_Descriptor.inputDocument)(descriptor.input) === void 0 ? {} : { inputSchema: (0, import_Descriptor.inputDocument)(descriptor.input) }
620
+ })) : yield* runtime.listFlows;
621
+ const result = page(available, bounds);
622
+ const diagnostics = warnings.length === 0 ? {} : { warnings };
623
+ return result.nextCursor === void 0 ? { _tag: "flows", items: result.items, ...diagnostics } : { _tag: "flows", items: result.items, ...diagnostics, nextCursor: result.nextCursor };
624
+ }
625
+ if (request._tag === "triggers") {
626
+ if (request.reader !== void 0) return { _tag: "triggers", items: [] };
627
+ let triggers = yield* dispatch.list(request);
628
+ if (request.filters?.triggerId !== void 0) {
629
+ triggers = triggers.filter((trigger) => trigger.triggerId === request.filters?.triggerId);
630
+ }
631
+ if (request.filters?.flowId !== void 0) {
632
+ triggers = triggers.filter((trigger) => trigger.flowId === request.filters?.flowId);
633
+ }
634
+ if (request.filters?.enabled !== void 0) {
635
+ triggers = triggers.filter((trigger) => trigger.enabled === request.filters?.enabled);
636
+ }
637
+ const result = page(triggers, bounds);
638
+ return result.nextCursor === void 0 ? { _tag: "triggers", items: result.items } : { _tag: "triggers", items: result.items, nextCursor: result.nextCursor };
639
+ }
640
+ if (request._tag === "fires") {
641
+ let fires = yield* dispatch.fires(request);
642
+ const reader2 = request.reader;
643
+ if (reader2 !== void 0) {
644
+ const seen = yield* import_effect.Effect.forEach(fires, (fire) => fire.runId === void 0 ? import_effect.Effect.succeed(false) : readerSees(reader2, fire.runId));
645
+ fires = fires.filter(
646
+ (_, index) => seen[index]
647
+ );
648
+ }
649
+ if (request.filters?.triggerId !== void 0) {
650
+ fires = fires.filter((fire) => fire.triggerId === request.filters?.triggerId);
651
+ }
652
+ if (request.filters?.runId !== void 0) {
653
+ fires = fires.filter((fire) => fire.runId === request.filters?.runId);
654
+ }
655
+ if (request.filters?.outcome !== void 0) {
656
+ fires = fires.filter((fire) => fire.outcome === request.filters?.outcome);
657
+ }
658
+ const result = page(fires, bounds);
659
+ return result.nextCursor === void 0 ? { _tag: "fires", items: result.items } : { _tag: "fires", items: result.items, nextCursor: result.nextCursor };
660
+ }
661
+ const filters = request.filters;
662
+ const reader = request.reader;
663
+ if (reader !== void 0 && filters?.principalId !== void 0 && filters.principalId !== reader.id) {
664
+ return { _tag: "runs", items: [] };
665
+ }
666
+ const launcher = reader !== void 0 ? { id: reader.id, kind: reader.kind } : filters?.principalId === void 0 ? void 0 : { id: filters.principalId };
667
+ const fingerprint2 = JSON.stringify([
668
+ filters?.runId ?? null,
669
+ filters?.flowId ?? null,
670
+ filters?.status ?? null,
671
+ filters?.parentRunId ?? null,
672
+ filters?.lineageId ?? null,
673
+ // Keep legacy cursor fingerprints byte-identical unless opting in.
674
+ ...filters?.terminal === void 0 && request.order === void 0 ? [] : [filters?.terminal ?? null, request.order ?? null],
675
+ ...filters?.since === void 0 && filters?.until === void 0 && filters?.triggerId === void 0 ? [] : [filters.since ?? null, filters.until ?? null, filters.triggerId ?? null],
676
+ ...launcher === void 0 ? [] : [launcher.id, launcher.kind ?? null]
677
+ ]);
678
+ const cursor = request.cursor === void 0 ? void 0 : yield* import_effect.Schema.decodeUnknownEffect(runCursor)(
679
+ request.cursor
680
+ ).pipe(import_effect.Effect.mapError(() => invalid("cursor: expected a run listing cursor")));
681
+ if (cursor !== void 0 && cursor.filters !== fingerprint2) {
682
+ return yield* invalid("cursor: belongs to different run filters");
683
+ }
684
+ if (filters?.runId !== void 0) {
685
+ let runs = yield* getRun(filters.runId).pipe(
686
+ import_effect.Effect.map((run) => [run]),
687
+ import_effect.Effect.catchTag("/control/RunNotFound", () => import_effect.Effect.succeed([]))
688
+ );
689
+ if (filters.flowId !== void 0) runs = runs.filter((run) => run.flowId === filters.flowId);
690
+ if (filters.status !== void 0) runs = runs.filter((run) => run.status === filters.status);
691
+ if (filters.terminal !== void 0) runs = runs.filter((run) => terminal(run.status) === filters.terminal);
692
+ if (filters.parentRunId !== void 0) runs = runs.filter((run) => run.parentRunId === filters.parentRunId);
693
+ if (filters.lineageId !== void 0) runs = runs.filter((run) => run.lineageId === filters.lineageId);
694
+ if (launcher !== void 0) runs = runs.filter((run) => (0, import_ControlRuntime.launchedByMatches)(run, launcher));
695
+ if (filters.since !== void 0) runs = runs.filter((run) => run.createdAt >= filters.since);
696
+ if (filters.until !== void 0) runs = runs.filter((run) => run.createdAt < filters.until);
697
+ if (filters.triggerId !== void 0) {
698
+ const started = yield* triggerRuns(filters.triggerId);
699
+ runs = runs.filter((run) => started.includes(run.runId));
700
+ }
701
+ return { _tag: "runs", items: yield* withSteering(yield* import_effect.Effect.forEach(runs, withCodeDrift)) };
702
+ }
703
+ const postFiltered = observing && (filters?.status !== void 0 || filters?.terminal !== void 0);
704
+ const { triggerId, principalId: _principalId, ...unowned } = filters ?? {};
705
+ const selected = launcher === void 0 ? unowned : { ...unowned, launchedBy: launcher };
706
+ const narrowed = triggerId === void 0 ? selected : { ...selected, runIds: yield* triggerRuns(triggerId) };
707
+ const sourceFilters = postFiltered ? Object.fromEntries(Object.entries(narrowed).filter(([key]) => key !== "status" && key !== "terminal")) : narrowed;
708
+ const collected = [];
709
+ let sourceCursor = cursor;
710
+ let sourceNext;
711
+ while (true) {
712
+ const result = yield* runtime.queryRuns({
713
+ filters: sourceFilters,
714
+ order: request.order,
715
+ cursor: sourceCursor,
716
+ limit: bounds.size - collected.length
717
+ });
718
+ const observed = yield* import_effect.Effect.forEach(result.items, observe);
719
+ for (const run of observed) {
720
+ if (!postFiltered || (filters?.status === void 0 || run.status === filters.status) && (filters?.terminal === void 0 || terminal(run.status) === filters.terminal)) collected.push(run);
721
+ }
722
+ sourceNext = result.nextCursor;
723
+ if (!postFiltered || sourceNext === void 0 || collected.length >= bounds.size) break;
724
+ sourceCursor = { version: 1, filters: fingerprint2, ...sourceNext };
725
+ }
726
+ const items = yield* withSteering(collected);
727
+ return sourceNext === void 0 ? { _tag: "runs", items } : {
728
+ _tag: "runs",
729
+ items,
730
+ nextCursor: JSON.stringify({ version: 1, filters: fingerprint2, ...sourceNext })
731
+ };
732
+ });
733
+ const streamForRun = (runId, filter) => journal.stream({
734
+ runId: import_journal.JournalEvent.RunId.make(runId),
735
+ ...filter.afterSequence === void 0 ? {} : { afterSequence: import_journal.JournalEvent.Seq.make(filter.afterSequence) }
736
+ }).pipe(
737
+ import_effect.Stream.map(eventFromEntry),
738
+ import_effect.Stream.mapError(watchReadFailed)
739
+ );
740
+ const snapshotHighWater = (runId) => journal.transact(
741
+ import_effect.Effect.gen(function* () {
742
+ const first = yield* journal.entries({ runId, limit: 1 });
743
+ const initial = first.entries[0];
744
+ if (initial === void 0) return void 0;
745
+ let lower = initial.seq;
746
+ let step = 1;
747
+ let upper = lower;
748
+ const maximumSequence = Number.MAX_SAFE_INTEGER - 1;
749
+ while (lower < maximumSequence) {
750
+ const probe = Math.min(maximumSequence, lower + step - 1);
751
+ const next = yield* journal.entries({
752
+ runId,
753
+ after: import_journal.JournalEvent.Seq.make(probe),
754
+ limit: 1
755
+ });
756
+ const entry = next.entries[0];
757
+ if (entry === void 0) {
758
+ upper = probe;
759
+ break;
760
+ }
761
+ lower = entry.seq;
762
+ if (lower === maximumSequence) return entry.seq;
763
+ step = Math.min(maximumSequence - lower, step * 2);
764
+ }
765
+ while (lower < upper) {
766
+ const middle = lower + Math.ceil((upper - lower) / 2);
767
+ const next = yield* journal.entries({
768
+ runId,
769
+ after: import_journal.JournalEvent.Seq.make(middle - 1),
770
+ limit: 1
771
+ });
772
+ const entry = next.entries[0];
773
+ if (entry === void 0) {
774
+ upper = middle - 1;
775
+ } else {
776
+ lower = entry.seq;
777
+ }
778
+ }
779
+ return import_journal.JournalEvent.Seq.make(lower);
780
+ })
781
+ ).pipe(import_effect.Effect.mapError(watchReadFailed));
782
+ const snapshotForRunAt = (runId, filter, highWater) => {
783
+ const journalRunId = import_journal.JournalEvent.RunId.make(runId);
784
+ const initialAfter = filter.afterSequence === void 0 ? void 0 : import_journal.JournalEvent.Seq.make(filter.afterSequence);
785
+ if (highWater === void 0 || initialAfter !== void 0 && initialAfter >= highWater) {
786
+ return import_effect.Stream.empty;
787
+ }
788
+ return import_effect.Stream.paginate(initialAfter, (after) => journal.entries({
789
+ runId: journalRunId,
790
+ ...after === void 0 ? {} : { after },
791
+ limit: snapshotPageSize
792
+ }).pipe(
793
+ import_effect.Effect.map((page2) => {
794
+ const entries2 = page2.entries.filter((entry) => entry.seq <= highWater);
795
+ const last = entries2.at(-1);
796
+ const next = last === void 0 || last.seq >= highWater || !page2.hasMore ? import_effect.Option.none() : import_effect.Option.some(last.seq);
797
+ return [entries2, next];
798
+ }),
799
+ import_effect.Effect.mapError(watchReadFailed)
800
+ )).pipe(import_effect.Stream.map(eventFromEntry));
801
+ };
802
+ const snapshotForRun = (runId, filter) => import_effect.Stream.unwrap(
803
+ import_effect.Effect.map(
804
+ snapshotHighWater(import_journal.JournalEvent.RunId.make(runId)),
805
+ (highWater) => snapshotForRunAt(runId, filter, highWater)
806
+ )
807
+ );
808
+ const inventory = (page2) => {
809
+ const first = {};
810
+ return import_effect.Stream.paginate(
811
+ first,
812
+ (cursor) => import_effect.Effect.map(
813
+ page2({ ...cursor, limit: partitionPageSize }),
814
+ (next) => [
815
+ next.ids,
816
+ next.next === void 0 ? import_effect.Option.none() : import_effect.Option.some({ after: next.next, through: next.through })
817
+ ]
818
+ )
819
+ );
820
+ };
821
+ const journalPartitions = import_effect.Stream.concat(
822
+ import_effect.Stream.map(inventory(runtime.pagePlanIds), (planId) => `plan:${planId}`),
823
+ inventory(runtime.pageRunIds)
824
+ );
825
+ const snapshot = (filter) => filter.runId !== void 0 ? snapshotForRun(filter.runId, filter) : import_effect.Stream.flatMap(journalPartitions, (partition) => snapshotForRun(partition, filter), {
826
+ concurrency: snapshotPartitionConcurrency
827
+ });
828
+ const entries = (filter) => filter.follow === false ? snapshot(filter) : filter.runId !== void 0 ? streamForRun(filter.runId, filter) : import_effect.Stream.unwrap(
829
+ import_effect.Effect.gen(function* () {
830
+ const subscription = yield* journal.changes;
831
+ const cutoffs = /* @__PURE__ */ new Map();
832
+ const pin = (partition) => {
833
+ const pinned = import_effect.Deferred.makeUnsafe();
834
+ cutoffs.set(partition, pinned);
835
+ return snapshotHighWater(import_journal.JournalEvent.RunId.make(partition)).pipe(
836
+ import_effect.Effect.onExit((exit) => import_effect.Deferred.done(pinned, exit))
837
+ );
838
+ };
839
+ const walk = journalPartitions.pipe(
840
+ import_effect.Stream.mapEffect(
841
+ (partition) => import_effect.Effect.suspend(
842
+ () => cutoffs.has(partition) ? import_effect.Effect.succeed(import_effect.Option.none()) : import_effect.Effect.map(pin(partition), (highWater) => import_effect.Option.some([partition, highWater]))
843
+ ),
844
+ { concurrency: snapshotPartitionConcurrency }
845
+ ),
846
+ import_effect.Stream.filter(import_effect.Option.isSome),
847
+ import_effect.Stream.map((pinned) => pinned.value),
848
+ import_effect.Stream.flatMap(([partition, highWater]) => snapshotForRunAt(partition, filter, highWater), {
849
+ concurrency: snapshotPartitionConcurrency
850
+ })
851
+ );
852
+ const cutoffFor = (partition) => import_effect.Effect.suspend(() => {
853
+ const known = cutoffs.get(partition);
854
+ if (known !== void 0) {
855
+ return import_effect.Effect.map(import_effect.Deferred.await(known), (highWater) => ({ highWater, claimed: false }));
856
+ }
857
+ return import_effect.Effect.map(pin(partition), (highWater) => ({ highWater, claimed: true }));
858
+ });
859
+ const seenByPartition = /* @__PURE__ */ new Map();
860
+ const gapCheck = (partition, expected, arrived) => journal.entries({
861
+ runId: import_journal.JournalEvent.RunId.make(partition),
862
+ ...expected === void 0 ? {} : { after: import_journal.JournalEvent.Seq.make(expected) },
863
+ limit: 1
864
+ }).pipe(
865
+ import_effect.Effect.mapError(watchReadFailed),
866
+ import_effect.Effect.flatMap((page2) => {
867
+ const missed = page2.entries[0];
868
+ if (missed === void 0 || missed.seq >= arrived) return import_effect.Effect.void;
869
+ return import_effect.Effect.fail(
870
+ new import_ControlError.PersistenceError({
871
+ operation: "watch",
872
+ message: `the live tail lost journal entries for ${partition}: sequence ${missed.seq} was committed but never delivered to this watcher`
873
+ })
874
+ );
875
+ })
876
+ );
877
+ const trackTail = (entry, cutoff) => {
878
+ const partition = String(entry.runId);
879
+ const expected = seenByPartition.get(partition) ?? cutoff;
880
+ if (expected !== void 0 && entry.seq <= expected) return import_effect.Effect.succeed(import_effect.Option.none());
881
+ seenByPartition.set(partition, entry.seq);
882
+ return expected !== void 0 && entry.seq === expected + 1 ? import_effect.Effect.succeed(import_effect.Option.some(entry)) : import_effect.Effect.map(gapCheck(partition, expected, entry.seq), () => import_effect.Option.some(entry));
883
+ };
884
+ const tail = import_effect.Stream.fromSubscription(subscription).pipe(
885
+ // Every committed entry names its partition. The first one from a
886
+ // partition nobody pinned yet pins it here and is preceded by that
887
+ // partition's snapshot, which also recovers any earlier notice the
888
+ // sliding buffer dropped.
889
+ import_effect.Stream.flatMap(
890
+ (entry) => import_effect.Stream.unwrap(import_effect.Effect.gen(function* () {
891
+ const partition = String(entry.runId);
892
+ const { highWater, claimed } = yield* cutoffFor(partition);
893
+ const history = claimed ? snapshotForRunAt(partition, filter, highWater) : import_effect.Stream.empty;
894
+ if (highWater !== void 0 && entry.seq <= highWater) return history;
895
+ const tracked = yield* trackTail(entry, highWater);
896
+ return import_effect.Option.isSome(tracked) ? import_effect.Stream.concat(history, import_effect.Stream.succeed(eventFromEntry(tracked.value))) : history;
897
+ }))
898
+ )
899
+ );
900
+ return import_effect.Stream.merge(walk, tail);
901
+ })
902
+ );
903
+ const watch = (filter) => {
904
+ const { reader, ...unrestricted } = filter;
905
+ if (reader === void 0) return watchAll(unrestricted);
906
+ if (filter.runId !== void 0) {
907
+ const runId = filter.runId;
908
+ return import_effect.Stream.unwrap(import_effect.Effect.map(
909
+ readerSees(reader, runId),
910
+ (visible) => visible ? watchAll(unrestricted) : import_effect.Stream.fail(new import_ControlError.RunNotFound({ runId }))
911
+ ));
912
+ }
913
+ const decided = /* @__PURE__ */ new Map();
914
+ return watchAll(unrestricted).pipe(
915
+ // A lost-tail failure names the partition and sequence it missed,
916
+ // which may be another principal's; a restricted reader learns only
917
+ // that its stream ended incomplete.
918
+ import_effect.Stream.mapError(
919
+ (error) => error._tag === "/control/PersistenceError" ? new import_ControlError.PersistenceError({ operation: "watch", message: "the watch lost journal entries and ended" }) : error
920
+ ),
921
+ import_effect.Stream.filterEffect((event) => {
922
+ const partition = event.runId;
923
+ const known = decided.get(partition);
924
+ if (known !== void 0) return import_effect.Effect.succeed(known);
925
+ return import_effect.Effect.tap(
926
+ readerSees(reader, partition),
927
+ (visible) => import_effect.Effect.sync(() => decided.set(partition, visible))
928
+ );
929
+ })
930
+ );
931
+ };
932
+ const watchAll = (filter) => {
933
+ if (filter.afterSequence !== void 0 && filter.runId === void 0) {
934
+ return import_effect.Stream.fail(invalid("afterSequence: a watch cursor resumes one run, so it requires runId"));
935
+ }
936
+ const cursor = filter.afterCursor;
937
+ if (cursor !== void 0) {
938
+ if (filter.runId === void 0) {
939
+ return import_effect.Stream.fail(invalid("afterCursor: a watch cursor resumes one run, so it requires runId"));
940
+ }
941
+ if (filter.afterSequence !== void 0) {
942
+ return import_effect.Stream.fail(invalid("afterCursor: cannot be combined with afterSequence"));
943
+ }
944
+ if (!import_effect.Schema.is(import_ControlSchema.WatchCursor)(cursor)) {
945
+ return import_effect.Stream.fail(invalid("afterCursor: sequence and offset must be nonnegative safe journal integers"));
946
+ }
947
+ }
948
+ const afterSequence = cursor === void 0 ? filter.afterSequence : cursor.offset === void 0 ? cursor.sequence : cursor.sequence === 0 ? void 0 : cursor.sequence - 1;
949
+ return entries({
950
+ ...filter,
951
+ afterSequence
952
+ }).pipe(
953
+ import_effect.Stream.map((event) => {
954
+ const lineage = Lineage.derive(event);
955
+ const expanded = [event, ...lineage === void 0 ? [] : [lineage], ...Steering.derive(event)];
956
+ const checkpointed = expanded.map((member, offset) => ({
957
+ ...member,
958
+ cursor: offset === expanded.length - 1 ? { sequence: event.sequence } : { sequence: event.sequence, offset }
959
+ }));
960
+ return cursor?.offset !== void 0 && event.sequence === cursor.sequence ? checkpointed.slice(cursor.offset + 1) : checkpointed;
961
+ }),
962
+ import_effect.Stream.flattenIterable
963
+ );
964
+ };
965
+ const service = {
966
+ plan: import_effect.Effect.fn("Control.plan")(
967
+ (input) => mutationSemaphore.withPermits(1)(
968
+ journal.transact(import_effect.Effect.gen(function* () {
969
+ const { card, created } = yield* runtime.plan(input);
970
+ const runId = import_journal.JournalEvent.RunId.make(`plan:${card.planId}`);
971
+ if (!created) {
972
+ let after;
973
+ while (true) {
974
+ const page2 = yield* journal.entries({
975
+ runId,
976
+ ...after === void 0 ? {} : { after },
977
+ limit: snapshotPageSize
978
+ });
979
+ if (page2.entries.some(
980
+ (entry) => entry.sourceId === sourceId && entry.eventType === "control.plan.created"
981
+ )) {
982
+ return card;
983
+ }
984
+ if (!page2.hasMore) break;
985
+ after = page2.entries[page2.entries.length - 1].seq;
986
+ }
987
+ }
988
+ yield* emit(runId, "control.plan.created", {
989
+ planId: card.planId,
990
+ flowId: card.flowId,
991
+ digest: card.digest
992
+ });
993
+ return card;
994
+ })).pipe(
995
+ import_effect.Effect.mapError(
996
+ (cause) => cause instanceof import_journal.Journal.JournalError ? new import_ControlError.PersistenceError({
997
+ operation: "plan",
998
+ message: "Failed to commit plan and its creation entry atomically",
999
+ cause
1000
+ }) : cause
1001
+ )
1002
+ )
1003
+ ).pipe(import_effect.Effect.flatMap(
1004
+ (card) => registry.warnings().pipe(import_effect.Effect.map((all) => {
1005
+ const warnings = all.filter((warning) => warning.name === input.flowId);
1006
+ return warnings.length === 0 ? card : { warnings, ...card };
1007
+ }))
1008
+ ))
1009
+ ),
1010
+ run: import_effect.Effect.fn("Control.run")(
1011
+ (submitted) => import_effect.Effect.gen(function* () {
1012
+ const input = yield* snapshotRun(submitted);
1013
+ if (input._tag === "Resume") return yield* runMutation(input);
1014
+ const principal = yield* runtime.stampPrincipal(input.principal);
1015
+ let admitted;
1016
+ const receipt = yield* mutate(
1017
+ "run",
1018
+ input.idempotencyKey,
1019
+ principal,
1020
+ fingerprint("run", principal, input),
1021
+ import_effect.Effect.gen(function* () {
1022
+ const launched = yield* runtime.launch(
1023
+ input.planId,
1024
+ input.digest,
1025
+ input.envelope,
1026
+ principal,
1027
+ input.reservedRunId
1028
+ );
1029
+ if (launched._tag === "Parked") {
1030
+ return { ...launched.receipt, receiptId: input.idempotencyKey };
1031
+ }
1032
+ const plan = yield* runtime.getPlan(input.planId);
1033
+ const trigger = declaredTrigger(plan.decodedInput);
1034
+ yield* emit(
1035
+ launched.run.runId,
1036
+ "control.run.accepted",
1037
+ {
1038
+ runId: launched.run.runId,
1039
+ planId: input.planId,
1040
+ digest: input.digest,
1041
+ status: launched.run.status,
1042
+ ...trigger === void 0 ? {} : { trigger },
1043
+ ...ControlFacts.runFact(launched.run, "created")
1044
+ }
1045
+ );
1046
+ admitted = { plan, run: launched.run };
1047
+ return {
1048
+ _tag: "Accepted",
1049
+ receiptId: input.idempotencyKey,
1050
+ runId: launched.run.runId
1051
+ };
1052
+ }),
1053
+ true,
1054
+ true
1055
+ );
1056
+ if (receipt._tag === "Accepted" && admitted !== void 0) yield* hand(admitted);
1057
+ else if (receipt._tag === "AlreadyApplied" && receipt.runId !== void 0) {
1058
+ yield* relaunchStranded(receipt.runId);
1059
+ }
1060
+ return receipt;
1061
+ })
1062
+ ),
1063
+ approve: import_effect.Effect.fn("Control.approve")((input) => decide("approved", input)),
1064
+ deny: import_effect.Effect.fn("Control.deny")((input) => decide("denied", input)),
1065
+ steer: import_effect.Effect.fn("Control.steer")(
1066
+ (submitted) => confined(
1067
+ submitted,
1068
+ import_effect.Effect.flatMap(snapshotSteer(submitted), (input) => mutate(
1069
+ "steer",
1070
+ input.idempotencyKey,
1071
+ input.message.principal,
1072
+ fingerprint("steer", input.message.principal, input),
1073
+ import_effect.Effect.gen(function* () {
1074
+ if (input.message.runId !== input.runId) {
1075
+ return yield* import_effect.Effect.fail(
1076
+ invalid(
1077
+ `message.runId: must be ${JSON.stringify(input.runId)}, received ${JSON.stringify(input.message.runId)}`
1078
+ )
1079
+ );
1080
+ }
1081
+ const run = yield* getRun(input.runId);
1082
+ if (terminal(run.status)) return { _tag: "Terminal", runId: run.runId, status: run.status };
1083
+ const item = (0, import_ControlSchema.steerItem)(input.message);
1084
+ const admission = yield* notifications.admit(input.runId, {
1085
+ _tag: "human-steer",
1086
+ id: input.message.messageId,
1087
+ delivery: "steer",
1088
+ targetLineageId: input.runId,
1089
+ provenance: {
1090
+ sourceRunId: input.runId,
1091
+ sourceLineageId: input.runId,
1092
+ sourceTurn: 0,
1093
+ sourceActor: `${input.message.principal.kind}:${input.message.principal.id}`,
1094
+ ...input.message.attribution === void 0 ? {} : { attribution: input.message.attribution }
1095
+ },
1096
+ payload: SteerPayload.encode(item)
1097
+ }, input.version).pipe(
1098
+ import_effect.Effect.mapError(
1099
+ (cause) => cause instanceof import_notifications.NotificationQueue.NotificationError ? cause : new import_ControlError.PersistenceError({
1100
+ operation: "control.steer.notification",
1101
+ message: "Failed to admit steering notification",
1102
+ cause
1103
+ })
1104
+ )
1105
+ );
1106
+ const inputReceipt = () => {
1107
+ if (input.version === void 0 || item.kind !== "Message") {
1108
+ return accepted(input.idempotencyKey, input.runId);
1109
+ }
1110
+ const payload = admission.consumedNotification?.payload;
1111
+ return {
1112
+ _tag: "Accepted",
1113
+ receiptId: input.idempotencyKey,
1114
+ runId: input.runId,
1115
+ inputConsumed: admission.consumed === true,
1116
+ inputBody: payload?.body ?? item.body
1117
+ };
1118
+ };
1119
+ if (input.version !== void 0 && (admission.consumed || admission.duplicate)) {
1120
+ return inputReceipt();
1121
+ }
1122
+ if (admission.decision === "rejected-full") {
1123
+ return yield* import_effect.Effect.fail(
1124
+ new import_notifications.NotificationQueue.NotificationError({
1125
+ code: "notification_full",
1126
+ notificationId: input.message.messageId,
1127
+ message: "Steering queue is full; retry after pending notifications are delivered"
1128
+ })
1129
+ );
1130
+ }
1131
+ yield* emit(input.runId, Steering.enqueuedEventType, {
1132
+ runId: input.runId,
1133
+ messageId: input.message.messageId,
1134
+ kind: item.kind,
1135
+ createdAt: input.message.createdAt
1136
+ });
1137
+ yield* wake(run, input.message.messageId);
1138
+ return inputReceipt();
1139
+ })
1140
+ ))
1141
+ )
1142
+ ),
1143
+ signal: import_effect.Effect.fn("Control.signal")(
1144
+ (submitted) => confined(
1145
+ submitted,
1146
+ import_effect.Effect.flatMap(snapshotSignal(submitted), (input) => import_effect.Effect.gen(function* () {
1147
+ const principal = yield* runtime.stampPrincipal(input.principal);
1148
+ const key = fingerprint("signal", principal, input);
1149
+ const durableKey = (0, import_planning.mutationKey)("signal", input.idempotencyKey, principal);
1150
+ const receipt = yield* mutate(
1151
+ "signal",
1152
+ input.idempotencyKey,
1153
+ principal,
1154
+ key,
1155
+ import_effect.Effect.gen(function* () {
1156
+ const current = yield* getRun(input.runId);
1157
+ if (terminal(current.status)) {
1158
+ return { _tag: "Terminal", runId: current.runId, status: current.status };
1159
+ }
1160
+ yield* runtime.admitSignal(durableKey, input.runId, input.signal, principal);
1161
+ yield* emit(input.runId, "control.signal.admitted", {
1162
+ commandId: durableKey,
1163
+ runId: input.runId,
1164
+ name: input.signal.name
1165
+ });
1166
+ return accepted(input.idempotencyKey, input.runId);
1167
+ }),
1168
+ true,
1169
+ true
1170
+ );
1171
+ if (receipt._tag !== "Accepted" && receipt._tag !== "AlreadyApplied") return receipt;
1172
+ const command = yield* runtime.signalCommand(durableKey);
1173
+ if (command === void 0 || command.state === "delivered" || command.state === "terminal") return receipt;
1174
+ if (command.state === "rejected") {
1175
+ return yield* new import_ControlError.NoMatchingWait({ runId: input.runId, waitName: input.signal.name });
1176
+ }
1177
+ const delivery = import_effect.Option.isNone(executor) ? "unknown" : yield* executor.value.deliverSignal({ ...command });
1178
+ if (delivery === "no-match") {
1179
+ yield* runtime.settleSignal(durableKey, "rejected");
1180
+ return yield* new import_ControlError.NoMatchingWait({ runId: input.runId, waitName: input.signal.name });
1181
+ }
1182
+ if (delivery === "refused") {
1183
+ yield* runtime.settleSignal(durableKey, "rejected");
1184
+ return yield* new import_ControlError.Unauthorized({
1185
+ message: `This caller has no authority to answer "${input.signal.name}" on run ${input.runId}`
1186
+ });
1187
+ }
1188
+ if (delivery === "delivered") {
1189
+ yield* runtime.settleSignal(durableKey, "delivered");
1190
+ }
1191
+ return receipt;
1192
+ }))
1193
+ )
1194
+ ),
1195
+ cancel: import_effect.Effect.fn("Control.cancel")(
1196
+ (submitted) => confined(
1197
+ submitted,
1198
+ import_effect.Effect.flatMap(
1199
+ snapshotReasonedMutation("cancel", submitted),
1200
+ (input) => import_effect.Effect.flatMap(runtime.stampPrincipal(input.principal), (principal) => mutate(
1201
+ "cancel",
1202
+ input.idempotencyKey,
1203
+ principal,
1204
+ fingerprint("cancel", principal, input),
1205
+ import_effect.Effect.gen(function* () {
1206
+ const current = yield* getRun(input.runId);
1207
+ if (terminal(current.status)) {
1208
+ yield* reconcileTerminal(input.runId, current.status);
1209
+ return { _tag: "Terminal", runId: current.runId, status: current.status };
1210
+ }
1211
+ const record2 = yield* executorRequestCancel(input.runId);
1212
+ if (typeof record2 !== "string") {
1213
+ yield* reconcileTerminal(input.runId, record2.status);
1214
+ return { _tag: "Terminal", runId: input.runId, status: record2.status };
1215
+ }
1216
+ const prior = yield* runtime.lookupMutation(
1217
+ (0, import_planning.mutationKey)("cancel", input.idempotencyKey, principal),
1218
+ fingerprint("cancel", principal, input)
1219
+ );
1220
+ if (record2 !== "already-requested" && prior === void 0) {
1221
+ yield* emit(
1222
+ input.runId,
1223
+ Cancellation.requestedEventType,
1224
+ json({
1225
+ runId: input.runId,
1226
+ source: "control",
1227
+ principal,
1228
+ ...input.reason === void 0 ? {} : { reason: input.reason }
1229
+ })
1230
+ );
1231
+ }
1232
+ return accepted(input.idempotencyKey, input.runId);
1233
+ }),
1234
+ false
1235
+ ).pipe(
1236
+ import_effect.Effect.flatMap(
1237
+ (receipt) => import_effect.Effect.gen(function* () {
1238
+ if (receipt._tag !== "Accepted") return receipt;
1239
+ const settle = (effect) => transact(
1240
+ "cancel",
1241
+ import_effect.Effect.gen(function* () {
1242
+ const run2 = yield* effect;
1243
+ yield* emit(
1244
+ input.runId,
1245
+ `control.run.${run2.status}`,
1246
+ {
1247
+ runId: input.runId,
1248
+ status: run2.status,
1249
+ ...ControlFacts.runFact(run2)
1250
+ }
1251
+ );
1252
+ return run2;
1253
+ })
1254
+ );
1255
+ const run = yield* runtime.interrupt(input.runId, settle).pipe(
1256
+ import_effect.Effect.catchTag("/control/ClaimLost", () => import_effect.Effect.gen(function* () {
1257
+ const current = yield* getRun(input.runId);
1258
+ if (terminal(current.status)) return current;
1259
+ if (live(current.status) && current.ownerId !== void 0) return void 0;
1260
+ return yield* runtime.resume(input.runId).pipe(
1261
+ import_effect.Effect.andThen(runtime.interrupt(input.runId, settle)),
1262
+ import_effect.Effect.catchTag("/control/ClaimLost", () => import_effect.Effect.succeed(void 0))
1263
+ );
1264
+ }))
1265
+ );
1266
+ return run === void 0 ? receipt : terminalOrAccepted(input.idempotencyKey, run);
1267
+ })
1268
+ ),
1269
+ // Both rows, before the process that asked goes away. The engine row
1270
+ // carries the request the moment the mutation commits, but nothing
1271
+ // drives a parked run, so the row stayed `suspended` until some
1272
+ // later long-lived engine happened to sweep it: `gc` collected the
1273
+ // run in `control.db` and skipped it in `engine.db` for fifteen
1274
+ // seconds and six commands in the release validation.
1275
+ import_effect.Effect.tap(
1276
+ () => import_effect.Effect.gen(function* () {
1277
+ yield* executorSettleCancelledPark(input.runId);
1278
+ const current = yield* getRun(input.runId);
1279
+ if (terminal(current.status)) yield* reconcileTerminal(input.runId, current.status);
1280
+ })
1281
+ )
1282
+ ))
1283
+ )
1284
+ )
1285
+ ),
1286
+ resume: import_effect.Effect.fn("Control.resume")((input) => confined(input, runMutation(input))),
1287
+ list,
1288
+ watch
1289
+ };
1290
+ return import_Control.Control.of(service);
1291
+ })
1292
+ );
1293
+ //# sourceMappingURL=ControlLive.js.map