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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/LICENSE +21 -0
  3. package/README.md +168 -2
  4. package/dist/cjs/ApprovalAuthority.d.ts +73 -0
  5. package/dist/cjs/ApprovalAuthority.d.ts.map +1 -0
  6. package/dist/cjs/ApprovalAuthority.js +62 -0
  7. package/dist/cjs/ApprovalAuthority.js.map +7 -0
  8. package/dist/cjs/Cancellation.d.ts +107 -0
  9. package/dist/cjs/Cancellation.d.ts.map +1 -0
  10. package/dist/cjs/Cancellation.js +72 -0
  11. package/dist/cjs/Cancellation.js.map +7 -0
  12. package/dist/cjs/Channels.d.ts +170 -0
  13. package/dist/cjs/Channels.d.ts.map +1 -0
  14. package/dist/cjs/Channels.js +278 -0
  15. package/dist/cjs/Channels.js.map +7 -0
  16. package/dist/cjs/Control.d.ts +202 -0
  17. package/dist/cjs/Control.d.ts.map +1 -0
  18. package/dist/cjs/Control.js +47 -0
  19. package/dist/cjs/Control.js.map +7 -0
  20. package/dist/cjs/ControlClient.d.ts +52 -0
  21. package/dist/cjs/ControlClient.d.ts.map +1 -0
  22. package/dist/cjs/ControlClient.js +191 -0
  23. package/dist/cjs/ControlClient.js.map +7 -0
  24. package/dist/cjs/ControlError.d.ts +318 -0
  25. package/dist/cjs/ControlError.d.ts.map +1 -0
  26. package/dist/cjs/ControlError.js +249 -0
  27. package/dist/cjs/ControlError.js.map +7 -0
  28. package/dist/cjs/ControlExecutor.d.ts +372 -0
  29. package/dist/cjs/ControlExecutor.d.ts.map +1 -0
  30. package/dist/cjs/ControlExecutor.js +123 -0
  31. package/dist/cjs/ControlExecutor.js.map +7 -0
  32. package/dist/cjs/ControlFacts.d.ts +454 -0
  33. package/dist/cjs/ControlFacts.d.ts.map +1 -0
  34. package/dist/cjs/ControlFacts.js +261 -0
  35. package/dist/cjs/ControlFacts.js.map +7 -0
  36. package/dist/cjs/ControlLive.d.ts +23 -0
  37. package/dist/cjs/ControlLive.d.ts.map +1 -0
  38. package/dist/cjs/ControlLive.js +1293 -0
  39. package/dist/cjs/ControlLive.js.map +7 -0
  40. package/dist/cjs/ControlRpcs.d.ts +1204 -0
  41. package/dist/cjs/ControlRpcs.d.ts.map +1 -0
  42. package/dist/cjs/ControlRpcs.js +247 -0
  43. package/dist/cjs/ControlRpcs.js.map +7 -0
  44. package/dist/cjs/ControlRuntime.d.ts +635 -0
  45. package/dist/cjs/ControlRuntime.d.ts.map +1 -0
  46. package/dist/cjs/ControlRuntime.js +744 -0
  47. package/dist/cjs/ControlRuntime.js.map +7 -0
  48. package/dist/cjs/ControlSchema.d.ts +2642 -0
  49. package/dist/cjs/ControlSchema.d.ts.map +1 -0
  50. package/dist/cjs/ControlSchema.js +634 -0
  51. package/dist/cjs/ControlSchema.js.map +7 -0
  52. package/dist/cjs/ControlServer.d.ts +51 -0
  53. package/dist/cjs/ControlServer.d.ts.map +1 -0
  54. package/dist/cjs/ControlServer.js +121 -0
  55. package/dist/cjs/ControlServer.js.map +7 -0
  56. package/dist/cjs/Credential.d.ts +136 -0
  57. package/dist/cjs/Credential.d.ts.map +1 -0
  58. package/dist/cjs/Credential.js +168 -0
  59. package/dist/cjs/Credential.js.map +7 -0
  60. package/dist/cjs/CredentialCipher.d.ts +90 -0
  61. package/dist/cjs/CredentialCipher.d.ts.map +1 -0
  62. package/dist/cjs/CredentialCipher.js +45 -0
  63. package/dist/cjs/CredentialCipher.js.map +7 -0
  64. package/dist/cjs/CredentialStore.d.ts +97 -0
  65. package/dist/cjs/CredentialStore.d.ts.map +1 -0
  66. package/dist/cjs/CredentialStore.js +81 -0
  67. package/dist/cjs/CredentialStore.js.map +7 -0
  68. package/dist/cjs/DispatchReader.d.ts +112 -0
  69. package/dist/cjs/DispatchReader.d.ts.map +1 -0
  70. package/dist/cjs/DispatchReader.js +45 -0
  71. package/dist/cjs/DispatchReader.js.map +7 -0
  72. package/dist/cjs/Health.d.ts +333 -0
  73. package/dist/cjs/Health.d.ts.map +1 -0
  74. package/dist/cjs/Health.js +311 -0
  75. package/dist/cjs/Health.js.map +7 -0
  76. package/dist/cjs/JevSessionChecker.d.ts +57 -0
  77. package/dist/cjs/JevSessionChecker.d.ts.map +1 -0
  78. package/dist/cjs/JevSessionChecker.js +113 -0
  79. package/dist/cjs/JevSessionChecker.js.map +7 -0
  80. package/dist/cjs/Lineage.d.ts +131 -0
  81. package/dist/cjs/Lineage.d.ts.map +1 -0
  82. package/dist/cjs/Lineage.js +81 -0
  83. package/dist/cjs/Lineage.js.map +7 -0
  84. package/dist/cjs/Migrations.d.ts +34 -0
  85. package/dist/cjs/Migrations.d.ts.map +1 -0
  86. package/dist/cjs/Migrations.js +60 -0
  87. package/dist/cjs/Migrations.js.map +7 -0
  88. package/dist/cjs/Monitor.d.ts +282 -0
  89. package/dist/cjs/Monitor.d.ts.map +1 -0
  90. package/dist/cjs/Monitor.js +283 -0
  91. package/dist/cjs/Monitor.js.map +7 -0
  92. package/dist/cjs/ScopedToken.d.ts +193 -0
  93. package/dist/cjs/ScopedToken.d.ts.map +1 -0
  94. package/dist/cjs/ScopedToken.js +135 -0
  95. package/dist/cjs/ScopedToken.js.map +7 -0
  96. package/dist/cjs/SqlControlRuntime.d.ts +161 -0
  97. package/dist/cjs/SqlControlRuntime.d.ts.map +1 -0
  98. package/dist/cjs/SqlControlRuntime.js +1522 -0
  99. package/dist/cjs/SqlControlRuntime.js.map +7 -0
  100. package/dist/cjs/SqlCredentialStore.d.ts +43 -0
  101. package/dist/cjs/SqlCredentialStore.d.ts.map +1 -0
  102. package/dist/cjs/SqlCredentialStore.js +113 -0
  103. package/dist/cjs/SqlCredentialStore.js.map +7 -0
  104. package/dist/cjs/Steering.d.ts +69 -0
  105. package/dist/cjs/Steering.d.ts.map +1 -0
  106. package/dist/cjs/Steering.js +49 -0
  107. package/dist/cjs/Steering.js.map +7 -0
  108. package/dist/cjs/SystemFlows.d.ts +223 -0
  109. package/dist/cjs/SystemFlows.d.ts.map +1 -0
  110. package/dist/cjs/SystemFlows.js +195 -0
  111. package/dist/cjs/SystemFlows.js.map +7 -0
  112. package/dist/cjs/WebCryptoCipher.d.ts +49 -0
  113. package/dist/cjs/WebCryptoCipher.d.ts.map +1 -0
  114. package/dist/cjs/WebCryptoCipher.js +129 -0
  115. package/dist/cjs/WebCryptoCipher.js.map +7 -0
  116. package/dist/cjs/WebhookChannel.d.ts +113 -0
  117. package/dist/cjs/WebhookChannel.d.ts.map +1 -0
  118. package/dist/cjs/WebhookChannel.js +98 -0
  119. package/dist/cjs/WebhookChannel.js.map +7 -0
  120. package/dist/cjs/index.d.ts +160 -0
  121. package/dist/cjs/index.d.ts.map +1 -0
  122. package/dist/cjs/index.js +91 -0
  123. package/dist/cjs/index.js.map +7 -0
  124. package/dist/cjs/internal/MutationBoundary.d.ts +27 -0
  125. package/dist/cjs/internal/MutationBoundary.d.ts.map +1 -0
  126. package/dist/cjs/internal/MutationBoundary.js +50 -0
  127. package/dist/cjs/internal/MutationBoundary.js.map +7 -0
  128. package/dist/cjs/internal/activeFibers.d.ts +12 -0
  129. package/dist/cjs/internal/activeFibers.d.ts.map +1 -0
  130. package/dist/cjs/internal/activeFibers.js +30 -0
  131. package/dist/cjs/internal/activeFibers.js.map +7 -0
  132. package/dist/cjs/internal/issues.d.ts +28 -0
  133. package/dist/cjs/internal/issues.d.ts.map +1 -0
  134. package/dist/cjs/internal/issues.js +34 -0
  135. package/dist/cjs/internal/issues.js.map +7 -0
  136. package/dist/cjs/internal/planning.d.ts +347 -0
  137. package/dist/cjs/internal/planning.d.ts.map +1 -0
  138. package/dist/cjs/internal/planning.js +137 -0
  139. package/dist/cjs/internal/planning.js.map +7 -0
  140. package/dist/cjs/internal/sqlSchemaErrors.d.ts +18 -0
  141. package/dist/cjs/internal/sqlSchemaErrors.d.ts.map +1 -0
  142. package/dist/cjs/internal/sqlSchemaErrors.js +40 -0
  143. package/dist/cjs/internal/sqlSchemaErrors.js.map +7 -0
  144. package/dist/cjs/migrations/0001_control_tables.d.ts +18 -0
  145. package/dist/cjs/migrations/0001_control_tables.d.ts.map +1 -0
  146. package/dist/cjs/migrations/0001_control_tables.js +115 -0
  147. package/dist/cjs/migrations/0001_control_tables.js.map +7 -0
  148. package/dist/cjs/migrations/0002_run_keys.d.ts +15 -0
  149. package/dist/cjs/migrations/0002_run_keys.d.ts.map +1 -0
  150. package/dist/cjs/migrations/0002_run_keys.js +44 -0
  151. package/dist/cjs/migrations/0002_run_keys.js.map +7 -0
  152. package/dist/cjs/migrations/0003_signal_commands.d.ts +13 -0
  153. package/dist/cjs/migrations/0003_signal_commands.d.ts.map +1 -0
  154. package/dist/cjs/migrations/0003_signal_commands.js +50 -0
  155. package/dist/cjs/migrations/0003_signal_commands.js.map +7 -0
  156. package/dist/cjs/migrations/0004_approval_decisions.d.ts +13 -0
  157. package/dist/cjs/migrations/0004_approval_decisions.d.ts.map +1 -0
  158. package/dist/cjs/migrations/0004_approval_decisions.js +45 -0
  159. package/dist/cjs/migrations/0004_approval_decisions.js.map +7 -0
  160. package/dist/cjs/migrations/0005_signal_principals.d.ts +18 -0
  161. package/dist/cjs/migrations/0005_signal_principals.d.ts.map +1 -0
  162. package/dist/cjs/migrations/0005_signal_principals.js +45 -0
  163. package/dist/cjs/migrations/0005_signal_principals.js.map +7 -0
  164. package/dist/cjs/migrations/0006_run_principals.d.ts +18 -0
  165. package/dist/cjs/migrations/0006_run_principals.d.ts.map +1 -0
  166. package/dist/cjs/migrations/0006_run_principals.js +49 -0
  167. package/dist/cjs/migrations/0006_run_principals.js.map +7 -0
  168. package/dist/cjs/migrations/0007_resume_consent.d.ts +18 -0
  169. package/dist/cjs/migrations/0007_resume_consent.d.ts.map +1 -0
  170. package/dist/cjs/migrations/0007_resume_consent.js +46 -0
  171. package/dist/cjs/migrations/0007_resume_consent.js.map +7 -0
  172. package/dist/cjs/package.json +1 -0
  173. package/dist/cjs/test/TestControl.d.ts +19 -0
  174. package/dist/cjs/test/TestControl.d.ts.map +1 -0
  175. package/dist/cjs/test/TestControl.js +62 -0
  176. package/dist/cjs/test/TestControl.js.map +7 -0
  177. package/dist/esm/ApprovalAuthority.d.ts +73 -0
  178. package/dist/esm/ApprovalAuthority.d.ts.map +1 -0
  179. package/dist/esm/ApprovalAuthority.js +72 -0
  180. package/dist/esm/ApprovalAuthority.js.map +1 -0
  181. package/dist/esm/Cancellation.d.ts +107 -0
  182. package/dist/esm/Cancellation.d.ts.map +1 -0
  183. package/dist/esm/Cancellation.js +116 -0
  184. package/dist/esm/Cancellation.js.map +1 -0
  185. package/dist/esm/Channels.d.ts +170 -0
  186. package/dist/esm/Channels.d.ts.map +1 -0
  187. package/dist/esm/Channels.js +312 -0
  188. package/dist/esm/Channels.js.map +1 -0
  189. package/dist/esm/Control.d.ts +202 -0
  190. package/dist/esm/Control.d.ts.map +1 -0
  191. package/dist/esm/Control.js +42 -0
  192. package/dist/esm/Control.js.map +1 -0
  193. package/dist/esm/ControlClient.d.ts +52 -0
  194. package/dist/esm/ControlClient.d.ts.map +1 -0
  195. package/dist/esm/ControlClient.js +217 -0
  196. package/dist/esm/ControlClient.js.map +1 -0
  197. package/dist/esm/ControlError.d.ts +318 -0
  198. package/dist/esm/ControlError.d.ts.map +1 -0
  199. package/dist/esm/ControlError.js +359 -0
  200. package/dist/esm/ControlError.js.map +1 -0
  201. package/dist/esm/ControlExecutor.d.ts +372 -0
  202. package/dist/esm/ControlExecutor.d.ts.map +1 -0
  203. package/dist/esm/ControlExecutor.js +212 -0
  204. package/dist/esm/ControlExecutor.js.map +1 -0
  205. package/dist/esm/ControlFacts.d.ts +454 -0
  206. package/dist/esm/ControlFacts.d.ts.map +1 -0
  207. package/dist/esm/ControlFacts.js +324 -0
  208. package/dist/esm/ControlFacts.js.map +1 -0
  209. package/dist/esm/ControlLive.d.ts +23 -0
  210. package/dist/esm/ControlLive.d.ts.map +1 -0
  211. package/dist/esm/ControlLive.js +1589 -0
  212. package/dist/esm/ControlLive.js.map +1 -0
  213. package/dist/esm/ControlRpcs.d.ts +1204 -0
  214. package/dist/esm/ControlRpcs.d.ts.map +1 -0
  215. package/dist/esm/ControlRpcs.js +299 -0
  216. package/dist/esm/ControlRpcs.js.map +1 -0
  217. package/dist/esm/ControlRuntime.d.ts +635 -0
  218. package/dist/esm/ControlRuntime.d.ts.map +1 -0
  219. package/dist/esm/ControlRuntime.js +808 -0
  220. package/dist/esm/ControlRuntime.js.map +1 -0
  221. package/dist/esm/ControlSchema.d.ts +2642 -0
  222. package/dist/esm/ControlSchema.d.ts.map +1 -0
  223. package/dist/esm/ControlSchema.js +1030 -0
  224. package/dist/esm/ControlSchema.js.map +1 -0
  225. package/dist/esm/ControlServer.d.ts +51 -0
  226. package/dist/esm/ControlServer.d.ts.map +1 -0
  227. package/dist/esm/ControlServer.js +145 -0
  228. package/dist/esm/ControlServer.js.map +1 -0
  229. package/dist/esm/Credential.d.ts +136 -0
  230. package/dist/esm/Credential.d.ts.map +1 -0
  231. package/dist/esm/Credential.js +190 -0
  232. package/dist/esm/Credential.js.map +1 -0
  233. package/dist/esm/CredentialCipher.d.ts +90 -0
  234. package/dist/esm/CredentialCipher.d.ts.map +1 -0
  235. package/dist/esm/CredentialCipher.js +56 -0
  236. package/dist/esm/CredentialCipher.js.map +1 -0
  237. package/dist/esm/CredentialStore.d.ts +97 -0
  238. package/dist/esm/CredentialStore.d.ts.map +1 -0
  239. package/dist/esm/CredentialStore.js +101 -0
  240. package/dist/esm/CredentialStore.js.map +1 -0
  241. package/dist/esm/DispatchReader.d.ts +112 -0
  242. package/dist/esm/DispatchReader.d.ts.map +1 -0
  243. package/dist/esm/DispatchReader.js +76 -0
  244. package/dist/esm/DispatchReader.js.map +1 -0
  245. package/dist/esm/Health.d.ts +333 -0
  246. package/dist/esm/Health.d.ts.map +1 -0
  247. package/dist/esm/Health.js +400 -0
  248. package/dist/esm/Health.js.map +1 -0
  249. package/dist/esm/JevSessionChecker.d.ts +57 -0
  250. package/dist/esm/JevSessionChecker.d.ts.map +1 -0
  251. package/dist/esm/JevSessionChecker.js +108 -0
  252. package/dist/esm/JevSessionChecker.js.map +1 -0
  253. package/dist/esm/Lineage.d.ts +131 -0
  254. package/dist/esm/Lineage.d.ts.map +1 -0
  255. package/dist/esm/Lineage.js +174 -0
  256. package/dist/esm/Lineage.js.map +1 -0
  257. package/dist/esm/Migrations.d.ts +34 -0
  258. package/dist/esm/Migrations.d.ts.map +1 -0
  259. package/dist/esm/Migrations.js +53 -0
  260. package/dist/esm/Migrations.js.map +1 -0
  261. package/dist/esm/Monitor.d.ts +282 -0
  262. package/dist/esm/Monitor.d.ts.map +1 -0
  263. package/dist/esm/Monitor.js +415 -0
  264. package/dist/esm/Monitor.js.map +1 -0
  265. package/dist/esm/ScopedToken.d.ts +193 -0
  266. package/dist/esm/ScopedToken.d.ts.map +1 -0
  267. package/dist/esm/ScopedToken.js +224 -0
  268. package/dist/esm/ScopedToken.js.map +1 -0
  269. package/dist/esm/SqlControlRuntime.d.ts +161 -0
  270. package/dist/esm/SqlControlRuntime.d.ts.map +1 -0
  271. package/dist/esm/SqlControlRuntime.js +1756 -0
  272. package/dist/esm/SqlControlRuntime.js.map +1 -0
  273. package/dist/esm/SqlCredentialStore.d.ts +43 -0
  274. package/dist/esm/SqlCredentialStore.d.ts.map +1 -0
  275. package/dist/esm/SqlCredentialStore.js +97 -0
  276. package/dist/esm/SqlCredentialStore.js.map +1 -0
  277. package/dist/esm/Steering.d.ts +69 -0
  278. package/dist/esm/Steering.d.ts.map +1 -0
  279. package/dist/esm/Steering.js +89 -0
  280. package/dist/esm/Steering.js.map +1 -0
  281. package/dist/esm/SystemFlows.d.ts +223 -0
  282. package/dist/esm/SystemFlows.d.ts.map +1 -0
  283. package/dist/esm/SystemFlows.js +198 -0
  284. package/dist/esm/SystemFlows.js.map +1 -0
  285. package/dist/esm/WebCryptoCipher.d.ts +49 -0
  286. package/dist/esm/WebCryptoCipher.d.ts.map +1 -0
  287. package/dist/esm/WebCryptoCipher.js +123 -0
  288. package/dist/esm/WebCryptoCipher.js.map +1 -0
  289. package/dist/esm/WebhookChannel.d.ts +113 -0
  290. package/dist/esm/WebhookChannel.d.ts.map +1 -0
  291. package/dist/esm/WebhookChannel.js +109 -0
  292. package/dist/esm/WebhookChannel.js.map +1 -0
  293. package/dist/esm/index.d.ts +160 -0
  294. package/dist/esm/index.d.ts.map +1 -0
  295. package/dist/esm/index.js +160 -0
  296. package/dist/esm/index.js.map +1 -0
  297. package/dist/esm/internal/MutationBoundary.d.ts +27 -0
  298. package/dist/esm/internal/MutationBoundary.d.ts.map +1 -0
  299. package/dist/esm/internal/MutationBoundary.js +40 -0
  300. package/dist/esm/internal/MutationBoundary.js.map +1 -0
  301. package/dist/esm/internal/activeFibers.d.ts +12 -0
  302. package/dist/esm/internal/activeFibers.d.ts.map +1 -0
  303. package/dist/esm/internal/activeFibers.js +17 -0
  304. package/dist/esm/internal/activeFibers.js.map +1 -0
  305. package/dist/esm/internal/issues.d.ts +28 -0
  306. package/dist/esm/internal/issues.d.ts.map +1 -0
  307. package/dist/esm/internal/issues.js +35 -0
  308. package/dist/esm/internal/issues.js.map +1 -0
  309. package/dist/esm/internal/planning.d.ts +347 -0
  310. package/dist/esm/internal/planning.d.ts.map +1 -0
  311. package/dist/esm/internal/planning.js +199 -0
  312. package/dist/esm/internal/planning.js.map +1 -0
  313. package/dist/esm/internal/sqlSchemaErrors.d.ts +18 -0
  314. package/dist/esm/internal/sqlSchemaErrors.d.ts.map +1 -0
  315. package/dist/esm/internal/sqlSchemaErrors.js +38 -0
  316. package/dist/esm/internal/sqlSchemaErrors.js.map +1 -0
  317. package/dist/esm/migrations/0001_control_tables.d.ts +18 -0
  318. package/dist/esm/migrations/0001_control_tables.d.ts.map +1 -0
  319. package/dist/esm/migrations/0001_control_tables.js +96 -0
  320. package/dist/esm/migrations/0001_control_tables.js.map +1 -0
  321. package/dist/esm/migrations/0002_run_keys.d.ts +15 -0
  322. package/dist/esm/migrations/0002_run_keys.d.ts.map +1 -0
  323. package/dist/esm/migrations/0002_run_keys.js +22 -0
  324. package/dist/esm/migrations/0002_run_keys.js.map +1 -0
  325. package/dist/esm/migrations/0003_signal_commands.d.ts +13 -0
  326. package/dist/esm/migrations/0003_signal_commands.d.ts.map +1 -0
  327. package/dist/esm/migrations/0003_signal_commands.js +26 -0
  328. package/dist/esm/migrations/0003_signal_commands.js.map +1 -0
  329. package/dist/esm/migrations/0004_approval_decisions.d.ts +13 -0
  330. package/dist/esm/migrations/0004_approval_decisions.d.ts.map +1 -0
  331. package/dist/esm/migrations/0004_approval_decisions.js +25 -0
  332. package/dist/esm/migrations/0004_approval_decisions.js.map +1 -0
  333. package/dist/esm/migrations/0005_signal_principals.d.ts +18 -0
  334. package/dist/esm/migrations/0005_signal_principals.d.ts.map +1 -0
  335. package/dist/esm/migrations/0005_signal_principals.js +27 -0
  336. package/dist/esm/migrations/0005_signal_principals.js.map +1 -0
  337. package/dist/esm/migrations/0006_run_principals.d.ts +18 -0
  338. package/dist/esm/migrations/0006_run_principals.d.ts.map +1 -0
  339. package/dist/esm/migrations/0006_run_principals.js +30 -0
  340. package/dist/esm/migrations/0006_run_principals.js.map +1 -0
  341. package/dist/esm/migrations/0007_resume_consent.d.ts +18 -0
  342. package/dist/esm/migrations/0007_resume_consent.d.ts.map +1 -0
  343. package/dist/esm/migrations/0007_resume_consent.js +27 -0
  344. package/dist/esm/migrations/0007_resume_consent.js.map +1 -0
  345. package/dist/esm/test/TestControl.d.ts +19 -0
  346. package/dist/esm/test/TestControl.d.ts.map +1 -0
  347. package/dist/esm/test/TestControl.js +30 -0
  348. package/dist/esm/test/TestControl.js.map +1 -0
  349. package/docs/README.md +189 -0
  350. package/docs/api.md +982 -0
  351. package/docs/concepts/authority.md +109 -0
  352. package/docs/concepts/cancellation.md +129 -0
  353. package/docs/concepts/lineage.md +132 -0
  354. package/docs/concepts/ownership.md +139 -0
  355. package/docs/concepts/projections.md +203 -0
  356. package/docs/concepts/receipts.md +128 -0
  357. package/docs/guides/approvals.md +284 -0
  358. package/docs/guides/cancel-and-resume.md +162 -0
  359. package/docs/guides/durable-storage.md +147 -0
  360. package/docs/guides/implement-an-executor.md +173 -0
  361. package/docs/guides/ingest-a-webhook.md +177 -0
  362. package/docs/guides/list-runs.md +160 -0
  363. package/docs/guides/monitor-runs.md +176 -0
  364. package/docs/guides/observe-health.md +147 -0
  365. package/docs/guides/postgres-tests.md +7 -0
  366. package/docs/guides/serve-over-rpc.md +220 -0
  367. package/docs/guides/signal-a-run.md +53 -0
  368. package/docs/guides/steer-a-run.md +138 -0
  369. package/docs/guides/store-credentials.md +164 -0
  370. package/docs/guides/testing.md +139 -0
  371. package/docs/guides/watch-a-run.md +154 -0
  372. package/docs/installation.md +106 -0
  373. package/docs/quickstart.md +163 -0
  374. package/docs/troubleshooting.md +208 -0
  375. package/package.json +405 -3
  376. package/src/ApprovalAuthority.ts +114 -0
  377. package/src/Cancellation.ts +172 -0
  378. package/src/Channels.ts +493 -0
  379. package/src/Control.ts +337 -0
  380. package/src/ControlClient.ts +319 -0
  381. package/src/ControlError.ts +378 -0
  382. package/src/ControlExecutor.ts +490 -0
  383. package/src/ControlFacts.ts +383 -0
  384. package/src/ControlLive.ts +2127 -0
  385. package/src/ControlRpcs.ts +443 -0
  386. package/src/ControlRuntime.ts +1601 -0
  387. package/src/ControlSchema.ts +1380 -0
  388. package/src/ControlServer.ts +182 -0
  389. package/src/Credential.ts +310 -0
  390. package/src/CredentialCipher.ts +110 -0
  391. package/src/CredentialStore.ts +152 -0
  392. package/src/DispatchReader.ts +122 -0
  393. package/src/Health.ts +591 -0
  394. package/src/JevSessionChecker.ts +127 -0
  395. package/src/Lineage.ts +203 -0
  396. package/src/Migrations.ts +56 -0
  397. package/src/Monitor.ts +600 -0
  398. package/src/ScopedToken.ts +306 -0
  399. package/src/SqlControlRuntime.ts +2478 -0
  400. package/src/SqlCredentialStore.ts +148 -0
  401. package/src/Steering.ts +96 -0
  402. package/src/SystemFlows.ts +225 -0
  403. package/src/WebCryptoCipher.ts +169 -0
  404. package/src/WebhookChannel.ts +166 -0
  405. package/src/index.ts +188 -0
  406. package/src/internal/MutationBoundary.ts +46 -0
  407. package/src/internal/activeFibers.ts +22 -0
  408. package/src/internal/issues.ts +40 -0
  409. package/src/internal/planning.ts +262 -0
  410. package/src/internal/sqlSchemaErrors.ts +37 -0
  411. package/src/migrations/0001_control_tables.ts +99 -0
  412. package/src/migrations/0002_run_keys.ts +23 -0
  413. package/src/migrations/0003_signal_commands.ts +27 -0
  414. package/src/migrations/0004_approval_decisions.ts +25 -0
  415. package/src/migrations/0005_signal_principals.ts +27 -0
  416. package/src/migrations/0006_run_principals.ts +31 -0
  417. package/src/migrations/0007_resume_consent.ts +28 -0
  418. package/src/test/TestControl.ts +47 -0
