@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,808 @@
1
+ /**
2
+ * Browser-safe port between the control plane and the execution engine, with a
3
+ * deterministic in-memory implementation.
4
+ *
5
+ * `layerMemory` is the deterministic one: it models the production fence and
6
+ * approval ordering seams but keeps everything in a `Map`, so nothing it
7
+ * decides survives the process. The durable adapter is
8
+ * {@link SqlControlRuntime}, and both are held to one shared contract suite —
9
+ * see `test/ControlContract.ts`.
10
+ *
11
+ * @since 0.1.0
12
+ */
13
+ import * as Fault from "@smthrs/flow/Fault";
14
+ import { Context, Crypto, Effect, Fiber, Layer, Option, Schema } from "effect";
15
+ import * as ApprovalAuthority from "./ApprovalAuthority.js";
16
+ import { AlreadyResolved, ClaimLost, EnvelopeMismatch, FlowNotFound, InvalidInput, PersistenceError, PlanDenied, PlanDigestMismatch, PlanNotFound, RunNotFound } from "./ControlError.js";
17
+ import { GrantScope as GrantScopeSchema, Principal as PrincipalSchema } from "./ControlSchema.js";
18
+ import { canonicalIssue } from "./internal/issues.js";
19
+ import { accepted, adoptedCode, alreadyApplied as replayReceipt, budgeted, canonical, codeDriftOf, emptyEnvelope, planCard, planFingerprint, sameEnvelope } from "./internal/planning.js";
20
+ import { plannable } from "./SystemFlows.js";
21
+ /**
22
+ * Whether `run` was launched by the principal `launcher` names: the same `id`,
23
+ * and the same `kind` when `launcher` gives one. A run with no recorded
24
+ * launcher never matches.
25
+ *
26
+ * @category predicates
27
+ * @since 1.0.0
28
+ */
29
+ export const launchedByMatches = (run, launcher) => run.launchedBy !== undefined && run.launchedBy.id === launcher.id &&
30
+ (launcher.kind === undefined || run.launchedBy.kind === launcher.kind);
31
+ /**
32
+ * The durable answer to an approval request. A decision is not inferred from
33
+ * the presence of a grant or from a boolean that also means denial.
34
+ *
35
+ * @category models
36
+ * @since 0.1.0
37
+ */
38
+ export const ApprovalDecision = Schema.Union([
39
+ Schema.Struct({ _tag: Schema.Literal("Pending") }),
40
+ Schema.Struct({
41
+ _tag: Schema.Literal("Approved"),
42
+ decisionPrincipal: PrincipalSchema,
43
+ decidedAt: Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)),
44
+ scope: GrantScopeSchema
45
+ }),
46
+ Schema.Struct({
47
+ _tag: Schema.Literal("Denied"),
48
+ decisionPrincipal: PrincipalSchema,
49
+ decidedAt: Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))
50
+ })
51
+ ]);
52
+ /**
53
+ * A gate that still needs a decision.
54
+ * @category errors
55
+ * @since 1.0.0
56
+ */
57
+ export class ApprovalPending extends Schema.TaggedError()("/control/ApprovalPending", {
58
+ tokenId: Schema.String
59
+ }) {
60
+ get message() {
61
+ return "Approval is still pending. Wait for a decision before proceeding.";
62
+ }
63
+ }
64
+ /**
65
+ * A gate that was denied, not merely resolved.
66
+ * @category errors
67
+ * @since 1.0.0
68
+ */
69
+ export class ApprovalDenied extends Schema.TaggedError()("/control/ApprovalDenied", {
70
+ tokenId: Schema.String,
71
+ decisionPrincipal: PrincipalSchema
72
+ }) {
73
+ get message() {
74
+ return "Approval was denied. This request cannot authorize the gated work.";
75
+ }
76
+ }
77
+ /**
78
+ * Opens only an explicitly approved gate. Callers may park on ApprovalPending;
79
+ * ApprovalDenied is terminal. This reads a runtime-issued token, not an
80
+ * authentication credential, and does not itself install permissions.
81
+ * @category combinators
82
+ * @since 1.0.0
83
+ */
84
+ export const requireApproved = (token) => token._tag === "Approved"
85
+ ? Effect.succeed(token)
86
+ : token._tag === "Pending"
87
+ ? Effect.fail(new ApprovalPending({ tokenId: token.tokenId }))
88
+ : Effect.fail(new ApprovalDenied({ tokenId: token.tokenId, decisionPrincipal: token.decisionPrincipal }));
89
+ /**
90
+ * Service key for the execution-engine port.
91
+ *
92
+ * @category services
93
+ * @since 0.1.0
94
+ */
95
+ export class ControlRuntime extends Context.Service()("/control/ControlRuntime") {
96
+ }
97
+ /**
98
+ * Constructs a runtime service from an implementation record.
99
+ *
100
+ * @category constructors
101
+ * @since 0.1.0
102
+ */
103
+ export const make = (implementation) => ControlRuntime.of(implementation);
104
+ /**
105
+ * Refuses an id page size outside 1 through 500, the `queryRuns` bound.
106
+ *
107
+ * @category validation
108
+ * @since 1.0.0
109
+ */
110
+ export const idPageLimit = (limit) => Number.isSafeInteger(limit) && limit >= 1 && limit <= 500
111
+ ? Effect.void
112
+ : Effect.fail(new InvalidInput({ issue: "limit: must be an integer between 1 and 500" }));
113
+ /**
114
+ * One inventory page over dense insertion positions, where `idAt` resolves a
115
+ * position by direct lookup. Visits at most `limit` positions, so a page
116
+ * costs and holds one page however many entries exist.
117
+ */
118
+ const pageByPosition = (request, newest, idAt) => {
119
+ const through = request.through ?? newest;
120
+ const ids = [];
121
+ let position = request.after ?? 0;
122
+ let visited = 0;
123
+ while (position < through && visited < request.limit) {
124
+ position += 1;
125
+ visited += 1;
126
+ const id = idAt(position);
127
+ if (id !== undefined)
128
+ ids.push(id);
129
+ }
130
+ return position < through ? { ids, next: position, through } : { ids, through };
131
+ };
132
+ // SQL persistence breaks caller reference identity through serialization. The
133
+ // memory adapter must copy at the same boundaries or its test results diverge
134
+ // from the durable implementation when a caller mutates an input or result.
135
+ const snapshot = (value) => structuredClone(value);
136
+ const asStored = (plan) => ({
137
+ card: snapshot(plan.card),
138
+ decodedInput: snapshot(plan.decodedInput),
139
+ decision: plan.decision
140
+ });
141
+ // JSON tuple encoding keeps caller-chosen ids in separate fields, so a node's
142
+ // request id cannot alias another run's request or a plan id.
143
+ const approvalKey = (target) => target._tag === "Plan"
144
+ ? JSON.stringify([target._tag, target.planId])
145
+ : JSON.stringify([target._tag, target.runId, target.requestId]);
146
+ const sameApprovalIdentity = (left, right) => left._tag === "Plan"
147
+ ? right._tag === "Plan" && left.planId === right.planId
148
+ : right._tag === "Node" && left.runId === right.runId && left.requestId === right.requestId;
149
+ const approvalToken = (token) => ({
150
+ tokenId: token.tokenId,
151
+ target: snapshot(token.target),
152
+ ...snapshot(token.decision)
153
+ });
154
+ /**
155
+ * Deterministic in-memory runtime. It models the production fence and approval
156
+ * ordering seams but intentionally does not claim durable process survival;
157
+ * see `control-runtime-engine-integration`.
158
+ *
159
+ * @category layers
160
+ * @since 0.1.0
161
+ */
162
+ export const layerMemory = (options = {}) => Layer.effect(ControlRuntime, Effect.gen(function* () {
163
+ const crypto = yield* Crypto.Crypto;
164
+ const now = options.now ?? Date.now;
165
+ // The in-memory adapter stamps `memory`/`test` on a caller that names no
166
+ // principal, so its default policy delegates that identity as well as
167
+ // the local operator. Production policies never carry it.
168
+ const approvalAuthority = options.approvalAuthority ?? (yield* ApprovalAuthority.make([
169
+ {
170
+ principal: { id: "local", kind: "operator" },
171
+ scopes: ["once", "run", "remembered"],
172
+ targets: ["Plan", "Node"]
173
+ },
174
+ { principal: { id: "memory", kind: "test" }, scopes: ["once", "run", "remembered"], targets: ["Plan", "Node"] }
175
+ ]).pipe(Effect.orDie));
176
+ const authorizeApproval = approvalAuthority.authorize.bind(approvalAuthority);
177
+ const configuredFlows = options.flows ?? plannable.map((entry) => ({
178
+ flowId: entry.flowId,
179
+ description: `Reserved ${entry.verb} system flow`,
180
+ deployClass: entry.deployClass,
181
+ envelope: emptyEnvelope
182
+ }));
183
+ const flows = new Map(configuredFlows.map((flow) => [
184
+ flow.flowId,
185
+ { ...flow, envelope: snapshot(flow.envelope) }
186
+ ]));
187
+ const plans = new Map();
188
+ const planKeys = new Map();
189
+ const tokens = new Map();
190
+ const runs = new Map();
191
+ const mutations = new Map();
192
+ const runKeys = new Map();
193
+ const installedGrants = [];
194
+ const installedGrantKeys = new Set();
195
+ let planSequence = 0;
196
+ const signalCommands = new Map();
197
+ let pendingSignalOffset = 0;
198
+ let runSequence = 0;
199
+ let fenceSequence = 0;
200
+ let resumeSequence = 0;
201
+ const requireRun = (runId) => Effect.fromOption(Option.fromNullishOr(runs.get(runId)), () => new RunNotFound({ runId }));
202
+ const updateSummary = (run, fields) => {
203
+ run.summary = snapshot({ ...run.summary, ...fields, updatedAt: now() });
204
+ return snapshot(run.summary);
205
+ };
206
+ const checkFence = (runId, run, fence) => run.fence === undefined || run.fence !== fence
207
+ ? Effect.fail(new ClaimLost({ runId }))
208
+ : Effect.void;
209
+ /** `resume`, recording the identity `adopt` answers on the claimed run when given. */
210
+ const resumeRun = (runId, adopt) => Effect.gen(function* () {
211
+ const run = yield* requireRun(runId);
212
+ if (run.summary.status === "cancelled" ||
213
+ run.summary.status === "completed" ||
214
+ run.summary.status === "failed")
215
+ return snapshot(run.summary);
216
+ // Accepted claims are owned too; releasePending clears both fences.
217
+ if (run.fence !== undefined) {
218
+ /* v8 ignore next 3 -- one process holds this whole runtime, and it writes `fence` and `localFence` together; the peer this refuses exists only over a shared database, which is `SqlControlRuntime`'s fence */
219
+ if (run.localFence === undefined || run.fence !== run.localFence) {
220
+ return yield* new ClaimLost({ runId });
221
+ }
222
+ return snapshot(run.summary);
223
+ }
224
+ const adopted = adopt === undefined ? undefined : yield* adopt(run);
225
+ const fence = `fence-${++fenceSequence}`;
226
+ run.fence = fence;
227
+ run.localFence = fence;
228
+ // Claiming ends the park, so it ends the record of who wrote it.
229
+ return updateSummary(run, {
230
+ ...adopted,
231
+ status: "accepted",
232
+ ownerId: "memory-owner",
233
+ parkedBy: undefined
234
+ });
235
+ });
236
+ const service = make({
237
+ authorizeApproval,
238
+ plan: Effect.fn("ControlRuntime.plan")(function* (input) {
239
+ const flow = flows.get(input.flowId);
240
+ if (flow === undefined)
241
+ return yield* new FlowNotFound({ flowId: input.flowId });
242
+ const requestFingerprint = yield* Effect.try({
243
+ // Validate before cloning. Canonicalization reports a throwing
244
+ // getter at its stable path, while `structuredClone` would invoke
245
+ // the getter first and erase that safe diagnostic.
246
+ try: () => planFingerprint(input),
247
+ catch: (cause) => new InvalidInput({ issue: canonicalIssue(cause) })
248
+ });
249
+ const submitted = yield* Effect.try({
250
+ try: () => snapshot(input),
251
+ catch: (cause) => new InvalidInput({ issue: canonicalIssue(cause) })
252
+ });
253
+ if (submitted.idempotencyKey !== undefined) {
254
+ const prior = planKeys.get(submitted.idempotencyKey);
255
+ if (prior !== undefined) {
256
+ if (prior.fingerprint !== requestFingerprint) {
257
+ return yield* new InvalidInput({
258
+ issue: `idempotency key ${submitted.idempotencyKey} was used for another plan`
259
+ });
260
+ }
261
+ const stored = plans.get(prior.planId);
262
+ /* v8 ignore next -- this map never loses a plan a key names; `SqlControlRuntime` covers the storage that can */
263
+ if (stored !== undefined)
264
+ return { card: snapshot(stored.card), created: false };
265
+ }
266
+ }
267
+ const decoded = yield* (flow.decode?.(submitted.input) ?? Effect.try({
268
+ try: () => {
269
+ canonical(submitted.input);
270
+ return submitted.input;
271
+ },
272
+ catch: (cause) => new InvalidInput({ issue: canonicalIssue(cause) })
273
+ }));
274
+ const planId = `plan-${++planSequence}`;
275
+ const handoff = flow.plan === undefined ? undefined : yield* flow.plan(decoded, planId);
276
+ const card = snapshot(yield* planCard({
277
+ planId,
278
+ flowId: submitted.flowId,
279
+ decodedInput: decoded,
280
+ envelope: budgeted(flow.envelope, submitted.budget),
281
+ deployClass: flow.deployClass,
282
+ executionDigest: flow.executionDigest,
283
+ handoff,
284
+ idempotencyKey: submitted.idempotencyKey
285
+ }).pipe(Effect.provideService(Crypto.Crypto, crypto)));
286
+ // Decoding, planning and hashing may yield to another keyed plan.
287
+ // Recheck immediately before publication, with no yield between a
288
+ // successful check and the three map writes below.
289
+ if (submitted.idempotencyKey !== undefined) {
290
+ const prior = planKeys.get(submitted.idempotencyKey);
291
+ if (prior !== undefined) {
292
+ if (prior.fingerprint !== requestFingerprint) {
293
+ return yield* new InvalidInput({
294
+ issue: `idempotency key ${submitted.idempotencyKey} was used for another plan`
295
+ });
296
+ }
297
+ const stored = plans.get(prior.planId);
298
+ /* v8 ignore next -- this map never loses a plan a key names; `SqlControlRuntime` covers the storage that can */
299
+ if (stored !== undefined)
300
+ return { card: snapshot(stored.card), created: false };
301
+ }
302
+ }
303
+ plans.set(planId, { card, decodedInput: snapshot(decoded), decision: "pending" });
304
+ tokens.set(approvalKey(card.approval.target), {
305
+ tokenId: planId,
306
+ target: snapshot(card.approval.target),
307
+ decision: { _tag: "Pending" }
308
+ });
309
+ if (submitted.idempotencyKey !== undefined) {
310
+ planKeys.set(submitted.idempotencyKey, {
311
+ fingerprint: requestFingerprint,
312
+ planId
313
+ });
314
+ }
315
+ return { card: snapshot(card), created: true };
316
+ }),
317
+ getPlan: Effect.fn("ControlRuntime.getPlan")((planId) => Effect.fromOption(Option.fromNullishOr(plans.get(planId)), () => new PlanNotFound({ planId })).pipe(Effect.map(asStored))),
318
+ pagePlanIds: Effect.fn("ControlRuntime.pagePlanIds")(function* (request) {
319
+ yield* idPageLimit(request.limit);
320
+ return pageByPosition(request, planSequence, (position) => plans.has(`plan-${position}`) ? `plan-${position}` : undefined);
321
+ }),
322
+ queryPlans: Effect.fn("ControlRuntime.queryPlans")(function* (request) {
323
+ yield* idPageLimit(request.limit);
324
+ const matched = [];
325
+ let position = request.after ?? 0;
326
+ while (position < planSequence && matched.length < request.limit) {
327
+ position += 1;
328
+ const plan = plans.get(`plan-${position}`);
329
+ if (plan !== undefined &&
330
+ (request.flowId === undefined || plan.card.flowId === request.flowId) &&
331
+ (request.decision === undefined || plan.decision === request.decision))
332
+ matched.push(asStored(plan));
333
+ }
334
+ return position < planSequence ? { plans: matched, next: position } : { plans: matched };
335
+ }),
336
+ lookupApproval: Effect.fn("ControlRuntime.lookupApproval")(function* (target) {
337
+ const requested = snapshot(target);
338
+ const tokenId = requested._tag === "Plan" ? requested.planId : requested.requestId;
339
+ const token = yield* Effect.fromOption(Option.fromNullishOr(tokens.get(approvalKey(requested))), () => requested._tag === "Node"
340
+ ? new RunNotFound({ runId: requested.runId })
341
+ : new PlanNotFound({ planId: requested.planId }));
342
+ // A stored target that disagrees with its composite key is corrupted;
343
+ // accepting only its digest and envelope would recreate the alias the
344
+ // composite identity closes. In memory the key is DERIVED from the
345
+ // identity, so only a caller reaching into the map can produce the
346
+ // disagreement; `SqlControlRuntime` stores the two apart and covers
347
+ // the same refusal against a rewritten row.
348
+ /* v8 ignore next 6 -- unreachable while the map key is derived from the identity it is compared against */
349
+ if (!sameApprovalIdentity(token.target, requested)) {
350
+ return yield* new PersistenceError({
351
+ operation: "validate an approval token",
352
+ message: "The stored approval target does not match its identity"
353
+ });
354
+ }
355
+ if (token.target.digest !== requested.digest) {
356
+ return yield* new PlanDigestMismatch({
357
+ planId: tokenId,
358
+ expected: token.target.digest,
359
+ actual: requested.digest
360
+ });
361
+ }
362
+ if (!sameEnvelope(token.target.envelope, requested.envelope)) {
363
+ return yield* new EnvelopeMismatch({
364
+ planId: tokenId,
365
+ expected: canonical(token.target.envelope),
366
+ actual: canonical(requested.envelope)
367
+ });
368
+ }
369
+ if (token.decision._tag !== "Pending")
370
+ return yield* new AlreadyResolved({ requestId: tokenId });
371
+ return approvalToken(token);
372
+ }),
373
+ registerApproval: Effect.fn("ControlRuntime.registerApproval")(function* (target) {
374
+ const requested = snapshot(target);
375
+ yield* requireRun(requested.runId);
376
+ const key = approvalKey(requested);
377
+ const existing = tokens.get(key);
378
+ if (existing === undefined) {
379
+ const stored = {
380
+ tokenId: requested.requestId,
381
+ target: requested,
382
+ decision: { _tag: "Pending" }
383
+ };
384
+ tokens.set(key, stored);
385
+ return approvalToken(stored);
386
+ }
387
+ /* v8 ignore next 6 -- as in `lookupApproval`: the map key is derived from the identity, so a stored target can only disagree with it after a reach into the map */
388
+ if (!sameApprovalIdentity(existing.target, requested)) {
389
+ return yield* new PersistenceError({
390
+ operation: "validate an approval token",
391
+ message: "The stored approval target does not match its identity"
392
+ });
393
+ }
394
+ if (existing.target.digest !== requested.digest) {
395
+ return yield* new PlanDigestMismatch({
396
+ planId: requested.requestId,
397
+ expected: existing.target.digest,
398
+ actual: requested.digest
399
+ });
400
+ }
401
+ if (!sameEnvelope(existing.target.envelope, requested.envelope)) {
402
+ return yield* new EnvelopeMismatch({
403
+ planId: requested.requestId,
404
+ expected: canonical(existing.target.envelope),
405
+ actual: canonical(requested.envelope)
406
+ });
407
+ }
408
+ return approvalToken(existing);
409
+ }),
410
+ installBulkGrant: Effect.fn("ControlRuntime.installBulkGrant")((token, envelope, scope) => Effect.sync(() => {
411
+ const storedToken = snapshot(token);
412
+ const key = approvalKey(storedToken.target);
413
+ if (installedGrantKeys.has(key))
414
+ return;
415
+ installedGrantKeys.add(key);
416
+ installedGrants.push({
417
+ tokenId: storedToken.tokenId,
418
+ envelope: snapshot(envelope),
419
+ scope,
420
+ installedAt: now()
421
+ });
422
+ })),
423
+ resolveApproval: Effect.fn("ControlRuntime.resolveApproval")(function* (token, decision, principal, scope = "once") {
424
+ const requested = snapshot(token);
425
+ const requestedPrincipal = snapshot(principal);
426
+ const answer = yield* Schema.decodeUnknownEffect(ApprovalDecision)(decision === "approved"
427
+ ? { _tag: "Approved", decisionPrincipal: requestedPrincipal, decidedAt: now(), scope }
428
+ : { _tag: "Denied", decisionPrincipal: requestedPrincipal, decidedAt: now() }).pipe(Effect.mapError(() => new PersistenceError({
429
+ operation: "record an approval decision",
430
+ message: "The approval decision metadata is invalid"
431
+ })));
432
+ yield* authorizeApproval({ principal: requestedPrincipal, target: requested.target, decision, scope });
433
+ const mutable = tokens.get(approvalKey(requested.target));
434
+ if (mutable === undefined || mutable.decision._tag !== "Pending") {
435
+ return yield* new AlreadyResolved({ requestId: requested.tokenId });
436
+ }
437
+ mutable.decision = answer;
438
+ if (requested.target._tag === "Plan") {
439
+ const plan = plans.get(requested.target.planId);
440
+ /* v8 ignore next -- a plan token is only ever registered by `plan`, which stores the plan in the same call */
441
+ if (plan !== undefined)
442
+ plan.decision = decision;
443
+ }
444
+ }),
445
+ launch: Effect.fn("ControlRuntime.launch")(function* (planId, requestedDigest, envelope, principal, reservedRunId) {
446
+ const plan = yield* Effect.fromOption(Option.fromNullishOr(plans.get(planId)), () => new PlanNotFound({ planId }));
447
+ if (plan.card.digest !== requestedDigest) {
448
+ return yield* new PlanDigestMismatch({
449
+ planId,
450
+ expected: plan.card.digest,
451
+ actual: requestedDigest
452
+ });
453
+ }
454
+ if (!sameEnvelope(plan.card.envelope, envelope)) {
455
+ return yield* new EnvelopeMismatch({
456
+ planId,
457
+ expected: canonical(plan.card.envelope),
458
+ actual: canonical(envelope)
459
+ });
460
+ }
461
+ if (plan.decision === "pending") {
462
+ return {
463
+ _tag: "Parked",
464
+ receipt: {
465
+ _tag: "Parked",
466
+ receiptId: `launch:${planId}`,
467
+ planId,
468
+ status: "waiting-approval"
469
+ }
470
+ };
471
+ }
472
+ if (plan.decision !== "approved") {
473
+ return yield* new PlanDenied({ planId });
474
+ }
475
+ const sequence = ++runSequence;
476
+ const runId = reservedRunId ?? `run-${sequence}`;
477
+ if (runs.has(runId)) {
478
+ return yield* new PersistenceError({ operation: "reserve a run", message: "Run identity already exists" });
479
+ }
480
+ const fence = `fence-${++fenceSequence}`;
481
+ const timestamp = now();
482
+ const summary = {
483
+ runId,
484
+ flowId: plan.card.flowId,
485
+ status: "accepted",
486
+ planId,
487
+ planDigest: plan.card.digest,
488
+ ...(plan.card.executionDigest === undefined ? {} : { executionDigest: plan.card.executionDigest }),
489
+ ...(options.engineVersion === undefined ? {} : { engineVersion: options.engineVersion }),
490
+ ownerId: "memory-owner",
491
+ ...(principal === undefined ? {} : { launchedBy: { id: principal.id, kind: principal.kind } }),
492
+ ...(plan.card.envelope.budget.deadline === undefined
493
+ ? {}
494
+ : { deadlineAt: timestamp + plan.card.envelope.budget.deadline }),
495
+ createdAt: timestamp,
496
+ updatedAt: timestamp
497
+ };
498
+ runs.set(runId, {
499
+ summary: snapshot(summary),
500
+ fence,
501
+ localFence: fence,
502
+ sequence: runSequence,
503
+ signals: []
504
+ });
505
+ return {
506
+ _tag: "Started",
507
+ receipt: accepted(`launch:${planId}:${runId}`, runId),
508
+ run: snapshot(summary)
509
+ };
510
+ }),
511
+ getRun: Effect.fn("ControlRuntime.getRun")((runId) => Effect.map(requireRun(runId), (run) => snapshot(run.summary))),
512
+ pageRunIds: Effect.fn("ControlRuntime.pageRunIds")(function* (request) {
513
+ yield* idPageLimit(request.limit);
514
+ return pageByPosition(request, runSequence, (position) => runs.has(`run-${position}`) ? `run-${position}` : undefined);
515
+ }),
516
+ queryRuns: Effect.fn("ControlRuntime.queryRuns")(function* (request) {
517
+ if (!Number.isSafeInteger(request.limit) || request.limit < 1 || request.limit > 500) {
518
+ return yield* new InvalidInput({ issue: "limit: must be an integer between 1 and 500" });
519
+ }
520
+ const selected = [];
521
+ const filters = request.filters;
522
+ const newest = request.order === "newest";
523
+ const oldest = request.order === "oldest";
524
+ const candidates = newest
525
+ ? Array.from(runs.values()).sort((a, b) => b.summary.createdAt - a.summary.createdAt || b.sequence - a.sequence)
526
+ : oldest
527
+ ? Array.from(runs.values()).sort((a, b) => a.summary.createdAt - b.summary.createdAt || a.sequence - b.sequence)
528
+ : runs.values();
529
+ for (const run of candidates) {
530
+ const after = request.cursor;
531
+ if (after !== undefined && (after.source !== 0 || (newest
532
+ ? run.summary.createdAt > after.createdAt ||
533
+ (run.summary.createdAt === after.createdAt && run.sequence >= after.sequence)
534
+ : oldest
535
+ ? run.summary.createdAt < after.createdAt ||
536
+ (run.summary.createdAt === after.createdAt && run.sequence <= after.sequence)
537
+ : run.sequence <= after.sequence)))
538
+ continue;
539
+ const summary = run.summary;
540
+ if (filters?.since !== undefined && summary.createdAt < filters.since)
541
+ continue;
542
+ if (filters?.until !== undefined && summary.createdAt >= filters.until)
543
+ continue;
544
+ if (filters?.runIds !== undefined && !filters.runIds.includes(summary.runId))
545
+ continue;
546
+ if (filters?.flowId !== undefined && summary.flowId !== filters.flowId)
547
+ continue;
548
+ if (filters?.status !== undefined && summary.status !== filters.status)
549
+ continue;
550
+ if (filters?.terminal !== undefined &&
551
+ ["completed", "failed", "cancelled"].includes(summary.status) !== filters.terminal)
552
+ continue;
553
+ if (filters?.parentRunId !== undefined && summary.parentRunId !== filters.parentRunId)
554
+ continue;
555
+ if (filters?.lineageId !== undefined && summary.lineageId !== filters.lineageId)
556
+ continue;
557
+ if (filters?.launchedBy !== undefined && !launchedByMatches(summary, filters.launchedBy))
558
+ continue;
559
+ selected.push(run);
560
+ if (selected.length > request.limit)
561
+ break;
562
+ }
563
+ const page = selected.slice(0, request.limit);
564
+ const last = page.at(-1);
565
+ return {
566
+ items: page.map((run) => snapshot(run.summary)),
567
+ ...(selected.length <= request.limit || last === undefined ? {} : {
568
+ nextCursor: {
569
+ source: 0,
570
+ sequence: last.sequence,
571
+ createdAt: last.summary.createdAt,
572
+ runId: last.summary.runId
573
+ }
574
+ })
575
+ };
576
+ }),
577
+ listFlows: Effect.fn("ControlRuntime.listFlows")(() => Effect.sync(() => Array.from(flows.values(), (flow) => ({
578
+ flowId: flow.flowId,
579
+ description: flow.description
580
+ }))))(),
581
+ deliverSignal: Effect.fn("ControlRuntime.deliverSignal")((runId, signal) => Effect.tap(requireRun(runId), (run) => Effect.sync(() => void run.signals.push(snapshot(signal))))),
582
+ admitSignal: Effect.fn("ControlRuntime.admitSignal")(function* (commandId, runId, signal, principal) {
583
+ yield* requireRun(runId);
584
+ if (!signalCommands.has(commandId)) {
585
+ signalCommands.set(commandId, snapshot({
586
+ commandId,
587
+ runId,
588
+ signal,
589
+ token: null,
590
+ state: "pending",
591
+ ...(principal === undefined ? {} : { principal })
592
+ }));
593
+ }
594
+ }),
595
+ signalCommand: (commandId) => Effect.sync(() => snapshot(signalCommands.get(commandId))),
596
+ pendingSignals: Effect.sync(() => {
597
+ const pending = Array.from(signalCommands.values()).filter((command) => command.state === "pending");
598
+ if (pendingSignalOffset >= pending.length)
599
+ pendingSignalOffset = 0;
600
+ const page = pending.slice(pendingSignalOffset, pendingSignalOffset + 100);
601
+ pendingSignalOffset += page.length;
602
+ return snapshot(page);
603
+ }),
604
+ bindSignal: (commandId, token) => Effect.gen(function* () {
605
+ const command = signalCommands.get(commandId);
606
+ if (command === undefined) {
607
+ return yield* new PersistenceError({
608
+ operation: "bind signal",
609
+ message: `No pending signal command ${commandId}`
610
+ });
611
+ }
612
+ if (command.state !== "pending")
613
+ return command.token;
614
+ if (command.token === null &&
615
+ Array.from(signalCommands.values()).some((other) => other.commandId !== commandId && other.token === token))
616
+ return null;
617
+ const bound = command.token ?? token;
618
+ signalCommands.set(commandId, { ...command, token: bound });
619
+ return bound;
620
+ }),
621
+ settleSignal: (commandId, state) => Effect.sync(() => {
622
+ const command = signalCommands.get(commandId);
623
+ if (command !== undefined && command.state === "pending") {
624
+ signalCommands.set(commandId, { ...command, state });
625
+ }
626
+ }),
627
+ deliveredSignals: Effect.fn("ControlRuntime.deliveredSignals")((runId) => Effect.map(requireRun(runId), (run) => snapshot([
628
+ ...run.signals,
629
+ ...Array.from(signalCommands.values()).filter((command) => command.runId === runId && command.state !== "rejected").map((command) => command.signal)
630
+ ]))),
631
+ requestResume: Effect.fn("ControlRuntime.requestResume")(function* (runId, options) {
632
+ const run = yield* requireRun(runId);
633
+ if (run.summary.status === "cancelled" ||
634
+ run.summary.status === "completed" ||
635
+ run.summary.status === "failed") {
636
+ return yield* new InvalidInput({
637
+ issue: `run ${runId} is ${run.summary.status} and cannot take a resume`
638
+ });
639
+ }
640
+ const sequence = ++resumeSequence;
641
+ run.pendingResume = sequence;
642
+ run.pendingResumeAtMs = now();
643
+ run.pendingConsent = options?.consent ?? run.pendingConsent;
644
+ updateSummary(run, { pendingResume: sequence });
645
+ return sequence;
646
+ }),
647
+ pendingResumes: Effect.sync(() => Array.from(runs.entries()).flatMap(([runId, run]) => run.pendingResume === undefined || run.pendingResumeAtMs === undefined ||
648
+ run.summary.status === "cancelled" ||
649
+ run.summary.status === "completed" || run.summary.status === "failed"
650
+ ? []
651
+ : [{
652
+ runId,
653
+ sequence: run.pendingResume,
654
+ requestedAtMs: run.pendingResumeAtMs,
655
+ ...(run.pendingConsent === undefined ? {} : { consent: run.pendingConsent })
656
+ }])),
657
+ clearResume: Effect.fn("ControlRuntime.clearResume")((runId, sequence) => Effect.sync(() => {
658
+ const run = runs.get(runId);
659
+ if (run === undefined || run.pendingResume !== sequence)
660
+ return;
661
+ run.pendingResume = undefined;
662
+ run.pendingResumeAtMs = undefined;
663
+ run.pendingConsent = undefined;
664
+ updateSummary(run, { pendingResume: undefined });
665
+ })),
666
+ registerFiber: Effect.fn("ControlRuntime.registerFiber")((runId, fiber) => Effect.tap(requireRun(runId), (run) => Effect.sync(() => {
667
+ run.fiber = fiber;
668
+ fiber.addObserver(() => {
669
+ if (run.fiber === fiber)
670
+ run.fiber = undefined;
671
+ });
672
+ }))),
673
+ interrupt: Effect.fn("ControlRuntime.interrupt")(function* (runId, settle = (effect) => effect) {
674
+ const run = yield* requireRun(runId);
675
+ // Terminal first, as `resume` does: a settled run released its fence,
676
+ // and answering `ClaimLost` there would name the wrong problem.
677
+ if (run.summary.status === "cancelled" ||
678
+ run.summary.status === "completed" ||
679
+ run.summary.status === "failed")
680
+ return snapshot(run.summary);
681
+ if (run.localFence === undefined)
682
+ return yield* new ClaimLost({ runId });
683
+ const fence = run.localFence;
684
+ yield* checkFence(runId, run, fence);
685
+ if (run.fiber !== undefined)
686
+ yield* Fiber.interrupt(run.fiber);
687
+ return yield* settle(Effect.gen(function* () {
688
+ // Cleanup may settle the run or release and replace its owner.
689
+ if (run.summary.status === "cancelled" ||
690
+ run.summary.status === "completed" ||
691
+ run.summary.status === "failed")
692
+ return snapshot(run.summary);
693
+ yield* checkFence(runId, run, fence);
694
+ run.fence = undefined;
695
+ run.localFence = undefined;
696
+ return updateSummary(run, { status: "cancelled", ownerId: undefined, parkedBy: undefined });
697
+ }));
698
+ }),
699
+ codeDrift: Effect.fn("ControlRuntime.codeDrift")(function* (runId) {
700
+ const run = yield* requireRun(runId);
701
+ return codeDriftOf(run.summary, flows.get(run.summary.flowId), options.engineVersion);
702
+ }),
703
+ recordedCode: Effect.fn("ControlRuntime.recordedCode")(function* (runId) {
704
+ const { executionDigest, engineVersion } = (yield* requireRun(runId)).summary;
705
+ return { executionDigest, engineVersion };
706
+ }),
707
+ resume: Effect.fn("ControlRuntime.resume")((runId) => resumeRun(runId)),
708
+ // `plan` refuses a flow this catalog does not hold, and the catalog is
709
+ // fixed at construction, so a run's flow is never gone here and there
710
+ // is always code to adopt. `SqlControlRuntime` reads a catalog that can
711
+ // lose a flow, and refuses that case before the claim.
712
+ resumeAdopting: Effect.fn("ControlRuntime.resumeAdopting")((runId) => resumeRun(runId, (run) => Effect.succeed(adoptedCode(run.summary, flows.get(run.summary.flowId), options.engineVersion)))),
713
+ claimFence: Effect.fn("ControlRuntime.claimFence")(function* (runId) {
714
+ const run = yield* requireRun(runId);
715
+ if (run.localFence === undefined)
716
+ return yield* new ClaimLost({ runId });
717
+ return run.localFence;
718
+ }),
719
+ releasePending: Effect.fn("ControlRuntime.releasePending")(function* (runId, fence) {
720
+ const run = yield* requireRun(runId);
721
+ yield* checkFence(runId, run, fence);
722
+ run.fence = undefined;
723
+ run.localFence = undefined;
724
+ return updateSummary(run, {
725
+ status: "accepted",
726
+ ownerId: undefined,
727
+ parkedBy: undefined
728
+ });
729
+ }),
730
+ writeStatus: Effect.fn("ControlRuntime.writeStatus")(function* (runId, fence, status) {
731
+ const run = yield* requireRun(runId);
732
+ yield* checkFence(runId, run, fence);
733
+ // Ownership is released by any status that is not being driven, so the
734
+ // fence that wrote a terminal or parked status is spent by writing it.
735
+ if (status === "accepted" || status === "running") {
736
+ return updateSummary(run, { status, parkedBy: undefined });
737
+ }
738
+ run.fence = undefined;
739
+ run.localFence = undefined;
740
+ // The spent fence is kept on a park, and only on a park: it is the
741
+ // only thing left on the row that says which host parked it.
742
+ return updateSummary(run, {
743
+ status,
744
+ ownerId: undefined,
745
+ parkedBy: status === "parked" || status === "waiting-approval" ? fence : undefined
746
+ });
747
+ }),
748
+ // Precedence matches `SqlControlRuntime`: the submitted identity is
749
+ // the one a server authenticated, and the configured one is this
750
+ // composition's fallback for a caller that named none.
751
+ stampPrincipal: Effect.fn("ControlRuntime.stampPrincipal")((submitted) => Effect.sync(() => ({
752
+ id: submitted?.id ?? options.principal?.id ?? "memory",
753
+ kind: submitted?.kind ?? options.principal?.kind ?? "test",
754
+ stampedAt: now()
755
+ }))),
756
+ lookupMutation: Effect.fn("ControlRuntime.lookupMutation")((key, fingerprint) => Effect.sync(() => {
757
+ const record = mutations.get(key);
758
+ if (record === undefined)
759
+ return undefined;
760
+ return record.fingerprint === fingerprint
761
+ ? replayReceipt(key, record.receipt)
762
+ : { _tag: "Conflict", message: `idempotency key ${key} was used for another mutation` };
763
+ })),
764
+ recordMutation: Effect.fn("ControlRuntime.recordMutation")((key, fingerprint, receipt) => Effect.gen(function* () {
765
+ const prior = mutations.get(key);
766
+ if (prior !== undefined &&
767
+ (prior.fingerprint !== fingerprint || canonical(prior.receipt) !== canonical(receipt))) {
768
+ return yield* Effect.fail(new PersistenceError({
769
+ operation: "record a mutation",
770
+ message: `Idempotency key ${key} was already settled by another mutation`
771
+ }));
772
+ }
773
+ mutations.set(key, { fingerprint, receipt: snapshot(receipt) });
774
+ })),
775
+ claimRunKey: Effect.fn("ControlRuntime.claimRunKey")(function* (key, fingerprint) {
776
+ const holder = runKeys.get(key);
777
+ if (holder !== undefined) {
778
+ if (holder.fingerprint !== fingerprint) {
779
+ return yield* new InvalidInput({
780
+ issue: `idempotency key ${key} was used for another run`
781
+ });
782
+ }
783
+ const record = mutations.get(key);
784
+ if (record === undefined) {
785
+ // One process owns this runtime, so reaching here takes a caller
786
+ // that claimed and never recorded: the cross-process winner this
787
+ // branch really models commits both in one transaction, which is
788
+ // `SqlControlRuntime`'s seam.
789
+ return yield* new PersistenceError({
790
+ operation: "read a mutation",
791
+ message: `run key ${key} was claimed by a mutation that recorded no receipt`
792
+ });
793
+ }
794
+ return { _tag: "Raced", receipt: snapshot(record.receipt) };
795
+ }
796
+ runKeys.set(key, { fingerprint });
797
+ return { _tag: "Claimed" };
798
+ }),
799
+ releaseRunKey: Effect.fn("ControlRuntime.releaseRunKey")((key) => Effect.sync(() => {
800
+ runKeys.delete(key);
801
+ })),
802
+ grants: Effect.fn("ControlRuntime.grants")(() => Effect.sync(() => snapshot(installedGrants)))()
803
+ });
804
+ return service;
805
+ }));
806
+ Fault.register("/control/ApprovalPending", "wait");
807
+ Fault.register("/control/ApprovalDenied", "user");
808
+ //# sourceMappingURL=ControlRuntime.js.map