@smthrs/control 0.0.0-stage → 1.0.0-rc.3

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 +1280 -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 +740 -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 +1521 -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 +1585 -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 +807 -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 +5 -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 +2113 -0
  385. package/src/ControlRpcs.ts +443 -0
  386. package/src/ControlRuntime.ts +1597 -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 +2476 -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,182 @@
1
+ /**
2
+ * RPC server layers for the control service.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+
7
+ import { Cause, Effect, Layer, Stream } from "effect"
8
+ import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
9
+ import { RpcServer } from "effect/unstable/rpc"
10
+ import { Control } from "./Control.ts"
11
+ import { Unauthorized } from "./ControlError.ts"
12
+ import { ControlPrincipal, ControlRpcs, RunVisibility } from "./ControlRpcs.ts"
13
+
14
+ /**
15
+ * Logs the raw defect of a failed RPC handler on the server, and nothing for
16
+ * a typed failure or an interruption. `ControlRpcs.ControlDefect` sends
17
+ * clients only the designed sentence, so this log is where the detail lives.
18
+ * Pass it to `Effect.tapCause` or `Stream.tapCause` around a handler.
19
+ *
20
+ * @category logging
21
+ * @since 1.0.0
22
+ */
23
+ export const logDefect = (cause: Cause.Cause<unknown>): Effect.Effect<void> =>
24
+ Cause.hasDies(cause) ? Effect.logError("A control RPC handler died", cause) : Effect.void
25
+
26
+ const logged = <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.tapCause(effect, logDefect)
27
+
28
+ /**
29
+ * Control RPC handlers delegating to the transport-independent service.
30
+ *
31
+ * Every mutation that records who asked reads `ControlPrincipal` and stamps
32
+ * it, rather than forwarding whatever the client sent. The identity the
33
+ * middleware authenticated is the only one the server can stand behind, and it
34
+ * is what reaches the journal, `RunSummary.cancellation`, and a steer's
35
+ * notification provenance.
36
+ *
37
+ * `List`, `Watch` and every run mutation read it too: a principal
38
+ * `ControlRpcs.RunVisibility` does not make an operator reads and mutates only
39
+ * the runs it launched; any other run answers `RunNotFound`.
40
+ *
41
+ * @category layers
42
+ * @since 0.1.0
43
+ */
44
+ export const layer = ControlRpcs.toLayer(
45
+ Effect.gen(function*() {
46
+ const control = yield* Control
47
+ // A reader that is not an operator is restricted to the runs its own
48
+ // principal launched. `reader` is not on the wire, so only this stamp sets
49
+ // it. The authentication middleware provides the rule with the principal.
50
+ const reader = Effect.gen(function*() {
51
+ const principal = yield* ControlPrincipal
52
+ const visibility = yield* RunVisibility
53
+ return visibility.seesAllRuns(principal) ? {} : { reader: principal }
54
+ })
55
+ return ControlRpcs.of({
56
+ Plan: Effect.fn("Control.plan")((input) => control.plan(input), logged),
57
+ Run: Effect.fn("Control.run")((input) =>
58
+ Effect.gen(function*() {
59
+ const principal = yield* ControlPrincipal
60
+ return yield* control.run({ ...input, principal })
61
+ }), logged),
62
+ Approve: Effect.fn("Control.approve")((input) =>
63
+ Effect.gen(function*() {
64
+ const principal = yield* ControlPrincipal
65
+ if (principal.id === "loopback" && principal.kind === "anonymous") {
66
+ return yield* new Unauthorized({ message: "An operator credential is required" })
67
+ }
68
+ return yield* control.approve({ ...input, principal })
69
+ }), logged),
70
+ Deny: Effect.fn("Control.deny")((input) =>
71
+ Effect.gen(function*() {
72
+ const principal = yield* ControlPrincipal
73
+ if (principal.id === "loopback" && principal.kind === "anonymous") {
74
+ return yield* new Unauthorized({ message: "An operator credential is required" })
75
+ }
76
+ return yield* control.deny({ ...input, principal })
77
+ }), logged),
78
+ Steer: Effect.fn("Control.steer")((input) =>
79
+ Effect.gen(function*() {
80
+ const principal = yield* ControlPrincipal
81
+ // A steer carries a principal on the wire because an in-process
82
+ // caller names one that is not an operator: `agent/send` attributes
83
+ // a child's steer to the parent flow. A remote client may not, so
84
+ // the authenticated identity replaces whatever arrived. It reaches
85
+ // the notification's `sourceActor` and the run transcript, which is
86
+ // exactly where a spoofed name would be read as truth.
87
+ const { attribution: _attribution, ...message } = input.message
88
+ return yield* control.steer({ ...input, message: { ...message, principal }, ...yield* reader })
89
+ }), logged),
90
+ Signal: Effect.fn("Control.signal")((input) =>
91
+ Effect.gen(function*() {
92
+ const principal = yield* ControlPrincipal
93
+ return yield* control.signal({ ...input, principal, ...yield* reader })
94
+ }), logged),
95
+ Cancel: Effect.fn("Control.cancel")((input) =>
96
+ Effect.gen(function*() {
97
+ const principal = yield* ControlPrincipal
98
+ return yield* control.cancel({ ...input, principal, ...yield* reader })
99
+ }), logged),
100
+ Resume: Effect.fn("Control.resume")((input) =>
101
+ Effect.gen(function*() {
102
+ const principal = yield* ControlPrincipal
103
+ return yield* control.resume({ ...input, principal, ...yield* reader })
104
+ }), logged),
105
+ List: Effect.fn("Control.list")(
106
+ (input) => Effect.flatMap(reader, (restriction) => control.list({ ...input, ...restriction })),
107
+ logged
108
+ ),
109
+ Watch: (input) =>
110
+ Stream.tapCause(
111
+ Stream.unwrap(Effect.map(reader, (restriction) => control.watch({ ...input, ...restriction }))),
112
+ logDefect
113
+ )
114
+ })
115
+ })
116
+ )
117
+
118
+ /**
119
+ * Whether a request's `Origin` may reach the control mounts.
120
+ *
121
+ * Non-browser clients send no `Origin`. A browser always sends one, on POST
122
+ * and on the WebSocket upgrade, and a cross-site page cannot forge it. Refusing
123
+ * any `Origin` whose authority differs from `Host` stops a page on another site
124
+ * from riding a credential that a proxy or cookie attaches on the victim's
125
+ * behalf (cross-site WebSocket hijacking of the `Watch` stream).
126
+ */
127
+ const sameOrigin = (headers: Readonly<Record<string, string>>): boolean => {
128
+ const origin = headers.origin
129
+ if (origin === undefined) return true
130
+ const host = headers.host
131
+ const parsed = URL.parse(origin)
132
+ return host !== undefined && parsed !== null &&
133
+ (parsed.protocol === "http:" || parsed.protocol === "https:") &&
134
+ parsed.origin === origin && parsed.host === host.toLowerCase()
135
+ }
136
+
137
+ /**
138
+ * Route middleware on the two control mounts only, so the router's own path
139
+ * matching decides what it guards, and a host's stricter global policy (the
140
+ * gateway's ingress) still answers first with its own typed refusal.
141
+ */
142
+ const originGuard = HttpRouter.middleware((httpEffect) =>
143
+ Effect.gen(function*() {
144
+ const request = yield* HttpServerRequest.HttpServerRequest
145
+ if (sameOrigin(request.headers)) return yield* httpEffect
146
+ return HttpServerResponse.text("The browser Origin must match the control server Host", { status: 403 })
147
+ })
148
+ ).layer
149
+
150
+ const server = RpcServer.layer(ControlRpcs, {
151
+ disableFatalDefects: true
152
+ })
153
+
154
+ const http = server.pipe(
155
+ Layer.provide(layer),
156
+ Layer.provideMerge(RpcServer.layerProtocolHttp({ path: "/rpc" }).pipe(Layer.provide(originGuard))),
157
+ Layer.fresh
158
+ )
159
+
160
+ const websocket = server.pipe(
161
+ Layer.provide(layer),
162
+ Layer.provideMerge(RpcServer.layerProtocolWebsocket({ path: "/rpc/ws" }).pipe(Layer.provide(originGuard))),
163
+ Layer.fresh
164
+ )
165
+
166
+ /**
167
+ * Mounts control RPC on the ambient `HttpRouter`: unary procedures over POST
168
+ * `/rpc` and the `watch` stream over WebSocket `/rpc/ws`. Both protocols are
169
+ * mounted together because `ControlClient` projects the same `Control` vtable
170
+ * across the two transports.
171
+ *
172
+ * A request carrying a browser `Origin` that does not match its `Host` is
173
+ * refused with 403 before authentication, so a page on another site cannot
174
+ * open the socket or post to `/rpc` with a credential a proxy attaches.
175
+ *
176
+ * @category layers
177
+ * @since 0.1.0
178
+ */
179
+ export const layerHttp = Layer.mergeAll(
180
+ http,
181
+ websocket
182
+ )
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Credential storage boundary.
3
+ *
4
+ * Credentials are capabilities. They must never enter flow input, plan
5
+ * digests, journal payloads, or model context, so only a {@link CredentialRef}
6
+ * — an id and a human-readable connection name — crosses the browser-safe
7
+ * contract. Plaintext exists in exactly two places: inside a `Redacted` handed
8
+ * to `create`/`rotate`, and inside the `Redacted` returned by `resolve`.
9
+ *
10
+ * The implementation composes two ports:
11
+ *
12
+ * - {@link CredentialStore} persists an opaque sealed record and enforces
13
+ * compare-and-set on writes, so concurrent rotations cannot interleave.
14
+ * `rotate` with `expected` extends that compare to the secret itself: the
15
+ * secret it checks and the version it writes over come from one read, so a
16
+ * writer that commits in between, in any process, is refused.
17
+ * - {@link CredentialCipher} seals and opens the secret under host-managed
18
+ * keys. `WebCryptoCipher` is the browser and Node adapter; a host with no
19
+ * secure key material fails with the typed `Unavailable`.
20
+ *
21
+ * Authorization is a host decision, injected as {@link Options.authorize}. A
22
+ * reference is also authenticated on every operation: caller-owned fields are
23
+ * snapshotted before policy effects run, and a caller cannot present a forged
24
+ * or stale `CredentialRef` because the snapshotted name must still match the
25
+ * stored record.
26
+ *
27
+ * @since 0.1.0
28
+ */
29
+
30
+ import { Context, Effect, Layer, Option, Redacted } from "effect"
31
+ import { CredentialConflict, InvalidInput, type PersistenceError, Unauthorized, Unavailable } from "./ControlError.ts"
32
+ import * as CredentialCipher from "./CredentialCipher.ts"
33
+ import * as CredentialStore from "./CredentialStore.ts"
34
+
35
+ // String.prototype.isWellFormed without the ES2024 lib, which every package
36
+ // compiling this source would also need. A loop, not a regex: a match would
37
+ // leave the rejected secret in the legacy RegExp.input static.
38
+ const wellFormed = (value: string): boolean => {
39
+ for (let index = 0; index < value.length; index++) {
40
+ const unit = value.charCodeAt(index)
41
+ if (unit >= 0xdc00 && unit <= 0xdfff) return false
42
+ if (unit >= 0xd800 && unit <= 0xdbff) {
43
+ const low = value.charCodeAt(++index)
44
+ if (!(low >= 0xdc00 && low <= 0xdfff)) return false
45
+ }
46
+ }
47
+ return true
48
+ }
49
+
50
+ /**
51
+ * A journal-safe name for a stored connection credential.
52
+ *
53
+ * @category models
54
+ * @since 0.1.0
55
+ */
56
+ export interface CredentialRef {
57
+ readonly id: string
58
+ readonly name: string
59
+ }
60
+
61
+ /**
62
+ * One credential operation, as seen by a host authorization policy.
63
+ *
64
+ * @category models
65
+ * @since 0.1.0
66
+ */
67
+ export type Operation = "list" | "get" | "create" | "resolve" | "rotate" | "revoke"
68
+
69
+ /**
70
+ * How {@link Credential.rotate} compares before it writes.
71
+ *
72
+ * @category models
73
+ * @since 1.0.0
74
+ */
75
+ export interface RotateOptions {
76
+ /** The secret the caller last saw; the rotation commits only over it. */
77
+ readonly expected?: Redacted.Redacted<string> | undefined
78
+ }
79
+
80
+ /**
81
+ * Adapter-bound credential resolution. References are safe to list and
82
+ * persist; only `resolve` crosses into a secret-bearing adapter boundary.
83
+ *
84
+ * @category services
85
+ * @since 0.1.0
86
+ */
87
+ export interface Credential {
88
+ readonly list: () => Effect.Effect<ReadonlyArray<CredentialRef>, Unavailable | Unauthorized>
89
+ readonly get: (id: string) => Effect.Effect<CredentialRef, Unavailable | Unauthorized>
90
+ readonly create: (
91
+ options: { readonly id: string; readonly name: string; readonly secret: Redacted.Redacted<string> }
92
+ ) => Effect.Effect<CredentialRef, Unavailable | Unauthorized | CredentialConflict | InvalidInput>
93
+ readonly resolve: (
94
+ reference: CredentialRef
95
+ ) => Effect.Effect<Redacted.Redacted<string>, Unavailable | Unauthorized | PersistenceError>
96
+ /**
97
+ * Replaces the secret. With `expected`, replaces it only while the stored
98
+ * secret still equals `expected`, and otherwise fails `CredentialConflict`
99
+ * at the stored version without writing.
100
+ */
101
+ readonly rotate: (
102
+ reference: CredentialRef,
103
+ secret: Redacted.Redacted<string>,
104
+ options?: RotateOptions | undefined
105
+ ) => Effect.Effect<
106
+ CredentialRef,
107
+ Unavailable | Unauthorized | CredentialConflict | InvalidInput | PersistenceError
108
+ >
109
+ readonly revoke: (reference: CredentialRef) => Effect.Effect<void, Unavailable | Unauthorized>
110
+ }
111
+
112
+ /**
113
+ * Service tag for credential resolution.
114
+ *
115
+ * @category services
116
+ * @since 0.1.0
117
+ */
118
+ export const Credential: Context.Service<Credential, Credential> = Context.Service("/control/Credential")
119
+
120
+ const unavailable = (): Unavailable =>
121
+ new Unavailable({
122
+ feature: "credential storage",
123
+ ticket: "control-credential-storage"
124
+ })
125
+
126
+ /**
127
+ * A credential implementation that explicitly reports unavailable storage.
128
+ *
129
+ * @category constructors
130
+ * @since 0.1.0
131
+ */
132
+ export const makeNoop = (): Credential =>
133
+ Credential.of({
134
+ list: Effect.fn("Credential.list")(() => Effect.fail(unavailable())),
135
+ get: Effect.fn("Credential.get")(() => Effect.fail(unavailable())),
136
+ create: Effect.fn("Credential.create")(() => Effect.fail(unavailable())),
137
+ resolve: Effect.fn("Credential.resolve")(() => Effect.fail(unavailable())),
138
+ rotate: Effect.fn("Credential.rotate")(() => Effect.fail(unavailable())),
139
+ revoke: Effect.fn("Credential.revoke")(() => Effect.fail(unavailable()))
140
+ })
141
+
142
+ /**
143
+ * Layer for the unavailable credential-storage boundary.
144
+ *
145
+ * @category layers
146
+ * @since 0.1.0
147
+ */
148
+ export const layerNoop = Layer.succeed(Credential, makeNoop())
149
+
150
+ /**
151
+ * The collaborators a working credential boundary needs.
152
+ *
153
+ * `authorize` is the host's policy hook. It defaults to allowing every
154
+ * operation, which is correct for a single-principal local process; a server
155
+ * composition supplies its own and gets typed `Unauthorized` failures for free.
156
+ *
157
+ * @category models
158
+ * @since 0.1.0
159
+ */
160
+ export interface Options {
161
+ readonly store: CredentialStore.Service
162
+ readonly cipher: CredentialCipher.Service
163
+ readonly authorize?:
164
+ | ((operation: Operation, reference: Option.Option<CredentialRef>) => Effect.Effect<void, Unauthorized>)
165
+ | undefined
166
+ }
167
+
168
+ const referenceOf = (value: { readonly id: string; readonly name: string }): CredentialRef =>
169
+ Object.freeze({
170
+ id: value.id,
171
+ name: value.name
172
+ })
173
+
174
+ const cipherContextOf = (
175
+ value: { readonly id: string; readonly name: string; readonly version: number }
176
+ ): CredentialCipher.Context =>
177
+ Object.freeze({
178
+ id: value.id,
179
+ name: value.name,
180
+ version: value.version
181
+ })
182
+
183
+ const missing = (id: string): Unauthorized =>
184
+ // Deliberately indistinguishable from a denied reference: telling an
185
+ // unauthorized caller which ids exist is itself a leak.
186
+ new Unauthorized({ message: `Credential ${id} is not available to this caller` })
187
+
188
+ /**
189
+ * Constructs a working credential boundary over a store and a cipher.
190
+ *
191
+ * @category constructors
192
+ * @since 0.1.0
193
+ */
194
+ export const make = (options: Options): Credential => {
195
+ const authorize = options.authorize ?? (() => Effect.void)
196
+ const store = options.store
197
+ const cipher = options.cipher
198
+
199
+ /** Reads the record `reference` names, refusing a forged or stale one. */
200
+ const authenticate = (
201
+ operation: Operation,
202
+ reference: CredentialRef
203
+ ): Effect.Effect<CredentialStore.SealedRecord, Unavailable | Unauthorized> =>
204
+ Effect.gen(function*() {
205
+ const snapshot = referenceOf(reference)
206
+ yield* authorize(operation, Option.some(snapshot))
207
+ const record = yield* store.read(snapshot.id)
208
+ if (Option.isNone(record)) return yield* Effect.fail(missing(snapshot.id))
209
+ if (record.value.name !== snapshot.name) return yield* Effect.fail(missing(snapshot.id))
210
+ return record.value
211
+ })
212
+
213
+ const put = (
214
+ record: CredentialStore.SealedRecord,
215
+ secret: Redacted.Redacted<string>
216
+ ): Effect.Effect<CredentialRef, Unavailable | CredentialConflict | InvalidInput> =>
217
+ Effect.gen(function*() {
218
+ if (!wellFormed(Redacted.value(secret))) {
219
+ return yield* Effect.fail(new InvalidInput({ issue: "Credential secret must be well-formed Unicode" }))
220
+ }
221
+ // The metadata written beside this blob is also its authenticated data;
222
+ // moving the blob to another id, name, or version must make it unreadable.
223
+ const context = cipherContextOf(record)
224
+ const sealed = yield* cipher.seal(secret, context)
225
+ const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis)
226
+ yield* store.write({ ...record, ...sealed, updatedAtMs: now })
227
+ return referenceOf(record)
228
+ })
229
+
230
+ return Credential.of({
231
+ list: Effect.fn("Credential.list")(function*() {
232
+ yield* authorize("list", Option.none())
233
+ const records = yield* store.list()
234
+ return records.map(referenceOf)
235
+ }),
236
+ get: Effect.fn("Credential.get")(function*(id) {
237
+ const record = yield* store.read(id)
238
+ if (Option.isNone(record)) return yield* Effect.fail(missing(id))
239
+ const reference = referenceOf(record.value)
240
+ yield* authorize("get", Option.some(reference))
241
+ return reference
242
+ }),
243
+ create: Effect.fn("Credential.create")(function*(input) {
244
+ const snapshot = Object.freeze({
245
+ reference: referenceOf(input),
246
+ secret: input.secret
247
+ })
248
+ yield* authorize("create", Option.some(snapshot.reference))
249
+ return yield* put(
250
+ {
251
+ id: snapshot.reference.id,
252
+ name: snapshot.reference.name,
253
+ ciphertext: "",
254
+ nonce: "",
255
+ version: 1,
256
+ updatedAtMs: 0
257
+ },
258
+ snapshot.secret
259
+ )
260
+ }),
261
+ resolve: Effect.fn("Credential.resolve")(function*(reference) {
262
+ const record = yield* authenticate("resolve", reference)
263
+ return yield* cipher.open(
264
+ { ciphertext: record.ciphertext, nonce: record.nonce },
265
+ cipherContextOf(record)
266
+ )
267
+ }),
268
+ rotate: Effect.fn("Credential.rotate")(function*(reference, secret, options) {
269
+ const expected = options?.expected
270
+ const record = yield* authenticate("rotate", reference)
271
+ if (expected !== undefined) {
272
+ const stored = yield* cipher.open(
273
+ { ciphertext: record.ciphertext, nonce: record.nonce },
274
+ cipherContextOf(record)
275
+ )
276
+ if (Redacted.value(stored) !== Redacted.value(expected)) {
277
+ return yield* Effect.fail(
278
+ new CredentialConflict({ id: record.id, expectedVersion: record.version, actualVersion: record.version })
279
+ )
280
+ }
281
+ }
282
+ // `put` writes over exactly the version read above, so the secret just
283
+ // compared is still the stored one when the write commits.
284
+ return yield* put({ ...record, version: record.version + 1 }, secret)
285
+ }),
286
+ revoke: Effect.fn("Credential.revoke")(function*(reference) {
287
+ const record = yield* authenticate("revoke", reference)
288
+ yield* store.remove(record.id)
289
+ })
290
+ })
291
+ }
292
+
293
+ /**
294
+ * Provides a working credential boundary over the ambient store and cipher.
295
+ *
296
+ * @category layers
297
+ * @since 0.1.0
298
+ */
299
+ export const layer = (
300
+ options: { readonly authorize?: Options["authorize"] } = {}
301
+ ): Layer.Layer<
302
+ Credential,
303
+ never,
304
+ CredentialStore.CredentialStore | CredentialCipher.CredentialCipher
305
+ > =>
306
+ Layer.effect(Credential)(Effect.gen(function*() {
307
+ const store = yield* CredentialStore.CredentialStore
308
+ const cipher = yield* CredentialCipher.CredentialCipher
309
+ return make({ store, cipher, ...(options.authorize === undefined ? {} : { authorize: options.authorize }) })
310
+ }))
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Browser-safe encryption port for credential material.
3
+ *
4
+ * The cipher is the only place plaintext exists outside a `Redacted`. Keys are
5
+ * host-managed: an adapter receives a key from its host and never persists one
6
+ * through {@link CredentialStore}, so a stolen store is ciphertext and nothing
7
+ * else.
8
+ *
9
+ * @since 0.1.0
10
+ */
11
+
12
+ import { Context as EffectContext, Effect, Layer, type Redacted } from "effect"
13
+ import { type PersistenceError, Unavailable } from "./ControlError.ts"
14
+
15
+ /**
16
+ * One encrypted secret: base64 ciphertext and the nonce it was sealed under.
17
+ *
18
+ * @category models
19
+ * @since 0.1.0
20
+ */
21
+ export interface Sealed {
22
+ readonly ciphertext: string
23
+ readonly nonce: string
24
+ }
25
+
26
+ /**
27
+ * Credential metadata authenticated with one sealed secret.
28
+ *
29
+ * Every field is immutable because the same values must be written beside the
30
+ * ciphertext and supplied again when it is opened.
31
+ *
32
+ * @category models
33
+ * @since 0.1.0
34
+ */
35
+ export interface Context {
36
+ readonly id: string
37
+ readonly name: string
38
+ readonly version: number
39
+ }
40
+
41
+ /**
42
+ * Authenticated encryption over credential plaintext.
43
+ *
44
+ * @category services
45
+ * @since 0.1.0
46
+ */
47
+ export interface Service {
48
+ readonly seal: (plaintext: Redacted.Redacted<string>, context: Context) => Effect.Effect<Sealed, Unavailable>
49
+ /**
50
+ * Fails with `PersistenceError` on operation `credential.open` when the
51
+ * record does not open: a malformed nonce, or ciphertext that fails
52
+ * authentication under this key and context.
53
+ */
54
+ readonly open: (
55
+ sealed: Sealed,
56
+ context: Context
57
+ ) => Effect.Effect<Redacted.Redacted<string>, Unavailable | PersistenceError>
58
+ }
59
+
60
+ /**
61
+ * Service key for credential encryption.
62
+ *
63
+ * @category services
64
+ * @since 0.1.0
65
+ */
66
+ export class CredentialCipher extends EffectContext.Service<CredentialCipher, Service>()(
67
+ "/control/CredentialCipher"
68
+ ) {}
69
+
70
+ /**
71
+ * Constructs a cipher from an implementation record.
72
+ *
73
+ * @category constructors
74
+ * @since 0.1.0
75
+ */
76
+ export const make = (implementation: Service): Service => CredentialCipher.of(implementation)
77
+
78
+ /**
79
+ * The typed failure a host reports when no secure key material is reachable.
80
+ *
81
+ * @category constructors
82
+ * @since 0.1.0
83
+ */
84
+ export const unavailable = (): Unavailable =>
85
+ new Unavailable({
86
+ feature: "credential encryption",
87
+ ticket: "control-credential-storage"
88
+ })
89
+
90
+ /**
91
+ * A cipher that reports unavailable key material for every operation.
92
+ *
93
+ * @category constructors
94
+ * @since 0.1.0
95
+ */
96
+ export const makeNoop = (overrides: Partial<Service> = {}): Service =>
97
+ make({
98
+ seal: Effect.fn("CredentialCipher.seal")(() => Effect.fail(unavailable())),
99
+ open: Effect.fn("CredentialCipher.open")(() => Effect.fail(unavailable())),
100
+ ...overrides
101
+ })
102
+
103
+ /**
104
+ * Provides a cipher that reports unavailable key material.
105
+ *
106
+ * @category layers
107
+ * @since 0.1.0
108
+ */
109
+ export const layerNoop = (overrides: Partial<Service> = {}): Layer.Layer<CredentialCipher> =>
110
+ Layer.succeed(CredentialCipher)(makeNoop(overrides))