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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/LICENSE +21 -0
  3. package/README.md +168 -2
  4. package/dist/cjs/ApprovalAuthority.d.ts +73 -0
  5. package/dist/cjs/ApprovalAuthority.d.ts.map +1 -0
  6. package/dist/cjs/ApprovalAuthority.js +62 -0
  7. package/dist/cjs/ApprovalAuthority.js.map +7 -0
  8. package/dist/cjs/Cancellation.d.ts +107 -0
  9. package/dist/cjs/Cancellation.d.ts.map +1 -0
  10. package/dist/cjs/Cancellation.js +72 -0
  11. package/dist/cjs/Cancellation.js.map +7 -0
  12. package/dist/cjs/Channels.d.ts +170 -0
  13. package/dist/cjs/Channels.d.ts.map +1 -0
  14. package/dist/cjs/Channels.js +278 -0
  15. package/dist/cjs/Channels.js.map +7 -0
  16. package/dist/cjs/Control.d.ts +202 -0
  17. package/dist/cjs/Control.d.ts.map +1 -0
  18. package/dist/cjs/Control.js +47 -0
  19. package/dist/cjs/Control.js.map +7 -0
  20. package/dist/cjs/ControlClient.d.ts +52 -0
  21. package/dist/cjs/ControlClient.d.ts.map +1 -0
  22. package/dist/cjs/ControlClient.js +191 -0
  23. package/dist/cjs/ControlClient.js.map +7 -0
  24. package/dist/cjs/ControlError.d.ts +318 -0
  25. package/dist/cjs/ControlError.d.ts.map +1 -0
  26. package/dist/cjs/ControlError.js +249 -0
  27. package/dist/cjs/ControlError.js.map +7 -0
  28. package/dist/cjs/ControlExecutor.d.ts +372 -0
  29. package/dist/cjs/ControlExecutor.d.ts.map +1 -0
  30. package/dist/cjs/ControlExecutor.js +123 -0
  31. package/dist/cjs/ControlExecutor.js.map +7 -0
  32. package/dist/cjs/ControlFacts.d.ts +454 -0
  33. package/dist/cjs/ControlFacts.d.ts.map +1 -0
  34. package/dist/cjs/ControlFacts.js +261 -0
  35. package/dist/cjs/ControlFacts.js.map +7 -0
  36. package/dist/cjs/ControlLive.d.ts +23 -0
  37. package/dist/cjs/ControlLive.d.ts.map +1 -0
  38. package/dist/cjs/ControlLive.js +1293 -0
  39. package/dist/cjs/ControlLive.js.map +7 -0
  40. package/dist/cjs/ControlRpcs.d.ts +1204 -0
  41. package/dist/cjs/ControlRpcs.d.ts.map +1 -0
  42. package/dist/cjs/ControlRpcs.js +247 -0
  43. package/dist/cjs/ControlRpcs.js.map +7 -0
  44. package/dist/cjs/ControlRuntime.d.ts +635 -0
  45. package/dist/cjs/ControlRuntime.d.ts.map +1 -0
  46. package/dist/cjs/ControlRuntime.js +744 -0
  47. package/dist/cjs/ControlRuntime.js.map +7 -0
  48. package/dist/cjs/ControlSchema.d.ts +2642 -0
  49. package/dist/cjs/ControlSchema.d.ts.map +1 -0
  50. package/dist/cjs/ControlSchema.js +634 -0
  51. package/dist/cjs/ControlSchema.js.map +7 -0
  52. package/dist/cjs/ControlServer.d.ts +51 -0
  53. package/dist/cjs/ControlServer.d.ts.map +1 -0
  54. package/dist/cjs/ControlServer.js +121 -0
  55. package/dist/cjs/ControlServer.js.map +7 -0
  56. package/dist/cjs/Credential.d.ts +136 -0
  57. package/dist/cjs/Credential.d.ts.map +1 -0
  58. package/dist/cjs/Credential.js +168 -0
  59. package/dist/cjs/Credential.js.map +7 -0
  60. package/dist/cjs/CredentialCipher.d.ts +90 -0
  61. package/dist/cjs/CredentialCipher.d.ts.map +1 -0
  62. package/dist/cjs/CredentialCipher.js +45 -0
  63. package/dist/cjs/CredentialCipher.js.map +7 -0
  64. package/dist/cjs/CredentialStore.d.ts +97 -0
  65. package/dist/cjs/CredentialStore.d.ts.map +1 -0
  66. package/dist/cjs/CredentialStore.js +81 -0
  67. package/dist/cjs/CredentialStore.js.map +7 -0
  68. package/dist/cjs/DispatchReader.d.ts +112 -0
  69. package/dist/cjs/DispatchReader.d.ts.map +1 -0
  70. package/dist/cjs/DispatchReader.js +45 -0
  71. package/dist/cjs/DispatchReader.js.map +7 -0
  72. package/dist/cjs/Health.d.ts +333 -0
  73. package/dist/cjs/Health.d.ts.map +1 -0
  74. package/dist/cjs/Health.js +311 -0
  75. package/dist/cjs/Health.js.map +7 -0
  76. package/dist/cjs/JevSessionChecker.d.ts +57 -0
  77. package/dist/cjs/JevSessionChecker.d.ts.map +1 -0
  78. package/dist/cjs/JevSessionChecker.js +113 -0
  79. package/dist/cjs/JevSessionChecker.js.map +7 -0
  80. package/dist/cjs/Lineage.d.ts +131 -0
  81. package/dist/cjs/Lineage.d.ts.map +1 -0
  82. package/dist/cjs/Lineage.js +81 -0
  83. package/dist/cjs/Lineage.js.map +7 -0
  84. package/dist/cjs/Migrations.d.ts +34 -0
  85. package/dist/cjs/Migrations.d.ts.map +1 -0
  86. package/dist/cjs/Migrations.js +60 -0
  87. package/dist/cjs/Migrations.js.map +7 -0
  88. package/dist/cjs/Monitor.d.ts +282 -0
  89. package/dist/cjs/Monitor.d.ts.map +1 -0
  90. package/dist/cjs/Monitor.js +283 -0
  91. package/dist/cjs/Monitor.js.map +7 -0
  92. package/dist/cjs/ScopedToken.d.ts +193 -0
  93. package/dist/cjs/ScopedToken.d.ts.map +1 -0
  94. package/dist/cjs/ScopedToken.js +135 -0
  95. package/dist/cjs/ScopedToken.js.map +7 -0
  96. package/dist/cjs/SqlControlRuntime.d.ts +161 -0
  97. package/dist/cjs/SqlControlRuntime.d.ts.map +1 -0
  98. package/dist/cjs/SqlControlRuntime.js +1522 -0
  99. package/dist/cjs/SqlControlRuntime.js.map +7 -0
  100. package/dist/cjs/SqlCredentialStore.d.ts +43 -0
  101. package/dist/cjs/SqlCredentialStore.d.ts.map +1 -0
  102. package/dist/cjs/SqlCredentialStore.js +113 -0
  103. package/dist/cjs/SqlCredentialStore.js.map +7 -0
  104. package/dist/cjs/Steering.d.ts +69 -0
  105. package/dist/cjs/Steering.d.ts.map +1 -0
  106. package/dist/cjs/Steering.js +49 -0
  107. package/dist/cjs/Steering.js.map +7 -0
  108. package/dist/cjs/SystemFlows.d.ts +223 -0
  109. package/dist/cjs/SystemFlows.d.ts.map +1 -0
  110. package/dist/cjs/SystemFlows.js +195 -0
  111. package/dist/cjs/SystemFlows.js.map +7 -0
  112. package/dist/cjs/WebCryptoCipher.d.ts +49 -0
  113. package/dist/cjs/WebCryptoCipher.d.ts.map +1 -0
  114. package/dist/cjs/WebCryptoCipher.js +129 -0
  115. package/dist/cjs/WebCryptoCipher.js.map +7 -0
  116. package/dist/cjs/WebhookChannel.d.ts +113 -0
  117. package/dist/cjs/WebhookChannel.d.ts.map +1 -0
  118. package/dist/cjs/WebhookChannel.js +98 -0
  119. package/dist/cjs/WebhookChannel.js.map +7 -0
  120. package/dist/cjs/index.d.ts +160 -0
  121. package/dist/cjs/index.d.ts.map +1 -0
  122. package/dist/cjs/index.js +91 -0
  123. package/dist/cjs/index.js.map +7 -0
  124. package/dist/cjs/internal/MutationBoundary.d.ts +27 -0
  125. package/dist/cjs/internal/MutationBoundary.d.ts.map +1 -0
  126. package/dist/cjs/internal/MutationBoundary.js +50 -0
  127. package/dist/cjs/internal/MutationBoundary.js.map +7 -0
  128. package/dist/cjs/internal/activeFibers.d.ts +12 -0
  129. package/dist/cjs/internal/activeFibers.d.ts.map +1 -0
  130. package/dist/cjs/internal/activeFibers.js +30 -0
  131. package/dist/cjs/internal/activeFibers.js.map +7 -0
  132. package/dist/cjs/internal/issues.d.ts +28 -0
  133. package/dist/cjs/internal/issues.d.ts.map +1 -0
  134. package/dist/cjs/internal/issues.js +34 -0
  135. package/dist/cjs/internal/issues.js.map +7 -0
  136. package/dist/cjs/internal/planning.d.ts +347 -0
  137. package/dist/cjs/internal/planning.d.ts.map +1 -0
  138. package/dist/cjs/internal/planning.js +137 -0
  139. package/dist/cjs/internal/planning.js.map +7 -0
  140. package/dist/cjs/internal/sqlSchemaErrors.d.ts +18 -0
  141. package/dist/cjs/internal/sqlSchemaErrors.d.ts.map +1 -0
  142. package/dist/cjs/internal/sqlSchemaErrors.js +40 -0
  143. package/dist/cjs/internal/sqlSchemaErrors.js.map +7 -0
  144. package/dist/cjs/migrations/0001_control_tables.d.ts +18 -0
  145. package/dist/cjs/migrations/0001_control_tables.d.ts.map +1 -0
  146. package/dist/cjs/migrations/0001_control_tables.js +115 -0
  147. package/dist/cjs/migrations/0001_control_tables.js.map +7 -0
  148. package/dist/cjs/migrations/0002_run_keys.d.ts +15 -0
  149. package/dist/cjs/migrations/0002_run_keys.d.ts.map +1 -0
  150. package/dist/cjs/migrations/0002_run_keys.js +44 -0
  151. package/dist/cjs/migrations/0002_run_keys.js.map +7 -0
  152. package/dist/cjs/migrations/0003_signal_commands.d.ts +13 -0
  153. package/dist/cjs/migrations/0003_signal_commands.d.ts.map +1 -0
  154. package/dist/cjs/migrations/0003_signal_commands.js +50 -0
  155. package/dist/cjs/migrations/0003_signal_commands.js.map +7 -0
  156. package/dist/cjs/migrations/0004_approval_decisions.d.ts +13 -0
  157. package/dist/cjs/migrations/0004_approval_decisions.d.ts.map +1 -0
  158. package/dist/cjs/migrations/0004_approval_decisions.js +45 -0
  159. package/dist/cjs/migrations/0004_approval_decisions.js.map +7 -0
  160. package/dist/cjs/migrations/0005_signal_principals.d.ts +18 -0
  161. package/dist/cjs/migrations/0005_signal_principals.d.ts.map +1 -0
  162. package/dist/cjs/migrations/0005_signal_principals.js +45 -0
  163. package/dist/cjs/migrations/0005_signal_principals.js.map +7 -0
  164. package/dist/cjs/migrations/0006_run_principals.d.ts +18 -0
  165. package/dist/cjs/migrations/0006_run_principals.d.ts.map +1 -0
  166. package/dist/cjs/migrations/0006_run_principals.js +49 -0
  167. package/dist/cjs/migrations/0006_run_principals.js.map +7 -0
  168. package/dist/cjs/migrations/0007_resume_consent.d.ts +18 -0
  169. package/dist/cjs/migrations/0007_resume_consent.d.ts.map +1 -0
  170. package/dist/cjs/migrations/0007_resume_consent.js +46 -0
  171. package/dist/cjs/migrations/0007_resume_consent.js.map +7 -0
  172. package/dist/cjs/package.json +1 -0
  173. package/dist/cjs/test/TestControl.d.ts +19 -0
  174. package/dist/cjs/test/TestControl.d.ts.map +1 -0
  175. package/dist/cjs/test/TestControl.js +62 -0
  176. package/dist/cjs/test/TestControl.js.map +7 -0
  177. package/dist/esm/ApprovalAuthority.d.ts +73 -0
  178. package/dist/esm/ApprovalAuthority.d.ts.map +1 -0
  179. package/dist/esm/ApprovalAuthority.js +72 -0
  180. package/dist/esm/ApprovalAuthority.js.map +1 -0
  181. package/dist/esm/Cancellation.d.ts +107 -0
  182. package/dist/esm/Cancellation.d.ts.map +1 -0
  183. package/dist/esm/Cancellation.js +116 -0
  184. package/dist/esm/Cancellation.js.map +1 -0
  185. package/dist/esm/Channels.d.ts +170 -0
  186. package/dist/esm/Channels.d.ts.map +1 -0
  187. package/dist/esm/Channels.js +312 -0
  188. package/dist/esm/Channels.js.map +1 -0
  189. package/dist/esm/Control.d.ts +202 -0
  190. package/dist/esm/Control.d.ts.map +1 -0
  191. package/dist/esm/Control.js +42 -0
  192. package/dist/esm/Control.js.map +1 -0
  193. package/dist/esm/ControlClient.d.ts +52 -0
  194. package/dist/esm/ControlClient.d.ts.map +1 -0
  195. package/dist/esm/ControlClient.js +217 -0
  196. package/dist/esm/ControlClient.js.map +1 -0
  197. package/dist/esm/ControlError.d.ts +318 -0
  198. package/dist/esm/ControlError.d.ts.map +1 -0
  199. package/dist/esm/ControlError.js +359 -0
  200. package/dist/esm/ControlError.js.map +1 -0
  201. package/dist/esm/ControlExecutor.d.ts +372 -0
  202. package/dist/esm/ControlExecutor.d.ts.map +1 -0
  203. package/dist/esm/ControlExecutor.js +212 -0
  204. package/dist/esm/ControlExecutor.js.map +1 -0
  205. package/dist/esm/ControlFacts.d.ts +454 -0
  206. package/dist/esm/ControlFacts.d.ts.map +1 -0
  207. package/dist/esm/ControlFacts.js +324 -0
  208. package/dist/esm/ControlFacts.js.map +1 -0
  209. package/dist/esm/ControlLive.d.ts +23 -0
  210. package/dist/esm/ControlLive.d.ts.map +1 -0
  211. package/dist/esm/ControlLive.js +1589 -0
  212. package/dist/esm/ControlLive.js.map +1 -0
  213. package/dist/esm/ControlRpcs.d.ts +1204 -0
  214. package/dist/esm/ControlRpcs.d.ts.map +1 -0
  215. package/dist/esm/ControlRpcs.js +299 -0
  216. package/dist/esm/ControlRpcs.js.map +1 -0
  217. package/dist/esm/ControlRuntime.d.ts +635 -0
  218. package/dist/esm/ControlRuntime.d.ts.map +1 -0
  219. package/dist/esm/ControlRuntime.js +808 -0
  220. package/dist/esm/ControlRuntime.js.map +1 -0
  221. package/dist/esm/ControlSchema.d.ts +2642 -0
  222. package/dist/esm/ControlSchema.d.ts.map +1 -0
  223. package/dist/esm/ControlSchema.js +1030 -0
  224. package/dist/esm/ControlSchema.js.map +1 -0
  225. package/dist/esm/ControlServer.d.ts +51 -0
  226. package/dist/esm/ControlServer.d.ts.map +1 -0
  227. package/dist/esm/ControlServer.js +145 -0
  228. package/dist/esm/ControlServer.js.map +1 -0
  229. package/dist/esm/Credential.d.ts +136 -0
  230. package/dist/esm/Credential.d.ts.map +1 -0
  231. package/dist/esm/Credential.js +190 -0
  232. package/dist/esm/Credential.js.map +1 -0
  233. package/dist/esm/CredentialCipher.d.ts +90 -0
  234. package/dist/esm/CredentialCipher.d.ts.map +1 -0
  235. package/dist/esm/CredentialCipher.js +56 -0
  236. package/dist/esm/CredentialCipher.js.map +1 -0
  237. package/dist/esm/CredentialStore.d.ts +97 -0
  238. package/dist/esm/CredentialStore.d.ts.map +1 -0
  239. package/dist/esm/CredentialStore.js +101 -0
  240. package/dist/esm/CredentialStore.js.map +1 -0
  241. package/dist/esm/DispatchReader.d.ts +112 -0
  242. package/dist/esm/DispatchReader.d.ts.map +1 -0
  243. package/dist/esm/DispatchReader.js +76 -0
  244. package/dist/esm/DispatchReader.js.map +1 -0
  245. package/dist/esm/Health.d.ts +333 -0
  246. package/dist/esm/Health.d.ts.map +1 -0
  247. package/dist/esm/Health.js +400 -0
  248. package/dist/esm/Health.js.map +1 -0
  249. package/dist/esm/JevSessionChecker.d.ts +57 -0
  250. package/dist/esm/JevSessionChecker.d.ts.map +1 -0
  251. package/dist/esm/JevSessionChecker.js +108 -0
  252. package/dist/esm/JevSessionChecker.js.map +1 -0
  253. package/dist/esm/Lineage.d.ts +131 -0
  254. package/dist/esm/Lineage.d.ts.map +1 -0
  255. package/dist/esm/Lineage.js +174 -0
  256. package/dist/esm/Lineage.js.map +1 -0
  257. package/dist/esm/Migrations.d.ts +34 -0
  258. package/dist/esm/Migrations.d.ts.map +1 -0
  259. package/dist/esm/Migrations.js +53 -0
  260. package/dist/esm/Migrations.js.map +1 -0
  261. package/dist/esm/Monitor.d.ts +282 -0
  262. package/dist/esm/Monitor.d.ts.map +1 -0
  263. package/dist/esm/Monitor.js +415 -0
  264. package/dist/esm/Monitor.js.map +1 -0
  265. package/dist/esm/ScopedToken.d.ts +193 -0
  266. package/dist/esm/ScopedToken.d.ts.map +1 -0
  267. package/dist/esm/ScopedToken.js +224 -0
  268. package/dist/esm/ScopedToken.js.map +1 -0
  269. package/dist/esm/SqlControlRuntime.d.ts +161 -0
  270. package/dist/esm/SqlControlRuntime.d.ts.map +1 -0
  271. package/dist/esm/SqlControlRuntime.js +1756 -0
  272. package/dist/esm/SqlControlRuntime.js.map +1 -0
  273. package/dist/esm/SqlCredentialStore.d.ts +43 -0
  274. package/dist/esm/SqlCredentialStore.d.ts.map +1 -0
  275. package/dist/esm/SqlCredentialStore.js +97 -0
  276. package/dist/esm/SqlCredentialStore.js.map +1 -0
  277. package/dist/esm/Steering.d.ts +69 -0
  278. package/dist/esm/Steering.d.ts.map +1 -0
  279. package/dist/esm/Steering.js +89 -0
  280. package/dist/esm/Steering.js.map +1 -0
  281. package/dist/esm/SystemFlows.d.ts +223 -0
  282. package/dist/esm/SystemFlows.d.ts.map +1 -0
  283. package/dist/esm/SystemFlows.js +198 -0
  284. package/dist/esm/SystemFlows.js.map +1 -0
  285. package/dist/esm/WebCryptoCipher.d.ts +49 -0
  286. package/dist/esm/WebCryptoCipher.d.ts.map +1 -0
  287. package/dist/esm/WebCryptoCipher.js +123 -0
  288. package/dist/esm/WebCryptoCipher.js.map +1 -0
  289. package/dist/esm/WebhookChannel.d.ts +113 -0
  290. package/dist/esm/WebhookChannel.d.ts.map +1 -0
  291. package/dist/esm/WebhookChannel.js +109 -0
  292. package/dist/esm/WebhookChannel.js.map +1 -0
  293. package/dist/esm/index.d.ts +160 -0
  294. package/dist/esm/index.d.ts.map +1 -0
  295. package/dist/esm/index.js +160 -0
  296. package/dist/esm/index.js.map +1 -0
  297. package/dist/esm/internal/MutationBoundary.d.ts +27 -0
  298. package/dist/esm/internal/MutationBoundary.d.ts.map +1 -0
  299. package/dist/esm/internal/MutationBoundary.js +40 -0
  300. package/dist/esm/internal/MutationBoundary.js.map +1 -0
  301. package/dist/esm/internal/activeFibers.d.ts +12 -0
  302. package/dist/esm/internal/activeFibers.d.ts.map +1 -0
  303. package/dist/esm/internal/activeFibers.js +17 -0
  304. package/dist/esm/internal/activeFibers.js.map +1 -0
  305. package/dist/esm/internal/issues.d.ts +28 -0
  306. package/dist/esm/internal/issues.d.ts.map +1 -0
  307. package/dist/esm/internal/issues.js +35 -0
  308. package/dist/esm/internal/issues.js.map +1 -0
  309. package/dist/esm/internal/planning.d.ts +347 -0
  310. package/dist/esm/internal/planning.d.ts.map +1 -0
  311. package/dist/esm/internal/planning.js +199 -0
  312. package/dist/esm/internal/planning.js.map +1 -0
  313. package/dist/esm/internal/sqlSchemaErrors.d.ts +18 -0
  314. package/dist/esm/internal/sqlSchemaErrors.d.ts.map +1 -0
  315. package/dist/esm/internal/sqlSchemaErrors.js +38 -0
  316. package/dist/esm/internal/sqlSchemaErrors.js.map +1 -0
  317. package/dist/esm/migrations/0001_control_tables.d.ts +18 -0
  318. package/dist/esm/migrations/0001_control_tables.d.ts.map +1 -0
  319. package/dist/esm/migrations/0001_control_tables.js +96 -0
  320. package/dist/esm/migrations/0001_control_tables.js.map +1 -0
  321. package/dist/esm/migrations/0002_run_keys.d.ts +15 -0
  322. package/dist/esm/migrations/0002_run_keys.d.ts.map +1 -0
  323. package/dist/esm/migrations/0002_run_keys.js +22 -0
  324. package/dist/esm/migrations/0002_run_keys.js.map +1 -0
  325. package/dist/esm/migrations/0003_signal_commands.d.ts +13 -0
  326. package/dist/esm/migrations/0003_signal_commands.d.ts.map +1 -0
  327. package/dist/esm/migrations/0003_signal_commands.js +26 -0
  328. package/dist/esm/migrations/0003_signal_commands.js.map +1 -0
  329. package/dist/esm/migrations/0004_approval_decisions.d.ts +13 -0
  330. package/dist/esm/migrations/0004_approval_decisions.d.ts.map +1 -0
  331. package/dist/esm/migrations/0004_approval_decisions.js +25 -0
  332. package/dist/esm/migrations/0004_approval_decisions.js.map +1 -0
  333. package/dist/esm/migrations/0005_signal_principals.d.ts +18 -0
  334. package/dist/esm/migrations/0005_signal_principals.d.ts.map +1 -0
  335. package/dist/esm/migrations/0005_signal_principals.js +27 -0
  336. package/dist/esm/migrations/0005_signal_principals.js.map +1 -0
  337. package/dist/esm/migrations/0006_run_principals.d.ts +18 -0
  338. package/dist/esm/migrations/0006_run_principals.d.ts.map +1 -0
  339. package/dist/esm/migrations/0006_run_principals.js +30 -0
  340. package/dist/esm/migrations/0006_run_principals.js.map +1 -0
  341. package/dist/esm/migrations/0007_resume_consent.d.ts +18 -0
  342. package/dist/esm/migrations/0007_resume_consent.d.ts.map +1 -0
  343. package/dist/esm/migrations/0007_resume_consent.js +27 -0
  344. package/dist/esm/migrations/0007_resume_consent.js.map +1 -0
  345. package/dist/esm/test/TestControl.d.ts +19 -0
  346. package/dist/esm/test/TestControl.d.ts.map +1 -0
  347. package/dist/esm/test/TestControl.js +30 -0
  348. package/dist/esm/test/TestControl.js.map +1 -0
  349. package/docs/README.md +189 -0
  350. package/docs/api.md +982 -0
  351. package/docs/concepts/authority.md +109 -0
  352. package/docs/concepts/cancellation.md +129 -0
  353. package/docs/concepts/lineage.md +132 -0
  354. package/docs/concepts/ownership.md +139 -0
  355. package/docs/concepts/projections.md +203 -0
  356. package/docs/concepts/receipts.md +128 -0
  357. package/docs/guides/approvals.md +284 -0
  358. package/docs/guides/cancel-and-resume.md +162 -0
  359. package/docs/guides/durable-storage.md +147 -0
  360. package/docs/guides/implement-an-executor.md +173 -0
  361. package/docs/guides/ingest-a-webhook.md +177 -0
  362. package/docs/guides/list-runs.md +160 -0
  363. package/docs/guides/monitor-runs.md +176 -0
  364. package/docs/guides/observe-health.md +147 -0
  365. package/docs/guides/postgres-tests.md +7 -0
  366. package/docs/guides/serve-over-rpc.md +220 -0
  367. package/docs/guides/signal-a-run.md +53 -0
  368. package/docs/guides/steer-a-run.md +138 -0
  369. package/docs/guides/store-credentials.md +164 -0
  370. package/docs/guides/testing.md +139 -0
  371. package/docs/guides/watch-a-run.md +154 -0
  372. package/docs/installation.md +106 -0
  373. package/docs/quickstart.md +163 -0
  374. package/docs/troubleshooting.md +208 -0
  375. package/package.json +405 -3
  376. package/src/ApprovalAuthority.ts +114 -0
  377. package/src/Cancellation.ts +172 -0
  378. package/src/Channels.ts +493 -0
  379. package/src/Control.ts +337 -0
  380. package/src/ControlClient.ts +319 -0
  381. package/src/ControlError.ts +378 -0
  382. package/src/ControlExecutor.ts +490 -0
  383. package/src/ControlFacts.ts +383 -0
  384. package/src/ControlLive.ts +2127 -0
  385. package/src/ControlRpcs.ts +443 -0
  386. package/src/ControlRuntime.ts +1601 -0
  387. package/src/ControlSchema.ts +1380 -0
  388. package/src/ControlServer.ts +182 -0
  389. package/src/Credential.ts +310 -0
  390. package/src/CredentialCipher.ts +110 -0
  391. package/src/CredentialStore.ts +152 -0
  392. package/src/DispatchReader.ts +122 -0
  393. package/src/Health.ts +591 -0
  394. package/src/JevSessionChecker.ts +127 -0
  395. package/src/Lineage.ts +203 -0
  396. package/src/Migrations.ts +56 -0
  397. package/src/Monitor.ts +600 -0
  398. package/src/ScopedToken.ts +306 -0
  399. package/src/SqlControlRuntime.ts +2478 -0
  400. package/src/SqlCredentialStore.ts +148 -0
  401. package/src/Steering.ts +96 -0
  402. package/src/SystemFlows.ts +225 -0
  403. package/src/WebCryptoCipher.ts +169 -0
  404. package/src/WebhookChannel.ts +166 -0
  405. package/src/index.ts +188 -0
  406. package/src/internal/MutationBoundary.ts +46 -0
  407. package/src/internal/activeFibers.ts +22 -0
  408. package/src/internal/issues.ts +40 -0
  409. package/src/internal/planning.ts +262 -0
  410. package/src/internal/sqlSchemaErrors.ts +37 -0
  411. package/src/migrations/0001_control_tables.ts +99 -0
  412. package/src/migrations/0002_run_keys.ts +23 -0
  413. package/src/migrations/0003_signal_commands.ts +27 -0
  414. package/src/migrations/0004_approval_decisions.ts +25 -0
  415. package/src/migrations/0005_signal_principals.ts +27 -0
  416. package/src/migrations/0006_run_principals.ts +31 -0
  417. package/src/migrations/0007_resume_consent.ts +28 -0
  418. package/src/test/TestControl.ts +47 -0
