@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,283 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var Monitor_exports = {};
30
+ __export(Monitor_exports, {
31
+ Health: () => Health,
32
+ attemptFinishedEventType: () => attemptFinishedEventType,
33
+ attemptStartedEventType: () => attemptStartedEventType,
34
+ beatEventType: () => beatEventType,
35
+ classify: () => classify,
36
+ healedEventType: () => healedEventType,
37
+ isBookkeepingEvent: () => isBookkeepingEvent,
38
+ remedyFor: () => remedyFor,
39
+ run: () => run
40
+ });
41
+ module.exports = __toCommonJS(Monitor_exports);
42
+ var import_journal = require("@smthrs/journal");
43
+ var import_effect = require("effect");
44
+ var import_Control = require("./Control.js");
45
+ var import_ControlError = require("./ControlError.js");
46
+ var SubjectHealth = __toESM(require("./Health.js"), 1);
47
+ const attemptStartedEventType = "flows.engine.attempt-started";
48
+ const attemptFinishedEventType = "flows.engine.attempt-finished";
49
+ const beatEventType = "control.monitor.beat";
50
+ const healedEventType = "control.monitor.healed";
51
+ const Health = SubjectHealth.HealthState;
52
+ const defaultRoundBound = 32;
53
+ const foldAttempt = (state, event) => {
54
+ if (event.kind === attemptStartedEventType) state.open += 1;
55
+ if (event.kind === attemptFinishedEventType) {
56
+ state.open -= 1;
57
+ const payload = event.payload;
58
+ state.failed = typeof payload === "object" && payload !== null && !Array.isArray(payload) && payload["state"] === "failed";
59
+ }
60
+ };
61
+ const classify = (observation) => {
62
+ const attempts = { open: 0, failed: false };
63
+ for (const event of observation.events) foldAttempt(attempts, event);
64
+ return classifyState(observation, attempts);
65
+ };
66
+ const classifyState = (observation, attempts) => {
67
+ const summary = observation.summary;
68
+ if (summary === void 0) return "unknown";
69
+ if (summary.status === "failed") return "failing";
70
+ if (summary.status === "completed" || summary.status === "cancelled") return "healthy";
71
+ if (summary.status === "waiting-approval") return "awaiting-human";
72
+ if (summary.status === "parked" && (summary.waitingReason === "approval" || summary.waitingReason === void 0)) {
73
+ return "awaiting-human";
74
+ }
75
+ if (SubjectHealth.waitReason(summary.status, summary.waitingReason) !== void 0) return "healthy";
76
+ const bound = observation.roundBound ?? defaultRoundBound;
77
+ if (summary.roundOrdinal !== void 0 && summary.roundOrdinal >= bound) return "runaway-loop";
78
+ if (attempts.failed) return "failing";
79
+ if (!observation.semanticProgress && observation.beatsWithoutProgress >= observation.stallBeats) {
80
+ return attempts.open > 0 ? "wedged-node" : "stalled";
81
+ }
82
+ return "healthy";
83
+ };
84
+ const remedyFor = (health) => {
85
+ switch (health) {
86
+ case "stalled":
87
+ case "wedged-node":
88
+ return "resume";
89
+ case "failing":
90
+ case "runaway-loop":
91
+ return "cancel";
92
+ default:
93
+ return "none";
94
+ }
95
+ };
96
+ const summaryOf = (runId) => import_effect.Effect.flatMap(import_Control.Control, (control) => control.list({ _tag: "runs", filters: { runId } }).pipe(
97
+ import_effect.Effect.map((listed) => listed._tag === "runs" ? listed.items[0] : void 0)
98
+ ));
99
+ const terminal = (summary) => summary !== void 0 && (summary.status === "completed" || summary.status === "failed" || summary.status === "cancelled");
100
+ const run = (options) => import_effect.Effect.gen(function* () {
101
+ const control = yield* import_Control.Control;
102
+ const journal = yield* import_journal.Journal.Journal;
103
+ const intervalMs = options.intervalMs ?? options.healthCheck?.policy.intervalMs ?? 1e3;
104
+ let consecutiveProbeFailures = 0;
105
+ let lastPublished;
106
+ const maxChecks = options.maxChecks ?? 10;
107
+ const stallBeats = options.stallBeats ?? 3;
108
+ const autoHeal = options.autoHeal ?? [];
109
+ const monitorId = options.monitorId ?? "default";
110
+ const heal = options.heal ?? ((input) => input.remedy === "resume" ? control.resume({
111
+ runId: input.runId,
112
+ idempotencyKey: `monitor:${monitorId}:resume:${input.runId}:${input.beat}`
113
+ }) : control.cancel({
114
+ runId: input.runId,
115
+ reason: `monitor:${input.health}`,
116
+ idempotencyKey: `monitor:${monitorId}:cancel:${input.runId}:${input.beat}`
117
+ }));
118
+ const emit = (eventType, payload, operation) => {
119
+ const unrecorded = (cause) => new import_ControlError.PersistenceError({
120
+ operation: eventType,
121
+ message: `Failed to record ${operation} for ${options.runId}`,
122
+ cause
123
+ });
124
+ return import_effect.Effect.try({
125
+ try: () => new import_journal.JournalEvent.Input({
126
+ runId: import_journal.JournalEvent.RunId.make(options.runId),
127
+ sourceId: import_journal.JournalEvent.SourceId.make(`/control/monitor/${monitorId}`),
128
+ eventType,
129
+ payload: { runId: options.runId, monitorId, ...payload }
130
+ }),
131
+ catch: unrecorded
132
+ }).pipe(
133
+ import_effect.Effect.flatMap((input) => import_effect.Effect.mapError(journal.emitDurableUnfenced(input), unrecorded)),
134
+ import_effect.Effect.asVoid
135
+ );
136
+ };
137
+ const record = (beat, remedy) => emit(
138
+ beatEventType,
139
+ {
140
+ beat: beat.beat,
141
+ health: beat.health,
142
+ sequence: beat.sequence,
143
+ ...remedy === "none" ? {} : { remedy }
144
+ },
145
+ `monitor beat ${beat.beat}`
146
+ );
147
+ const recordHealed = (beat, remedy, receipt) => emit(
148
+ healedEventType,
149
+ { beat: beat.beat, health: beat.health, healed: remedy, receipt: receipt._tag },
150
+ `monitor heal ${beat.beat}`
151
+ );
152
+ const beats = [];
153
+ let beatsWithoutProgress = 0;
154
+ let lastSequence = -1;
155
+ let checkpoint = {};
156
+ const attempts = { open: 0, failed: false };
157
+ let sequence = -1;
158
+ for (let beat = 0; beat < maxChecks; beat += 1) {
159
+ if (beat > 0 && intervalMs > 0) {
160
+ yield* import_effect.Effect.sleep(import_effect.Duration.millis(
161
+ options.healthCheck === void 0 ? intervalMs : SubjectHealth.nextDelay(options.healthCheck.policy, consecutiveProbeFailures)
162
+ ));
163
+ }
164
+ const summary = yield* summaryOf(options.runId);
165
+ const newEvents = [];
166
+ const sinceCursor = Math.max(0, sequence);
167
+ yield* control.watch({ runId: options.runId, follow: false, ...checkpoint }).pipe(
168
+ import_effect.Stream.runForEach(
169
+ (event) => import_effect.Effect.sync(() => {
170
+ checkpoint = event.cursor === void 0 ? { afterSequence: event.sequence } : { afterCursor: event.cursor };
171
+ if (isBookkeepingEvent(event.kind)) return;
172
+ newEvents.push(event);
173
+ if (newEvents.length > 256) newEvents.shift();
174
+ sequence = event.sequence;
175
+ foldAttempt(attempts, event);
176
+ })
177
+ )
178
+ );
179
+ beatsWithoutProgress = sequence === lastSequence ? beatsWithoutProgress + 1 : 0;
180
+ lastSequence = sequence;
181
+ const health = classifyState({
182
+ ...summary === void 0 ? {} : { summary },
183
+ beatsWithoutProgress,
184
+ stallBeats,
185
+ ...options.roundBound === void 0 ? {} : { roundBound: options.roundBound }
186
+ }, attempts);
187
+ if (options.healthCheck !== void 0 && summary !== void 0) {
188
+ const check = options.healthCheck;
189
+ const subjectId = `run:${options.runId}`;
190
+ const incarnation = SubjectHealth.runIncarnation(summary);
191
+ const probe = SubjectHealth.evaluate(check, {
192
+ subjectId,
193
+ state: summary.status,
194
+ summary,
195
+ events: newEvents,
196
+ sinceCursor
197
+ }, { monitorId, incarnation, evidenceSeq: Math.max(0, sequence) });
198
+ let observation = yield* options.withProbePermit?.(probe) ?? probe;
199
+ const current = yield* summaryOf(options.runId);
200
+ if (current === void 0 || SubjectHealth.runIncarnation(current) !== incarnation) {
201
+ observation = { ...observation, outcome: "discarded", report: void 0, reason: "owner-changed" };
202
+ }
203
+ consecutiveProbeFailures = observation.outcome === "ok" ? 0 : consecutiveProbeFailures + 1;
204
+ const semanticProgress = observation.outcome === "ok" && observation.report?.activity === "working";
205
+ const baseHealth = classifyState({
206
+ summary,
207
+ beatsWithoutProgress,
208
+ stallBeats,
209
+ roundBound: options.roundBound,
210
+ semanticProgress
211
+ }, attempts);
212
+ observation = { ...observation, baseHealth };
213
+ const status = SubjectHealth.rollup({
214
+ subjectId,
215
+ state: summary.status,
216
+ incarnation,
217
+ waitingReason: summary.waitingReason,
218
+ baseHealth,
219
+ latest: { observation, sequence: 0 },
220
+ now: observation.observedAt,
221
+ updatedAt: summary.updatedAt
222
+ });
223
+ const changed = lastPublished === void 0 || lastPublished.incarnation !== observation.incarnation || lastPublished.outcome !== observation.outcome || lastPublished.baseHealth !== observation.baseHealth || lastPublished.evidenceSeq !== observation.evidenceSeq || JSON.stringify(lastPublished.report) !== JSON.stringify(observation.report) || lastPublished.reason !== observation.reason;
224
+ if (changed || observation.observedAt >= lastPublished.observedAt + check.policy.ttlMs / 2) {
225
+ yield* emit(SubjectHealth.statusObservedEventType, {
226
+ ...observation,
227
+ status: summary.status,
228
+ health: status.health,
229
+ activity: status.activity,
230
+ attention: status.attention,
231
+ freshness: status.freshness,
232
+ waitingReason: summary.waitingReason ?? ""
233
+ }, "health observation");
234
+ lastPublished = observation;
235
+ }
236
+ }
237
+ const remedy = autoHeal.includes(health) ? remedyFor(health) : "none";
238
+ const observed = { beat, health, sequence };
239
+ if (options.recordBeats !== false) yield* record(observed, remedy);
240
+ if (remedy === "none") {
241
+ beats.push(observed);
242
+ } else {
243
+ const receipt = yield* heal({ runId: options.runId, health, remedy, beat }).pipe(
244
+ // A remedy that fails must not abort the run of beats: the monitor
245
+ // is an unattended loop, and one failing heal leaves every other
246
+ // beat — and every other run it would have observed — unwatched.
247
+ // The failure is logged with the beat it failed on, and the beat is
248
+ // recorded without `healed` or `receipt`, exactly as a remedy the
249
+ // monitor never got to apply reports.
250
+ import_effect.Effect.catch(
251
+ (failure) => import_effect.Effect.annotateLogs(
252
+ import_effect.Effect.logWarning("A monitor remedy failed and was skipped"),
253
+ { runId: options.runId, beat, health, remedy, cause: String(failure) }
254
+ ).pipe(import_effect.Effect.as(void 0))
255
+ )
256
+ );
257
+ if (receipt === void 0) {
258
+ beats.push(observed);
259
+ } else {
260
+ const applied = receipt._tag === "Accepted" || receipt._tag === "AlreadyApplied";
261
+ if (applied) {
262
+ yield* recordHealed(observed, remedy, receipt);
263
+ beatsWithoutProgress = 0;
264
+ }
265
+ beats.push(applied ? { ...observed, healed: remedy, receipt } : { ...observed, receipt });
266
+ if (receipt._tag === "Terminal") break;
267
+ }
268
+ }
269
+ if (options.retainBeats !== void 0 && beats.length > options.retainBeats) {
270
+ beats.splice(0, beats.length - options.retainBeats);
271
+ }
272
+ if (terminal(summary)) {
273
+ break;
274
+ }
275
+ }
276
+ return {
277
+ runId: options.runId,
278
+ beats,
279
+ health: beats[beats.length - 1]?.health ?? "unknown"
280
+ };
281
+ });
282
+ const isBookkeepingEvent = (kind) => kind.startsWith("control.monitor.") || kind.startsWith("control.status.") || kind.startsWith("flows.alerts.") || kind.startsWith("flows.notifications.") || kind.startsWith("flows.notification.");
283
+ //# sourceMappingURL=Monitor.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/Monitor.ts"],
4
+ "sourcesContent": ["/**\n * Run health over the control plane: what a run's state means, and what to do\n * about it.\n *\n * A control plane answers \"what is this run doing\". A monitor answers the\n * question after it \u2014 \"is that all right, and if not, what now\" \u2014 and the two\n * are deliberately separate modules. {@link classify} is a pure function of\n * one observation, so the vocabulary an operator reads on a dashboard is the\n * same one a heal loop branches on and the same one a test can enumerate.\n * {@link run} is the loop that beats over `Control` and journals what it saw.\n *\n * The classification is decided from durable evidence only: the run summary\n * the control plane projects, and the journal entries `watch` replays. Nothing\n * here reads an in-process fiber, which is what makes a monitor able to watch\n * a run in another process at all.\n *\n * @since 0.1.0\n */\n\nimport { Journal, JournalEvent } from \"@smthrs/journal\"\nimport { Duration, Effect, Stream } from \"effect\"\nimport { Control } from \"./Control.ts\"\nimport type { ControlError } from \"./ControlError.ts\"\nimport { PersistenceError } from \"./ControlError.ts\"\nimport type { ControlEvent, Receipt, RunId, RunSummary, WatchFilter } from \"./ControlSchema.ts\"\nimport * as SubjectHealth from \"./Health.ts\"\n\n/**\n * The journal event type the engine records when an action attempt starts.\n *\n * Named as a string rather than imported, exactly as in `Lineage`: a monitor\n * reads journals, not engines.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const attemptStartedEventType = \"flows.engine.attempt-started\"\n\n/**\n * The journal event type the engine records when an action attempt settles.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const attemptFinishedEventType = \"flows.engine.attempt-finished\"\n\n/**\n * The journal event type one monitor beat is recorded under.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const beatEventType = \"control.monitor.beat\"\n\n/**\n * The journal event type one applied remedy is recorded under.\n *\n * A beat and a heal are two records because they are two facts, and only one\n * of them is true before the remedy runs. A single record carrying `healed`\n * has to be written either before the remedy \u2014 leaving durable evidence of a\n * heal that a crash one instruction later never performed \u2014 or after it, which\n * loses the evidence of what a monitor decided when the remedy is what killed\n * it. Splitting them keeps both.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const healedEventType = \"control.monitor.healed\"\n\n/**\n * What a run looks like to a monitor.\n *\n * `healthy` covers both \"moving\" and \"finished\": a completed run needs nothing\n * done to it, and neither does one that is making progress. The other six name\n * a specific thing that is wrong, because a heal that cannot tell them apart\n * would resume a run that is waiting for a human.\n *\n * @category models\n * @since 0.1.0\n */\nexport const Health = SubjectHealth.HealthState\n\n/**\n * What a run looks like to a monitor.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Health = typeof Health.Type\n\n/**\n * Everything one classification is decided from.\n *\n * `events` is the run's journal as `watch` projects it, oldest first.\n * `beatsWithoutProgress` counts consecutive beats that added no entry, and\n * `stallBeats` is how many of those make a stall. Splitting the count from the\n * threshold is what lets the same pure function serve a monitor that beats\n * every second and one that beats every hour.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Observation {\n /** The run's projection, or nothing when the control plane has no such run. */\n readonly summary?: RunSummary | undefined\n readonly events: ReadonlyArray<ControlEvent>\n readonly beatsWithoutProgress: number\n readonly stallBeats: number\n /**\n * The trampoline round at which a lineage stops being a loop and starts\n * being a runaway. A run past it is looping without converging.\n */\n readonly roundBound?: number | undefined\n /** Fresh, explicitly reported semantic work; never inferred from bytes or monitor events. */\n readonly semanticProgress?: boolean | undefined\n}\n\n/** How many rounds a trampoline may take before a monitor calls it runaway. */\nconst defaultRoundBound = 32\n\ninterface AttemptState {\n open: number\n failed: boolean\n}\n\nconst foldAttempt = (state: AttemptState, event: ControlEvent): void => {\n if (event.kind === attemptStartedEventType) state.open += 1\n if (event.kind === attemptFinishedEventType) {\n state.open -= 1\n const payload = event.payload\n state.failed = typeof payload === \"object\" && payload !== null && !Array.isArray(payload) &&\n (payload as { readonly state?: unknown })[\"state\"] === \"failed\"\n }\n}\n\n/**\n * Decides what a run's state means.\n *\n * The order is the order an operator would read it in, and each rule earns its\n * place by naming a different response:\n *\n * | Condition | Health | Because |\n * | --- | --- | --- |\n * | No summary | `unknown` | Nothing to say, and nothing to do. |\n * | `failed` | `failing` | The run itself reported the failure. |\n * | `completed`, `cancelled` | `healthy` | A finished run needs nothing. |\n * | `waiting-approval`, or parked on `approval` | `awaiting-human` | A human owes it an answer; no machine can supply one. |\n * | Parked with no waiting reason | `awaiting-human` | Only an operator's own park writes no waiting reason, so a person stopped it and a person restarts it. |\n * | `roundOrdinal` at or past the bound | `runaway-loop` | The lineage is looping without converging. |\n * | The last settled attempt failed | `failing` | The run is still alive but its work is not landing. |\n * | No progress for `stallBeats`, an attempt open | `wedged-node` | One attempt started and never settled: the work is stuck, not the run. |\n * | No progress for `stallBeats` | `stalled` | Nothing is happening and nothing is in flight. |\n * | Anything else | `healthy` | Entries are still arriving. |\n *\n * `awaiting-human` deliberately outranks `failing`: a run parked for approval\n * after a failed attempt is waiting for a person, and resuming or cancelling\n * it would take the decision away from them.\n *\n * A park with no reason is the same case. The engine names every park it makes\n * \u2014 `event`, `approval`, `timer`, `quota`, `released` \u2014 so a parked run whose\n * `waitingReason` is absent was parked by an operator through\n * `ControlRuntime.writeStatus`. Calling that a stall and resuming it would undo\n * a deliberate act, which is the worst thing an unattended heal loop can do.\n *\n * @param observation what the beat saw\n * @category projections\n * @since 0.1.0\n */\nexport const classify = (observation: Observation): Health => {\n const attempts: AttemptState = { open: 0, failed: false }\n for (const event of observation.events) foldAttempt(attempts, event)\n return classifyState(observation, attempts)\n}\n\nconst classifyState = (observation: Omit<Observation, \"events\">, attempts: AttemptState): Health => {\n const summary = observation.summary\n if (summary === undefined) return \"unknown\"\n if (summary.status === \"failed\") return \"failing\"\n if (summary.status === \"completed\" || summary.status === \"cancelled\") return \"healthy\"\n if (summary.status === \"waiting-approval\") return \"awaiting-human\"\n if (summary.status === \"parked\" && (summary.waitingReason === \"approval\" || summary.waitingReason === undefined)) {\n return \"awaiting-human\"\n }\n if (SubjectHealth.waitReason(summary.status, summary.waitingReason) !== undefined) return \"healthy\"\n const bound = observation.roundBound ?? defaultRoundBound\n if (summary.roundOrdinal !== undefined && summary.roundOrdinal >= bound) return \"runaway-loop\"\n if (attempts.failed) return \"failing\"\n if (!observation.semanticProgress && observation.beatsWithoutProgress >= observation.stallBeats) {\n return attempts.open > 0 ? \"wedged-node\" : \"stalled\"\n }\n return \"healthy\"\n}\n\n/**\n * What a monitor does about one unhealthy run.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Remedy = \"resume\" | \"cancel\" | \"none\"\n\n/**\n * The remedy a health warrants, before `autoHeal` decides whether to apply it.\n *\n * A stalled or wedged run is one nobody is driving, and a resume claims it. A\n * failing or runaway run is one that will not get better by being driven\n * harder. Everything else is left alone.\n *\n * @param health what the beat classified\n * @category projections\n * @since 0.1.0\n */\nexport const remedyFor = (health: Health): Remedy => {\n switch (health) {\n case \"stalled\":\n case \"wedged-node\":\n return \"resume\"\n case \"failing\":\n case \"runaway-loop\":\n return \"cancel\"\n default:\n return \"none\"\n }\n}\n\n/**\n * One beat of a monitor.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Beat {\n /** Which beat this was, counting from zero. */\n readonly beat: number\n readonly health: Health\n /** The newest journal sequence the run had, ignoring the monitor's own records. */\n readonly sequence: number\n /**\n * The remedy that ran and returned, absent when none did.\n *\n * Present only after the heal succeeded, which is also when\n * `control.monitor.healed` is journaled. A beat that decided on a remedy the\n * monitor never got to apply reports nothing here.\n */\n readonly healed?: Remedy | undefined\n /** The receipt the remedy returned, absent when none was applied. */\n readonly receipt?: Receipt | undefined\n}\n\n/**\n * What a monitor found.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Report {\n readonly runId: RunId\n readonly beats: ReadonlyArray<Beat>\n /** The last beat's health, or `unknown` when no beat ran. */\n readonly health: Health\n}\n\n/**\n * How a monitor beats.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Options {\n readonly runId: RunId\n /** Optional configured observational checker; checker results never authorize a remedy. */\n readonly healthCheck?: SubjectHealth.ResolvedCheck | undefined\n /** Shared host admission limit around probes. */\n readonly withProbePermit?: (<A>(effect: Effect.Effect<A>) => Effect.Effect<A>) | undefined\n /** Bounded retained beat history for long-lived host monitoring. */\n readonly retainBeats?: number | undefined\n /** Hosts with structured status observations omit redundant legacy heartbeat records. */\n readonly recordBeats?: boolean | undefined\n /**\n * Who is watching. Defaults to `default`.\n *\n * It reaches the journal twice: as the source of every record this monitor\n * writes, and as `monitorId` in each payload. Two monitors pointed at one\n * run both beat and both remedy \u2014 nothing on the control plane leases a run\n * to one watcher \u2014 so the identity is what makes their evidence tellable\n * apart, and what keeps their remedies on separate idempotency keys.\n */\n readonly monitorId?: string | undefined\n /** How long to wait between beats. Defaults to one second. */\n readonly intervalMs?: number | undefined\n /** How many beats to take before reporting. Defaults to ten. */\n readonly maxChecks?: number | undefined\n /** How many beats without progress make a stall. Defaults to three. */\n readonly stallBeats?: number | undefined\n /** The round at which a trampoline is a runaway. Defaults to 32. */\n readonly roundBound?: number | undefined\n /** Which healths the monitor is allowed to act on. Defaults to none. */\n readonly autoHeal?: ReadonlyArray<Health> | undefined\n /**\n * What to do about an unhealthy run. Defaults to `Control.resume` and\n * `Control.cancel` per {@link remedyFor}.\n *\n * A heal that fails does not abort the monitor: the failure is logged, the\n * beat is recorded without `healed` or `receipt`, and the loop moves to the\n * next beat, where the stall evidence still stands and the remedy is\n * retried under that beat's own idempotency key.\n */\n readonly heal?: (\n input: { readonly runId: RunId; readonly health: Health; readonly remedy: Remedy; readonly beat: number }\n ) => Effect.Effect<Receipt, ControlError>\n}\n\nconst summaryOf = (runId: RunId): Effect.Effect<RunSummary | undefined, ControlError, Control> =>\n Effect.flatMap(Control, (control) =>\n control.list({ _tag: \"runs\", filters: { runId } }).pipe(\n Effect.map((listed) => listed._tag === \"runs\" ? listed.items[0] : undefined)\n ))\n\nconst terminal = (summary: RunSummary | undefined): boolean =>\n summary !== undefined &&\n (summary.status === \"completed\" || summary.status === \"failed\" || summary.status === \"cancelled\")\n\n/**\n * Watches one run and, when told to, heals it.\n *\n * The loop stops early on a terminal run, because a finished run has nothing\n * left to observe and beating at it would fill its journal with heartbeats.\n * Otherwise it takes `maxChecks` beats and reports what it saw.\n *\n * Every beat is journaled as `control.monitor.beat` before any remedy is\n * applied, carrying the `remedy` it is about to attempt, so a monitor that\n * crashes mid-heal leaves the evidence of what it decided. The remedy itself\n * is journaled separately as `control.monitor.healed`, and only once the heal\n * returned a receipt: a heal that failed, or a process that died running one,\n * must not leave a durable record saying the run was healed.\n *\n * `autoHeal` is empty by default. A monitor that healed by default would be a\n * monitor that cancels a run the first time it looks at one, which is the\n * wrong default for a thing an operator points at production.\n *\n * Nothing here leases the run. Two monitors on one run both beat and both\n * remedy, so a remedy has to be idempotent on the control plane \u2014 which the\n * two defaults are, through `Control.resume` and `Control.cancel`\n * idempotency keys that name the monitor, the run, and the beat. A custom\n * `heal` owes the same property.\n *\n * @param options how to beat and what to heal\n * @category constructors\n * @since 0.1.0\n */\nexport const run = (\n options: Options\n): Effect.Effect<Report, ControlError, Control | Journal.Journal> =>\n Effect.gen(function*() {\n const control = yield* Control\n const journal = yield* Journal.Journal\n const intervalMs = options.intervalMs ?? options.healthCheck?.policy.intervalMs ?? 1_000\n let consecutiveProbeFailures = 0\n let lastPublished: SubjectHealth.HealthObservation | undefined\n const maxChecks = options.maxChecks ?? 10\n const stallBeats = options.stallBeats ?? 3\n const autoHeal = options.autoHeal ?? []\n const monitorId = options.monitorId ?? \"default\"\n const heal = options.heal ?? ((input) =>\n input.remedy === \"resume\"\n ? control.resume({\n runId: input.runId,\n idempotencyKey: `monitor:${monitorId}:resume:${input.runId}:${input.beat}`\n })\n : control.cancel({\n runId: input.runId,\n reason: `monitor:${input.health}`,\n idempotencyKey: `monitor:${monitorId}:cancel:${input.runId}:${input.beat}`\n }))\n\n const emit = (\n eventType: string,\n payload: Record<string, unknown>,\n operation: string\n ): Effect.Effect<void, ControlError> => {\n const unrecorded = (cause: unknown) =>\n new PersistenceError({\n operation: eventType,\n message: `Failed to record ${operation} for ${options.runId}`,\n cause\n })\n // The record is BUILT inside the failure channel, not before it.\n // `JournalEvent.RunId`/`SourceId` refuse an identifier the store cannot\n // tell apart from another one \u2014 a lone UTF-16 surrogate, an embedded NUL\n // \u2014 by THROWING, and `monitorId` reaches here from an operator's flag. A\n // thrown constructor is a defect, which the RPC boundary reports as an\n // opaque `TransportError` and a caller cannot handle; a beat whose record\n // cannot be built is the same event as a beat whose record cannot be\n // appended, so both fail as `PersistenceError` naming the record.\n return Effect.try({\n try: () =>\n new JournalEvent.Input({\n runId: JournalEvent.RunId.make(options.runId),\n sourceId: JournalEvent.SourceId.make(`/control/monitor/${monitorId}`),\n eventType,\n payload: { runId: options.runId, monitorId, ...payload }\n }),\n catch: unrecorded\n }).pipe(\n Effect.flatMap((input) => Effect.mapError(journal.emitDurableUnfenced(input), unrecorded)),\n Effect.asVoid\n )\n }\n\n /** What the beat saw, and what it is about to do about it. */\n const record = (beat: Beat, remedy: Remedy): Effect.Effect<void, ControlError> =>\n emit(\n beatEventType,\n {\n beat: beat.beat,\n health: beat.health,\n sequence: beat.sequence,\n ...(remedy === \"none\" ? {} : { remedy })\n },\n `monitor beat ${beat.beat}`\n )\n\n /** What the remedy did, once it returned. */\n const recordHealed = (\n beat: Beat,\n remedy: Remedy,\n receipt: Receipt\n ): Effect.Effect<void, ControlError> =>\n emit(\n healedEventType,\n { beat: beat.beat, health: beat.health, healed: remedy, receipt: receipt._tag },\n `monitor heal ${beat.beat}`\n )\n\n const beats: Array<Beat> = []\n let beatsWithoutProgress = 0\n let lastSequence = -1\n let checkpoint: Pick<WatchFilter, \"afterCursor\" | \"afterSequence\"> = {}\n const attempts: AttemptState = { open: 0, failed: false }\n let sequence = -1\n for (let beat = 0; beat < maxChecks; beat += 1) {\n if (beat > 0 && intervalMs > 0) {\n yield* Effect.sleep(Duration.millis(\n options.healthCheck === undefined\n ? intervalMs\n : SubjectHealth.nextDelay(options.healthCheck.policy, consecutiveProbeFailures)\n ))\n }\n const summary = yield* summaryOf(options.runId)\n const newEvents: Array<ControlEvent> = []\n const sinceCursor = Math.max(0, sequence)\n yield* control.watch({ runId: options.runId, follow: false, ...checkpoint }).pipe(\n Stream.runForEach((event) =>\n Effect.sync(() => {\n // Advance over bookkeeping too, but never count it as run progress.\n checkpoint = event.cursor === undefined\n ? { afterSequence: event.sequence }\n : { afterCursor: event.cursor }\n if (isBookkeepingEvent(event.kind)) return\n newEvents.push(event)\n if (newEvents.length > 256) newEvents.shift()\n sequence = event.sequence\n foldAttempt(attempts, event)\n })\n )\n )\n beatsWithoutProgress = sequence === lastSequence ? beatsWithoutProgress + 1 : 0\n lastSequence = sequence\n const health = classifyState({\n ...(summary === undefined ? {} : { summary }),\n beatsWithoutProgress,\n stallBeats,\n ...(options.roundBound === undefined ? {} : { roundBound: options.roundBound })\n }, attempts)\n if (options.healthCheck !== undefined && summary !== undefined) {\n const check = options.healthCheck\n const subjectId = `run:${options.runId}`\n const incarnation = SubjectHealth.runIncarnation(summary)\n const probe = SubjectHealth.evaluate(check, {\n subjectId,\n state: summary.status,\n summary,\n events: newEvents,\n sinceCursor\n }, { monitorId, incarnation, evidenceSeq: Math.max(0, sequence) })\n let observation = yield* (options.withProbePermit?.(probe) ?? probe)\n const current = yield* summaryOf(options.runId)\n if (current === undefined || SubjectHealth.runIncarnation(current) !== incarnation) {\n observation = { ...observation, outcome: \"discarded\", report: undefined, reason: \"owner-changed\" }\n }\n consecutiveProbeFailures = observation.outcome === \"ok\" ? 0 : consecutiveProbeFailures + 1\n const semanticProgress = observation.outcome === \"ok\" && observation.report?.activity === \"working\"\n const baseHealth = classifyState({\n summary,\n beatsWithoutProgress,\n stallBeats,\n roundBound: options.roundBound,\n semanticProgress\n }, attempts)\n observation = { ...observation, baseHealth }\n const status = SubjectHealth.rollup({\n subjectId,\n state: summary.status,\n incarnation,\n waitingReason: summary.waitingReason,\n baseHealth,\n latest: { observation, sequence: 0 },\n now: observation.observedAt,\n updatedAt: summary.updatedAt\n })\n const changed = lastPublished === undefined || lastPublished.incarnation !== observation.incarnation ||\n lastPublished.outcome !== observation.outcome || lastPublished.baseHealth !== observation.baseHealth ||\n lastPublished.evidenceSeq !== observation.evidenceSeq ||\n JSON.stringify(lastPublished.report) !== JSON.stringify(observation.report) ||\n lastPublished.reason !== observation.reason\n // Renew before expiry, but do not fill the journal with identical per-probe records.\n if (changed || observation.observedAt >= lastPublished!.observedAt + check.policy.ttlMs / 2) {\n yield* emit(SubjectHealth.statusObservedEventType, {\n ...observation,\n status: summary.status,\n health: status.health,\n activity: status.activity,\n attention: status.attention,\n freshness: status.freshness,\n waitingReason: summary.waitingReason ?? \"\"\n }, \"health observation\")\n lastPublished = observation\n }\n }\n // Checker output is observational and never participates in remedy authorization.\n const remedy = autoHeal.includes(health) ? remedyFor(health) : \"none\"\n const observed: Beat = { beat, health, sequence }\n if (options.recordBeats !== false) yield* record(observed, remedy)\n if (remedy === \"none\") {\n beats.push(observed)\n } else {\n const receipt = yield* heal({ runId: options.runId, health, remedy, beat }).pipe(\n // A remedy that fails must not abort the run of beats: the monitor\n // is an unattended loop, and one failing heal leaves every other\n // beat \u2014 and every other run it would have observed \u2014 unwatched.\n // The failure is logged with the beat it failed on, and the beat is\n // recorded without `healed` or `receipt`, exactly as a remedy the\n // monitor never got to apply reports.\n Effect.catch((failure) =>\n Effect.annotateLogs(\n Effect.logWarning(\"A monitor remedy failed and was skipped\"),\n { runId: options.runId, beat, health, remedy, cause: String(failure) }\n ).pipe(Effect.as(undefined))\n )\n )\n if (receipt === undefined) {\n // The stall evidence stands: nothing provably moved the run, so the\n // next beat compares against the same progress mark and the remedy\n // is retried with the beat's own idempotency key.\n beats.push(observed)\n } else {\n // A remedy that returned is not a remedy that was applied. `Terminal`\n // says the run had already settled, so nothing was healed; `Conflict`\n // says the key belonged to another mutation, so this monitor's remedy\n // never ran. Recording either as `healed` claimed something that did\n // not happen, and resetting the stall count on either erased the\n // evidence the next beat needs to notice the run is still stuck.\n const applied = receipt._tag === \"Accepted\" || receipt._tag === \"AlreadyApplied\"\n if (applied) {\n // Journaled here and not a line earlier: `healed` is a claim about\n // something that happened, and until the receipt came back it had not.\n yield* recordHealed(observed, remedy, receipt)\n // The heal moved the run, so the next beat compares against a run\n // that has changed. Counting the beats before it as stall evidence\n // again would heal a second time for the same stall.\n beatsWithoutProgress = 0\n }\n beats.push(applied ? { ...observed, healed: remedy, receipt } : { ...observed, receipt })\n // A remedy that answered `Terminal` observed the run settle. The loop\n // ends on the same evidence a terminal summary ends it on, rather than\n // beating against a run nothing can move.\n if (receipt._tag === \"Terminal\") break\n }\n }\n if (options.retainBeats !== undefined && beats.length > options.retainBeats) {\n beats.splice(0, beats.length - options.retainBeats)\n }\n if (terminal(summary)) {\n break\n }\n }\n return {\n runId: options.runId,\n beats,\n health: beats[beats.length - 1]?.health ?? \"unknown\"\n }\n })\n\n/** Observer and notification bookkeeping is never execution progress.\n * @category predicates\n * @since 1.0.0\n */\nexport const isBookkeepingEvent = (kind: string): boolean =>\n kind.startsWith(\"control.monitor.\") || kind.startsWith(\"control.status.\") ||\n kind.startsWith(\"flows.alerts.\") || kind.startsWith(\"flows.notifications.\") || kind.startsWith(\"flows.notification.\")\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Scoped, expiring gateway tokens.
3
+ *
4
+ * A gateway's configured bearer credential grants everything the gateway
5
+ * serves, for as long as it is configured. A scoped token is a grant the
6
+ * holder of that credential mints for someone else: a closed list of
7
+ * procedures, optionally confined to one run or one flow, and an expiry. It
8
+ * is signed with HMAC-SHA256 under the bearer credential, so a gateway
9
+ * verifies it with the key it already holds and stores nothing; rotating the
10
+ * bearer credential revokes every token minted under it.
11
+ *
12
+ * The wire form is `smt1.<claims>.<signature>`, both parts unpadded base64url,
13
+ * the claims a canonical JSON document. A token names procedures rather than
14
+ * scope words so that what it authorizes is readable off the token itself and
15
+ * does not change when a scope grows.
16
+ *
17
+ * Web Crypto does the signing, so the same module runs on Node, Bun, and in a
18
+ * browser that holds a token.
19
+ *
20
+ * @since 1.0.0
21
+ */
22
+ import { Effect, Schema } from "effect";
23
+ import { Unauthorized } from "./ControlError.ts";
24
+ import { type Authenticator, type Call } from "./ControlRpcs.ts";
25
+ import type { Principal } from "./ControlSchema.ts";
26
+ /**
27
+ * The version prefix every scoped token starts with.
28
+ *
29
+ * @category constants
30
+ * @since 1.0.0
31
+ */
32
+ export declare const prefix = "smt1";
33
+ /**
34
+ * The scopes `smthrs token mint --scope` accepts, and the procedures each
35
+ * one names.
36
+ *
37
+ * `read:runs` covers every read the control plane and the gateway serve.
38
+ * `write:runs` covers the mutations that start, steer, signal, cancel, and
39
+ * resume a run, and the gateway's `Run.Fork` and `Run.Verify`, which branch a
40
+ * run or drive the project's flow code over a copy of it. `approve:runs` covers the approval decisions on both mounts.
41
+ * A token holding `approve:runs` still needs the host's `ApprovalAuthority`
42
+ * to delegate to its principal, exactly as the bearer does.
43
+ *
44
+ * @category constants
45
+ * @since 1.0.0
46
+ */
47
+ export declare const scopes: {
48
+ readonly "read:runs": readonly ["List", "Watch", "Projection.Snapshot", "Projection.Subscribe"];
49
+ readonly "write:runs": readonly ["Plan", "Run", "Steer", "Signal", "Cancel", "Resume", "Run.Fork", "Run.Verify"];
50
+ readonly "approve:runs": readonly ["Approve", "Deny", "Approval.Submit"];
51
+ };
52
+ /**
53
+ * One scope name.
54
+ *
55
+ * @category models
56
+ * @since 1.0.0
57
+ */
58
+ export type Scope = keyof typeof scopes;
59
+ /**
60
+ * Every scope name, in the order {@link scopes} declares them.
61
+ *
62
+ * @category constants
63
+ * @since 1.0.0
64
+ */
65
+ export declare const scopeNames: ReadonlyArray<Scope>;
66
+ /**
67
+ * What a token grants, as signed.
68
+ *
69
+ * @category models
70
+ * @since 1.0.0
71
+ */
72
+ export declare const Claims: Schema.Struct<{
73
+ readonly v: Schema.Literal<1>;
74
+ /** A random identifier, so two tokens with the same grant differ. */
75
+ readonly id: Schema.String;
76
+ readonly procedures: Schema.$Array<Schema.String>;
77
+ readonly runId: Schema.optional<Schema.String>;
78
+ readonly flowId: Schema.optional<Schema.String>;
79
+ /** Minted at, milliseconds since the epoch. */
80
+ readonly iat: Schema.Number;
81
+ /** Expires at, milliseconds since the epoch; refused once `now >= exp`. */
82
+ readonly exp: Schema.Number;
83
+ }>;
84
+ /**
85
+ * What a token grants, as signed.
86
+ *
87
+ * @category models
88
+ * @since 1.0.0
89
+ */
90
+ export type Claims = typeof Claims.Type;
91
+ /**
92
+ * What `mint` needs.
93
+ *
94
+ * @category models
95
+ * @since 1.0.0
96
+ */
97
+ export interface MintOptions {
98
+ /** The signing key: the gateway's configured bearer credential. */
99
+ readonly key: string;
100
+ readonly scopes: ReadonlyArray<Scope>;
101
+ /** How long the token lives, in milliseconds; must be positive. */
102
+ readonly ttlMillis: number;
103
+ /** Confine every call to this run. */
104
+ readonly runId?: string | undefined;
105
+ /** Confine every call to this flow. */
106
+ readonly flowId?: string | undefined;
107
+ readonly now?: (() => number) | undefined;
108
+ }
109
+ /**
110
+ * A minted token beside the claims it carries.
111
+ *
112
+ * @category models
113
+ * @since 1.0.0
114
+ */
115
+ export interface Minted {
116
+ readonly token: string;
117
+ readonly claims: Claims;
118
+ }
119
+ /**
120
+ * Whether a bearer credential is spelled as a scoped token.
121
+ *
122
+ * @category predicates
123
+ * @since 1.0.0
124
+ */
125
+ export declare const isScopedToken: (credential: string) => boolean;
126
+ /**
127
+ * The procedures a list of scopes names, each once, in scope order.
128
+ *
129
+ * @category getters
130
+ * @since 1.0.0
131
+ */
132
+ export declare const procedures: (names: ReadonlyArray<Scope>) => ReadonlyArray<string>;
133
+ /**
134
+ * Mints one token under the key.
135
+ *
136
+ * An empty key, no scopes, or a non-positive lifetime is a caller defect: it
137
+ * cannot produce a token anyone should hold, so it dies rather than signs.
138
+ *
139
+ * @category constructors
140
+ * @since 1.0.0
141
+ */
142
+ export declare const mint: (options: MintOptions) => Effect.Effect<Minted>;
143
+ /**
144
+ * Verifies a token's signature and expiry under the key and returns its
145
+ * claims. Every malformation, a wrong key, and an expired token all fail with
146
+ * the same `Unauthorized`, so the token is not a parsing oracle.
147
+ *
148
+ * @param now the moment to judge `exp` against, in milliseconds
149
+ * @category verification
150
+ * @since 1.0.0
151
+ */
152
+ export declare const verify: (key: string, token: string, now: number) => Effect.Effect<Claims, Unauthorized>;
153
+ /**
154
+ * Whether verified claims authorize one call.
155
+ *
156
+ * The procedure must be named. A token confined to a run authorizes only a
157
+ * call that names that run, and one confined to a flow only a call that names
158
+ * that flow: a call that names neither, such as an unfiltered `List`, is
159
+ * refused rather than widened.
160
+ *
161
+ * @category verification
162
+ * @since 1.0.0
163
+ */
164
+ export declare const authorizes: (claims: Claims, call: Call) => boolean;
165
+ /**
166
+ * What the scoped-token authenticator needs.
167
+ *
168
+ * @category models
169
+ * @since 1.0.0
170
+ */
171
+ export interface AuthenticatorOptions {
172
+ /** The verifying key: the gateway's configured bearer credential. */
173
+ readonly key: string;
174
+ /** The principal every valid scoped token is stamped as. */
175
+ readonly principal: Omit<Principal, "stampedAt">;
176
+ readonly now?: (() => number) | undefined;
177
+ }
178
+ /**
179
+ * Authenticates a bearer credential spelled as a scoped token: the signature
180
+ * and expiry always, and the procedure plus run or flow confinement whenever
181
+ * the boundary knows which call it is guarding. An edge that authenticates an
182
+ * upgrade knows no call, so it admits the socket and leaves each frame to the
183
+ * in-band check.
184
+ *
185
+ * A credential that is not a scoped token is refused here; compose with
186
+ * `ControlRpcs.bearerAuthenticator` through `ControlRpcs.anyAuthenticator`
187
+ * to accept both.
188
+ *
189
+ * @category constructors
190
+ * @since 1.0.0
191
+ */
192
+ export declare const authenticator: (options: AuthenticatorOptions) => Authenticator;
193
+ //# sourceMappingURL=ScopedToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScopedToken.d.ts","sourceRoot":"","sources":["../../src/ScopedToken.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,MAAM,EAAoB,MAAM,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,KAAK,aAAa,EAAoB,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAClF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,MAAM,SAAS,CAAA;AAE5B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,MAAM;;;;CAIuC,CAAA;AAE1D;;;;;GAKG;AACH,MAAM,MAAM,KAAK,GAAG,MAAM,OAAO,MAAM,CAAA;AAEvC;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,KAAK,CAAuC,CAAA;AAEnF;;;;;GAKG;AACH,eAAO,MAAM,MAAM;;IAEjB,qEAAqE;;;;;IAKrE,+CAA+C;;IAE/C,2EAA2E;;EAE3E,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,mEAAmE;IACnE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,CAAA;IACrC,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,sCAAsC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,uCAAuC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAA;CAC1C;AAED;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAeD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,YAAY,MAAM,KAAG,OAA8C,CAAA;AAEjG;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GACrB,OAAO,aAAa,CAAC,KAAK,CAAC,KAC1B,aAAa,CAAC,MAAM,CAAwD,CAAA;AAE/E;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI,GAAI,SAAS,WAAW,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAwB5D,CAAA;AAIJ;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,GAAI,KAAK,MAAM,EAAE,OAAO,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CA4B/F,CAAA;AAeJ;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,EAAE,MAAM,IAAI,KAAG,OAGoB,CAAA;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IAChD,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAA;CAC1C;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,oBAAoB,KAAG,aAa5D,CAAA"}