@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,116 @@
1
+ /**
2
+ * Cancellation attribution as the control plane reads it back.
3
+ *
4
+ * A cancellation is durable but anonymous. `flows_runs.cancel_requested_at_ms`
5
+ * records that somebody asked, and nothing in the row records who, why, or
6
+ * whether the run was asked at all rather than swept up in an ancestor's
7
+ * cascade. Three journals hold the missing halves: the control plane's own
8
+ * `control.run.cancel-requested`, the engine's `flows.engine.interrupted`, and
9
+ * the ancestry the run store already keeps. This module folds them into one
10
+ * answer per run.
11
+ *
12
+ * The fold is pure and scope-independent: it reads whatever evidence it is
13
+ * handed and never issues a query, so the caller chooses how much to read.
14
+ * `SqlControlRuntime` uses two scopes. A listing folds the whole database,
15
+ * because every row is going to be answered for anyway. A single-run read
16
+ * folds only that run and its ancestor chain, which is the smallest scope that
17
+ * can still answer the question: cascade is a fact about a run's ancestors, so
18
+ * it cannot be decided one row at a time — a child's cancellation is
19
+ * attributed to whoever asked for its nearest cancelled ancestor's, however
20
+ * many rounds up that is.
21
+ *
22
+ * The two foreign event types are named as strings rather than imported,
23
+ * exactly as in {@link Lineage}: a control plane reads journals, not engines.
24
+ *
25
+ * @since 0.1.0
26
+ */
27
+ /**
28
+ * The journal event type the control plane records an attributed cancel under.
29
+ *
30
+ * @category constants
31
+ * @since 0.1.0
32
+ */
33
+ export const requestedEventType = "control.run.cancel-requested";
34
+ /**
35
+ * The journal event type the engine records an interruption under.
36
+ *
37
+ * @category constants
38
+ * @since 0.1.0
39
+ */
40
+ export const interruptedEventType = "flows.engine.interrupted";
41
+ const cancelled = (evidence, requests) => evidence.cancelRequestedAt !== undefined || evidence.cancelledAt !== undefined ||
42
+ requests.has(evidence.runId);
43
+ const requestedAtOf = (evidence, request) => {
44
+ /* v8 ignore next -- the trailing zero is unreachable: a run is attributed only once one of the three sources above exists, which is exactly what `cancelled` reads */
45
+ return request?.requestedAt ?? evidence.cancelRequestedAt ?? evidence.cancelledAt ?? 0;
46
+ };
47
+ const attributed = (source, requestedAt, request, cascadedFrom) => ({
48
+ requestedAt,
49
+ source,
50
+ ...(request?.principal === undefined ? {} : { principal: request.principal }),
51
+ ...(request?.reason === undefined ? {} : { reason: request.reason }),
52
+ ...(cascadedFrom === undefined ? {} : { cascadedFrom })
53
+ });
54
+ /**
55
+ * Attributes every cancelled run in one pass.
56
+ *
57
+ * Three sources, in the order a run's own evidence outranks its ancestors':
58
+ *
59
+ * 1. A `control.run.cancel-requested` entry names this run. Somebody asked for
60
+ * this run by name, and the entry says who and why.
61
+ * 2. A cancelled ancestor exists. The run was swept up in that cancellation,
62
+ * so it reports `cascade`, names the ancestor whose request started it, and
63
+ * inherits that request's principal and reason — the honest answer to "who
64
+ * cancelled this child" is the operator who cancelled its parent.
65
+ * 3. Neither. The engine cancelled the run on its own account: a lease
66
+ * expiry, a budget, a supervisor. There is no principal to report, and
67
+ * inventing one would be worse than saying nothing.
68
+ *
69
+ * The ancestor walk carries a visited set. A cyclic parent chain is not
70
+ * reachable through the engine's cycle detection, but a projection that hung
71
+ * on corrupt ancestry would take the control plane down with it.
72
+ *
73
+ * @param input the run evidence and the attributed requests
74
+ * @category projections
75
+ * @since 0.1.0
76
+ */
77
+ export const attribute = (input) => {
78
+ const evidence = new Map(input.runs.map((run) => [run.runId, run]));
79
+ const result = new Map();
80
+ for (const run of input.runs) {
81
+ if (!cancelled(run, input.requests))
82
+ continue;
83
+ const own = input.requests.get(run.runId);
84
+ const requestedAt = requestedAtOf(run, own);
85
+ if (own !== undefined) {
86
+ result.set(run.runId, attributed("control", requestedAt, own));
87
+ continue;
88
+ }
89
+ const visited = new Set([run.runId]);
90
+ let ancestorId = run.parentRunId;
91
+ let cascadedFrom;
92
+ let origin;
93
+ while (ancestorId !== undefined && !visited.has(ancestorId)) {
94
+ visited.add(ancestorId);
95
+ const ancestor = evidence.get(ancestorId);
96
+ if (ancestor === undefined)
97
+ break;
98
+ if (cancelled(ancestor, input.requests)) {
99
+ // The nearest cancelled ancestor is the run this one was swept up
100
+ // with; the request that started it may be further up still, because
101
+ // a cascade cascades.
102
+ if (cascadedFrom === undefined)
103
+ cascadedFrom = ancestorId;
104
+ origin = input.requests.get(ancestorId);
105
+ if (origin !== undefined)
106
+ break;
107
+ }
108
+ ancestorId = ancestor.parentRunId;
109
+ }
110
+ result.set(run.runId, cascadedFrom === undefined
111
+ ? attributed("engine", requestedAt, undefined)
112
+ : attributed("cascade", requestedAt, origin, cascadedFrom));
113
+ }
114
+ return result;
115
+ };
116
+ //# sourceMappingURL=Cancellation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Cancellation.js","sourceRoot":"","sources":["../../src/Cancellation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Cancellation attribution as the control plane reads it back.\n *\n * A cancellation is durable but anonymous. `flows_runs.cancel_requested_at_ms`\n * records that somebody asked, and nothing in the row records who, why, or\n * whether the run was asked at all rather than swept up in an ancestor's\n * cascade. Three journals hold the missing halves: the control plane's own\n * `control.run.cancel-requested`, the engine's `flows.engine.interrupted`, and\n * the ancestry the run store already keeps. This module folds them into one\n * answer per run.\n *\n * The fold is pure and scope-independent: it reads whatever evidence it is\n * handed and never issues a query, so the caller chooses how much to read.\n * `SqlControlRuntime` uses two scopes. A listing folds the whole database,\n * because every row is going to be answered for anyway. A single-run read\n * folds only that run and its ancestor chain, which is the smallest scope that\n * can still answer the question: cascade is a fact about a run's ancestors, so\n * it cannot be decided one row at a time — a child's cancellation is\n * attributed to whoever asked for its nearest cancelled ancestor's, however\n * many rounds up that is.\n *\n * The two foreign event types are named as strings rather than imported,\n * exactly as in {@link Lineage}: a control plane reads journals, not engines.\n *\n * @since 0.1.0\n */\n\nimport type { Cancellation, Principal } from \"./ControlSchema.ts\"\n\n/**\n * The journal event type the control plane records an attributed cancel under.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const requestedEventType = \"control.run.cancel-requested\"\n\n/**\n * The journal event type the engine records an interruption under.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const interruptedEventType = \"flows.engine.interrupted\"\n\n/**\n * One attributed cancel request, as the control plane journaled it.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Request {\n readonly requestedAt: number\n readonly principal?: Principal | undefined\n readonly reason?: string | undefined\n}\n\n/**\n * What one run row discloses about its own cancellation.\n *\n * `cancelRequestedAt` is the run store's unfenced request column, and\n * `cancelledAt` is the moment the engine journaled the interruption. Either\n * one alone is evidence that this run was cancelled, and so is an attributed\n * request naming the run: a control plane that cancels a run it owns\n * interrupts the fiber rather than asking the column, and the journal entry it\n * wrote is the whole record.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Evidence {\n readonly runId: string\n readonly parentRunId?: string | undefined\n readonly cancelRequestedAt?: number | undefined\n readonly cancelledAt?: number | undefined\n}\n\n/**\n * Everything the fold reads.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Input {\n readonly runs: ReadonlyArray<Evidence>\n /** Attributed requests the control plane journaled, by run id. */\n readonly requests: ReadonlyMap<string, Request>\n}\n\nconst cancelled = (evidence: Evidence, requests: Input[\"requests\"]): boolean =>\n evidence.cancelRequestedAt !== undefined || evidence.cancelledAt !== undefined ||\n requests.has(evidence.runId)\n\nconst requestedAtOf = (evidence: Evidence, request: Request | undefined): number => {\n /* v8 ignore next -- the trailing zero is unreachable: a run is attributed only once one of the three sources above exists, which is exactly what `cancelled` reads */\n return request?.requestedAt ?? evidence.cancelRequestedAt ?? evidence.cancelledAt ?? 0\n}\n\nconst attributed = (\n source: Cancellation[\"source\"],\n requestedAt: number,\n request: Request | undefined,\n cascadedFrom?: string\n): Cancellation => ({\n requestedAt,\n source,\n ...(request?.principal === undefined ? {} : { principal: request.principal }),\n ...(request?.reason === undefined ? {} : { reason: request.reason }),\n ...(cascadedFrom === undefined ? {} : { cascadedFrom })\n})\n\n/**\n * Attributes every cancelled run in one pass.\n *\n * Three sources, in the order a run's own evidence outranks its ancestors':\n *\n * 1. A `control.run.cancel-requested` entry names this run. Somebody asked for\n * this run by name, and the entry says who and why.\n * 2. A cancelled ancestor exists. The run was swept up in that cancellation,\n * so it reports `cascade`, names the ancestor whose request started it, and\n * inherits that request's principal and reason — the honest answer to \"who\n * cancelled this child\" is the operator who cancelled its parent.\n * 3. Neither. The engine cancelled the run on its own account: a lease\n * expiry, a budget, a supervisor. There is no principal to report, and\n * inventing one would be worse than saying nothing.\n *\n * The ancestor walk carries a visited set. A cyclic parent chain is not\n * reachable through the engine's cycle detection, but a projection that hung\n * on corrupt ancestry would take the control plane down with it.\n *\n * @param input the run evidence and the attributed requests\n * @category projections\n * @since 0.1.0\n */\nexport const attribute = (input: Input): ReadonlyMap<string, Cancellation> => {\n const evidence = new Map(input.runs.map((run) => [run.runId, run]))\n const result = new Map<string, Cancellation>()\n for (const run of input.runs) {\n if (!cancelled(run, input.requests)) continue\n const own = input.requests.get(run.runId)\n const requestedAt = requestedAtOf(run, own)\n if (own !== undefined) {\n result.set(run.runId, attributed(\"control\", requestedAt, own))\n continue\n }\n const visited = new Set<string>([run.runId])\n let ancestorId = run.parentRunId\n let cascadedFrom: string | undefined\n let origin: Request | undefined\n while (ancestorId !== undefined && !visited.has(ancestorId)) {\n visited.add(ancestorId)\n const ancestor = evidence.get(ancestorId)\n if (ancestor === undefined) break\n if (cancelled(ancestor, input.requests)) {\n // The nearest cancelled ancestor is the run this one was swept up\n // with; the request that started it may be further up still, because\n // a cascade cascades.\n if (cascadedFrom === undefined) cascadedFrom = ancestorId\n origin = input.requests.get(ancestorId)\n if (origin !== undefined) break\n }\n ancestorId = ancestor.parentRunId\n }\n result.set(\n run.runId,\n cascadedFrom === undefined\n ? attributed(\"engine\", requestedAt, undefined)\n : attributed(\"cascade\", requestedAt, origin, cascadedFrom)\n )\n }\n return result\n}\n"]}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Channel contracts and the in-memory channel coordinator.
3
+ *
4
+ * A channel verifies opaque transport data before it decodes or maps it. The
5
+ * resulting request is then dispatched through the Control service; channels
6
+ * do not acquire an execution path of their own.
7
+ *
8
+ * @since 0.1.0
9
+ */
10
+ import { Context, Effect, Layer, type Schema } from "effect";
11
+ import { Control } from "./Control.ts";
12
+ import { type ControlError, InvalidInput, type Unauthorized, Unavailable } from "./ControlError.ts";
13
+ import { ControlRuntime } from "./ControlRuntime.ts";
14
+ import type { FlowId, IdempotencyKey, Receipt, RunId, RunSummary, SignalPayload } from "./ControlSchema.ts";
15
+ /**
16
+ * Opaque request data passed to a channel before decoding.
17
+ *
18
+ * @category models
19
+ * @since 0.1.0
20
+ */
21
+ export interface RawInbound {
22
+ readonly body: Uint8Array;
23
+ readonly headers: Readonly<Record<string, string | undefined>>;
24
+ readonly idempotencyKey: IdempotencyKey;
25
+ }
26
+ /**
27
+ * The channel mapping after a verified payload has been decoded.
28
+ *
29
+ * @category models
30
+ * @since 0.1.0
31
+ */
32
+ export type InboundResult = {
33
+ readonly _tag: "Start";
34
+ readonly flowId: FlowId;
35
+ readonly input: unknown;
36
+ } | {
37
+ readonly _tag: "Signal";
38
+ readonly runId: RunId;
39
+ readonly signal: SignalPayload;
40
+ };
41
+ /**
42
+ * A process-local per-channel delivery record. `messageId` identifies an existing
43
+ * platform message, so a reconnect can update it instead of posting again.
44
+ *
45
+ * @category models
46
+ * @since 0.1.0
47
+ */
48
+ export interface Delivery {
49
+ readonly cursor: string;
50
+ readonly messageId?: string | undefined;
51
+ }
52
+ /**
53
+ * A side-effect-free outbound projection. Network delivery belongs to the
54
+ * transport adapter, which consumes this value after it is journaled.
55
+ *
56
+ * @category models
57
+ * @since 0.1.0
58
+ */
59
+ export interface DeliveryProjection {
60
+ readonly cursor: string;
61
+ readonly messageId?: string | undefined;
62
+ readonly operation: "post" | "edit" | "noop";
63
+ readonly message: unknown;
64
+ }
65
+ /**
66
+ * A bidirectional platform adapter.
67
+ *
68
+ * `verify` must only inspect opaque bytes and headers. It always precedes
69
+ * `decode`, preventing untrusted public requests from reaching planning.
70
+ *
71
+ * @category services
72
+ * @since 0.1.0
73
+ */
74
+ export interface Channel<A = unknown> {
75
+ readonly name: string;
76
+ readonly schema: Schema.Schema<A>;
77
+ /**
78
+ * Non-secret HTTP header names whose values change the decoded command.
79
+ * Names are matched case-insensitively and folded into durable idempotency;
80
+ * signature, authorization, and credential headers must not be declared.
81
+ */
82
+ readonly fingerprintHeaders?: ReadonlyArray<string> | undefined;
83
+ readonly verify: (raw: RawInbound) => Effect.Effect<void, Unauthorized>;
84
+ /** Deterministic, side-effect-free decoding; retries may evaluate it again. */
85
+ readonly decode: (raw: RawInbound) => Effect.Effect<A, InvalidInput>;
86
+ /** Deterministic, side-effect-free mapping; retries may evaluate it again. */
87
+ readonly map: (payload: A) => Effect.Effect<InboundResult, InvalidInput>;
88
+ readonly project: (run: RunSummary, delivery: Delivery | undefined) => DeliveryProjection;
89
+ }
90
+ /**
91
+ * A registered channel with its payload type hidden. Decoding and mapping stay
92
+ * paired, so lookup never exposes a mapper accepting arbitrary unknown data.
93
+ *
94
+ * @category models
95
+ * @since 1.0.0
96
+ */
97
+ export interface RegisteredChannel extends Omit<Channel, "decode" | "map"> {
98
+ readonly decodeAndMap: (raw: RawInbound) => Effect.Effect<InboundResult, InvalidInput>;
99
+ }
100
+ /**
101
+ * Arguments for ingesting one authenticated channel request.
102
+ *
103
+ * @category models
104
+ * @since 0.1.0
105
+ */
106
+ export interface IngestRequest {
107
+ readonly channel: string;
108
+ readonly raw: RawInbound;
109
+ }
110
+ /**
111
+ * Arguments for projecting one run onto a channel.
112
+ *
113
+ * @category models
114
+ * @since 0.1.0
115
+ */
116
+ export interface ProjectRequest {
117
+ readonly channel: string;
118
+ readonly run: RunSummary;
119
+ }
120
+ /**
121
+ * The channel coordinator.
122
+ *
123
+ * @category services
124
+ * @since 0.1.0
125
+ */
126
+ export interface Channels {
127
+ readonly register: <A>(channel: Channel<A>) => Effect.Effect<void>;
128
+ readonly lookup: (name: string) => Effect.Effect<RegisteredChannel, Unavailable>;
129
+ readonly ingest: (request: IngestRequest) => Effect.Effect<Receipt, ControlError>;
130
+ readonly project: (request: ProjectRequest) => Effect.Effect<DeliveryProjection, Unavailable>;
131
+ }
132
+ /**
133
+ * Service tag for channel registration, ingestion, and pure projection.
134
+ *
135
+ * @category services
136
+ * @since 0.1.0
137
+ */
138
+ export declare const Channels: Context.Service<Channels, Channels>;
139
+ /**
140
+ * Builds the coordinator over Control's durable mutation store.
141
+ *
142
+ * @category constructors
143
+ * @since 1.0.0
144
+ */
145
+ export declare const make: Effect.Effect<Channels, never, Control | ControlRuntime>;
146
+ /**
147
+ * Builds a process-local coordinator for adapter unit tests.
148
+ *
149
+ * Production hosts use {@link layer}; this constructor deliberately makes no
150
+ * restart guarantee.
151
+ *
152
+ * @category constructors
153
+ * @since 1.0.0
154
+ */
155
+ export declare const makeMemory: Effect.Effect<Channels, never, Control>;
156
+ /**
157
+ * Channel layer with durable inbound receipts supplied by `ControlRuntime`.
158
+ *
159
+ * @category layers
160
+ * @since 0.1.0
161
+ */
162
+ export declare const layer: Layer.Layer<Channels, never, Control | ControlRuntime>;
163
+ /**
164
+ * Process-local channel layer for adapter unit tests only.
165
+ *
166
+ * @category layers
167
+ * @since 1.0.0
168
+ */
169
+ export declare const layerMemory: Layer.Layer<Channels, never, Control>;
170
+ //# sourceMappingURL=Channels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Channels.d.ts","sourceRoot":"","sources":["../../src/Channels.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Channel contracts and the in-memory channel coordinator.
3
+ *
4
+ * A channel verifies opaque transport data before it decodes or maps it. The
5
+ * resulting request is then dispatched through the Control service; channels
6
+ * do not acquire an execution path of their own.
7
+ *
8
+ * @since 0.1.0
9
+ */
10
+ import * as Sha256 from "@smthrs/crypto/Sha256";
11
+ import { Clock, Context, Effect, Layer, Ref, Semaphore } from "effect";
12
+ import { Control } from "./Control.js";
13
+ import { InvalidInput, Unavailable } from "./ControlError.js";
14
+ import { ControlRuntime } from "./ControlRuntime.js";
15
+ import { alreadyApplied } from "./internal/planning.js";
16
+ const eraseChannel = (channel) => ({
17
+ name: channel.name,
18
+ schema: channel.schema,
19
+ fingerprintHeaders: channel.fingerprintHeaders,
20
+ verify: (raw) => channel.verify(raw),
21
+ decodeAndMap: (raw) => Effect.flatMap(channel.decode(raw), (payload) => channel.map(payload)),
22
+ project: (run, delivery) => channel.project(run, delivery)
23
+ });
24
+ /**
25
+ * Service tag for channel registration, ingestion, and pure projection.
26
+ *
27
+ * @category services
28
+ * @since 0.1.0
29
+ */
30
+ export const Channels = Context.Service("/control/Channels");
31
+ const unavailable = (feature) => new Unavailable({
32
+ feature,
33
+ ticket: "control-channel-registry"
34
+ });
35
+ const deliveryKey = (channel, run) => `${channel.length}:${channel}:${run.runId}`;
36
+ /** Collision-free control-plane key for one channel-owned external key. */
37
+ const scopedKey = (channel, key) => `channel:${channel.length}:${channel}:${key}`;
38
+ const mutationKey = (channel, key) => `channel.ingest:${scopedKey(channel, key)}`;
39
+ const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
40
+ const typedArrayBuffer = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer")?.get;
41
+ const typedArrayByteOffset = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteOffset")?.get;
42
+ const typedArrayByteLength = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength")?.get;
43
+ class InboundBoundaryError extends TypeError {
44
+ issue;
45
+ constructor(issue) {
46
+ super(issue);
47
+ this.issue = issue;
48
+ }
49
+ }
50
+ const boundary = (issue) => {
51
+ throw new InboundBoundaryError(issue);
52
+ };
53
+ const plainRecord = (input, path) => {
54
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
55
+ return boundary(`${path}: must be a plain record`);
56
+ }
57
+ const prototype = Object.getPrototypeOf(input);
58
+ if (prototype !== Object.prototype && prototype !== null) {
59
+ return boundary(`${path}: must have a plain prototype`);
60
+ }
61
+ return input;
62
+ };
63
+ const ownData = (input, key, path) => {
64
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
65
+ if (descriptor === undefined || !("value" in descriptor))
66
+ return boundary(`${path}: must be an own data property`);
67
+ return descriptor.value;
68
+ };
69
+ const copyBody = (input) => {
70
+ /* v8 ignore next 4 -- every host that has `Uint8Array` has its prototype accessors; the refusal exists so a host without them fails closed rather than reading a caller-owned buffer */
71
+ if (typedArrayBuffer === undefined || typedArrayByteOffset === undefined ||
72
+ typedArrayByteLength === undefined)
73
+ return boundary("raw.body: this host cannot inspect typed arrays");
74
+ try {
75
+ const buffer = Reflect.apply(typedArrayBuffer, input, []);
76
+ const byteOffset = Reflect.apply(typedArrayByteOffset, input, []);
77
+ const byteLength = Reflect.apply(typedArrayByteLength, input, []);
78
+ const source = new Uint8Array(buffer, byteOffset, byteLength);
79
+ const snapshot = new Uint8Array(byteLength);
80
+ snapshot.set(source);
81
+ return snapshot;
82
+ }
83
+ catch {
84
+ return boundary("raw.body: must be a Uint8Array");
85
+ }
86
+ };
87
+ const snapshotHeaders = (input) => {
88
+ const record = plainRecord(input, "raw.headers");
89
+ const snapshot = Object.create(null);
90
+ for (const key of Reflect.ownKeys(record)) {
91
+ if (typeof key !== "string")
92
+ return boundary("raw.headers: symbol keys are not supported");
93
+ const descriptor = Object.getOwnPropertyDescriptor(record, key);
94
+ if (descriptor === undefined || !("value" in descriptor) || !descriptor.enumerable) {
95
+ return boundary(`raw.headers.${key}: must be an enumerable data property`);
96
+ }
97
+ if (descriptor.value !== undefined && typeof descriptor.value !== "string") {
98
+ return boundary(`raw.headers.${key}: must be a string or undefined`);
99
+ }
100
+ const normalized = key.toLowerCase();
101
+ if (Object.hasOwn(snapshot, normalized)) {
102
+ return boundary(`raw.headers.${normalized}: duplicate case-insensitive name`);
103
+ }
104
+ snapshot[normalized] = descriptor.value;
105
+ }
106
+ return Object.freeze(snapshot);
107
+ };
108
+ const snapshotRequest = (input) => Effect.try({
109
+ try: () => {
110
+ const request = plainRecord(input, "request");
111
+ const channel = ownData(request, "channel", "request.channel");
112
+ const candidate = plainRecord(ownData(request, "raw", "request.raw"), "request.raw");
113
+ const idempotencyKey = ownData(candidate, "idempotencyKey", "raw.idempotencyKey");
114
+ if (typeof channel !== "string" || channel.length === 0)
115
+ return boundary("request.channel: must be non-empty");
116
+ if (typeof idempotencyKey !== "string" || idempotencyKey.length === 0) {
117
+ return boundary("raw.idempotencyKey: must be non-empty");
118
+ }
119
+ return Object.freeze({
120
+ channel,
121
+ raw: Object.freeze({
122
+ body: copyBody(ownData(candidate, "body", "raw.body")),
123
+ headers: snapshotHeaders(ownData(candidate, "headers", "raw.headers")),
124
+ idempotencyKey
125
+ })
126
+ });
127
+ },
128
+ catch: (cause) => new InvalidInput({
129
+ issue: cause instanceof InboundBoundaryError ? cause.issue : "channel request could not be inspected safely"
130
+ })
131
+ });
132
+ const normalizedFingerprintHeaders = (channel) => Array.from(new Set((channel.fingerprintHeaders ?? []).map((name) => name.toLowerCase()))).sort();
133
+ /** Digests the body plus only the adapter-declared, non-secret semantic headers. */
134
+ const fingerprint = (body, headers, names) => {
135
+ const document = JSON.stringify([
136
+ Sha256.digestSync(body),
137
+ names.map((name) => [name, headers[name] ?? null])
138
+ ]);
139
+ return `channel-ingress:v2:${Sha256.digestSync(new TextEncoder().encode(document))}`;
140
+ };
141
+ const externalReceipt = (receipt, key) => {
142
+ switch (receipt._tag) {
143
+ case "Accepted":
144
+ case "AlreadyApplied":
145
+ case "Parked":
146
+ return { ...receipt, receiptId: key };
147
+ default:
148
+ return receipt;
149
+ }
150
+ };
151
+ /**
152
+ * Builds an in-memory channel registry and delivery map over Control's durable
153
+ * mutation store. Inbound idempotency therefore survives coordinator restarts;
154
+ * only registration and outbound projection cursors are process-local.
155
+ */
156
+ const makeWith = (runtime) => Effect.gen(function* () {
157
+ const channels = yield* Ref.make(new Map());
158
+ const deliveries = new Map();
159
+ // Live identities are never evicted. Terminal identities have a FIFO window
160
+ // for reconnects; retaining one per historical run would grow without bound.
161
+ const terminalDeliveries = new Set();
162
+ const maximumTerminalDeliveries = 1024;
163
+ const ingestion = yield* Semaphore.make(1);
164
+ const control = yield* Control;
165
+ const lookup = Effect.fn("Channels.lookup")(function* (name) {
166
+ return yield* Effect.flatMap(Ref.get(channels), (registered) => {
167
+ const channel = registered.get(name);
168
+ return channel === undefined
169
+ ? Effect.fail(unavailable(`channel "${name}" is not registered`))
170
+ : Effect.succeed(channel);
171
+ });
172
+ });
173
+ return Channels.of({
174
+ register: Effect.fn("Channels.register")((channel) => Ref.update(channels, (registered) => {
175
+ const next = new Map(registered);
176
+ next.set(channel.name, eraseChannel(channel));
177
+ return next;
178
+ })),
179
+ lookup,
180
+ ingest: Effect.fn("Channels.ingest")((request) => Effect.flatMap(snapshotRequest(request), (snapshot) => ingestion.withPermits(1)(Effect.gen(function* () {
181
+ const channel = yield* lookup(snapshot.channel);
182
+ const fingerprintHeaders = normalizedFingerprintHeaders(channel);
183
+ const bodyFingerprint = fingerprint(snapshot.raw.body, snapshot.raw.headers, fingerprintHeaders);
184
+ // This ordering is intentional: signature verification is the
185
+ // amplification guard and must happen before decode or Control access.
186
+ // The verifier receives its own body copy, so even a verifier that
187
+ // edits bytes cannot change what the decoder sees after approval.
188
+ yield* channel.verify({ ...snapshot.raw, body: copyBody(snapshot.raw.body) });
189
+ const externalKey = snapshot.raw.idempotencyKey;
190
+ const durableKey = mutationKey(snapshot.channel, externalKey);
191
+ const prior = yield* runtime.lookupMutation(durableKey, bodyFingerprint);
192
+ if (prior !== undefined)
193
+ return externalReceipt(prior, externalKey);
194
+ const mapped = yield* channel.decodeAndMap(snapshot.raw);
195
+ const key = scopedKey(snapshot.channel, externalKey);
196
+ let receipt;
197
+ if (mapped._tag === "Signal") {
198
+ // The channel is the caller, not the local operator: a webhook
199
+ // body names the run and wait, so a signal it maps to answers a
200
+ // human wait only if the host delegates approval to this channel.
201
+ receipt = yield* control.signal({
202
+ runId: mapped.runId,
203
+ signal: mapped.signal,
204
+ idempotencyKey: key,
205
+ principal: { id: snapshot.channel, kind: "channel", stampedAt: yield* Clock.currentTimeMillis }
206
+ });
207
+ }
208
+ else {
209
+ const plan = yield* control.plan({
210
+ flowId: mapped.flowId,
211
+ input: mapped.input,
212
+ idempotencyKey: key
213
+ });
214
+ receipt = yield* control.run({
215
+ _tag: "Plan",
216
+ planId: plan.planId,
217
+ digest: plan.digest,
218
+ envelope: plan.envelope,
219
+ idempotencyKey: key
220
+ });
221
+ }
222
+ // A parked start has not launched. Leave ingress unsettled so a
223
+ // redelivery can retry the same Control key after plan approval.
224
+ if (receipt._tag !== "Conflict" && receipt._tag !== "Parked") {
225
+ yield* runtime.recordMutation(durableKey, bodyFingerprint, receipt);
226
+ }
227
+ return externalReceipt(receipt, externalKey);
228
+ })))),
229
+ project: Effect.fn("Channels.project")(function* (request) {
230
+ const channel = yield* lookup(request.channel);
231
+ const key = deliveryKey(request.channel, request.run);
232
+ // Keep the read, projection and update synchronous so concurrent fibers
233
+ // cannot project from the same stale delivery identity.
234
+ return yield* Effect.sync(() => {
235
+ const prior = deliveries.get(key);
236
+ const projection = channel.project(request.run, prior);
237
+ if (projection.operation !== "noop" &&
238
+ (prior === undefined || prior.cursor !== projection.cursor || prior.messageId !== projection.messageId)) {
239
+ deliveries.set(key, { cursor: projection.cursor, messageId: projection.messageId });
240
+ }
241
+ if (deliveries.has(key)) {
242
+ const status = request.run.status;
243
+ if (status === "completed" || status === "failed" || status === "cancelled") {
244
+ terminalDeliveries.add(key);
245
+ if (terminalDeliveries.size > maximumTerminalDeliveries) {
246
+ // The set is nonempty because it just exceeded the limit.
247
+ const oldest = terminalDeliveries.values().next().value;
248
+ terminalDeliveries.delete(oldest);
249
+ deliveries.delete(oldest);
250
+ }
251
+ }
252
+ else {
253
+ terminalDeliveries.delete(key);
254
+ }
255
+ }
256
+ return projection;
257
+ });
258
+ })
259
+ });
260
+ });
261
+ /**
262
+ * Builds the coordinator over Control's durable mutation store.
263
+ *
264
+ * @category constructors
265
+ * @since 1.0.0
266
+ */
267
+ export const make = Effect.gen(function* () {
268
+ const runtime = yield* ControlRuntime;
269
+ return yield* makeWith(runtime);
270
+ });
271
+ /**
272
+ * Builds a process-local coordinator for adapter unit tests.
273
+ *
274
+ * Production hosts use {@link layer}; this constructor deliberately makes no
275
+ * restart guarantee.
276
+ *
277
+ * @category constructors
278
+ * @since 1.0.0
279
+ */
280
+ export const makeMemory = Effect.gen(function* () {
281
+ const records = yield* Ref.make(new Map());
282
+ return yield* makeWith({
283
+ lookupMutation: (key, candidate) => Ref.get(records).pipe(Effect.map((stored) => {
284
+ const prior = stored.get(key);
285
+ if (prior === undefined)
286
+ return undefined;
287
+ return prior.fingerprint === candidate
288
+ ? alreadyApplied(key, prior.receipt)
289
+ : { _tag: "Conflict", message: `idempotency key ${key} was used for another mutation` };
290
+ })),
291
+ recordMutation: (key, candidate, receipt) => Ref.update(records, (stored) => {
292
+ const next = new Map(stored);
293
+ next.set(key, { fingerprint: candidate, receipt });
294
+ return next;
295
+ })
296
+ });
297
+ });
298
+ /**
299
+ * Channel layer with durable inbound receipts supplied by `ControlRuntime`.
300
+ *
301
+ * @category layers
302
+ * @since 0.1.0
303
+ */
304
+ export const layer = Layer.effect(Channels, make);
305
+ /**
306
+ * Process-local channel layer for adapter unit tests only.
307
+ *
308
+ * @category layers
309
+ * @since 1.0.0
310
+ */
311
+ export const layerMemory = Layer.effect(Channels, makeMemory);
312
+ //# sourceMappingURL=Channels.js.map