package/docs/api.md ADDED
@@ -0,0 +1,982 @@
1
+ ---
2
+ title: "API reference"
3
+ description: "Every public export of @smthrs/control, module by module: the Control service and its ten operations, the wire schemas, the typed failures, the three ports, the RPC boundary, the projections, and the credential surface."
4
+ ---
5
+
6
+ Every module is importable from the root entry point as a namespace and from
7
+ its own subpath:
8
+
9
+ ```ts
10
+ import { Control, ControlLive, Monitor } from "@smthrs/control"
11
+ import * as ControlSchema from "@smthrs/control/ControlSchema"
12
+ ```
13
+
14
+ `@smthrs/control/internal/*`, `@smthrs/control/migrations/*`, and every nested
15
+ `*/index` are blocked in the export map. `@smthrs/control/package.json` is
16
+ exported, and so is `@smthrs/control/test/TestControl`.
17
+
18
+ Signatures in this reference use the usual shorthand: `Effect<A, E, R>` for
19
+ `Effect.Effect`, `Stream<A, E>` for `Stream.Stream`, `Layer<A, E, R>` for
20
+ `Layer.Layer`, and `Redacted<A>` for `Redacted.Redacted`.
21
+
22
+ ## Example
23
+
24
+ ```ts
25
+ import { Control } from "@smthrs/control/Control"
26
+ import * as Effect from "effect/Effect"
27
+
28
+ const program = Effect.gen(function*() {
29
+ const control = yield* Control
30
+ const card = yield* control.plan({ flowId: "quickstart/Deploy", input: { build: "v1.4.0" } })
31
+ yield* control.approve(card.approval)
32
+ return yield* control.run({
33
+ _tag: "Plan",
34
+ planId: card.planId,
35
+ digest: card.digest,
36
+ envelope: card.envelope,
37
+ idempotencyKey: "deploy:v1.4.0"
38
+ })
39
+ })
40
+ ```
41
+
42
+ ## Control
43
+
44
+ The transport-independent control vtable. Every implementation in this package
45
+ and every client projects onto this one interface. Each operation includes
46
+ `TransportError` and `Unauthorized` in its error channel for remote transport
47
+ and authentication failures.
48
+
49
+ | Export | Kind | Signature |
50
+ | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
51
+ | `Control` | class | `Context.Service<Control, Service>` at key `/control/Control` |
52
+ | `Service` | interface | The ten operations in the following table |
53
+ | `make` | function | `(implementation: Service) => Service` |
54
+ | `layerNoop` | layer | `Layer<Control>`. Every operation fails `Unavailable`, naming the verb as `feature` and the constant `control-runtime-engine-integration` as `ticket`. |
55
+
56
+ ### Service
57
+
58
+ | Operation | Signature | Returns |
59
+ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
60
+ | `plan` | `(input: PlanInput) => Effect<PlanCard, FlowNotFound \| InvalidInput \| PersistenceError \| Unavailable \| TransportError \| Unauthorized>` | The reviewable card, whether or not this call created it. |
61
+ | `run` | `(input: RunInput) => Effect<Receipt, RunNotFound \| PlanNotFound \| PlanDenied \| PlanDigestMismatch \| EnvelopeMismatch \| ClaimLost \| InvalidInput \| LaunchFailed \| PersistenceError \| Unavailable \| TransportError \| Unauthorized>` | `Accepted`, `AlreadyApplied`, `Conflict`, or `Parked` for a plan; a resume answers as `resume` does. |
62
+ | `approve` | `(input: ApprovalInput) => Effect<Receipt, PlanDigestMismatch \| EnvelopeMismatch \| AlreadyResolved \| PlanNotFound \| RunNotFound \| InvalidInput \| PersistenceError \| Unavailable \| TransportError \| Unauthorized>` | `Accepted`, `AlreadyApplied`, `Conflict`, or `Terminal`. |
63
+ | `deny` | same as `approve` | same as `approve`. |
64
+ | `steer` | `(input: SteerInput) => Effect<Receipt, NotificationError \| RunNotFound \| InvalidInput \| PersistenceError \| Unavailable \| TransportError \| Unauthorized>` | `Accepted`, `AlreadyApplied`, `Conflict`, or `Terminal`. |
65
+ | `signal` | `(input: SignalInput) => Effect<Receipt, RunNotFound \| NoMatchingWait \| InvalidInput \| PersistenceError \| Unavailable \| TransportError \| Unauthorized>` | `Accepted`, `AlreadyApplied`, `Conflict`, or `Terminal`. |
66
+ | `cancel` | `(input: RunMutationInput) => Effect<Receipt, RunNotFound \| ClaimLost \| InvalidInput \| PersistenceError \| Unavailable \| TransportError \| Unauthorized>` | `Accepted` or `Terminal`. Never replays its recorded receipt. |
67
+ | `resume` | same as `cancel` | `Accepted`, `AlreadyApplied`, `Conflict`, or `Terminal`. |
68
+ | `list` | `(input: ListRequest) => Effect<ListResponse, ControlError>` | A bounded page of flows, runs, triggers, or trigger fires, or exact run-scoped native execution observations. |
69
+ | `watch` | `(filter: WatchFilter) => Stream<ControlEvent, ControlError>` | Committed journal entries, plus the deltas the plane derives. |
70
+
71
+ There is no `pause`. An operator park is written through
72
+ `ControlRuntime.writeStatus(runId, fence, "parked")`.
73
+
74
+ ### Inputs
75
+
76
+ | Type | Shape |
77
+ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
78
+ | `PlanInput` | `{ flowId: FlowId; input: unknown; idempotencyKey?: IdempotencyKey }`. `input` is `unknown` so the runtime can decode its own flow's schema before anything crosses a transport. |
79
+ | `RunInput` | `ControlSchema.RunInputSchema.Type & { principal?: Principal }`, so either `{ _tag: "Plan", planId, digest, envelope, idempotencyKey }` or `{ _tag: "Resume", runId, idempotencyKey }`. |
80
+ | `ApprovalInput` | `ApprovalPayload & { principal?: Principal }`: `{ target, scope, idempotencyKey }`. |
81
+ | `SteerInput` | `{ runId: RunId; message: SteerMessage; idempotencyKey: IdempotencyKey }`. |
82
+ | `SignalInput` | `{ runId: RunId; signal: SignalPayload; idempotencyKey: IdempotencyKey; principal?: Principal }`. |
83
+ | `RunMutationInput` | `{ runId: RunId; idempotencyKey: IdempotencyKey; reason?: string; principal?: Principal }`. `reason` is recorded on the journal entry the mutation writes. |
84
+
85
+ `ApprovalTarget` is re-exported from `ControlSchema` for convenience.
86
+
87
+ `principal` is present on the local contracts because a runtime stamps it. The
88
+ RPC schemas that exclude it do so on purpose: an authenticated server names the
89
+ identity, and a remote client cannot claim another.
90
+
91
+ ## ControlSchema
92
+
93
+ `Control.list({ _tag: "executions", runId, executionIds })` reads at most 200 exact native executions under an authorized control run. It returns `{ _tag: "executions", source, revision, items }`. Each snapshot is `Observed` with a native observation, `Missing`, or `Unavailable`. Unrelated IDs and incomplete ancestry never expose another run. Hosts without this observer return `Unavailable`; historical journal statuses remain historical. A batch and its ancestry checks share one engine transaction and source revision. `runs show` reads every known ID in bounded batches and applies only matching observed rows. Its `executionSnapshots` array retains each batch's source and revision; batches may observe different revisions while a run progresses. A changed source refuses the show until retried.
94
+
95
+ The serializable values both halves of the wire decode. Every entry has a
96
+ schema constant and a type of the same name unless noted.
97
+
98
+ ### Identifiers and identity
99
+
100
+ | Export | Shape |
101
+ | ----------------------------------- | ----------------------------------------------------------------------------------- |
102
+ | `RunId`, `FlowId`, `IdempotencyKey` | `Schema.String` aliases that name what a string is. |
103
+ | `Principal` | `{ id: string; kind: string; stampedAt: number }`. Stamped at the control boundary. |
104
+
105
+ ### Authority
106
+
107
+ | Export | Shape |
108
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
109
+ | `Envelope` | `{ capabilities: string[]; flows: string[]; budget: { tokens?: number; milliseconds?: number; usd?: number; onExceeded?: BudgetOnExceeded }; host?: string }`. |
110
+ | `GrantScope` | `"once" \| "run" \| "remembered"`. |
111
+ | `ApprovalTarget` | `{ _tag: "Plan", planId, digest, envelope }` or `{ _tag: "Node", runId, requestId, digest, envelope }`. |
112
+ | `ApprovalPayload` | `{ target: ApprovalTarget; scope: GrantScope; idempotencyKey: IdempotencyKey }`. |
113
+
114
+ ### Plans
115
+
116
+ | Export | Shape |
117
+ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
118
+ | `PlanNodeStatus` | `"cached" \| "run"`. The two outcomes a step key already decides: reuse the cached result, or run the step. A card reports nothing else. |
119
+ | `PlanNode` | The persisted plan node's fields plus `status`. `key` is the step key [`@smthrs/plan`](/api/plan) compiled, so a node named here and a node in the persisted plan are the same node. |
120
+ | `PlanCard` | `{ planId, flowId, digest, inputSummary, warnings?: DiscoveryWarning[], envelope, deployClass, executionDigest?, plan?, nodes, graph?, approval }`. `approval` is the complete payload a reviewer resubmits unchanged. |
121
+ | `PlanEdgeReason` | `"value" \| "continuation" \| "failure" \| "conflict" \| "lane-merge"`. Why one node waits for another, in the vocabulary of whichever graph builder the host planned with. |
122
+ | `PlanEdge` | `{ from, to, reason: PlanEdgeReason }`. One labelled edge of the graph the plan was built from. |
123
+ | `PlanGraphNode` | `{ id, declaredAt?: { path, line } }`. Where one node was declared, repo-relative under `@smthrs/journal`'s own rule, which refuses an absolute path. A host that cannot make a path relative to its root omits it. |
124
+ | `PlanGraph` | `{ edges: PlanEdge[], nodes?: PlanGraphNode[], sourceRevision?: string }`. A `PlanNode` carries `dependsOn`, one unlabelled edge set that cannot tell a value dependency from a recovery arm or from an ordering edge a write conflict added; a host that graphs a flow reports the reasons here instead, and the declaration sites the key material deliberately does not carry. `sourceRevision` is the immutable name of the tree those sites were read out of: a jj working-copy commit id, or a git commit for a tree that still matches one, so a reader can ask for the file AT that revision instead of whatever is on disk now. A host that cannot name one omits it. |
125
+
126
+ `graph` sits deliberately OUTSIDE the digest an approval binds to. The edges
127
+ and the declaration sites describe the plan a reader draws, and nothing in
128
+ them changes what will run,
129
+ so a host that starts reporting them re-plans to the digest it planned to
130
+ before and every parked approval still validates. A host that graphs nothing
131
+ omits the field, and a card stored before the field existed still decodes.
132
+
133
+ `warnings` carries discovery diagnostics for the selected flow, including source
134
+ locations for conservative authority fallbacks. Like `graph`, it is outside the
135
+ approval digest and does not change the approval payload. Generic hosts and
136
+ cards stored before the field existed may omit it.
137
+
138
+ `executionDigest` binds a discovery-based host's measured source and metadata
139
+ to the approved card digest. It is optional for generic control-plane hosts,
140
+ but `AgentSession` requires it for prompt execution and checks it again at
141
+ launch and on every drive or resume. Changing prompt bytes, model, parameters,
142
+ or other discovered metadata requires a new plan and approval.
143
+
144
+ ### Runs
145
+
146
+ | Export | Shape |
147
+ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
148
+ | `RunStatus` | `"accepted" \| "running" \| "parked" \| "waiting-approval" \| "cancelled" \| "completed" \| "failed"`. |
149
+ | `RunOrigin` | `Lineage.Origin`, re-exported so a serializable projection needs one import. |
150
+ | `CancelSource` | `"control" \| "engine" \| "cascade"`. |
151
+ | `Cancellation` | `{ requestedAt; source; principal?; reason?; cascadedFrom? }`. See [cancellation attribution](./concepts/cancellation.md). |
152
+ | `RunSummary` | The projection every listing returns. Required: `runId`, `flowId`, `status`, `createdAt`, `updatedAt`. Optional: `planId`, `planDigest`, `ownerId`, `parentRunId`, `lineageId`, `roundOrdinal`, `origin`, `waitingReason`, `steering`, `pendingResume`, `parkedBy`, `cancellation`. |
153
+
154
+ `waitingReason` is the run row's own column, written by the engine and only
155
+ read here. The CLI's `runs list` and `runs show` also render `executor` in that
156
+ position for a run that has sat at `accepted` with no owner past the launch
157
+ handoff window; that value is computed at render time and never stored, so a
158
+ reader going through the RPC, the gateway, or a plugin sees the field absent on
159
+ the same run.
160
+
161
+ ### Steering
162
+
163
+ | Export | Shape |
164
+ | --------------- | ------------------------------------------------------------------------------------------------------------------- |
165
+ | `MessageSteer` | The envelope plus `kind?: "Message"` and `body: string`. |
166
+ | `SeatSteer` | The envelope plus the notification package's seat payload fields. |
167
+ | `ThinkingSteer` | The envelope plus its thinking payload fields. |
168
+ | `ToolsSteer` | The envelope plus its tools payload fields. |
169
+ | `SteerMessage` | The union of those four. The shared envelope is `{ messageId, runId, principal, createdAt }`. |
170
+ | `steerItem` | `(message: SteerMessage) => SteerPayload`. Strips the control envelope and returns the item the harness reads back. |
171
+
172
+ ### Signals and events
173
+
174
+ | Export | Shape |
175
+ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
176
+ | `SignalPayload` | `{ name: string; payload: Json }`. |
177
+ | `WatchCursor` | `{ sequence: number; offset?: number }`. A present offset is the last consumed expansion index; absent means the source entry is fully consumed. |
178
+ | `WatchFilter` | `{ runId?: RunId; afterSequence?: number; afterCursor?: WatchCursor; follow?: boolean }`. Either cursor requires `runId`; do not combine them. Omitting `follow` keeps the live stream; `false` requests a finite snapshot. |
179
+ | `ControlEvent` | `{ cursor?: WatchCursor; sequence: number; kind: string; runId?: RunId; occurredAt: number; payload: Json }`. |
180
+
181
+ ### Listing
182
+
183
+ | Export | Shape |
184
+ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
185
+ | `defaultPageSize` | `100`. |
186
+ | `maxPageSize` | `500`. |
187
+ | `PageLimit` | An integer between 1 and `maxPageSize`. |
188
+ | `ListRequest` | `{ _tag: "flows", filters?, cursor?, limit? }`, `{ _tag: "runs", filters?: { runId?, flowId?, status?, principalId?, parentRunId?, lineageId? }, cursor?, limit? }`, `{ _tag: "triggers", filters?: { triggerId?, flowId?, enabled? }, cursor?, limit? }`, `{ _tag: "fires", filters?: { triggerId?, runId?, outcome? }, cursor?, limit? }`, or `{ _tag: "plans", filters?: { flowId?, decision? }, cursor?, limit? }`. |
189
+ | `ListResponse` | `{ _tag: "flows", items, warnings?, nextCursor? }`, `{ _tag: "runs", items: RunSummary[], nextCursor? }`, `{ _tag: "triggers", items: TriggerSummary[], nextCursor? }`, `{ _tag: "fires", items: FireSummary[], nextCursor? }`, or `{ _tag: "plans", items: PlanSummary[], nextCursor? }`. |
190
+ | `TriggerSummary` | `{ triggerId, flowId, input: Json, cron, timezone?, overlap: "skip" \| "buffer-one" \| "supersede", catchUp: "none" \| "one" \| "all", maxCatchUp?, enabled, revision, lastFiredAtMs?, pendingAtMs?, activeRunId?, nextOccurrencesMs: number[], schedulerLastTickMs? }`. One registered trigger. `schedulerLastTickMs` absent means no scheduler has ticked on this host. |
191
+ | `FireOutcome` | `"launched" \| "completed" \| "skipped" \| "buffered" \| "superseded" \| "failed"`. The same words the triggers package records in its fire ledger. |
192
+ | `FireSummary` | `{ triggerId, occurrenceAtMs, outcome: FireOutcome \| null, runId?, error?, waiting?: "approval" }`. One claimed occurrence; `outcome: null` is the window between the claim and its result. |
193
+ | `PlanDecision` | `"pending" \| "approved" \| "denied"`. |
194
+ | `PlanSummary` | `{ card: PlanCard, input: Json, decision: PlanDecision }`. One stored plan; `card.approval` is the payload `approve` or `deny` takes. |
195
+
196
+ `plans` lists stored plans oldest first, narrowed by `flowId` and `decision`.
197
+ A build target that declares `approval: "required"` leaves a pending
198
+ `system/target` plan whose `input` is `{ label, digest }`; list them with
199
+ `{ _tag: "plans", filters: { flowId: "system/target", decision: "pending" } }`
200
+ and submit `card.approval` to `approve` or `deny`. A page returns at most
201
+ `limit` plans and `nextCursor` while later plans may match. Plans are an
202
+ operator's to read: a reader restricted to its own runs lists none.
203
+
204
+ Run listings default to 100 items and accept limits from 1 through 500. Pass
205
+ `nextCursor` back unchanged with the same filters. Run cursors contain stable
206
+ ordering keys, not numeric offsets. Control-launched runs retain launch order;
207
+ engine-created runs follow in creation-time and run-id order. Removing a prior
208
+ row does not skip the next row. Listings are live, not a fixed snapshot.
209
+
210
+ A run page returns at most `limit` rows; `limit` does not bound the work done
211
+ to fill it. Without a `status` or `terminal` filter, the runtime selects at most
212
+ `limit` rows on durable summary fields before decoding summaries, reading their
213
+ ancestry, or observing execution, and executor observations enrich only those
214
+ rows. When the executor reports observed status, a `status` or `terminal`
215
+ filter applies to that observed status instead: the runtime selects on the
216
+ remaining filters, observes each selected row, and keeps walking the source
217
+ until the page is full or no runs remain. One page can therefore observe many
218
+ more runs than `limit`, so a host cannot treat the page size as a bound on
219
+ `readExecution` calls. Pending steering counts enrich only the returned rows.
220
+ An exact `runId` filter keeps the direct lookup and applies the remaining
221
+ filters to that observation.
222
+
223
+ `principalId` selects the runs whose `RunSummary.launchedBy.id` it names. A
224
+ run the control plane launched records its launcher; one the engine created
225
+ (a child, a fork, a later round) records none. Over RPC the server restricts a
226
+ reader that `ControlRpcs.RunVisibility` does not make an operator to the runs
227
+ its own principal launched: `List` sets `ListInput.reader` and `Watch` sets
228
+ `WatchInput.reader`, neither of which is on the wire. Such a reader lists and
229
+ watches only those runs, sees only the fires that started them and no
230
+ triggers, receives nothing from a plan partition, and gets `RunNotFound` for
231
+ any other run. A lost-tail watch failure reaches it without partition names.
232
+ `steer`, `signal`, `cancel` and `resume` take the same `reader`: over RPC the
233
+ server sets it, and such a reader mutates only those runs and gets
234
+ `RunNotFound` for any other.
235
+
236
+ The `triggers` and `fires` variants are answered through the `DispatchReader`
237
+ port. A host without one refuses both with `InvalidInput` whose issue is
238
+ `this host serves no trigger store`, never with an empty page.
239
+
240
+ ### Receipts
241
+
242
+ `Receipt` is the union every mutation answers:
243
+
244
+ | Member | Fields |
245
+ | ---------------- | ------------------------------------------------------------------- |
246
+ | `Accepted` | `{ receiptId: string; runId?: RunId; handedTo?: RunHost }` |
247
+ | `AlreadyApplied` | `{ receiptId: string; runId?: RunId }` |
248
+ | `Parked` | `{ receiptId: string; planId: string; status: "waiting-approval" }` |
249
+ | `Conflict` | `{ message: string }` |
250
+ | `Terminal` | `{ runId: RunId; status: RunStatus }` |
251
+
252
+ `RunHost` is `{ hostId: string; pid: number }`. An `Accepted` resume with
253
+ `handedTo` names the live host that parked the run and now drives it.
254
+
255
+ ### RPC request schemas
256
+
257
+ `PlanInputSchema`, `RunInputSchema`, `ApprovalInputSchema`,
258
+ `SteerInputSchema`, `SignalInputSchema`, `RunMutationInputSchema`,
259
+ `ReasonedMutationInputSchema`, and `CancelInputSchema` are the wire forms.
260
+ `PlanInputSchema` takes `Schema.Json` where the local contract takes `unknown`.
261
+ `ReasonedMutationInputSchema` adds `reason` and omits `principal`, because the
262
+ server stamps the identity it authenticated. `CancelInputSchema` is a named
263
+ alias of it, so cancellation's public contract stays explicit.
264
+
265
+ ## ControlError
266
+
267
+ Every stable failure the plane emits. Each class carries a constant `code` a
268
+ client may branch on.
269
+
270
+ | Class | `code` | Fields | Meaning |
271
+ | -------------------- | --------------------------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------- |
272
+ | `RunNotFound` | `run_not_found` | `runId` | No run with this id exists. |
273
+ | `PlanNotFound` | `plan_not_found` | `planId` | No plan with this id. Carries an operator-facing `message`. |
274
+ | `PlanDenied` | `plan_denied` | `planId` | The plan was denied. Carries an operator-facing `message`. |
275
+ | `FlowNotFound` | `flow_not_found` | `flowId` | No flow with this id is registered. |
276
+ | `PlanDigestMismatch` | `plan_digest_mismatch` | `planId`, `expected`, `actual` | The submitted plan does not hash to the declared digest. |
277
+ | `EnvelopeMismatch` | `envelope_mismatch` | `planId`, `expected`, `actual` | The plan's effect envelope differs from the declared one. |
278
+ | `ClaimLost` | `claim_lost` | `runId`, `reason?`, `parkedBy?` | The caller's claim lapsed or was fenced by a newer owner, or a live host parked the run. |
279
+ | `AlreadyResolved` | `already_resolved` | `requestId` | This request was already answered. |
280
+ | `InvalidInput` | `invalid_input` | `issue` | The request missed its schema or a stated precondition. |
281
+ | `Unauthorized` | `unauthorized` | `message` | No usable credential for this operation. |
282
+ | `Unavailable` | `unavailable` | `feature`, `ticket` | Not implemented in this deployment. |
283
+ | `TransportError` | `transport_error` | `message`, `retryable`, `cause?` | The request failed before a declared response arrived. |
284
+ | `PersistenceError` | `persistence_failed` | `operation`, `message`, `cause?` | A store operation failed. |
285
+ | `LaunchFailed` | `launch_failed` | `runId`, `message`, `cause?` | The executor refused or could not start the run. |
286
+ | `NoMatchingWait` | `no_matching_wait` | `runId`, `waitName` | A signal named a wait point the run does not have open. |
287
+ | `CredentialConflict` | `credential_conflict` | `id`, `expectedVersion`, `actualVersion` | A credential write lost a compare-and-set race. |
288
+ | `NotificationError` | `notification_closed`, `notification_full`, and existing notification codes | `message`, `notificationId?`, `path?` | Existing notification error class, now preserved by steering locally and through RPC. |
289
+
290
+ | Export | Kind | Meaning |
291
+ | -------------------- | ------ | -------------------------------------------------------------------------------------------------------------------- |
292
+ | `ControlErrorSchema` | schema | The single membership list. `ControlClient.isControlError` is `Schema.is` of it, so a class added here reaches both. |
293
+ | `ControlError` | type | `typeof ControlErrorSchema.Type`. |
294
+
295
+ `NoMatchingWait` spells its field `waitName` rather than `name`, because a
296
+ field named `name` on an `Error` subclass shadows `Error.prototype.name`, which
297
+ every renderer in the tree reads.
298
+
299
+ `TransportError.retryable` classifies the transport phase alone. Resend a
300
+ retryable mutation only when its idempotency key makes replay safe; a keyless
301
+ request can have reached the server even when its response was lost.
302
+
303
+ ### New public steering refusal channel
304
+
305
+ `Control.steer` now preserves the existing notifications package's
306
+ `NotificationError`, including new `notification_closed` and `notification_full`
307
+ codes. This is an extension to the public error union and the Steer RPC schema;
308
+ it does not introduce a new error class or change successful receipts.
309
+ Existing `notification_unavailable`, `notification_id_reused`, and
310
+ `notification_invalid` failures also now retain this tag, replacing their former
311
+ `PersistenceError` wrapper with operation `control.steer.notification`. Update
312
+ callers that matched that wrapper to handle `NotificationError` directly.
313
+
314
+ ```ts
315
+ import { Effect } from "effect"
316
+
317
+ const outcome = yield* control.steer(input).pipe(
318
+ Effect.map(receipt => ({ kind: "receipt" as const, receipt })),
319
+ Effect.catchTag("/notifications/NotificationError", error => Effect.gen(function*() {
320
+ if (error.code === "notification_closed") return { kind: "start-new-request" as const }
321
+ if (error.code === "notification_full") return { kind: "retry-after-drain" as const }
322
+ return yield* Effect.fail(error)
323
+ }))
324
+ )
325
+ ```
326
+
327
+ A closed receiver cannot accept a new message, even if its surrounding run is
328
+ still finishing. A full queue has retained nothing; after a boundary drains it,
329
+ the caller can retry the same notification and idempotency key. Control records
330
+ no accepted mutation for a capacity refusal. Duplicate accepted messages retain
331
+ the existing `AlreadyApplied` behavior. Journal I/O failures still become
332
+ `PersistenceError`; their diagnostic cause is retained separately from the fixed
333
+ operator-facing message.
334
+
335
+ Deploy updated clients/UI and server together. Older RPC decoders do not know
336
+ this error variant and may report a decode or transport failure instead of the
337
+ closed/full reason. The change is additive in the source API but is **not fully
338
+ wire-compatible with older exhaustive error decoders**. Do not retry an unknown
339
+ decode failure as if it proved the request was never admitted. Existing error
340
+ codes, successful response shapes, and notification events are unchanged.
341
+
342
+ ## ControlLive
343
+
344
+ | Export | Signature |
345
+ | ------- | ----------------------------------------------------------------------------------- |
346
+ | `layer` | `Layer<Control, never, ControlRuntime \| Journal \| NotificationQueue \| Registry>` |
347
+
348
+ Writes delegate to `ControlRuntime`; journal events are observational records
349
+ committed with the state they describe. `watch` only replays and follows
350
+ committed entries. `ControlExecutor` is read optionally, so a composition
351
+ without one records but starts nothing. `DispatchReader` is read optionally
352
+ too: without one, `list` still answers `flows` and `runs`, and refuses
353
+ `triggers` and `fires` with the typed issue `this host serves no trigger store`.
354
+
355
+ ## ControlRuntime
356
+
357
+ The persistence port `ControlLive` writes through, and its deterministic
358
+ in-memory implementation. A production adapter fences every owner-sensitive
359
+ write, implements resume as join-or-claim, releases claims on every waiting or
360
+ terminal transition, and translates conflicts into typed failures.
361
+
362
+ | Export | Kind | Signature |
363
+ | ----------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------- |
364
+ | `ControlRuntime` | class | `Context.Service<ControlRuntime, Service>` at key `/control/ControlRuntime` |
365
+ | `make` | function | `(implementation: Service) => Service` |
366
+ | `layerMemory` | layer | `(options?: MemoryOptions) => Layer<ControlRuntime, never, Crypto>` |
367
+ | `requireApproved` | function | `(token: ApprovalToken) => Effect<ApprovalToken & { _tag: "Approved" }, ApprovalPending \| ApprovalDenied>` |
368
+ | `ApprovalDecision` | schema | Tagged `Pending \| Approved \| Denied` decision |
369
+ | `ApprovalPending`, `ApprovalDenied` | error schemas | Fail-closed gate outcomes; include them in action/flow error schemas |
370
+
371
+ ### Service
372
+
373
+ | Group | Members |
374
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
375
+ | Plans | `plan(input: PlanInput) => Effect<PlanOutcome, FlowNotFound \| InvalidInput \| PersistenceError>`, `getPlan(planId)`, `listPlanIds` |
376
+ | Approvals | `authorizeApproval(request)`, `lookupApproval(target)`, `registerApproval(nodeTarget)`, `resolveApproval(token, decision, principal, scope?)`, `installBulkGrant(token, envelope, scope)`, `grants` |
377
+ | Runs | `launch(planId, digest, envelope) => Effect<LaunchResult, ...>`, `getRun(runId)`, `queryRuns({ filters?, cursor?, limit })`, `listRuns`, `listFlows` |
378
+ | Signal history | `deliverSignal(runId, signal)`, `deliveredSignals(runId)` |
379
+ | Resume delegation | `requestResume(runId) => Effect<number, ...>`, `pendingResumes`, `clearResume(runId, sequence)` |
380
+ | Ownership | `registerFiber(runId, fiber)`, `interrupt(runId, settle?)`, `resume(runId, options?)`, `claimFence(runId)`, `releasePending(runId, fence)`, `writeStatus(runId, fence, status)` |
381
+ | Identity | `stampPrincipal(submitted?)`, `lookupMutation(key, fingerprint)`, `recordMutation(key, fingerprint, receipt)` |
382
+
383
+ `queryRuns` accepts `RunQuery`: optional `flowId`, `status`, `terminal`,
384
+ `parentRunId`, `lineageId`, `since` (inclusive creation epoch ms), `until`
385
+ (exclusive), and `runIds` filters, an optional `order` (`newest` or `oldest`
386
+ creation time), an optional `RunCursor`, and a required integer `limit` from 1
387
+ through 500. `Control.list` resolves a `runs` request's `triggerId` filter to
388
+ `runIds` from the trigger's recorded fires. It returns `RunPage` with `items` and optional `nextCursor`.
389
+ `RunCursor` contains `source` (0 for control launches, 1 for engine runs),
390
+ `sequence`, `createdAt`, and `runId`. Adapters must select the page before
391
+ summary decoding and ancestry projection. SQL reads one extra ordering key to
392
+ determine continuation. `listRuns` remains the full inventory for recovery and
393
+ journal partition discovery; interactive listings use `queryRuns`.
394
+
395
+ Steering uses `NotificationQueue.enqueue` and `NotificationQueue.drain`.
396
+ `enqueueSteer` and `drainSteering` have been removed from the runtime port and
397
+ both adapters. The legacy signal-history reader and database migrations remain;
398
+ old steering rows are not drained or delivered by the runtime.
399
+
400
+ `interrupt` must be called without mutation locks. Its optional `settle` wrapper
401
+ runs only after the fiber's finalizers finish and wraps the fenced status
402
+ reconciliation. `ControlLive` uses it to commit the terminal event alongside
403
+ the status. Direct callers may omit it.
404
+
405
+ `resume` takes `{ scope?: "launched" \| "any" }`. `scope: "launched"`
406
+ restricts claims to the shared `control_runs` launch index. Both public
407
+ spellings, `Control.resume` and `Control.run` with a Resume input, and every
408
+ steer wake pass it. `scope: "any"`, also the default, is a trusted low-level
409
+ runtime capability for hosts that can drive the claimed execution.
410
+ An owned non-terminal run is joined without replacing its fence, including
411
+ `accepted`; a run released by `releasePending` can be claimed again.
412
+
413
+ Explicit resume journals `control.run.resume` and never calls
414
+ `ControlExecutor.resumeRun`. A caller or journal subscriber must drive the
415
+ execution; polling `pendingResumes` does not take up a resume the caller
416
+ claimed. A run a live host parked fails the claim with `ClaimLost` naming the
417
+ host in `parkedBy`. `Control.resume` then hands it to that host: it records
418
+ `requestResume(runId, { consent })`, where `consent` is the journal sequence of
419
+ its `control.run.resume`, and answers `Accepted` with `handedTo`.
420
+ `pendingResumes` reports that `consent`, and the parking host records the
421
+ per-release retry permission under it before it re-drives the run.
422
+ A suspended engine-created run stays unclaimed and receives an `Accepted`
423
+ receipt for the journal intent. A live peer's owned run fails with `ClaimLost`.
424
+ A running engine-created run outside the launch index also fails with
425
+ `ClaimLost` when its owner is dead; its persisted execution state is preserved.
426
+ Node-approval decisions use the durable resume delegation instead.
427
+
428
+ `registerApproval` is idempotent and returns the token with its current
429
+ tagged decision. `Pending` parks, `Approved` opens a gate, and `Denied` fails it.
430
+ Use `requireApproved` to enforce this distinction. A registration that disagrees
431
+ with the stored digest or envelope is refused exactly as `lookupApproval`
432
+ refuses it. Terminal decisions carry `decisionPrincipal` and `decidedAt`;
433
+ `Approved` also carries `scope`. The low-level `resolveApproval` defaults scope
434
+ to `once`; when installing a wider grant, pass that same scope explicitly.
435
+ `Control.approve` does this automatically. Resolution checks the owning
436
+ `ApprovalAuthority` again and may fail with `Unauthorized`; it does not install
437
+ a grant. `installBulkGrant` is a trusted storage port, not an authorization API.
438
+
439
+ For a new decision, authenticate the principal and call `authorizeApproval`
440
+ before target reads or receipt replay. Then call `lookupApproval`,
441
+ `resolveApproval` exactly once with an authority recheck, `installBulkGrant` only
442
+ on approval, and journal the decision. Commit the decision, grant, journal entry,
443
+ receipt, and any node resume delegation atomically. Resolution must not require
444
+ an installed grant or a flushed journal decision.
445
+
446
+ Migration 6004 preserves legacy rows. Unknown old terminal decisions are
447
+ refused with `PersistenceError`; pending rows remain pending. Preserve the old
448
+ database and start a new run/request instead of inferring approval from a grant.
449
+
450
+ `requestResume` returns the durable sequence `clearResume` checks, so a resume
451
+ requested while one is being taken up is not lost with it.
452
+
453
+ ### Models
454
+
455
+ | Type | Shape |
456
+ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
457
+ | `StoredPlan` | `{ card: PlanCard; decodedInput: unknown; decision: "pending" \| "approved" \| "denied" }` |
458
+ | `ApprovalToken` | `{ tokenId: string; target: ApprovalTarget } & ApprovalDecision`; `_tag: "Pending"`, or `_tag: "Approved"` with principal/time/scope, or `_tag: "Denied"` with principal/time |
459
+ | `BulkGrant` | `{ tokenId: string; envelope: Envelope; scope: GrantScope; installedAt: number }` |
460
+ | `LaunchResult` | `{ _tag: "Started"; receipt; run }` or `{ _tag: "Parked"; receipt }` |
461
+ | `PlanOutcome` | `{ card: PlanCard; created: boolean }`. `created` is what lets `plan` journal one creation per plan rather than one per retry. |
462
+ | `MutationRecord` | `{ fingerprint: string; receipt: Receipt }` |
463
+ | `PendingResume` | `{ runId: RunId; sequence: number; requestedAtMs: number }` |
464
+ | `MemoryFlow` | `{ flowId; description; deployClass; envelope; executionDigest?; decode?; plan? }`. The optional execution identity is included in the approved card; `decode` validates input and `plan` projects it into the keyed node graph, answering `{ plan, statuses?, graph? }`. |
465
+ | `MemoryOptions` | `{ flows?: MemoryFlow[]; now?: () => number; principal?: Omit<Principal, "stampedAt">; approvalAuthority?: ApprovalAuthority.Service }` |
466
+
467
+ `layerMemory` models the production fence and approval ordering seams but keeps
468
+ everything in a `Map`. Nothing it decides survives the process.
469
+
470
+ ## ApprovalAuthority
471
+
472
+ Host-owned approval policy, separate from authentication and granted workflow
473
+ capabilities. Import `@smthrs/control/ApprovalAuthority` or its root namespace.
474
+
475
+ - `Request`: `{ principal, target, decision: "approved" | "denied", scope }`.
476
+ - `Service.authorize(request)`: `Effect<void, Unauthorized | PersistenceError>`.
477
+ - `Delegation`: schema/type for `{ principal: { id, kind }, scopes, targets }`.
478
+ Exact scopes are `once`, `run`, `remembered`; target kinds are `Plan`, `Node`.
479
+ - `make(delegations)`: validates and snapshots up to 1,024 explicit delegations;
480
+ returns `Effect<Service, InvalidInput>`. Empty configuration denies everyone.
481
+ - `local`: default policy for the fixed `local/operator` identity only. Custom
482
+ identities, including bearer and agent identities, need explicit delegation.
483
+ A principal's `kind` is not itself a role grant. The memory adapter's own
484
+ default also delegates its `memory/test` identity; `local` never does.
485
+
486
+ `Control.approve` and `deny` check before reads and receipt replay. Both runtime
487
+ adapters check again at resolution. Denial requires a delegated target kind but
488
+ does not require a grant scope because it grants nothing. See the
489
+ [approval guide](./guides/approvals.md#who-may-decide) for host composition.
490
+
491
+ ## SqlControlRuntime
492
+
493
+ The durable `ControlRuntime` over a SQL database and the fenced run store from
494
+ [`@smthrs/run-store`](/api/run-store).
495
+
496
+ | Export | Kind | Signature |
497
+ | ---------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
498
+ | `DurableFlow` | type | `MemoryFlow`, so one catalog serves either runtime. |
499
+ | `Options` | interface | `{ flows?: ReadonlyArray<DurableFlow>; loadFlows?: () => Effect<ReadonlyArray<DurableFlow>, PersistenceError>; owner?: Ownership.OwnerId; isAlive?: Ownership.LivenessCheck; principal?: Omit<Principal, "stampedAt">; approvalAuthority?: ApprovalAuthority.Service }`; with `isAlive`, `resume` takes over a running run whose owner is gone once its lease expired |
500
+ | `migrate` | effect | `Effect<void, PersistenceError, SqlClient>`. Creates every control-plane table, idempotently. |
501
+ | `make` | function | `(options?: Options) => Effect<Service, PersistenceError, Crypto \| DurableWriter \| SqlClient \| RunStore>` |
502
+ | `layer` | layer | `(options?: Options) => Layer<ControlRuntime, PersistenceError, Crypto \| DurableWriter \| SqlClient \| RunStore>` |
503
+ | `layerWithStore` | layer | The same, with `RunStore.layer` provided. |
504
+
505
+ Dead-owner takeovers with `scope: "launched"` require a launch-index entry;
506
+ unindexed engine continuations remain unchanged.
507
+
508
+ `loadFlows` replaces the static `flows` or default system catalog. It runs afresh
509
+ for each `plan` and `listFlows` operation, and one plan uses one complete catalog
510
+ snapshot. Loader failures remain typed `PersistenceError`s. A host using a
511
+ refreshable registry can therefore plan from newly discovered or edited flows
512
+ without restarting the runtime. Stored plans and approvals retain the execution
513
+ identity they originally captured; refreshing the catalog does not rewrite them.
514
+
515
+ Omitting `owner` mints one synthetic identity for this runtime only, so
516
+ separately constructed runtimes cannot cross each other's fences. Hosts that
517
+ can report a real process identity should supply it.
518
+
519
+ The run lifecycle is not reimplemented here. `RunStore` owns it, and every
520
+ ownership move is a single SQL compare-and-swap. See
521
+ [Ownership, fences, and claims](./concepts/ownership.md) for the status
522
+ mapping.
523
+
524
+ ## ControlExecutor
525
+
526
+ The acceptance port from the control plane into a real run executor.
527
+
528
+ `ControlExecutor.makeReadOnly({ readExecution, readExecutions })` accepts optional
529
+ point and batch readers in one options object. Omit the object for a host without
530
+ engine observations. Mutation methods remain refused.
531
+
532
+ | Export | Kind | Signature |
533
+ | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
534
+ | `ControlExecutor` | class | `Context.Service<ControlExecutor, Service>` at key `/control/ControlExecutor` |
535
+ | `make` | function | `(implementation: Service) => Service` |
536
+ | `makeNoop` | function | `(overrides?: Partial<Service>) => Service`. Accepts every launch as `pending` and starts nothing. |
537
+ | `makeObserving` | function | `(service: Service) => Service`. The same executor with `launch` and `resumeRun` refused as defects, for a host composed to observe runs and drive none. |
538
+ | `layer` | layer | `(implementation: Service) => Layer<ControlExecutor>` |
539
+ | `layerNoop` | layer | `(overrides?: Partial<Service>) => Layer<ControlExecutor>` |
540
+
541
+ ### Service
542
+
543
+ | Method | Signature |
544
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
545
+ | `launch` | `(input: Launch) => Effect<Acceptance, LaunchFailed>` |
546
+ | `requestComplete` | Optional `(input: { runId: RunId; receiptId: string }) => Effect<Receipt, PersistenceError>`. Host close after the last native module completes. Since 1.0.0. |
547
+ | `requestCancel` | `(input: CancelRequest) => Effect<CancelRecord, PersistenceError>` |
548
+ | `deliverSignal` | `(input: Signal) => Effect<SignalDelivery, PersistenceError>` |
549
+ | `resumeRun` | `(input: ResumeRequest) => Effect<ResumeUptake, PersistenceError>` |
550
+ | `settleCancelledPark` | `(input: CancelRequest) => Effect<void, PersistenceError>` |
551
+
552
+ ### Models
553
+
554
+ | Type | Shape |
555
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
556
+ | `Launch` | `{ plan: StoredPlan; run: RunSummary }`. `run.runId` is the execution id the executor must start. |
557
+ | `Acceptance` | `"accepted"` (taken now) or `"pending"` (queued). |
558
+ | `CancelRequest`, `ResumeRequest` | `{ runId: RunId }` |
559
+ | `CancelTerminal` | `{ _tag: "Terminal"; status: "completed" \| "failed" \| "cancelled" }`. The engine's own status, which the plane cannot read itself. |
560
+ | `CancelRecord` | `"recorded" \| "already-requested" \| "unknown" \| CancelTerminal` |
561
+ | `ResumeUptake` | `"resuming" \| "unknown"` |
562
+ | `Signal` | `{ runId: RunId; signal: SignalPayload }` |
563
+ | `SignalDelivery` | `"delivered" \| "no-match" \| "unknown"` |
564
+
565
+ `settleCancelledPark` is called after the cancel mutation commits, never inside
566
+ it: driving a run re-enters the engine, whose writes would wait on the writer
567
+ the transaction holds.
568
+
569
+ ## DispatchReader
570
+
571
+ The read port from the control plane into a host's trigger store. `Control.list`
572
+ answers `{ _tag: "triggers" }` and `{ _tag: "fires" }` through it. The port
573
+ lives here rather than in `@smthrs/triggers` because that package depends on
574
+ this one (its scheduler launches runs through `Control`), so the adapter over a
575
+ real `TriggerStore` is composed by the host.
576
+
577
+ | Export | Kind | Signature |
578
+ | ---------------- | -------- | --------------------------------------------------------------------------- |
579
+ | `DispatchReader` | class | `Context.Service<DispatchReader, Service>` at key `/control/DispatchReader` |
580
+ | `make` | function | `(implementation: Service) => Service` |
581
+ | `makeNone` | function | `() => Service`. Both methods fail with `refuse()`. |
582
+ | `refuse` | function | `() => InvalidInput` with code `invalid_input` and issue `noStoreIssue`. |
583
+ | `noStoreIssue` | constant | `"this host serves no trigger store"`. |
584
+ | `layer` | layer | `(implementation: Service) => Layer<DispatchReader>` |
585
+ | `layerNone` | layer | `Layer<DispatchReader>` providing `makeNone()`. |
586
+
587
+ ### Service
588
+
589
+ | Method | Signature |
590
+ | ------- | ----------------------------------------------------------------------------------- |
591
+ | `list` | `(request: TriggersRequest) => Effect<ReadonlyArray<TriggerSummary>, ControlError>` |
592
+ | `fires` | `(request: FiresRequest) => Effect<ReadonlyArray<FireSummary>, ControlError>` |
593
+
594
+ Each method receives the whole listing request and answers every row it has,
595
+ newest fire first. A reader may narrow by `filters`; `Control.list` applies the
596
+ same filters again and pages the rows with `cursor` and `limit`, so a reader
597
+ that returns every row is still correct and both variants page exactly as
598
+ `flows` and `runs` do. `TriggersRequest` and `FiresRequest` are the two
599
+ `ListRequest` members by tag.
600
+
601
+ ## ControlRpcs
602
+
603
+ The schema-backed RPC projection of the service.
604
+
605
+ | Export | Kind | Meaning |
606
+ | --------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
607
+ | `ControlRpcs` | group | Ten procedures: `Plan`, `Run`, `Approve`, `Deny`, `Steer`, `Signal`, `Cancel`, `Resume`, `List`, and the streaming `Watch`. Carries the `ControlAuth` middleware. |
608
+ | `ControlPrincipal` | class | The authenticated principal, provided to every handler. Key `/control/ControlPrincipal`. |
609
+ | `ControlAuth` | class | The middleware boundary. Key `/control/ControlAuth`, error `Unauthorized`. |
610
+ | `Call` | interface | `{ rpc: string; payload: unknown }`: the frame a boundary is authenticating, absent at a transport edge. |
611
+ | `Authenticator` | interface | `{ authenticate: (headers: Record<string, string>, call?: Call) => Effect<Principal, Unauthorized> }` |
612
+ | `BearerAuthOptions` | interface | `{ token: string; principal: Omit<Principal, "stampedAt">; now?: () => number }` |
613
+ | `bearerCredential` | function | `(headers) => string \| undefined`. The bearer a request carries, or nothing. |
614
+ | `bearerAuthenticator` | function | `(options: BearerAuthOptions) => Authenticator`. Constant-time comparison; missing, malformed, empty, and incorrect credentials all fail closed identically. |
615
+ | `anyAuthenticator` | function | `(authenticators: ReadonlyArray<Authenticator>) => Authenticator`. The first to accept answers; none accepting fails with the last refusal. |
616
+ | `layerAuth` | layer | `(authenticator: Authenticator) => Layer<ControlAuth>` |
617
+ | `layerBearerAuth` | layer | `(options: BearerAuthOptions) => Layer<ControlAuth>` |
618
+ | `layerNoopAuth` | layer | `(principal?: Principal) => Layer<ControlAuth>`. Authenticates nothing. |
619
+ | `ControlDefect` | schema | The defect schema of every procedure. Encodes any non-string defect as `{ name: "Error", message: defectMessage }`; decodes like `Schema.Defect()`. |
620
+ | `defectMessage` | constant | `"Something went wrong on our side. Not your fault."` |
621
+
622
+ `List` and `Watch` declare the whole `ControlError` union rather than restating
623
+ its members.
624
+
625
+ A handler defect, an untyped failure no procedure declares, reaches a client as
626
+ a `Die` whose defect is `{ "name": "Error", "message": "Something went wrong on
627
+ our side. Not your fault." }`. Its raw message and stack never cross the wire;
628
+ the server logs them through `ControlServer.logDefect`. A string defect passes
629
+ unchanged, so a payload that fails to decode still answers with the request
630
+ decoder's own sentence. The encoded shape is the one `Schema.Defect()` decodes,
631
+ so older clients and servers read each other's defects.
632
+
633
+ ## ScopedToken
634
+
635
+ Scoped, expiring tokens minted under a gateway's bearer credential: an
636
+ HMAC-SHA256 grant of named procedures, optionally confined to one run or flow,
637
+ that the gateway verifies with the credential it already holds. The wire form
638
+ is `smt1.<claims>.<signature>`. `smthrs token mint` is the command over it.
639
+
640
+ | Export | Kind | Meaning |
641
+ | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
642
+ | `scopes`, `Scope` | constant | `read:runs`, `write:runs`, and `approve:runs`, each naming the procedures it grants across `ControlRpcs` and the gateway's `GatewayRpcs`. |
643
+ | `scopeNames` | constant | The scope names in declaration order. |
644
+ | `Claims` | schema | `{ v: 1; id; procedures; runId?; flowId?; iat; exp }`, milliseconds since the epoch. |
645
+ | `mint` | function | `(options: MintOptions) => Effect<Minted>`. Dies on an empty key, no scopes, or a non-positive lifetime. |
646
+ | `verify` | function | `(key, token, now) => Effect<Claims, Unauthorized>`. Signature and expiry; every malformation is the same refusal. |
647
+ | `authorizes` | function | `(claims, call) => boolean`. The procedure must be named; a confined token authorizes only calls naming its run or flow. |
648
+ | `authenticator` | function | `(options: AuthenticatorOptions) => Authenticator`. Signature and expiry always; procedure and confinement when the boundary knows the call. |
649
+ | `isScopedToken` | function | `(credential) => boolean`. |
650
+ | `procedures` | function | `(scopes) => ReadonlyArray<string>`, each once. |
651
+ | `prefix` | constant | `"smt1"`. |
652
+
653
+ ## ControlServer
654
+
655
+ | Export | Meaning |
656
+ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
657
+ | `layer` | The handlers, delegating to `Control`. Every mutation that records who asked reads `ControlPrincipal` and stamps it rather than forwarding what the client sent. |
658
+ | `layerHttp` | Mounts both protocols on the ambient `HttpRouter`: unary procedures over `POST /rpc`, and `watch` over `WebSocket /rpc/ws`. |
659
+ | `logDefect` | `(cause: Cause<unknown>) => Effect<void>`. Logs a handler's raw defect at error level and ignores typed failures. Tap it around a handler with `Effect.tapCause` or `Stream.tapCause`. |
660
+
661
+ ## ControlClient
662
+
663
+ | Export | Kind | Signature |
664
+ | ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------- |
665
+ | `ClientConfig` | interface | `{ url: string; credential?: string }`. `credential` is attached as a bearer token on every HTTP RPC request. |
666
+ | `layer` | layer | `(config: ClientConfig) => Layer<Control, ...>` |
667
+ | `isControlError` | refinement | `(value: unknown) => value is ControlError`, derived from `ControlErrorSchema`. |
668
+
669
+ Unary procedures use HTTP at `url`; `watch` uses the abstract WebSocket the
670
+ platform layer supplies. Declared control failures cross the wire as
671
+ themselves; everything else becomes a `TransportError` whose `retryable` flag
672
+ classifies the transport phase.
673
+
674
+ In rc.0, `ClientConfig.credential` authenticates HTTP calls only; it does not
675
+ authenticate the `watch` WebSocket upgrade. Authenticated remote watch requires
676
+ a socket implementation that sends the Authorization header, or a trusted
677
+ proxy that authenticates the caller and supplies it. The default client fails
678
+ closed against a credentialed gateway. Tokens in URL query strings are not
679
+ supported.
680
+
681
+ ## Lineage
682
+
683
+ Run ancestry as the control plane reads it. See
684
+ [Run lineage](./concepts/lineage.md).
685
+
686
+ | Export | Kind | Signature |
687
+ | ---------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------- |
688
+ | `Origin` | schema and type | `"child" \| "fork" \| "continuation"` |
689
+ | `Ancestry` | interface | `{ parentRunId?: string; roundOrdinal?: number; forked?: boolean }` |
690
+ | `runDecisionEventType` | constant | `"flows.engine.run-decision"` |
691
+ | `forkCreatedEventType` | constant | `"flows.time-travel.fork-created"` |
692
+ | `lineageEventType` | constant | `"control.run.lineage"` |
693
+ | `originOf` | function | `(ancestry: Ancestry) => Origin \| undefined`. A fork wins over a plain child, because a fork records a parent too. |
694
+ | `derive` | function | `(event: ControlEvent) => ControlEvent \| undefined`. The ancestry delta one entry discloses, if it discloses one. |
695
+ | `expand` | function | `(event: ControlEvent) => ReadonlyArray<ControlEvent>`. The entry plus any delta. |
696
+
697
+ ## Cancellation
698
+
699
+ Cancellation attribution as the plane reads it back. See
700
+ [Cancellation attribution](./concepts/cancellation.md).
701
+
702
+ | Export | Kind | Signature |
703
+ | ---------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- |
704
+ | `requestedEventType` | constant | `"control.run.cancel-requested"` |
705
+ | `interruptedEventType` | constant | `"flows.engine.interrupted"` |
706
+ | `Request` | interface | `{ requestedAt: number; principal?: Principal; reason?: string }` |
707
+ | `Evidence` | interface | `{ runId: string; parentRunId?: string; cancelRequestedAt?: number; cancelledAt?: number }` |
708
+ | `Input` | interface | `{ runs: ReadonlyArray<Evidence>; requests: ReadonlyMap<string, Request> }` |
709
+ | `attribute` | function | `(input: Input) => ReadonlyMap<string, Cancellation>`. Pure and scope-independent: it reads what it is handed and never queries. |
710
+
711
+ ## Steering
712
+
713
+ The steer lifecycle as the plane reads it back. See
714
+ [Steer a running agent](./guides/steer-a-run.md).
715
+
716
+ | Export | Kind | Signature |
717
+ | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
718
+ | `enqueuedEventType` | constant | `"control.steer.enqueued"`, written by `Control.steer`. |
719
+ | `promotedEventType` | constant | `"flows/notifications/Promoted"`, written by the queue. |
720
+ | `deliveredEventType` | constant | `"control.steer.delivered"`, derived. |
721
+ | `derive` | function | `(event: ControlEvent) => ReadonlyArray<ControlEvent>`. One delta per message a promotion named. A promotion that named nothing derives nothing. |
722
+ | `expand` | function | `(event: ControlEvent) => ReadonlyArray<ControlEvent>` |
723
+
724
+ ## Health
725
+
726
+ Observational health for flows and native sessions. See
727
+ [Configure observational health](./guides/observe-health.md).
728
+
729
+ `HealthChecker<C>` accepts a read-only `ProbeContext` and schema-decoded config,
730
+ returning an Effect of `ProbeReport`. `makeRegistry(config, kind)` admits host
731
+ bindings and policies synchronously; `registry.resolve(key)` returns the selected
732
+ `ResolvedCheck`. `evaluate(check, context, stamp)` runs it with a timeout and safe
733
+ failure codes. The host rechecks ownership and commits the `HealthObservation`
734
+ before publication.
735
+
736
+ `rollup(input)` combines authoritative lifecycle, optional independent base
737
+ health, and the latest `{ observation, sequence }` into `StatusRollup`. The wire
738
+ axes are `state`, `activity`, `health`, `attention`, and `freshness`. Attention
739
+ is `none`, `awaiting-approval`, `needs-input`, `needs-resume`, or `unhealthy`;
740
+ `needs-resume` (reason `released`) marks a run parked over executions its owner
741
+ released, which only an explicit resume restarts. Provenance
742
+ carries checker/monitor IDs, opaque incarnation, evidence position, durable
743
+ version, observation time, and expiry. `latestObservation` compares only matching
744
+ incarnations and uses journal order for equal evidence. `runIncarnation` derives
745
+ an opaque fingerprint from current run ownership and lifecycle.
746
+
747
+ `CheckPolicy` configures interval, timeout, TTL, no-progress grace (`stallAfterMs`),
748
+ and failure backoff. `defaultPolicy` uses 5s/2s/20s/120s respectively and caps
749
+ backoff at 60s. Defaults report unknown semantic activity; no checker output grants
750
+ approval or authorizes a remedy.
751
+
752
+ ## JevSessionChecker
753
+
754
+ The one registered checker that reads semantic activity, bound by the ID
755
+ `jev.session`. See [Configure observational health](./guides/observe-health.md).
756
+
757
+ `makeJevSessionChecker({ evaluator, timeoutMs })` uses the host's existing
758
+ subscription judge. `Health.makeRegistry(config, kind, evaluator)` binds it to
759
+ `jev.session`. It asks one choice question over `working`, `idle`, and
760
+ `needs-input`, plus a boolean question about waiting for a person. Evidence is
761
+ `alive`, `exitCode`, and the newest `jevStateTailCharacters` of output.
762
+ It reads no gateway key and has no separate authentication path.
763
+
764
+ An answer becomes a report only at confidence `jevConfidenceFloor` or above:
765
+ `needs-input` reports reason `prompt-detected`, `working` and `idle` report `ok`.
766
+ A reading below the floor is Jev's own answer and reports
767
+ `{ activity: "unknown", reason: "ok" }`.
768
+
769
+ There is no fallback to another model or to a healthy-looking answer. An
770
+ unexposed output tail and a session that is not alive keep the lifecycle report,
771
+ because there is nothing to ask about. Every other way the probe cannot ask
772
+ fails it with `JevProbeError`, whose `reason` is `unconfigured` (no host evaluator), `http` (with the provider's `status`), `timeout`,
773
+ `unreachable`, or `malformed` (a body that does not answer the question asked).
774
+ `Health.evaluate` records a failing probe as `outcome: "error"` with reason
775
+ `probe-error` and no report, so `rollup` reads the subject `stale`, activity
776
+ `unknown`, health `unknown`, reason `probe-error`, never healthy, and
777
+ `CheckPolicy.backoff` spaces the retries. A host that binds `jev.session` must
778
+ supply its evaluator through `Health.makeRegistry(config, kind, evaluator)`.
779
+
780
+ `jevRequestTimeoutMs` is the deadline on the call and `jevProbeTimeoutMs` the
781
+ wider probe budget this checker asks a binding for, so the typed `timeout`
782
+ failure surfaces instead of a bare `probe-timeout`.
783
+
784
+ ## Monitor
785
+
786
+ Run health over the control plane. See
787
+ [Monitor a run and heal it](./guides/monitor-runs.md).
788
+
789
+ | Export | Kind | Signature |
790
+ | -------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
791
+ | `Health` | schema and type | `"healthy" \| "stalled" \| "wedged-node" \| "runaway-loop" \| "awaiting-human" \| "failing" \| "unknown"` |
792
+ | `Observation` | interface | `{ summary?: RunSummary; events: ReadonlyArray<ControlEvent>; beatsWithoutProgress: number; stallBeats: number; roundBound?: number }` |
793
+ | `classify` | function | `(observation: Observation) => Health`. Pure. |
794
+ | `Remedy` | type | `"resume" \| "cancel" \| "none"` |
795
+ | `remedyFor` | function | `(health: Health) => Remedy` |
796
+ | `Beat` | interface | `{ beat: number; health: Health; sequence: number; healed?: Remedy; receipt?: Receipt }` |
797
+ | `Report` | interface | `{ runId: RunId; beats: ReadonlyArray<Beat>; health: Health }` |
798
+ | `Options` | interface | `{ runId; monitorId?; intervalMs?; maxChecks?; stallBeats?; roundBound?; autoHeal?; heal? }` |
799
+ | `run` | function | `(options: Options) => Effect<Report, ControlError, Control \| Journal>` |
800
+ | `attemptStartedEventType` | constant | `"flows.engine.attempt-started"` |
801
+ | `attemptFinishedEventType` | constant | `"flows.engine.attempt-finished"` |
802
+ | `beatEventType` | constant | `"control.monitor.beat"` |
803
+ | `healedEventType` | constant | `"control.monitor.healed"` |
804
+
805
+ Defaults: `monitorId` is `default`, `intervalMs` is 1,000, `maxChecks` is 10,
806
+ `stallBeats` is 3, `roundBound` is 32, and `autoHeal` is empty.
807
+
808
+ ## Channels
809
+
810
+ Verified ingress. A channel verifies opaque transport data before it decodes or
811
+ maps it, and dispatches the result through `Control`.
812
+
813
+ | Export | Kind | Signature |
814
+ | -------------------- | ----------------- | ---------------------------------------------------------------------------------------------------- |
815
+ | `Channels` | interface and tag | `{ register; lookup; ingest; project }` at key `/control/Channels` |
816
+ | `Channel<A>` | interface | `{ name; schema; fingerprintHeaders?; verify; decode; map; project }` |
817
+ | `RawInbound` | interface | `{ body: Uint8Array; headers: Record<string, string \| undefined>; idempotencyKey: IdempotencyKey }` |
818
+ | `InboundResult` | type | `{ _tag: "Start"; flowId; input }` or `{ _tag: "Signal"; runId; signal }` |
819
+ | `IngestRequest` | interface | `{ channel: string; raw: RawInbound }` |
820
+ | `ProjectRequest` | interface | `{ channel: string; run: RunSummary }` |
821
+ | `Delivery` | interface | `{ cursor: string; messageId?: string }` |
822
+ | `DeliveryProjection` | interface | `{ cursor; messageId?; operation: "post" \| "edit" \| "noop"; message: unknown }` |
823
+ | `make` | effect | Builds the coordinator over `ControlRuntime`'s durable mutation store. |
824
+ | `makeMemory` | effect | Builds a process-local coordinator for adapter unit tests. |
825
+ | `layer` | layer | `Layer<Channels, never, ControlRuntime \| Control>` |
826
+ | `layerMemory` | layer | `Layer<Channels, never, Control>` |
827
+
828
+ `verify` inspects only opaque bytes and headers, and always precedes `decode`,
829
+ which is what keeps an untrusted public request from reaching planning.
830
+ `decode` and `map` must be deterministic and side-effect free; a retry may
831
+ evaluate either again. `fingerprintHeaders` names only the non-secret headers
832
+ that change the decoded command.
833
+ Outbound delivery cursors are scoped to the exact channel name and run ID,
834
+ including names and IDs containing `:`.
835
+
836
+ ## WebhookChannel
837
+
838
+ | Export | Kind | Signature |
839
+ | ------------------- | --------- | ----------------------------------------------------------------------------------------------------- |
840
+ | `SignatureVerifier` | type | `(raw: RawInbound, credential: Redacted<CredentialRef>) => Effect<void, Unauthorized>` |
841
+ | `Config<A>` | interface | `{ name; schema; credential; fingerprintHeaders?; verify; map; project }` |
842
+ | `make` | function | `<A>(config: Config<A>) => Channel<A>` |
843
+ | `maximumBodyBytes` | constant | `1048576`, the default body ceiling for one mount. |
844
+ | `HandlerOptions` | interface | `{ maximumBodyBytes?: number }` |
845
+ | `handler` | function | `(channel: string, idempotencyKey: IdempotencyKey, options?: HandlerOptions) => Effect<Receipt, ...>` |
846
+
847
+ The body is bounded twice: a `content-length` over the limit is refused before
848
+ the body is read, and each streamed chunk is measured before it is retained.
849
+ Reading stops at the first chunk exceeding the limit, before verification.
850
+ Both refusals are `InvalidInput` naming the two byte counts and no body content.
851
+ Malformed JSON returns the fixed issue `invalid webhook JSON` without parser
852
+ messages or payload fragments.
853
+
854
+ ## Credential
855
+
856
+ The credential boundary. Only a `CredentialRef` crosses it. See
857
+ [Store and resolve a credential](./guides/store-credentials.md).
858
+
859
+ | Export | Kind | Signature |
860
+ | --------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
861
+ | `CredentialRef` | interface | `{ id: string; name: string }` |
862
+ | `Operation` | type | `"list" \| "get" \| "create" \| "resolve" \| "rotate" \| "revoke"` |
863
+ | `Credential` | interface and tag | The six operations, at key `/control/Credential` |
864
+ | `Options` | interface | `{ store: CredentialStore.Service; cipher: CredentialCipher.Service; authorize?: (operation, reference) => Effect<void, Unauthorized> }` |
865
+ | `make` | function | `(options: Options) => Credential` |
866
+ | `layer` | layer | `(options?: { authorize? }) => Layer<Credential, never, CredentialStore \| CredentialCipher>` |
867
+ | `makeNoop` | function | `() => Credential`. Every operation fails `Unavailable`. |
868
+ | `layerNoop` | layer | `Layer<Credential>` |
869
+
870
+ | Operation | Signature |
871
+ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
872
+ | `list` | `() => Effect<ReadonlyArray<CredentialRef>, Unavailable \| Unauthorized>` |
873
+ | `get` | `(id: string) => Effect<CredentialRef, Unavailable \| Unauthorized>` |
874
+ | `create` | `({ id, name, secret: Redacted<string> }) => Effect<CredentialRef, Unavailable \| Unauthorized \| CredentialConflict>` |
875
+ | `resolve` | `(reference: CredentialRef) => Effect<Redacted<string>, Unavailable \| Unauthorized \| PersistenceError>` |
876
+ | `rotate` | `(reference: CredentialRef, secret: Redacted<string>, options?: { expected? }) => Effect<CredentialRef, Unavailable \| Unauthorized \| CredentialConflict \| PersistenceError>` |
877
+ | `revoke` | `(reference: CredentialRef) => Effect<void, Unavailable \| Unauthorized>` |
878
+
879
+ `authorize` defaults to allowing every operation, which is correct for a
880
+ single-principal local process. A reference is authenticated on every
881
+ operation, so a forged or stale one is refused.
882
+
883
+ ## CredentialStore
884
+
885
+ | Export | Kind | Signature |
886
+ | ----------------------- | --------------- | -------------------------------------------------------------- |
887
+ | `SealedRecord` | interface | `{ id; name; ciphertext; nonce; version; updatedAtMs }` |
888
+ | `Service` | interface | `{ list(); read(id); write(record); remove(id) }` |
889
+ | `CredentialStore` | class | Key `/control/CredentialStore` |
890
+ | `make` | function | `(implementation: Service) => Service` |
891
+ | `makeMemory` | function | `() => Service`. Process-local and browser-safe. |
892
+ | `layerMemory` | layer | `Layer<CredentialStore>` |
893
+ | `makeNoop`, `layerNoop` | function, layer | Every operation fails `Unavailable`. Accept partial overrides. |
894
+
895
+ `write` commits `record` only if the stored version is `record.version - 1`,
896
+ and fails `CredentialConflict` otherwise. Plaintext never reaches this
897
+ boundary.
898
+
899
+ ## CredentialCipher
900
+
901
+ | Export | Kind | Signature |
902
+ | ----------------------- | --------------- | ---------------------------------------------------------------------------------- |
903
+ | `Sealed` | interface | `{ ciphertext: string; nonce: string }`, both base64. |
904
+ | `Context` | interface | `{ id: string; name: string; version: number }`, the authenticated data. |
905
+ | `Service` | interface | `{ seal(plaintext, context); open(sealed, context) }` |
906
+ | `CredentialCipher` | class | Key `/control/CredentialCipher` |
907
+ | `make` | function | `(implementation: Service) => Service` |
908
+ | `unavailable` | function | `() => Unavailable`, the typed failure a host reports with no secure key material. |
909
+ | `makeNoop`, `layerNoop` | function, layer | Every operation fails `Unavailable`. Accept partial overrides. |
910
+
911
+ ## SqlCredentialStore
912
+
913
+ | Export | Kind | Signature |
914
+ | --------- | ------ | -------------------------------------------------------------------------------- |
915
+ | `migrate` | effect | `Effect<void, Unavailable, SqlClient>`. Creates `control_credentials` if absent. |
916
+ | `make` | effect | `Effect<CredentialStore.Service, Unavailable, DurableWriter \| SqlClient>` |
917
+ | `layer` | layer | `Layer<CredentialStore, Unavailable, DurableWriter \| SqlClient>` |
918
+
919
+ The read and the compare-and-set write run in one transaction, so two
920
+ concurrent rotations serialize.
921
+
922
+ ## WebCryptoCipher
923
+
924
+ | Export | Kind | Signature |
925
+ | --------- | --------- | ------------------------------------------------------------------------------------- |
926
+ | `Options` | interface | `{ key: Redacted<string> }`, 32 raw bytes base64-encoded. |
927
+ | `make` | effect | `(options: Options) => Effect<CredentialCipher.Service, Unavailable \| InvalidInput>` |
928
+ | `layer` | layer | `(options: Options) => Layer<CredentialCipher, Unavailable \| InvalidInput>` |
929
+
930
+ AES-256-GCM over the Web Crypto API, which serves both Node and the browser.
931
+ The key is imported as a non-extractable `CryptoKey` and never reaches
932
+ `CredentialStore`. A host without Web Crypto fails with `Unavailable` rather
933
+ than a defect, and a key that is not 32 base64-encoded bytes fails with
934
+ `InvalidInput`. `open` fails with `PersistenceError` on operation
935
+ `credential.open` when the stored nonce is malformed or the ciphertext fails
936
+ authentication under this key and context.
937
+
938
+ ## Migrations
939
+
940
+ | Export | Kind | Signature |
941
+ | ------- | ------------- | --------------------------------------------------------------------- |
942
+ | `set` | migration set | Namespace `control`, at the migration id block after time travel. |
943
+ | `run` | effect | Creates every durable control-plane and credential table. |
944
+ | `layer` | layer | Runs the migrations before exposing the database to control services. |
945
+
946
+ Hosts compose this set with the journal and run-store sets before opening a
947
+ shared control database. See
948
+ [Store control state in a database](./guides/durable-storage.md).
949
+
950
+ ## SystemFlows
951
+
952
+ The reserved command-line verb to flow-id map the CLI projects.
953
+
954
+ | Export | Kind | Signature |
955
+ | ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
956
+ | `SystemFlowEntry` | interface | `{ verb: string; flowId: "system/" template literal; projection: "procedure" \| "systemFlow"; deployClass: boolean; planBearing: boolean; plannable: boolean }` |
957
+ | `catalog` | constant | Every reserved verb, including the ones a runtime may not plan. |
958
+ | `plannable` | constant | The entries a control runtime may offer as flows. |
959
+
960
+ `plannable: false` means the row is command-line metadata and nothing else: the
961
+ verb is named so the binary can refuse it by name. `system/replay` is the case
962
+ that matters. It is in `catalog` and not in `plannable`, so a runtime that
963
+ offers `plannable` as its flow catalog refuses it at `plan` rather than minting
964
+ an approval card no `run` can honor.
965
+
966
+ Both runtimes default their flow catalog to `plannable`, so a composition that
967
+ builds its own map and the runtimes' defaults cannot disagree about which
968
+ reserved ids exist.
969
+
970
+ ## test/TestControl
971
+
972
+ Importable only from `@smthrs/control/test/TestControl`.
973
+
974
+ | Export | Signature |
975
+ | ------- | -------------------------------------------------------------------------------------------- |
976
+ | `layer` | `(options?: ControlRuntime.MemoryOptions, executor?: ControlExecutor.Service) => Layer<...>` |
977
+
978
+ Provides `Control` together with every collaborator it built: the deterministic
979
+ runtime, the in-memory journal bundle, a notification queue over that journal,
980
+ the executor (`ControlExecutor.makeNoop()` by default), and an empty registry.
981
+ Runtime flow metadata falls back to the reserved system catalog. See
982
+ [Test against the control plane](./guides/testing.md).