@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,148 @@
1
+ /**
2
+ * Durable credential persistence over `@smthrs/database`.
3
+ *
4
+ * This is the host adapter behind {@link CredentialStore} on any runtime with
5
+ * a SQL database — the Node composition uses `@smthrs/database/node`, and
6
+ * tests use its in-memory SQLite driver. The module itself imports no `node:*`
7
+ * API: it speaks the driver-neutral SQL contract, so it stays bundleable and a
8
+ * browser host that ships a SQL driver can use it unchanged.
9
+ *
10
+ * Only ciphertext is stored. The row carries no key material, and the
11
+ * compare-and-set on `version` is what makes two concurrent rotations
12
+ * serializable: the loser is refused with `CredentialConflict` instead of
13
+ * clobbering the winner.
14
+ *
15
+ * @since 0.1.0
16
+ */
17
+
18
+ import { DurableWriter } from "@smthrs/database/DurableWriter"
19
+ import { Effect, Layer, Option } from "effect"
20
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
21
+ import { CredentialConflict, Unavailable } from "./ControlError.ts"
22
+ import * as CredentialStore from "./CredentialStore.ts"
23
+ import { initial } from "./migrations/0001_control_tables.ts"
24
+
25
+ interface Row {
26
+ readonly id: string
27
+ readonly name: string
28
+ readonly ciphertext: string
29
+ readonly nonce: string
30
+ readonly version: number
31
+ readonly updated_at_ms: number
32
+ }
33
+
34
+ const toRecord = (row: Row): CredentialStore.SealedRecord => ({
35
+ id: row.id,
36
+ name: row.name,
37
+ ciphertext: row.ciphertext,
38
+ nonce: row.nonce,
39
+ version: Number(row.version),
40
+ updatedAtMs: Number(row.updated_at_ms)
41
+ })
42
+
43
+ const unavailable = (operation: string) => (): Unavailable =>
44
+ new Unavailable({
45
+ feature: `credential storage (${operation})`,
46
+ ticket: "control-credential-storage"
47
+ })
48
+
49
+ /**
50
+ * Creates the credential table if it does not exist.
51
+ *
52
+ * @category migrations
53
+ * @since 0.1.0
54
+ */
55
+ export const migrate: Effect.Effect<void, Unavailable, SqlClient.SqlClient> = initial.pipe(
56
+ Effect.mapError(unavailable("migrate"))
57
+ )
58
+
59
+ /**
60
+ * Constructs a durable credential store over the ambient database.
61
+ *
62
+ * @category constructors
63
+ * @since 0.1.0
64
+ */
65
+ export const make: Effect.Effect<
66
+ CredentialStore.Service,
67
+ Unavailable,
68
+ DurableWriter | SqlClient.SqlClient
69
+ > = Effect.gen(function*() {
70
+ const sql = yield* Effect.service(SqlClient.SqlClient)
71
+ const writer = yield* DurableWriter
72
+ yield* migrate
73
+
74
+ const list = (): Effect.Effect<ReadonlyArray<CredentialStore.SealedRecord>, Unavailable> =>
75
+ sql<Row>`SELECT * FROM control_credentials ORDER BY name`.pipe(
76
+ Effect.map((rows) => rows.map(toRecord)),
77
+ Effect.mapError(unavailable("list"))
78
+ )
79
+
80
+ const read = (id: string): Effect.Effect<Option.Option<CredentialStore.SealedRecord>, Unavailable> =>
81
+ sql<Row>`SELECT * FROM control_credentials WHERE id = ${id}`.pipe(
82
+ Effect.map((rows) => Option.map(Option.fromNullishOr(rows[0]), toRecord)),
83
+ Effect.mapError(unavailable("read"))
84
+ )
85
+
86
+ // One write transaction, so the version this writer read and the row it
87
+ // guards cannot interleave with a concurrent rotation.
88
+ const write = (
89
+ record: CredentialStore.SealedRecord
90
+ ): Effect.Effect<void, Unavailable | CredentialConflict> =>
91
+ writer.write(Effect.gen(function*() {
92
+ const rows = yield* sql<{ readonly version: number }>`
93
+ SELECT version FROM control_credentials WHERE id = ${record.id}
94
+ `
95
+ const stored = rows[0]
96
+ const actual = stored === undefined ? 0 : Number(stored.version)
97
+ if (actual !== record.version - 1) {
98
+ return yield* Effect.fail(
99
+ new CredentialConflict({
100
+ id: record.id,
101
+ expectedVersion: record.version - 1,
102
+ actualVersion: actual
103
+ })
104
+ )
105
+ }
106
+ yield* sql`
107
+ INSERT INTO control_credentials (id, name, ciphertext, nonce, version, updated_at_ms)
108
+ VALUES (
109
+ ${record.id},
110
+ ${record.name},
111
+ ${record.ciphertext},
112
+ ${record.nonce},
113
+ ${record.version},
114
+ ${record.updatedAtMs}
115
+ )
116
+ ON CONFLICT (id) DO UPDATE SET
117
+ name = excluded.name,
118
+ ciphertext = excluded.ciphertext,
119
+ nonce = excluded.nonce,
120
+ version = excluded.version,
121
+ updated_at_ms = excluded.updated_at_ms
122
+ `
123
+ })).pipe(
124
+ Effect.mapError((cause): Unavailable | CredentialConflict =>
125
+ cause instanceof CredentialConflict ? cause : unavailable("write")()
126
+ )
127
+ )
128
+
129
+ const remove = (id: string): Effect.Effect<void, Unavailable> =>
130
+ sql`DELETE FROM control_credentials WHERE id = ${id}`.pipe(
131
+ Effect.asVoid,
132
+ Effect.mapError(unavailable("remove"))
133
+ )
134
+
135
+ return CredentialStore.make({ list, read, write, remove })
136
+ })
137
+
138
+ /**
139
+ * Provides durable credential persistence over the ambient database.
140
+ *
141
+ * @category layers
142
+ * @since 0.1.0
143
+ */
144
+ export const layer: Layer.Layer<
145
+ CredentialStore.CredentialStore,
146
+ Unavailable,
147
+ DurableWriter | SqlClient.SqlClient
148
+ > = Layer.effect(CredentialStore.CredentialStore)(make)
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The steer lifecycle as the control plane reads it back.
3
+ *
4
+ * A steer has two durable moments and two different writers. The control plane
5
+ * records the first — `control.steer.enqueued`, written beside the admission
6
+ * it caused. The second belongs to `@smthrs/notifications`: a turn boundary
7
+ * promotes the notification and the queue journals the promotion. This module
8
+ * projects that promotion into the control vocabulary, so a watcher folds one
9
+ * stream instead of two, and so nothing writes "delivered" twice.
10
+ *
11
+ * Deriving rather than re-recording is what keeps the two halves honest. A
12
+ * control plane that emitted its own delivery record would be asserting a fact
13
+ * it did not observe: the boundary that delivered the steer runs in the agent
14
+ * process, not this one.
15
+ *
16
+ * @since 0.1.0
17
+ */
18
+
19
+ import type { ControlEvent } from "./ControlSchema.ts"
20
+
21
+ /**
22
+ * The journal event type the notification queue records a promotion under.
23
+ *
24
+ * Named as a string rather than imported so this projection reads a journal
25
+ * written by any queue implementation, exactly as `Lineage` reads engine
26
+ * decisions.
27
+ *
28
+ * @category constants
29
+ * @since 0.1.0
30
+ */
31
+ export const promotedEventType = "flows/notifications/Promoted"
32
+
33
+ /**
34
+ * The kind `watch` reports one delivered steer under.
35
+ *
36
+ * @category constants
37
+ * @since 0.1.0
38
+ */
39
+ export const deliveredEventType = "control.steer.delivered"
40
+
41
+ /**
42
+ * The kind `Control.steer` records an accepted steer under.
43
+ *
44
+ * @category constants
45
+ * @since 0.1.0
46
+ */
47
+ export const enqueuedEventType = "control.steer.enqueued"
48
+
49
+ const record = (value: unknown): Readonly<Record<string, unknown>> | undefined =>
50
+ typeof value === "object" && value !== null && !Array.isArray(value)
51
+ ? value as Readonly<Record<string, unknown>>
52
+ : undefined
53
+
54
+ const strings = (value: unknown): ReadonlyArray<string> =>
55
+ Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string") : []
56
+
57
+ /**
58
+ * Derives one delivery delta per message a promotion entry named.
59
+ *
60
+ * A boundary promotes a batch, so one entry can disclose several deliveries.
61
+ * Each delta carries the source sequence and the boundary that took it.
62
+ * `ControlLive.watch` adds an expansion cursor so a consumer can resume
63
+ * between deliveries in the same batch.
64
+ *
65
+ * A promotion that named nothing derives nothing: an idle boundary drains an
66
+ * empty queue on every turn, and reporting that as a delivery would bury the
67
+ * real ones.
68
+ *
69
+ * @param event the projected journal entry
70
+ * @category projections
71
+ * @since 0.1.0
72
+ */
73
+ export const derive = (event: ControlEvent): ReadonlyArray<ControlEvent> => {
74
+ const runId = event.runId
75
+ if (event.kind !== promotedEventType || runId === undefined) return []
76
+ const payload = record(event.payload)
77
+ if (payload === undefined) return []
78
+ const boundary = typeof payload["boundary"] === "string" ? payload["boundary"] : undefined
79
+ if (boundary === undefined) return []
80
+ return strings(payload["ids"]).map((messageId): ControlEvent => ({
81
+ sequence: event.sequence,
82
+ kind: deliveredEventType,
83
+ runId,
84
+ occurredAt: event.occurredAt,
85
+ payload: { runId, messageId, boundary }
86
+ }))
87
+ }
88
+
89
+ /**
90
+ * Expands one projected entry into itself plus any deliveries it discloses.
91
+ *
92
+ * @param event the projected journal entry
93
+ * @category projections
94
+ * @since 0.1.0
95
+ */
96
+ export const expand = (event: ControlEvent): ReadonlyArray<ControlEvent> => [event, ...derive(event)]
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Reserved system-flow identifiers projected by the command-line interface.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+
7
+ /**
8
+ * The projection metadata for one reserved system flow.
9
+ *
10
+ * @since 0.1.0
11
+ * @category models
12
+ */
13
+ export interface SystemFlowEntry {
14
+ readonly verb: string
15
+ readonly flowId: `system/${string}`
16
+ readonly projection: "procedure" | "systemFlow"
17
+ readonly deployClass: boolean
18
+ readonly planBearing: boolean
19
+ /**
20
+ * Whether a control plane may plan and run this reserved id.
21
+ *
22
+ * `false` means the row is command-line metadata and nothing else: the verb
23
+ * is named here so the binary can refuse it by name, and no runtime may
24
+ * offer it as a flow. `replay` was removed by the frozen rc.0 contract yet
25
+ * every runtime turned the whole catalog into plannable flows, so
26
+ * `plan({flowId: "system/replay"})` returned a real approval card and only a
27
+ * later `run` failed. A removed feature that half works is exactly what the
28
+ * contract forbids.
29
+ */
30
+ readonly plannable: boolean
31
+ }
32
+
33
+ /**
34
+ * The authoritative command-line verb to reserved-flow map.
35
+ *
36
+ * `gc` is the retention verb: it resolves `@smthrs/engine-store`'s `retain`
37
+ * operation, which deletes aged terminal runs with their attempts, clock
38
+ * deadlines, deferred completions, journal entries, and time-travel archive
39
+ * rows. It is a procedure rather than a deploy-class system flow because it
40
+ * takes no plan and bears no approval envelope; the deletion is gated by the
41
+ * operator invoking it, and automatic retention stays opt-in.
42
+ *
43
+ * @since 0.1.0
44
+ * @category models
45
+ */
46
+ export const catalog = [
47
+ {
48
+ verb: "plan",
49
+ flowId: "system/plan",
50
+ projection: "procedure",
51
+ deployClass: false,
52
+ planBearing: false,
53
+ plannable: true
54
+ },
55
+ {
56
+ verb: "run",
57
+ flowId: "system/run",
58
+ projection: "procedure",
59
+ deployClass: false,
60
+ planBearing: false,
61
+ plannable: true
62
+ },
63
+ {
64
+ verb: "release",
65
+ flowId: "system/release",
66
+ projection: "systemFlow",
67
+ deployClass: true,
68
+ planBearing: true,
69
+ plannable: true
70
+ },
71
+ {
72
+ verb: "serve",
73
+ flowId: "system/serve",
74
+ projection: "systemFlow",
75
+ deployClass: true,
76
+ planBearing: true,
77
+ plannable: true
78
+ },
79
+ { verb: "up", flowId: "system/up", projection: "systemFlow", deployClass: true, planBearing: true, plannable: true },
80
+ { verb: "ls", flowId: "system/ls", projection: "procedure", deployClass: false, planBearing: false, plannable: true },
81
+ { verb: "ps", flowId: "system/ps", projection: "procedure", deployClass: false, planBearing: false, plannable: true },
82
+ {
83
+ verb: "logs",
84
+ flowId: "system/logs",
85
+ projection: "procedure",
86
+ deployClass: false,
87
+ planBearing: false,
88
+ plannable: true
89
+ },
90
+ {
91
+ verb: "status",
92
+ flowId: "system/status",
93
+ projection: "procedure",
94
+ deployClass: false,
95
+ planBearing: false,
96
+ plannable: true
97
+ },
98
+ {
99
+ verb: "cancel",
100
+ flowId: "system/cancel",
101
+ projection: "procedure",
102
+ deployClass: false,
103
+ planBearing: false,
104
+ plannable: true
105
+ },
106
+ {
107
+ verb: "approve",
108
+ flowId: "system/approve",
109
+ projection: "procedure",
110
+ deployClass: false,
111
+ planBearing: false,
112
+ plannable: true
113
+ },
114
+ {
115
+ verb: "deny",
116
+ flowId: "system/deny",
117
+ projection: "procedure",
118
+ deployClass: false,
119
+ planBearing: false,
120
+ plannable: true
121
+ },
122
+ {
123
+ verb: "signal",
124
+ flowId: "system/signal",
125
+ projection: "procedure",
126
+ deployClass: false,
127
+ planBearing: false,
128
+ plannable: true
129
+ },
130
+ { verb: "gc", flowId: "system/gc", projection: "procedure", deployClass: false, planBearing: false, plannable: true },
131
+ {
132
+ verb: "replay",
133
+ flowId: "system/replay",
134
+ projection: "systemFlow",
135
+ deployClass: false,
136
+ planBearing: false,
137
+ plannable: false
138
+ },
139
+ {
140
+ verb: "add",
141
+ flowId: "system/add",
142
+ projection: "systemFlow",
143
+ deployClass: false,
144
+ planBearing: true,
145
+ plannable: true
146
+ },
147
+ {
148
+ verb: "remove",
149
+ flowId: "system/remove",
150
+ projection: "systemFlow",
151
+ deployClass: false,
152
+ planBearing: true,
153
+ plannable: true
154
+ },
155
+ {
156
+ verb: "eject",
157
+ flowId: "system/eject",
158
+ projection: "systemFlow",
159
+ deployClass: false,
160
+ planBearing: true,
161
+ plannable: true
162
+ },
163
+ {
164
+ verb: "test",
165
+ flowId: "system/test",
166
+ projection: "systemFlow",
167
+ deployClass: false,
168
+ planBearing: false,
169
+ plannable: true
170
+ },
171
+ {
172
+ verb: "init",
173
+ flowId: "system/init",
174
+ projection: "systemFlow",
175
+ deployClass: false,
176
+ planBearing: true,
177
+ plannable: true
178
+ },
179
+ {
180
+ verb: "doctor",
181
+ flowId: "system/doctor",
182
+ projection: "systemFlow",
183
+ deployClass: false,
184
+ planBearing: false,
185
+ plannable: true
186
+ },
187
+ {
188
+ verb: "migrate",
189
+ flowId: "system/migrate",
190
+ projection: "systemFlow",
191
+ deployClass: false,
192
+ planBearing: true,
193
+ plannable: true
194
+ },
195
+ {
196
+ // A build target declaring `approval: "required"`: its plan input is the
197
+ // target's label and revision digest, and the build planner runs it only
198
+ // once that plan is approved. The build executes it, never a control run.
199
+ verb: "target",
200
+ flowId: "system/target",
201
+ projection: "systemFlow",
202
+ deployClass: true,
203
+ planBearing: true,
204
+ plannable: true
205
+ },
206
+ {
207
+ verb: "docs",
208
+ flowId: "system/docs",
209
+ projection: "systemFlow",
210
+ deployClass: false,
211
+ planBearing: false,
212
+ plannable: true
213
+ }
214
+ ] as const satisfies ReadonlyArray<SystemFlowEntry>
215
+
216
+ /**
217
+ * The catalog entries a control runtime may offer as flows.
218
+ *
219
+ * One list, so a composition that builds its own flow map (the CLI does) and
220
+ * the runtimes' own defaults cannot disagree about which reserved ids exist.
221
+ *
222
+ * @since 0.1.0
223
+ * @category models
224
+ */
225
+ export const plannable: ReadonlyArray<SystemFlowEntry> = catalog.filter((entry) => entry.plannable)
@@ -0,0 +1,169 @@
1
+ /**
2
+ * AES-256-GCM credential encryption over the Web Crypto API.
3
+ *
4
+ * One adapter serves both hosts the project supports: `globalThis.crypto.subtle`
5
+ * is the browser's own API and has been Node's since v19, so this module
6
+ * imports nothing from `node:*` and still runs unmodified on the server. A host
7
+ * without Web Crypto, such as an old runtime or a locked-down worker, fails
8
+ * with the typed `Unavailable`, never a defect. A key that is not 32
9
+ * base64-encoded bytes fails with `InvalidInput`. A record that does not open
10
+ * fails with `PersistenceError` on operation `credential.open`, which names
11
+ * whether the stored nonce was malformed or the ciphertext failed
12
+ * authentication (a different key, changed metadata, or tampered bytes).
13
+ *
14
+ * The key is host-managed and supplied at layer construction. It is held as a
15
+ * non-extractable `CryptoKey`, so it cannot be read back out of the cipher, and
16
+ * it never reaches `CredentialStore`.
17
+ *
18
+ * @since 0.1.0
19
+ */
20
+
21
+ import { canonicalize } from "@smthrs/canonical"
22
+ import { Effect, Layer, Redacted } from "effect"
23
+ import { InvalidInput, PersistenceError, type Unavailable } from "./ControlError.ts"
24
+ import * as CredentialCipher from "./CredentialCipher.ts"
25
+
26
+ const algorithm = "AES-GCM"
27
+ const nonceBytes = 12
28
+ const contextFormatVersion = 1
29
+
30
+ const toBase64 = (bytes: Uint8Array): string => {
31
+ let binary = ""
32
+ for (const byte of bytes) binary += String.fromCharCode(byte)
33
+ return btoa(binary)
34
+ }
35
+
36
+ const fromBase64 = (value: string): Uint8Array<ArrayBuffer> => {
37
+ const binary = atob(value)
38
+ const bytes = new Uint8Array(binary.length)
39
+ for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index)
40
+ return bytes
41
+ }
42
+
43
+ const subtle = (): Effect.Effect<SubtleCrypto, Unavailable> =>
44
+ Effect.suspend(() => {
45
+ const available = globalThis.crypto as Crypto | undefined
46
+ return available?.subtle === undefined
47
+ ? Effect.fail(CredentialCipher.unavailable())
48
+ : Effect.succeed(available.subtle)
49
+ })
50
+
51
+ const randomNonce = (): Effect.Effect<Uint8Array<ArrayBuffer>, Unavailable> =>
52
+ Effect.suspend(() => {
53
+ const available = globalThis.crypto as Crypto | undefined
54
+ return available?.getRandomValues === undefined
55
+ ? Effect.fail(CredentialCipher.unavailable())
56
+ : Effect.succeed(available.getRandomValues(new Uint8Array(nonceBytes)))
57
+ })
58
+
59
+ const encodeContext = (context: CredentialCipher.Context): Effect.Effect<Uint8Array<ArrayBuffer>, Unavailable> =>
60
+ Effect.try({
61
+ try: () => {
62
+ // Canonical JSON names and escapes each field, so embedded delimiters
63
+ // cannot make two metadata tuples authenticate as the same bytes. The
64
+ // explicit format version makes a future byte contract distinguishable.
65
+ const document = canonicalize({
66
+ formatVersion: contextFormatVersion,
67
+ id: context.id,
68
+ name: context.name,
69
+ version: context.version
70
+ })
71
+ return new TextEncoder().encode(document)
72
+ },
73
+ catch: CredentialCipher.unavailable
74
+ })
75
+
76
+ const invalidKey = (): InvalidInput => new InvalidInput({ issue: "Credential key must be 32 base64-encoded bytes" })
77
+
78
+ const unopenable = (reason: "malformed_nonce" | "authentication_failed", cause: unknown): PersistenceError =>
79
+ new PersistenceError({
80
+ operation: "credential.open",
81
+ message: reason === "malformed_nonce"
82
+ ? "Stored credential nonce is malformed"
83
+ : "Credential failed authentication: a different key, changed metadata, or tampered ciphertext",
84
+ cause
85
+ })
86
+
87
+ /**
88
+ * Host-managed key material for the cipher.
89
+ *
90
+ * `key` is 32 raw bytes, base64-encoded, held redacted so it cannot be printed
91
+ * or serialized by accident.
92
+ *
93
+ * @category models
94
+ * @since 0.1.0
95
+ */
96
+ export interface Options {
97
+ readonly key: Redacted.Redacted<string>
98
+ }
99
+
100
+ /**
101
+ * Imports the host key and constructs the cipher.
102
+ *
103
+ * @category constructors
104
+ * @since 0.1.0
105
+ */
106
+ export const make = (options: Options): Effect.Effect<CredentialCipher.Service, Unavailable | InvalidInput> =>
107
+ Effect.gen(function*() {
108
+ const crypto = yield* subtle()
109
+ const raw = yield* Effect.try({
110
+ try: () => fromBase64(Redacted.value(options.key)),
111
+ catch: invalidKey
112
+ })
113
+ if (raw.length !== 32) return yield* Effect.fail(invalidKey())
114
+ const key = yield* Effect.tryPromise({
115
+ try: () => crypto.importKey("raw", raw, algorithm, false, ["encrypt", "decrypt"]),
116
+ catch: CredentialCipher.unavailable
117
+ })
118
+
119
+ return CredentialCipher.make({
120
+ seal: Effect.fn("WebCryptoCipher.seal")(function*(plaintext, context) {
121
+ const additionalData = yield* encodeContext(context)
122
+ const nonce = yield* randomNonce()
123
+ const ciphertext = yield* Effect.tryPromise({
124
+ try: () =>
125
+ crypto.encrypt(
126
+ { name: algorithm, iv: nonce, additionalData: additionalData },
127
+ key,
128
+ new TextEncoder().encode(Redacted.value(plaintext))
129
+ ),
130
+ catch: CredentialCipher.unavailable
131
+ })
132
+ return { ciphertext: toBase64(new Uint8Array(ciphertext)), nonce: toBase64(nonce) }
133
+ }),
134
+ open: Effect.fn("WebCryptoCipher.open")(function*(sealed, context) {
135
+ const additionalData = yield* encodeContext(context)
136
+ const plaintext = yield* Effect.try({
137
+ try: () => fromBase64(sealed.nonce),
138
+ catch: (cause) => unopenable("malformed_nonce", cause)
139
+ }).pipe(
140
+ Effect.flatMap((nonce) =>
141
+ Effect.tryPromise({
142
+ try: () =>
143
+ crypto.decrypt(
144
+ { name: algorithm, iv: nonce, additionalData: additionalData },
145
+ key,
146
+ fromBase64(sealed.ciphertext)
147
+ ),
148
+ catch: (cause) => unopenable("authentication_failed", cause)
149
+ })
150
+ ),
151
+ Effect.tapError((error) =>
152
+ Effect.logWarning({ message: error.message, operation: error.operation, credentialId: context.id })
153
+ )
154
+ )
155
+ return Redacted.make(new TextDecoder().decode(plaintext))
156
+ })
157
+ })
158
+ })
159
+
160
+ /**
161
+ * Provides AES-256-GCM credential encryption under a host-managed key.
162
+ *
163
+ * @category layers
164
+ * @since 0.1.0
165
+ */
166
+ export const layer = (
167
+ options: Options
168
+ ): Layer.Layer<CredentialCipher.CredentialCipher, Unavailable | InvalidInput> =>
169
+ Layer.effect(CredentialCipher.CredentialCipher)(make(options))