@@ -0,0 +1,203 @@
1
+ ---
2
+ title: "Journal projections"
3
+ description: "How watch turns committed journal entries into ControlEvent values, why a cursor scopes to one run, how the snapshot hands off to the live tail, and which deltas the plane derives rather than records."
4
+ sidebar:
5
+ order: 4
6
+ ---
7
+
8
+ `watch` is a projection, not a bus. It reads committed journal entries and maps
9
+ each one onto a `ControlEvent`:
10
+
11
+ ```ts
12
+ interface ControlEvent {
13
+ readonly sequence: number
14
+ readonly kind: string
15
+ readonly runId?: string | undefined
16
+ readonly occurredAt: number
17
+ readonly payload: Json
18
+ }
19
+ ```
20
+
21
+ A consumer that subscribes after the fact still receives what it missed,
22
+ because the cursor is durable and the source is a table rather than a live
23
+ fan-out that forgets.
24
+
25
+ ## Partitions, and why a cursor needs a run
26
+
27
+ The journal is partitioned. Every run is a partition, and each plan gets one of
28
+ its own under the id `plan:<planId>`. Sequences are partition-local: the plan
29
+ partition and every run partition each start at 0.
30
+
31
+ One scalar cursor applied to all of them would therefore skip every lower
32
+ unseen sequence in every partition but the one the cursor came from. So
33
+ `watch` refuses either `afterSequence` or `afterCursor` without a `runId`:
34
+
35
+ ```text
36
+ InvalidInput: afterSequence: a watch cursor resumes one run, so it requires runId
37
+ ```
38
+
39
+ Exactly-once resumption is a promise about a scoped watch, and only about a
40
+ scoped one.
41
+
42
+ ## Snapshot, follow, and the handoff between them
43
+
44
+ `WatchFilter.follow` selects the delivery mode.
45
+
46
+ - `follow: false` asks for a finite snapshot of what is durable when the
47
+ request is handled. The stream ends. This is the mode a test and a one-shot
48
+ reader want.
49
+ - Omitting `follow` opens the live stream a UI subscribes to. It does not end.
50
+
51
+ The live stream is a handoff, not a deduplicated overlap. The projection
52
+ subscribes to journal changes first, then pins a high-water sequence for each
53
+ partition it can see. A row committed at or below its partition's mark is read
54
+ from the finite snapshot; a row above it is read from the buffered tail. An
55
+ entry from a partition the snapshot never read has no mark and passes straight
56
+ through.
57
+
58
+ The unscoped watch reads eight partition snapshots at a time and keeps one
59
+ reserved slot so the live tail is never starved behind snapshot work. An
60
+ unbounded merge would read every partition of an unbounded database at once,
61
+ which is an allocation a remote watcher could force.
62
+
63
+ ## What the plane writes
64
+
65
+ These entries are the control plane's own records. With the SQL runtime and
66
+ journal on the same database, each commits inside the same transaction as the
67
+ state change it describes.
68
+
69
+ | Kind | Written by | Partition |
70
+ | ---------------------------------------------------------------------- | --------------------------------------------------- | ------------------- |
71
+ | `control.plan.created` | `plan`, on creation or repair of a missing entry | `plan:<planId>` |
72
+ | `control.approval.approved`, `control.approval.denied` | `approve`, `deny` | the plan or the run |
73
+ | `control.run.accepted` | `run`, once the row exists | the run |
74
+ | `control.run.running` | `run`, when the executor took the launch | the run |
75
+ | `control.run.pending` | `run`, when it did not | the run |
76
+ | `control.run.resumed` | an approval on a node target, naming the delegation | the run |
77
+ | `control.run.resume` | `resume`, carrying the principal and the reason | the run |
78
+ | `control.run.cancel-requested` | `cancel`, carrying the principal and the reason | the run |
79
+ | `control.run.cancelled`, `control.run.completed`, `control.run.failed` | `cancel` and launch settlement | the run |
80
+ | `control.signal.delivered` | `signal` | the run |
81
+ | `control.steer.enqueued` | `steer` | the run |
82
+ | `control.steer.woke` | `steer`, when it ended a park | the run |
83
+ | `control.monitor.beat`, `control.monitor.healed` | `Monitor.run` | the run |
84
+
85
+ `plan` commits the card, idempotency key, approval token and creation entry in
86
+ one journal transaction. A keyed retry returns the stored card and checks its
87
+ partition for the creation entry. If an older write left that entry missing,
88
+ the retry appends it once before returning.
89
+
90
+ The memory runtime publishes one card per key, including concurrent requests.
91
+ It cannot roll back its maps with a journal transaction. A keyed retry repairs
92
+ a failed creation entry while retaining the original card.
93
+
94
+ ## What the plane derives
95
+
96
+ Two kinds are computed from entries other packages wrote, and are emitted
97
+ beside their source entry rather than recorded:
98
+
99
+ | Derived kind | Derived from | Module |
100
+ | ------------------------- | ------------------------------------------------------------- | ---------- |
101
+ | `control.run.lineage` | `flows.engine.run-decision`, `flows.time-travel.fork-created` | `Lineage` |
102
+ | `control.steer.delivered` | `flows/notifications/Promoted` | `Steering` |
103
+
104
+ Deriving rather than re-recording is what keeps the halves honest. The boundary
105
+ that delivers a steer runs in the agent process, not this one, so a control
106
+ plane that wrote its own delivery record would be asserting a fact it did not
107
+ observe.
108
+
109
+ Each derived event carries its source sequence. `watch` also assigns a
110
+ composite `cursor` that distinguishes members of an expansion. Checkpoint
111
+ `event.cursor` and resume with `afterCursor` to retain unconsumed deltas.
112
+ `afterSequence` skips the whole source entry, including its deltas. Expansion
113
+ runs after the snapshot-to-tail handoff.
114
+
115
+ `Lineage.derive`, `Lineage.expand`, `Steering.derive`, and `Steering.expand`
116
+ are exported, so a client reading the journal directly reaches the same
117
+ conclusions the server does.
118
+
119
+ The two foreign event types are named as strings rather than imported. A
120
+ control plane reads journals, not engines, and one that depended on the engine
121
+ could not project a journal a different engine wrote.
122
+
123
+ ## Where to go next
124
+
125
+ - [Watch a run's events](../guides/watch-a-run.md): the projection as a task.
126
+ - [Run lineage](./lineage.md): what a `control.run.lineage` delta says.
127
+ - [Steer a running agent](../guides/steer-a-run.md): the two moments a steer
128
+ has, and their two writers.
129
+
130
+ ## Versioned lifecycle and approval producer contract
131
+
132
+ `ControlFacts` exports the version-1 control producer contract and the shared
133
+ pure run/approval fold consumed by gateway snapshots and subscriptions. This
134
+ version is independent of the harness transcript's `journalVersion` and of
135
+ journal cursor generations. Existing event kind names and source identities
136
+ remain unchanged; old consumers can continue reading their original fields.
137
+
138
+ A lifecycle fact adds `{factVersion: 1, baseline, run}` to the usual event
139
+ payload. `run` is the complete, detached `RunSummary` returned by that fenced
140
+ control write. `control.run.accepted` starts a `created` baseline. A first
141
+ upgraded status, resume claim, pending handoff, or reconciliation starts a
142
+ `legacy` baseline at its own committed sequence. Earlier history is retained
143
+ without claiming that lost transitions have been reconstructed. Unknown or
144
+ missing lifecycle facts invalidate continuous coverage until another complete
145
+ snapshot establishes a new legacy baseline. No read fabricates migration events.
146
+
147
+ `ControlFacts.commitRun` commits a control write and its fact with
148
+ `Journal.transact`. `AgentSession` uses it for terminal/parked status and resume
149
+ claims; failure does not leave a newer status with no event. `ControlLive`'s
150
+ normal admission, resume, steer, and cancellation transactions carry the same
151
+ facts, and its exceptional launch settlement and terminal reconciliation also
152
+ join a journal transaction. Low-level `ControlRuntime` calls remain available
153
+ for ownership mechanics and legacy integrations; calling them outside these
154
+ producer boundaries does not establish event coverage.
155
+
156
+ `commitApprovalRequest` validates and captures the full request, then commits
157
+ its token registration and `control.approval.requested` event together. A
158
+ resolved token does not emit a new pending request. Decisions add
159
+ `{factVersion: 1, tokenId, approvalTarget}` to their existing payload and commit
160
+ with the decision, grant, idempotency receipt, and durable node-resume intent.
161
+ The pure fold joins current requests and decisions by run/request identity and
162
+ digest. Repeated requests do not reopen a decision. Legacy records retain their
163
+ historical read contract; only legacy requests are eligible for unnamed legacy
164
+ decision fallback.
165
+
166
+ A request a runaway guard makes carries an optional `incident`
167
+ (`ControlFacts.GuardIncident`): its `Runaway` or `Stuck` classification, the
168
+ guard `source` (`tokens`, `usd`, `latency`, `model-call`, `tool-call`, `cell`), the
169
+ triggering `message`, and the numbers frozen when it tripped (`used`,
170
+ `reserved`, `max`, `next`, the `allowance` Continue authorizes, and the
171
+ timed-out `subject`). A host that re-parks the run reuses the recorded request
172
+ and its incident rather than measuring again.
173
+
174
+ These atomic guarantees require the SQL control runtime and journal to share
175
+ the same database/writer, as the production control composition does. The
176
+ in-memory test runtime has no transactional rollback protocol. `SqlJournal`
177
+ publishes committed rows after the owning writer's COMMIT; a failed insert or
178
+ outer transaction publishes no fact. Followers also replay from disk, so a
179
+ process dying after commit and before an in-process notification does not lose
180
+ the recorded transition.
181
+
182
+ This is a control-plane boundary, not a claim that all runtime state is now a
183
+ control-event fold. Native lifecycle observations commit with their own fenced
184
+ state in the engine database. The durable engine bridge copies those facts and
185
+ an authenticated `control.engine.bound` root binding into the control journal.
186
+ Gateway snapshots and subscriptions use the shared `ExecutionFact` fold to
187
+ compare that evidence with the executor's coherent root/current-round view.
188
+ The separate `executionProvenance` reports native `events`, `legacy-observation`
189
+ or `unverified-observation`; `lifecycleProvenance` still labels the executor
190
+ overlay `engine-observed` rather than calling it control replay. Bridge lag,
191
+ missing bindings, generation gaps and unknown versions retain explicit fallback.
192
+ The two databases do not become one transaction, and operational ownership,
193
+ heartbeats and protected resolver credentials retain their native authorities.
194
+
195
+ Authorized native cell calls also commit `flows.harness.call-fact.v1` invocation
196
+ and controller-result facts in their owning action transactions. The immutable
197
+ native journal is their outbox through that same bridge. Shared call projections
198
+ prefer committed facts over matching identified telemetry while preserving
199
+ display identities. Legacy trace producer hashes remain unchanged; unidentified
200
+ calls keep their limited fallback, and low-level custom ports without these
201
+ annotations remain legacy. Model deltas, printed output and other trace records
202
+ still use the best-effort channel. Durable call facts do not reconstruct missing
203
+ trace history or promise exactly-once external effects.
@@ -0,0 +1,128 @@
1
+ ---
2
+ title: "Receipts and idempotency"
3
+ description: "The five receipts a control mutation can answer, the bounded identity boundary every mutation crosses first, and why cancel and resume read a run's terminality before replaying a recorded receipt."
4
+ sidebar:
5
+ order: 2
6
+ ---
7
+
8
+ Every control mutation answers a `Receipt` rather than throwing on a second
9
+ ask. A receipt is the plane's whole answer to "did my request take effect, and
10
+ what happened to it?".
11
+
12
+ | Receipt | Meaning |
13
+ | ---------------- | --------------------------------------------------------------------------------------- |
14
+ | `Accepted` | This call admitted the mutation. Carries `receiptId`, and `runId` when a run exists. |
15
+ | `AlreadyApplied` | An earlier call under this key admitted the mutation. Carries the same `runId`. |
16
+ | `Parked` | The plan is waiting for an approval. Carries `planId` and `status: "waiting-approval"`. |
17
+ | `Conflict` | The key names a different intent than the one it was first used for. Carries a message. |
18
+ | `Terminal` | The run had already settled. Carries `runId` and the status it settled with. |
19
+
20
+ `plan` is the exception: it returns a `PlanCard`, because a plan is a value to
21
+ review rather than an outcome to acknowledge.
22
+
23
+ ## The identity boundary
24
+
25
+ Before a mutation waits on anything, it copies its own input and decodes the
26
+ copy. Nothing downstream ever sees the caller's object.
27
+
28
+ The copy admits only enumerable own data properties, so an accessor, a
29
+ `toJSON`, a sparse array, a symbol key, a cycle, a non-plain object, or
30
+ ill-formed text is refused with `InvalidInput` before a collaborator is
31
+ touched. That is not defensive tidying: a getter that returns one value to the
32
+ fingerprint and another to the write is the whole exploit.
33
+
34
+ The copy is also bounded:
35
+
36
+ | Bound | Value |
37
+ | ----------------------------------- | --------------------------------- |
38
+ | Canonical bytes | 4 MiB |
39
+ | Nesting depth | 128 |
40
+ | Total JSON values | 100,000 |
41
+ | Total array items and object fields | 100,000 |
42
+ | Idempotency key length | 1 to 1,024 well-formed characters |
43
+
44
+ ## What a key identifies
45
+
46
+ The durable key is the operation, the caller's key, and, when a principal is
47
+ present, a digest of that principal's stable `kind` and `id`. The stamped
48
+ `stampedAt` is deliberately excluded: it is a wall clock, and keeping it made a
49
+ bearer authenticated retry of one cancel look like a different mutation under
50
+ the same key.
51
+
52
+ Beside the key the runtime stores a **fingerprint**: a canonical SHA-256 digest
53
+ of the operation, the actor's `id` and `kind`, and the decoded mutation with
54
+ its two server-stamped `principal` fields removed. A second call whose
55
+ fingerprint matches replays the recorded receipt. A second call under the same
56
+ key with a different fingerprint answers `Conflict`, which is the honest answer
57
+ to "this key already means something else".
58
+
59
+ Only `input.principal` and `input.message.principal` are removed, and only at
60
+ their own depth. A `principal` nested inside a signal payload is part of the
61
+ fingerprint, because two signals that differ only there are two different
62
+ signals.
63
+
64
+ ## Replay, and the two mutations that do not replay it
65
+
66
+ For a mutation that changes something once, the recorded receipt is everything:
67
+ it is the proof the change was made, and replaying it is the whole guarantee.
68
+
69
+ `cancel` and `resume` are different, because their receipt is an answer _about
70
+ a run_, and the run moves on afterwards. Both read the run's terminality first,
71
+ before the idempotency lookup:
72
+
73
+ - `cancel` runs with replay disabled. A second ask re-executes, reads the run,
74
+ and answers what is true now. A replayed answer would describe the moment of
75
+ the first ask, so a caller could keep cancelling a run that never moved and
76
+ keep being told it was cancelled.
77
+ - `resume` reads terminality before the replay for the same reason. Asking to
78
+ restart a completed run must answer `Terminal`, not `AlreadyApplied`, which
79
+ describes an earlier call and says nothing about the run you named.
80
+
81
+ Cancellation needs no receipt to be idempotent. The run's own terminality is a
82
+ stronger guarantee, and it is what the second ask reads.
83
+
84
+ ## Run admission precedes execution
85
+
86
+ The run row, approval facts, and idempotency receipt commit before the plane
87
+ calls the executor. `Accepted` proves admission; the run's current status proves
88
+ whether execution started or finished. Another connection can read the committed
89
+ run as soon as the executor receives it.
90
+
91
+ If the executor refuses the launch, the plane records the run as failed and
92
+ retains its admission receipt. Repeating the same key returns `AlreadyApplied`
93
+ for that run. An explicit retry uses a new key. A failed admission commit never
94
+ reaches the executor.
95
+
96
+ If the admitting process dies after the commit and before the executor takes
97
+ the run, the run stays `accepted` under that process. Repeating the same key
98
+ once the process's heartbeat lease has lapsed claims the run and launches it,
99
+ then answers `AlreadyApplied` as usual. A live admitter keeps its run.
100
+
101
+ ## A parked receipt is not recorded
102
+
103
+ `run` against an undecided plan answers `Parked` and records nothing. That is
104
+ what lets the [Quickstart](../quickstart.md) launch, park, approve, and launch
105
+ again under one key: the key was never spent on the refusal.
106
+
107
+ ## Where a key comes from
108
+
109
+ A caller chooses it, and the choice is a contract with itself:
110
+
111
+ - A CLI or an operator uses something that names the intent:
112
+ `deploy:v1.4.0`, `cancel:run-17`.
113
+ - A [channel](../guides/ingest-a-webhook.md) uses the platform's own delivery
114
+ id, namespaced by the channel, so a webhook redelivery is the same mutation.
115
+ - A [monitor](../guides/monitor-runs.md) uses
116
+ `monitor:<monitorId>:<remedy>:<runId>:<beat>`, so two monitors watching one
117
+ run never share a key.
118
+
119
+ Reusing a key for a different intent is a caller mistake the plane reports
120
+ rather than absorbs.
121
+
122
+ ## Where to go next
123
+
124
+ - [Ownership, fences, and claims](./ownership.md): the other reason a mutation
125
+ refuses.
126
+ - [Cancel a run, and restart one](../guides/cancel-and-resume.md): the two
127
+ verbs that read terminality first.
128
+ - [Troubleshooting](../troubleshooting.md): what each refusal means in practice.
@@ -0,0 +1,284 @@
1
+ ---
2
+ title: "Gate work behind an approval"
3
+ description: "Take a plan approval before a run starts, and a node approval inside a run that already started: what each gate pins, how a step registers its own request, and what a decision restarts."
4
+ sidebar:
5
+ order: 1
6
+ ---
7
+
8
+ Two gates carry an approval, and they are different mechanisms worth keeping
9
+ apart. A **plan approval** decides whether a run starts. A **node approval**
10
+ decides something inside a run that already started.
11
+
12
+ Both are decided with the same two verbs, `approve` and `deny`, and both pin
13
+ what was reviewed with a digest and an envelope, so a decision cannot be
14
+ re-aimed at a different request afterwards.
15
+
16
+ ## Who may decide
17
+
18
+ An approval payload identifies the request; it is not permission to decide it.
19
+ Authentication supplies a trusted `Principal`, while `ApprovalAuthority` supplies
20
+ the independent host policy. `Control.approve` and `deny` check that policy before
21
+ target reads or receipt replay, and the runtime checks it again at resolution.
22
+ Refusal is `Unauthorized`; unavailable policy storage fails closed with
23
+ `PersistenceError`.
24
+
25
+ The default policy recognizes only the fixed local identity `local/operator`.
26
+ The memory adapter's own default also recognizes its `memory/test` identity, and
27
+ no production policy does. A custom `principal` option,
28
+ an actor's `kind`, or a valid bearer credential does not delegate approval.
29
+ Hosts may replace the policy explicitly:
30
+
31
+ ```ts
32
+ import * as ApprovalAuthority from "@smthrs/control/ApprovalAuthority"
33
+ import * as SqlControlRuntime from "@smthrs/control/SqlControlRuntime"
34
+ import { Effect } from "effect"
35
+
36
+ const runtime = Effect.gen(function*() {
37
+ const approvalAuthority = yield* ApprovalAuthority.make([
38
+ {
39
+ principal: { id: "local", kind: "operator" },
40
+ scopes: ["once", "run", "remembered"],
41
+ targets: ["Plan", "Node"]
42
+ },
43
+ {
44
+ principal: { id: "release-bot", kind: "agent" },
45
+ scopes: ["once"],
46
+ targets: ["Node"]
47
+ }
48
+ ])
49
+ return yield* SqlControlRuntime.make({ approvalAuthority })
50
+ })
51
+ ```
52
+
53
+ Delegations bind an exact identity tuple, target kind, and approval scope. Scopes
54
+ are not hierarchical. A delegated actor may deny its listed target kinds without
55
+ installing any grant. Delegation is reusable: `scopes: ["once"]` permits
56
+ once-scoped grants; it is not a single-use delegation. A custom host policy must
57
+ enforce expiry, revocation, or one-use delegation when needed.
58
+ A custom `ApprovalAuthority.Service` can additionally
59
+ restrict specific runs, plans, or envelopes. Keep that policy bounded and safe
60
+ inside the writer transaction; never invoke Control recursively from it.
61
+
62
+ Transport adapters must authenticate identities and overwrite caller-supplied
63
+ principal fields. Direct Control/runtime references and database access are
64
+ trusted host capabilities, not endpoints for untrusted input. In particular,
65
+ `installBulkGrant` is a storage port, not an authorization API. Use Control for
66
+ an atomic durable decision, grant, journal entry, and receipt.
67
+
68
+ Default MCP surfaces omit approval decisions and auto-approving starts. The
69
+ compatibility server requires both host tool exposure and a separately delegated
70
+ agent identity. Delegating an agent to approve is automated approval, not an
71
+ independent human review. None of these checks sandboxes a caller that already
72
+ has arbitrary host shell, code execution, or direct database write access.
73
+ On loopback, `smthrs serve` prints a fresh per-session approval token to
74
+ stderr (even with `--quiet`). Send `Authorization: Bearer <token>` to approve,
75
+ deny, or answer a human wait over `/rpc` or `/projections`, HTTP or WebSocket.
76
+ Calls without that token retain read access, but cannot decide approvals.
77
+ Keep the token away from agents: a caller holding it has operator approval authority.
78
+ A network bind still requires the configured bearer credential for all protected calls.
79
+
80
+ ## Gate the launch: a plan approval
81
+
82
+ `plan` produces the reviewable card. `run` against an undecided plan starts
83
+ nothing and says so:
84
+
85
+ ```ts
86
+ import { Control } from "@smthrs/control/Control"
87
+ import * as Effect from "effect/Effect"
88
+
89
+ const gate = Effect.gen(function*() {
90
+ const control = yield* Control
91
+
92
+ const card = yield* control.plan({ flowId: "ops/Deploy", input: { build: "v1.4.0" } })
93
+
94
+ const launch = {
95
+ _tag: "Plan" as const,
96
+ planId: card.planId,
97
+ digest: card.digest,
98
+ envelope: card.envelope
99
+ }
100
+
101
+ // { _tag: "Parked", planId, status: "waiting-approval" }
102
+ yield* control.run({ ...launch, idempotencyKey: "deploy:v1.4.0" })
103
+
104
+ // The card carries the exact approval payload, so a reviewer resubmits it
105
+ // unchanged rather than reconstructing authority on the client.
106
+ yield* control.approve(card.approval)
107
+
108
+ // Now the same call launches.
109
+ return yield* control.run({ ...launch, idempotencyKey: "deploy:v1.4.0" })
110
+ })
111
+ ```
112
+
113
+ Deny it instead and the launch refuses with `PlanDenied` rather than parking.
114
+ A denied plan cannot be revived: create and approve a new plan.
115
+
116
+ ### What the card pins
117
+
118
+ `PlanCard.digest` covers the flow id, the decoded input, the envelope, the
119
+ deploy-class flag, and the digest of the persisted plan. `nodes` is the keyed
120
+ node graph the plan phase produced, and it is part of the digest because
121
+ "approve this flow with this input" and "approve this graph of keyed work" are
122
+ different promises: a change that re-keys a node changes what will run, and an
123
+ approval taken against the old graph must not authorize the new one. A host
124
+ that has not built a graph reports an empty one and loses nothing.
125
+
126
+ `Envelope` is the authority itself: the capabilities, the collaborator flows,
127
+ the budget, and the placement being granted.
128
+
129
+ | Submitted value differs from the stored one | Failure |
130
+ | ------------------------------------------- | --------------------------------------------------------------------- |
131
+ | `digest` | `PlanDigestMismatch`, carrying `expected` and `actual` |
132
+ | `envelope` | `EnvelopeMismatch`, compared by canonical bytes rather than key order |
133
+
134
+ `GrantScope` says how long the grant lasts: `once`, `run`, or `remembered`. The
135
+ card defaults to `run`.
136
+
137
+ ## Gate a step: a node approval
138
+
139
+ A node approval belongs to a run that already exists, and the _step_ registers
140
+ it. Nothing outside the run asks for it, so `approve` can only decide a request
141
+ a step actually made.
142
+
143
+ The step registers the token and handles its explicit `Pending`, `Approved`, or
144
+ `Denied` tag. Only approval may open the gate:
145
+
146
+ ```ts
147
+ import * as ControlRuntime from "@smthrs/control/ControlRuntime"
148
+ import type * as ControlSchema from "@smthrs/control/ControlSchema"
149
+ import { Action, DurableDeferred, FlowRuntime } from "@smthrs/flow"
150
+ import * as Effect from "effect/Effect"
151
+ import * as Schema from "effect/Schema"
152
+
153
+ /** The step that asks, declared like any other. */
154
+ const Clearance = Action.make("ops/Clearance", {
155
+ payload: { requestId: Schema.String },
156
+ success: Schema.String,
157
+ error: Schema.Union([ControlRuntime.ApprovalPending, ControlRuntime.ApprovalDenied])
158
+ })
159
+
160
+ /**
161
+ * The wait point the park awaits. Nothing ever completes it, which is the
162
+ * point: an approval is not a value arriving, it is a decision recorded
163
+ * somewhere else. The wake is the run being re-driven, and the step reads the
164
+ * decision off the token rather than off this deferred.
165
+ */
166
+ const clearanceGate = DurableDeferred.make("Approval/ship-clearance", { success: Schema.Json })
167
+
168
+ /** The exact request this step registers and an operator decides. */
169
+ const request = (runId: string): Extract<ControlSchema.ApprovalTarget, { readonly _tag: "Node" }> => ({
170
+ _tag: "Node",
171
+ runId: runId as ControlSchema.RunId,
172
+ requestId: "ship-clearance",
173
+ digest: "ops/Ship:clearance",
174
+ envelope: { capabilities: [], flows: [], budget: {} }
175
+ })
176
+
177
+ const clearance = Clearance.toLayer(({ requestId }) =>
178
+ Effect.gen(function*() {
179
+ const instance = yield* FlowRuntime.FlowInstance
180
+ const runtime = yield* ControlRuntime.ControlRuntime
181
+ const target = request(instance.executionId)
182
+ let token = yield* Effect.orDie(runtime.registerApproval(target))
183
+ if (token._tag === "Pending") {
184
+ yield* FlowRuntime.annotateWaiting({ reason: "approval", token: requestId })
185
+ yield* DurableDeferred.await(clearanceGate)
186
+ // Completing a wait point is not approval. Read the durable answer.
187
+ token = yield* Effect.orDie(runtime.registerApproval(target))
188
+ }
189
+ return (yield* ControlRuntime.requireApproved(token)).tokenId
190
+ })
191
+ )
192
+ ```
193
+
194
+ `registerApproval` is idempotent. It creates the token on the first attempt and
195
+ answers the existing one afterwards. `requireApproved` succeeds only for
196
+ `Approved`, fails with `ApprovalDenied` on denial, and fails with
197
+ `ApprovalPending` if no decision exists. Include these errors in the enclosing
198
+ flow's error schema. Use `Node.andThen(clearance, next)` to gate all of `next`.
199
+ Both terminal tags carry `decisionPrincipal` and `decidedAt`; `Approved` also
200
+ carries the installed grant's `scope`. A denied token cannot be changed to
201
+ approved. A changed target digest or envelope is refused.
202
+
203
+ The token describes a decision; it is not an authentication credential and
204
+ `requireApproved` does not install permissions or establish who may approve.
205
+ Keep approval authority separate from permission to execute a workflow.
206
+
207
+ ### Upgrading an unfinished run
208
+
209
+ Migration 6004 adds explicit durable decisions. Old pending tokens remain
210
+ pending. An old resolved token did not retain whether it was approved or
211
+ denied, so reads fail with an actionable `PersistenceError` instead of guessing
212
+ from grants or journal projections. Preserve the database for review and use a
213
+ new run with a new approval request. This is refusal, not automatic migration of
214
+ unfinished executions. The action and flow error schema changes also require
215
+ newly planned/approved work; they do not retrofit old cached action results.
216
+
217
+ The complete example, with the two drives around the decision, is
218
+ [`examples/src/18-approval-and-signal.ts`](https://github.com/smithersai/smithers/blob/main/examples/src/18-approval-and-signal.ts).
219
+
220
+ ### Decide it from outside the run
221
+
222
+ ```ts
223
+ const receipt = yield * control.approve({
224
+ target: request("run-17"),
225
+ scope: "once",
226
+ idempotencyKey: "approve:ship-clearance"
227
+ })
228
+ ```
229
+
230
+ The node digest is deliberately not the plan's. The two gates are separate
231
+ mechanisms, and a run that was never planned still has steps worth gating.
232
+
233
+ ## What a decision does
234
+
235
+ For a new decision, the adapter contract follows this order:
236
+
237
+ | Step | Plan target | Node target |
238
+ | ------------------------------ | ------------------------------------------------------------------------------ | ----------------------------------------------------------- |
239
+ | Authenticate and authorize | Authenticate the principal; `authorizeApproval` before reads or receipt replay | same |
240
+ | Look the token up | `lookupApproval` refuses an unknown or already-resolved token | same |
241
+ | Resolve the token | `resolveApproval` exactly once, with an authority recheck | same |
242
+ | Install the grant, on approval | `installBulkGrant` with the submitted envelope and scope | same |
243
+ | Journal the decision | `control.approval.approved` or `.denied` on `plan:<planId>` | the same kinds, on the run |
244
+ | Record the restart | nothing to restart | records a resume delegation, journals `control.run.resumed` |
245
+
246
+ Commit the decision, grant, journal entry, receipt, and any node resume delegation
247
+ atomically. Resolution must not require an installed grant or a flushed journal
248
+ decision. An authority refusal at resolution prevents grant installation.
249
+
250
+ A decision on a node target restarts the run the ask parked, in the same call.
251
+ Nothing else wakes that run: without the restart it would sit at
252
+ `waiting-approval` holding a decision it never reads, and a denial it never
253
+ learns about would decide nothing.
254
+
255
+ The restart is _recorded_, not performed, and the deciding plane does not claim
256
+ the row. See [a resume is a delegation before it is a claim](../concepts/ownership.md).
257
+
258
+ A decision on a run that has already settled answers `Terminal` and decides
259
+ nothing, read before the idempotency replay so the answer describes the run
260
+ rather than an earlier call.
261
+
262
+ ## Refusals
263
+
264
+ | Failure | Cause |
265
+ | -------------------- | ------------------------------------------------------------------------ |
266
+ | `Unauthorized` | The authenticated caller lacks approval authority for this target/scope. |
267
+ | `PlanNotFound` | No plan or node token with this id. Its `message` names the next action. |
268
+ | `PlanDenied` | The plan was denied. Create and approve a new one. |
269
+ | `PlanDigestMismatch` | The submitted digest is not the stored one. |
270
+ | `EnvelopeMismatch` | The submitted envelope is not the stored one. |
271
+ | `AlreadyResolved` | This token already carries a terminal decision. |
272
+ | `RunNotFound` | A node target names a run this plane cannot find. |
273
+
274
+ `AlreadyResolved` is also the durable evidence that a decision stuck: a second
275
+ `lookupApproval` on a decided token refuses rather than answering.
276
+
277
+ ## Where to go next
278
+
279
+ - [Receipts and idempotency](../concepts/receipts.md): why the launch and the
280
+ retry share one key.
281
+ - [Ownership, fences, and claims](../concepts/ownership.md): what the recorded
282
+ resume reaches.
283
+ - [Plan, approve, run on smithers.sh](/docs/guides/plan-approve-run/): the same
284
+ gate from the CLI.