cloving 1.3.0 → 1.5.0

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 (484) hide show
  1. package/README.md +329 -215
  2. package/dist/src/agent-harness-shell.d.ts +2 -1
  3. package/dist/src/agent-harness-shell.js +8 -5
  4. package/dist/src/authority/command-wait-checks.d.ts +8 -0
  5. package/dist/src/authority/command-wait-checks.js +88 -0
  6. package/dist/src/authority/command-wait-results.d.ts +15 -0
  7. package/dist/src/authority/command-wait-results.js +73 -0
  8. package/dist/src/authority/command-wait-view.d.ts +21 -0
  9. package/dist/src/authority/command-wait-view.js +34 -0
  10. package/dist/src/authority/command-waits.d.ts +39 -0
  11. package/dist/src/authority/command-waits.js +97 -0
  12. package/dist/src/authority/commands.js +22 -1
  13. package/dist/src/authority/dispatch-decision.d.ts +8 -0
  14. package/dist/src/authority/dispatch-decision.js +26 -1
  15. package/dist/src/authority/effect-requests.js +6 -3
  16. package/dist/src/authority/effect-results.js +4 -2
  17. package/dist/src/authority/history-state.d.ts +2 -0
  18. package/dist/src/authority/history-state.js +13 -1
  19. package/dist/src/authority/instants.d.ts +1 -0
  20. package/dist/src/authority/instants.js +21 -0
  21. package/dist/src/authority/reduction-state.d.ts +2 -0
  22. package/dist/src/authority/reduction-state.js +5 -0
  23. package/dist/src/authority/reduction.js +24 -0
  24. package/dist/src/authority/status-input.d.ts +2 -0
  25. package/dist/src/authority/status.d.ts +2 -0
  26. package/dist/src/authority/status.js +1 -0
  27. package/dist/src/authority/stop-sequence.d.ts +1 -0
  28. package/dist/src/authority/stop-sequence.js +2 -1
  29. package/dist/src/authority/transitions.d.ts +6 -4
  30. package/dist/src/authority/transitions.js +7 -0
  31. package/dist/src/broker/actor/authority-health.d.ts +9 -0
  32. package/dist/src/broker/actor/authority-health.js +24 -0
  33. package/dist/src/broker/actor/authority-queries.d.ts +2 -1
  34. package/dist/src/broker/actor/authority-queries.js +14 -5
  35. package/dist/src/broker/actor/authority-receipts.js +1 -1
  36. package/dist/src/broker/actor/authority-runtime.d.ts +4 -2
  37. package/dist/src/broker/actor/authority-runtime.js +29 -20
  38. package/dist/src/broker/actor/authority-session.d.ts +6 -19
  39. package/dist/src/broker/actor/authority-session.js +66 -88
  40. package/dist/src/broker/actor/authority-settings.d.ts +2 -1
  41. package/dist/src/broker/actor/authority-settings.js +6 -3
  42. package/dist/src/broker/actor/authority-upgrades.d.ts +23 -2
  43. package/dist/src/broker/actor/authority-upgrades.js +8 -2
  44. package/dist/src/broker/actor/authority-watchdog.d.ts +6 -2
  45. package/dist/src/broker/actor/authority-watchdog.js +31 -9
  46. package/dist/src/broker/actor/authority.js +3 -1
  47. package/dist/src/broker/actor/delivery-intents.js +2 -0
  48. package/dist/src/broker/actor/effect-delivery.d.ts +1 -1
  49. package/dist/src/broker/actor/effect-delivery.js +15 -2
  50. package/dist/src/broker/actor/effect-plan.js +11 -2
  51. package/dist/src/broker/actor/effect-queries.d.ts +4 -0
  52. package/dist/src/broker/actor/effect-queries.js +9 -1
  53. package/dist/src/broker/actor/effect-turn.js +2 -0
  54. package/dist/src/broker/actor/projection-memory.d.ts +43 -0
  55. package/dist/src/broker/actor/projection-memory.js +207 -0
  56. package/dist/src/broker/actor/runtime-parts.d.ts +5 -0
  57. package/dist/src/broker/actor/runtime-parts.js +8 -3
  58. package/dist/src/broker/actor/stop-plan.js +9 -3
  59. package/dist/src/broker/actor/stop-turn.d.ts +2 -0
  60. package/dist/src/broker/actor/stop-turn.js +24 -11
  61. package/dist/src/broker/actor/turn-draft.d.ts +1 -0
  62. package/dist/src/broker/actor/turn-draft.js +4 -0
  63. package/dist/src/broker/actor/turn-state.js +5 -1
  64. package/dist/src/broker/authority-admission.js +60 -0
  65. package/dist/src/broker/authority-restore.d.ts +2 -0
  66. package/dist/src/broker/authority-restore.js +35 -1
  67. package/dist/src/broker/command-wait-restore.d.ts +31 -0
  68. package/dist/src/broker/command-wait-restore.js +86 -0
  69. package/dist/src/broker/decision-restore.d.ts +1 -1
  70. package/dist/src/broker/decision-restore.js +1 -0
  71. package/dist/src/broker/effect-upgrade.d.ts +3 -0
  72. package/dist/src/broker/effect-upgrade.js +11 -2
  73. package/dist/src/cli/arguments.d.ts +13 -0
  74. package/dist/src/cli/arguments.js +43 -3
  75. package/dist/src/cli/chat-stores.d.ts +2 -0
  76. package/dist/src/cli/chat-stores.js +113 -0
  77. package/dist/src/cli/client.d.ts +7 -2
  78. package/dist/src/cli/client.js +13 -7
  79. package/dist/src/cli/command-requests.d.ts +46 -0
  80. package/dist/src/cli/command-requests.js +84 -0
  81. package/dist/src/cli/command-waits.d.ts +9 -0
  82. package/dist/src/cli/command-waits.js +51 -0
  83. package/dist/src/cli/commands.js +70 -88
  84. package/dist/src/cli/config-file.d.ts +2 -2
  85. package/dist/src/cli/config-file.js +4 -2
  86. package/dist/src/cli/config-notice.js +5 -2
  87. package/dist/src/cli/doctor-retention.d.ts +2 -0
  88. package/dist/src/cli/doctor-retention.js +93 -0
  89. package/dist/src/cli/doctor-workflows.d.ts +5 -2
  90. package/dist/src/cli/doctor-workflows.js +12 -7
  91. package/dist/src/cli/doctor.js +11 -9
  92. package/dist/src/cli/harness-probes.d.ts +2 -0
  93. package/dist/src/cli/harness-probes.js +1 -1
  94. package/dist/src/cli/harness-processes.d.ts +6 -0
  95. package/dist/src/cli/harness-processes.js +36 -0
  96. package/dist/src/cli/init-command.d.ts +9 -0
  97. package/dist/src/cli/init-command.js +52 -0
  98. package/dist/src/cli/init-prompt-command.d.ts +10 -0
  99. package/dist/src/cli/init-prompt-command.js +145 -0
  100. package/dist/src/cli/init-prompt.d.ts +35 -0
  101. package/dist/src/cli/init-prompt.js +74 -0
  102. package/dist/src/cli/init-selection.d.ts +13 -0
  103. package/dist/src/cli/init-selection.js +75 -0
  104. package/dist/src/cli/main.d.ts +2 -0
  105. package/dist/src/cli/main.js +37 -7
  106. package/dist/src/cli/mcp-server.d.ts +1 -0
  107. package/dist/src/cli/mcp-server.js +156 -0
  108. package/dist/src/cli/mcp-tools.d.ts +15 -0
  109. package/dist/src/cli/mcp-tools.js +242 -0
  110. package/dist/src/cli/queue-arguments.d.ts +15 -0
  111. package/dist/src/cli/queue-arguments.js +31 -0
  112. package/dist/src/cli/queue-render.d.ts +3 -0
  113. package/dist/src/cli/queue-render.js +26 -0
  114. package/dist/src/cli/reload-render.d.ts +2 -0
  115. package/dist/src/cli/reload-render.js +12 -0
  116. package/dist/src/cli/render.d.ts +1 -1
  117. package/dist/src/cli/render.js +54 -38
  118. package/dist/src/cli/report-ci-jobs.d.ts +23 -0
  119. package/dist/src/cli/report-ci-jobs.js +103 -0
  120. package/dist/src/cli/report-ci-usage.d.ts +9 -0
  121. package/dist/src/cli/report-ci-usage.js +185 -0
  122. package/dist/src/cli/report-ci.d.ts +13 -0
  123. package/dist/src/cli/report-ci.js +74 -0
  124. package/dist/src/cli/report-closures.d.ts +6 -0
  125. package/dist/src/cli/report-closures.js +97 -0
  126. package/dist/src/cli/report-flow.d.ts +7 -0
  127. package/dist/src/cli/report-flow.js +122 -0
  128. package/dist/src/cli/report-github-reader.d.ts +29 -0
  129. package/dist/src/cli/report-github-reader.js +157 -0
  130. package/dist/src/cli/report-github.d.ts +15 -8
  131. package/dist/src/cli/report-github.js +131 -150
  132. package/dist/src/cli/report-operator.d.ts +3 -0
  133. package/dist/src/cli/report-operator.js +40 -0
  134. package/dist/src/cli/report-ready.d.ts +3 -0
  135. package/dist/src/cli/report-ready.js +96 -0
  136. package/dist/src/cli/report-text.d.ts +9 -0
  137. package/dist/src/cli/report-text.js +33 -0
  138. package/dist/src/cli/report.d.ts +1 -0
  139. package/dist/src/cli/report.js +104 -27
  140. package/dist/src/cli/runtime-arguments.d.ts +18 -3
  141. package/dist/src/cli/runtime-arguments.js +29 -20
  142. package/dist/src/cli/schedule-arguments.d.ts +14 -0
  143. package/dist/src/cli/schedule-arguments.js +26 -0
  144. package/dist/src/cli/schedule-render.d.ts +22 -0
  145. package/dist/src/cli/schedule-render.js +73 -0
  146. package/dist/src/cli/terminal-text.d.ts +3 -0
  147. package/dist/src/cli/terminal-text.js +28 -0
  148. package/dist/src/cli/toolchain.d.ts +1 -0
  149. package/dist/src/cli/toolchain.js +1 -0
  150. package/dist/src/cli/usage-render.d.ts +1 -0
  151. package/dist/src/cli/usage-render.js +28 -1
  152. package/dist/src/cli/usage.d.ts +2 -2
  153. package/dist/src/cli/usage.js +89 -11
  154. package/dist/src/cli/watch-actions.js +2 -2
  155. package/dist/src/cli/watch-api-document.d.ts +430 -0
  156. package/dist/src/cli/watch-api-document.js +291 -0
  157. package/dist/src/cli/watch-api-run.d.ts +56 -0
  158. package/dist/src/cli/watch-api-run.js +112 -0
  159. package/dist/src/cli/watch-api.d.ts +17 -0
  160. package/dist/src/cli/watch-api.js +230 -0
  161. package/dist/src/cli/watch-board-view.js +19 -2
  162. package/dist/src/cli/watch-board.d.ts +4 -2
  163. package/dist/src/cli/watch-board.js +53 -43
  164. package/dist/src/cli/watch-card-forms.d.ts +1 -1
  165. package/dist/src/cli/watch-card-forms.js +11 -10
  166. package/dist/src/cli/watch-client.d.ts +1 -1
  167. package/dist/src/cli/watch-client.js +2 -11
  168. package/dist/src/cli/watch-header-view.d.ts +1 -5
  169. package/dist/src/cli/watch-header-view.js +17 -21
  170. package/dist/src/cli/watch-model.d.ts +5 -1
  171. package/dist/src/cli/watch-model.js +20 -11
  172. package/dist/src/cli/watch-page.js +15 -15
  173. package/dist/src/cli/watch-phases.d.ts +12 -3
  174. package/dist/src/cli/watch-phases.js +68 -11
  175. package/dist/src/cli/watch-pr-board.js +1 -1
  176. package/dist/src/cli/watch-pr-command.d.ts +1 -0
  177. package/dist/src/cli/watch-pull-request.js +18 -8
  178. package/dist/src/cli/watch-reads.d.ts +3 -0
  179. package/dist/src/cli/watch-reads.js +22 -20
  180. package/dist/src/cli/watch-render.d.ts +15 -0
  181. package/dist/src/cli/watch-run-input.d.ts +0 -1
  182. package/dist/src/cli/watch-run-input.js +0 -13
  183. package/dist/src/cli/watch-server.js +24 -10
  184. package/dist/src/cli/watch-view.d.ts +0 -3
  185. package/dist/src/cli/watch-view.js +13 -13
  186. package/dist/src/cli.js +73 -12
  187. package/dist/src/command-wait.d.ts +4 -0
  188. package/dist/src/command-wait.js +45 -0
  189. package/dist/src/configuration-change.d.ts +7 -0
  190. package/dist/src/configuration-change.js +14 -0
  191. package/dist/src/configuration-display.js +2 -0
  192. package/dist/src/configuration-schema.d.ts +14 -0
  193. package/dist/src/configuration-schema.js +6 -2
  194. package/dist/src/daemon/attachment-file.d.ts +6 -0
  195. package/dist/src/daemon/attachment-file.js +86 -0
  196. package/dist/src/daemon/attachments.d.ts +14 -0
  197. package/dist/src/daemon/attachments.js +17 -0
  198. package/dist/src/daemon/claude-recovery.d.ts +50 -0
  199. package/dist/src/daemon/claude-recovery.js +208 -0
  200. package/dist/src/daemon/claude-stream.d.ts +27 -0
  201. package/dist/src/daemon/claude-stream.js +63 -0
  202. package/dist/src/daemon/claude-turn.d.ts +11 -25
  203. package/dist/src/daemon/claude-turn.js +64 -81
  204. package/dist/src/daemon/claude-worker.d.ts +5 -4
  205. package/dist/src/daemon/claude-worker.js +127 -126
  206. package/dist/src/daemon/daemon-adapters.d.ts +9 -0
  207. package/dist/src/daemon/daemon-adapters.js +33 -5
  208. package/dist/src/daemon/daemon-loops.d.ts +5 -1
  209. package/dist/src/daemon/daemon-loops.js +18 -10
  210. package/dist/src/daemon/daemon.js +80 -61
  211. package/dist/src/daemon/github-attach.d.ts +2 -0
  212. package/dist/src/daemon/github-attach.js +222 -0
  213. package/dist/src/daemon/github-calls.js +1 -1
  214. package/dist/src/daemon/github-ci-filters.d.ts +6 -0
  215. package/dist/src/daemon/github-ci-filters.js +138 -0
  216. package/dist/src/daemon/github-ci-workflows.d.ts +10 -0
  217. package/dist/src/daemon/github-ci-workflows.js +148 -0
  218. package/dist/src/daemon/github-ci.js +22 -4
  219. package/dist/src/daemon/github-issue-methods.d.ts +2 -0
  220. package/dist/src/daemon/github-issue-methods.js +37 -2
  221. package/dist/src/daemon/github-merge-call.d.ts +2 -1
  222. package/dist/src/daemon/github-merge-call.js +10 -3
  223. package/dist/src/daemon/github-merge.js +52 -24
  224. package/dist/src/daemon/github-methods.js +18 -4
  225. package/dist/src/daemon/github-operation.d.ts +4 -0
  226. package/dist/src/daemon/github-operation.js +6 -0
  227. package/dist/src/daemon/github-worker.d.ts +4 -0
  228. package/dist/src/daemon/github-worker.js +6 -0
  229. package/dist/src/daemon/issue-queue.d.ts +4 -1
  230. package/dist/src/daemon/issue-queue.js +76 -78
  231. package/dist/src/daemon/merge-turns.d.ts +10 -0
  232. package/dist/src/daemon/merge-turns.js +37 -0
  233. package/dist/src/daemon/notify-events.d.ts +6 -0
  234. package/dist/src/daemon/notify-events.js +84 -0
  235. package/dist/src/daemon/notify-journal.js +1 -1
  236. package/dist/src/daemon/notify-message.d.ts +4 -1
  237. package/dist/src/daemon/notify-message.js +36 -0
  238. package/dist/src/daemon/notify.d.ts +1 -1
  239. package/dist/src/daemon/notify.js +5 -77
  240. package/dist/src/daemon/operation-journal.d.ts +1 -0
  241. package/dist/src/daemon/queue-control.d.ts +13 -0
  242. package/dist/src/daemon/queue-control.js +86 -0
  243. package/dist/src/daemon/queue-journal.d.ts +20 -0
  244. package/dist/src/daemon/queue-journal.js +89 -0
  245. package/dist/src/daemon/queue-status.d.ts +9 -3
  246. package/dist/src/daemon/queue-status.js +17 -4
  247. package/dist/src/daemon/record-archive.d.ts +19 -0
  248. package/dist/src/daemon/record-archive.js +82 -0
  249. package/dist/src/daemon/record-sweep.d.ts +24 -0
  250. package/dist/src/daemon/record-sweep.js +147 -0
  251. package/dist/src/daemon/resume-index-lock.d.ts +13 -0
  252. package/dist/src/daemon/resume-index-lock.js +160 -0
  253. package/dist/src/daemon/resume-worktree.d.ts +7 -0
  254. package/dist/src/daemon/resume-worktree.js +29 -0
  255. package/dist/src/daemon/retrying-turn.d.ts +2 -12
  256. package/dist/src/daemon/retrying-turn.js +98 -172
  257. package/dist/src/daemon/schedule-control.d.ts +26 -0
  258. package/dist/src/daemon/schedule-control.js +110 -0
  259. package/dist/src/daemon/schedule-journal.d.ts +2 -15
  260. package/dist/src/daemon/schedule-journal.js +67 -4
  261. package/dist/src/daemon/settings-reload.d.ts +26 -0
  262. package/dist/src/daemon/settings-reload.js +82 -0
  263. package/dist/src/daemon/ticket-registration.d.ts +2 -7
  264. package/dist/src/daemon/ticket-registration.js +15 -81
  265. package/dist/src/daemon/turn-attempt-journal.d.ts +38 -0
  266. package/dist/src/daemon/turn-attempt-journal.js +136 -0
  267. package/dist/src/daemon/turn-attempts.d.ts +47 -0
  268. package/dist/src/daemon/turn-attempts.js +166 -0
  269. package/dist/src/daemon/turn-details.d.ts +4 -0
  270. package/dist/src/daemon/turn-details.js +33 -0
  271. package/dist/src/daemon/turn-report.d.ts +8 -1
  272. package/dist/src/daemon/turn-report.js +39 -0
  273. package/dist/src/daemon/work-tree.d.ts +9 -0
  274. package/dist/src/daemon/work-tree.js +20 -0
  275. package/dist/src/daemon/worker-profile.d.ts +9 -0
  276. package/dist/src/daemon/worker-profile.js +44 -0
  277. package/dist/src/daemon/workflow-generations.d.ts +7 -4
  278. package/dist/src/daemon/workflow-generations.js +22 -14
  279. package/dist/src/daemon/workflow-reload.d.ts +41 -0
  280. package/dist/src/daemon/workflow-reload.js +244 -0
  281. package/dist/src/daemon/workflow-replay.d.ts +2 -1
  282. package/dist/src/daemon/workflow-replay.js +7 -3
  283. package/dist/src/daemon/workflow-schedules.d.ts +3 -0
  284. package/dist/src/daemon/workflow-schedules.js +98 -45
  285. package/dist/src/daemon/worktree-processes.d.ts +9 -0
  286. package/dist/src/daemon/worktree-processes.js +151 -0
  287. package/dist/src/doctor-report.d.ts +4 -6
  288. package/dist/src/doctor-report.js +8 -13
  289. package/dist/src/harness-start.d.ts +15 -0
  290. package/dist/src/harness-start.js +37 -0
  291. package/dist/src/host/command-wait.d.ts +38 -0
  292. package/dist/src/host/command-wait.js +115 -0
  293. package/dist/src/host/start-delivery.d.ts +5 -7
  294. package/dist/src/host/start-delivery.js +3 -15
  295. package/dist/src/host/step-bridge-host.d.ts +2 -0
  296. package/dist/src/host/step-bridge-host.js +21 -0
  297. package/dist/src/operation-identity.d.ts +1 -0
  298. package/dist/src/operation-identity.js +4 -0
  299. package/dist/src/private-config.schema.json +71 -0
  300. package/dist/src/project-config.schema.json +40 -0
  301. package/dist/src/project-settings.d.ts +16 -2
  302. package/dist/src/project-settings.js +35 -1
  303. package/dist/src/queue-spend.d.ts +4 -0
  304. package/dist/src/queue-spend.js +21 -0
  305. package/dist/src/queue-state.d.ts +26 -0
  306. package/dist/src/queue-state.js +34 -0
  307. package/dist/src/record-retention.d.ts +46 -0
  308. package/dist/src/record-retention.js +81 -0
  309. package/dist/src/relay/relay-service.d.ts +3 -0
  310. package/dist/src/relay/relay-service.js +5 -3
  311. package/dist/src/report-ci.d.ts +164 -0
  312. package/dist/src/report-ci.js +32 -0
  313. package/dist/src/report-effects.d.ts +15 -0
  314. package/dist/src/report-effects.js +212 -0
  315. package/dist/src/report-flow-types.d.ts +38 -0
  316. package/dist/src/report-flow-types.js +7 -0
  317. package/dist/src/report-flow.d.ts +43 -0
  318. package/dist/src/report-flow.js +193 -0
  319. package/dist/src/report-operator.d.ts +67 -0
  320. package/dist/src/report-operator.js +25 -0
  321. package/dist/src/report-quality.d.ts +221 -0
  322. package/dist/src/report-quality.js +94 -0
  323. package/dist/src/report-ready.d.ts +23 -0
  324. package/dist/src/report-ready.js +117 -0
  325. package/dist/src/report-statistics.d.ts +2 -1
  326. package/dist/src/report-statistics.js +40 -4
  327. package/dist/src/report.d.ts +588 -47
  328. package/dist/src/report.js +156 -41
  329. package/dist/src/schedule-progress.d.ts +33 -0
  330. package/dist/src/schedule-progress.js +45 -0
  331. package/dist/src/service/active-tickets.js +11 -6
  332. package/dist/src/service/command-waits.d.ts +34 -0
  333. package/dist/src/service/command-waits.js +86 -0
  334. package/dist/src/service/dispatcher.d.ts +4 -0
  335. package/dist/src/service/dispatcher.js +33 -0
  336. package/dist/src/service/expiry.js +2 -0
  337. package/dist/src/service/gateway.js +3 -1
  338. package/dist/src/service/queue-handlers.d.ts +4 -0
  339. package/dist/src/service/queue-handlers.js +32 -0
  340. package/dist/src/service/queue-spend.d.ts +6 -0
  341. package/dist/src/service/queue-spend.js +68 -0
  342. package/dist/src/service/registry.d.ts +28 -2
  343. package/dist/src/service/registry.js +48 -0
  344. package/dist/src/service/replies.d.ts +22 -8
  345. package/dist/src/service/replies.js +16 -2
  346. package/dist/src/service/run-handlers.js +37 -12
  347. package/dist/src/service/run-metadata.d.ts +15 -1
  348. package/dist/src/service/run-metadata.js +93 -8
  349. package/dist/src/service/service.d.ts +11 -3
  350. package/dist/src/service/service.js +22 -37
  351. package/dist/src/service/submission-handler.js +18 -4
  352. package/dist/src/service/worker-port.js +9 -1
  353. package/dist/src/service/worker-totals.d.ts +6 -0
  354. package/dist/src/service/worker-totals.js +8 -1
  355. package/dist/src/service/workflow-reload.d.ts +35 -0
  356. package/dist/src/service/workflow-reload.js +52 -0
  357. package/dist/src/step-bridge.d.ts +20 -0
  358. package/dist/src/steps/command-wait.d.ts +12 -0
  359. package/dist/src/steps/command-wait.js +26 -0
  360. package/dist/src/steps/open-decision-wait.d.ts +2 -0
  361. package/dist/src/steps/{decision-gate.js → open-decision-wait.js} +0 -7
  362. package/dist/src/steps/review-by-person.js +4 -1
  363. package/dist/src/steps/wait-for-command.d.ts +3 -0
  364. package/dist/src/steps/wait-for-command.js +17 -0
  365. package/dist/src/steps/wait-for-decision.js +1 -1
  366. package/dist/src/storage/command-wait-rows.d.ts +30 -0
  367. package/dist/src/storage/command-wait-rows.js +96 -0
  368. package/dist/src/storage/decision-snapshots.d.ts +8 -2
  369. package/dist/src/storage/decision-snapshots.js +16 -8
  370. package/dist/src/storage/domain-changes.d.ts +3 -1
  371. package/dist/src/storage/domain-changes.js +4 -0
  372. package/dist/src/storage/domain-schema.d.ts +16 -1
  373. package/dist/src/storage/domain-schema.js +35 -4
  374. package/dist/src/storage/effect-schema.d.ts +2 -0
  375. package/dist/src/storage/effect-schema.js +9 -0
  376. package/dist/src/storage/projection-changes.d.ts +12 -0
  377. package/dist/src/storage/projection-changes.js +194 -0
  378. package/dist/src/storage/projection-lookups.d.ts +7 -0
  379. package/dist/src/storage/projection-lookups.js +30 -0
  380. package/dist/src/testing-requests.d.ts +15 -0
  381. package/dist/src/testing-requests.js +93 -0
  382. package/dist/src/testing.d.ts +4 -10
  383. package/dist/src/testing.js +33 -86
  384. package/dist/src/worker-usage.d.ts +1 -0
  385. package/dist/src/worker-usage.js +3 -0
  386. package/dist/src/workflow-candidate.d.ts +1 -0
  387. package/dist/src/workflow-candidate.js +12 -1
  388. package/dist/src/workflow-contracts.d.ts +1 -0
  389. package/dist/src/workflow-contracts.js +9 -2
  390. package/dist/src/workflow-declaration.d.ts +1 -0
  391. package/dist/src/workflow-declaration.js +3 -0
  392. package/dist/src/workflow-discovery.d.ts +2 -13
  393. package/dist/src/workflow-discovery.js +1 -13
  394. package/dist/src/workflow-refusals.js +0 -1
  395. package/dist/src/workflows.d.ts +1 -1
  396. package/dist/src/workflows.js +1 -1
  397. package/dist/tools/compile-workflows.js +3 -0
  398. package/dist/tools/project-configuration.d.ts +7 -2
  399. package/dist/tools/project-configuration.js +30 -1
  400. package/dist/tools/red-green-command.d.ts +1 -0
  401. package/dist/tools/red-green-command.js +174 -0
  402. package/dist/tools/red-green-process.d.ts +10 -0
  403. package/dist/tools/red-green-process.js +59 -0
  404. package/dist/tools/self-host-processes.d.ts +2 -0
  405. package/dist/tools/self-host-processes.js +69 -26
  406. package/dist/tools/self-host-project.d.ts +2 -0
  407. package/dist/tools/self-host-project.js +30 -77
  408. package/dist/tools/self-host-runtime.js +1 -1
  409. package/dist/tools/self-host.js +76 -34
  410. package/dist/tools/template-command.d.ts +1 -0
  411. package/dist/tools/template-command.js +160 -0
  412. package/dist/tools/template-copy.d.ts +11 -0
  413. package/dist/tools/template-copy.js +130 -0
  414. package/dist/tools/template-files.d.ts +17 -0
  415. package/dist/tools/template-files.js +74 -0
  416. package/dist/tools/workflow-bundles.js +1 -0
  417. package/dist/tools/workflow-candidate-counts.js +2 -2
  418. package/dist/tools/workflow-candidate-program.d.ts +4 -2
  419. package/dist/tools/workflow-candidate-program.js +30 -11
  420. package/dist/tools/workflow-candidate-shape.d.ts +3 -0
  421. package/dist/tools/workflow-candidate-shape.js +90 -0
  422. package/dist/tools/workflow-candidate-source.d.ts +1 -0
  423. package/dist/tools/workflow-candidate-source.js +26 -4
  424. package/dist/tools/workflow-candidate-syntax.d.ts +2 -2
  425. package/dist/tools/workflow-candidate-syntax.js +24 -94
  426. package/dist/tools/workflow-command.js +1 -2
  427. package/dist/tools/workflow-folders.d.ts +2 -3
  428. package/dist/tools/workflow-folders.js +1 -1
  429. package/package.json +17 -9
  430. package/templates/develop/README.md +93 -4
  431. package/templates/develop/develop/flow-tests.ts +38 -1
  432. package/templates/develop/develop/interruption-tests.ts +157 -0
  433. package/templates/develop/develop/local.ts +1 -0
  434. package/templates/develop/develop/option-tests.ts +138 -9
  435. package/templates/develop/develop/pull.ts +5 -1
  436. package/templates/develop/develop/red-green.ts +55 -0
  437. package/templates/develop/develop/refusal-tests.ts +77 -0
  438. package/templates/develop/develop/report.ts +31 -11
  439. package/templates/develop/develop/round.ts +1 -0
  440. package/templates/develop/develop/scenarios.ts +27 -0
  441. package/templates/develop/develop/turn.ts +28 -7
  442. package/templates/develop/develop.declaration.json +1 -1
  443. package/templates/develop/develop.test.ts +4 -0
  444. package/templates/develop/template.json +29 -2
  445. package/templates/idea-to-issue/README.md +27 -0
  446. package/templates/idea-to-issue/idea-to-issue/effects.ts +52 -0
  447. package/templates/idea-to-issue/idea-to-issue/leave.ts +10 -0
  448. package/templates/idea-to-issue/idea-to-issue/next.ts +29 -0
  449. package/templates/idea-to-issue/idea-to-issue/post-person.ts +18 -0
  450. package/templates/idea-to-issue/idea-to-issue/post.ts +50 -0
  451. package/templates/idea-to-issue/idea-to-issue/queue.ts +27 -0
  452. package/templates/idea-to-issue/idea-to-issue/review.ts +42 -0
  453. package/templates/idea-to-issue/idea-to-issue/scenarios.ts +50 -0
  454. package/templates/idea-to-issue/idea-to-issue/turn.ts +40 -0
  455. package/templates/idea-to-issue/idea-to-issue.declaration.json +16 -0
  456. package/templates/idea-to-issue/idea-to-issue.test.ts +79 -0
  457. package/templates/idea-to-issue/idea-to-issue.ts +49 -0
  458. package/templates/idea-to-issue/template.json +81 -0
  459. package/templates/review-day/README.md +97 -0
  460. package/templates/review-day/review-day/critique.ts +58 -0
  461. package/templates/review-day/review-day/decision.ts +29 -0
  462. package/templates/review-day/review-day/facts.ts +69 -0
  463. package/templates/review-day/review-day/label.ts +29 -0
  464. package/templates/review-day/review-day/next.ts +28 -0
  465. package/templates/review-day/review-day/post.ts +27 -0
  466. package/templates/review-day/review-day/report.ts +98 -0
  467. package/templates/review-day/review-day/rules.ts +12 -0
  468. package/templates/review-day/review-day/scenarios.ts +76 -0
  469. package/templates/review-day/review-day/turn.ts +73 -0
  470. package/templates/review-day/review-day.declaration.json +16 -0
  471. package/templates/review-day/review-day.test.ts +362 -0
  472. package/templates/review-day/review-day.ts +37 -0
  473. package/templates/review-day/template.json +56 -0
  474. package/dist/src/cli/watch-start-view.d.ts +0 -18
  475. package/dist/src/cli/watch-start-view.js +0 -77
  476. package/dist/src/steps/decision-gate.d.ts +0 -3
  477. package/dist/workflows/develop-prd.d.ts +0 -7
  478. package/dist/workflows/develop-prd.js +0 -165
  479. package/dist/workflows/develop-ticket.d.ts +0 -9
  480. package/dist/workflows/develop-ticket.js +0 -207
  481. package/dist/workflows/issue-labels.d.ts +0 -16
  482. package/dist/workflows/issue-labels.js +0 -70
  483. package/dist/workflows/ticket-prompts.d.ts +0 -35
  484. package/dist/workflows/ticket-prompts.js +0 -147
@@ -0,0 +1,43 @@
1
+ import type { Database, Logger } from "solid-objects";
2
+ import { type EffectIntent } from "../../authority/reducer.ts";
3
+ import { type ProjectionSnapshot } from "../../storage/decision-snapshots.ts";
4
+ import type { DomainChange } from "../../storage/domain-changes.ts";
5
+ import { type StoredState } from "../authority-restore.ts";
6
+ import type { Sequences } from "./authority-queries.ts";
7
+ export interface OperationRecord {
8
+ readonly operationId: string;
9
+ readonly kind: EffectIntent["kind"];
10
+ readonly runId: string;
11
+ readonly createdSequence: number;
12
+ readonly releasedSequence: number | null;
13
+ readonly deliveries: readonly {
14
+ readonly effectId: string;
15
+ readonly outcome: string | null;
16
+ readonly receipt: string | null;
17
+ readonly purpose: string;
18
+ readonly epoch: number | null;
19
+ }[];
20
+ }
21
+ export interface Projection {
22
+ readonly snapshot: ProjectionSnapshot;
23
+ readonly state: () => StoredState;
24
+ readonly eligible: () => readonly EffectIntent[];
25
+ readonly operations: () => readonly OperationRecord[];
26
+ readonly sequences: () => Sequences;
27
+ readonly workflowIds: (runIds?: readonly string[]) => Readonly<Record<string, string>>;
28
+ }
29
+ type Source = Pick<Database, "connection">;
30
+ export interface ProjectionMemory {
31
+ readonly record: (change: RecordedChange) => void;
32
+ readonly load: (database: Source) => Promise<void>;
33
+ readonly read: (database: Source) => Promise<Projection>;
34
+ }
35
+ export interface RecordedChange {
36
+ readonly messageId: string;
37
+ readonly change: DomainChange;
38
+ }
39
+ export declare function projectionMemory(options: {
40
+ readonly logger: Logger;
41
+ }): ProjectionMemory;
42
+ export declare function operationRecords(snapshot: Pick<ProjectionSnapshot, "outbox" | "deliveries">): readonly OperationRecord[];
43
+ export {};
@@ -0,0 +1,207 @@
1
+ import { eligibleIntents } from "../../authority/reducer.js";
2
+ import { loadProjections, } from "../../storage/decision-snapshots.js";
3
+ import { messageOutcomes, recordedTimes, runTimes, storedWorkflows, submittedWorkflows, } from "../../storage/projection-lookups.js";
4
+ import { changedProjection, followingSequence, projectedChange, } from "../../storage/projection-changes.js";
5
+ import { currentState, intentKind, } from "../authority-restore.js";
6
+ const pendingLimit = 1_000;
7
+ export function projectionMemory(options) {
8
+ const memory = {
9
+ held: undefined,
10
+ stale: null,
11
+ recorded: new Map(),
12
+ logger: options.logger,
13
+ };
14
+ return {
15
+ record: ({ messageId, change }) => {
16
+ memory.recorded.delete(messageId);
17
+ memory.recorded.set(messageId, projectedChange(change));
18
+ if (memory.recorded.size <= pendingLimit)
19
+ return;
20
+ memory.recorded.clear();
21
+ memory.held = undefined;
22
+ },
23
+ load: (database) => database.connection(async (connection) => {
24
+ memory.recorded.clear();
25
+ memory.held = await rebuilt(connection);
26
+ }),
27
+ read: (database) => database.connection(async (connection) => {
28
+ const held = await advanced(memory, connection);
29
+ return held.projection;
30
+ }),
31
+ };
32
+ }
33
+ async function advanced(memory, connection) {
34
+ const recorded = [...memory.recorded];
35
+ memory.recorded.clear();
36
+ try {
37
+ memory.held = await updated(memory, { recorded, connection });
38
+ }
39
+ catch (error) {
40
+ memory.held = undefined;
41
+ memory.stale = `update failed: ${String(Reflect.get(Object(error), "message"))}`;
42
+ throw error;
43
+ }
44
+ return memory.held;
45
+ }
46
+ async function updated(memory, update) {
47
+ const { recorded, connection } = update;
48
+ if (memory.held === undefined)
49
+ return rebuiltAfter(memory, connection);
50
+ if (recorded.length === 0)
51
+ return memory.held;
52
+ const outcomes = await messageOutcomes(connection, recorded.map(([messageId]) => messageId));
53
+ const committed = [];
54
+ let sequence = memory.held.snapshot.sequence;
55
+ for (const [messageId, change] of recorded) {
56
+ const outcome = outcomes.get(messageId) ?? "missing";
57
+ if (outcome === "rolled-back")
58
+ continue;
59
+ const reason = mismatch({ messageId, change, outcome, sequence });
60
+ if (reason !== null) {
61
+ memory.stale = reason;
62
+ return rebuiltAfter(memory, connection);
63
+ }
64
+ committed.push(change);
65
+ sequence = followingSequence(sequence, change);
66
+ }
67
+ return applied(memory.held, { changes: committed, connection });
68
+ }
69
+ async function rebuiltAfter(memory, connection) {
70
+ const held = await rebuilt(connection);
71
+ if (memory.stale !== null)
72
+ memory.logger.warn({
73
+ event: "authority-projection-rebuilt",
74
+ reason: memory.stale,
75
+ });
76
+ memory.stale = null;
77
+ return held;
78
+ }
79
+ function mismatch(recorded) {
80
+ const { messageId, change, outcome, sequence } = recorded;
81
+ if (outcome === "missing")
82
+ return `message ${messageId} is not stored`;
83
+ if (change.previous === sequence)
84
+ return null;
85
+ return `sequence ${String(change.previous)} does not follow ${String(sequence)}`;
86
+ }
87
+ async function rebuilt(connection) {
88
+ const snapshot = await loadProjections(connection);
89
+ return withProjection({
90
+ snapshot,
91
+ times: await runTimes(connection),
92
+ workflows: new Map([
93
+ ...snapshot.details.flatMap((row) => row.submission === null ? [] : [[row.submission, null]]),
94
+ ...(await submittedWorkflows(connection)),
95
+ ]),
96
+ });
97
+ }
98
+ async function applied(previous, input) {
99
+ const { changes } = input;
100
+ if (changes.length === 0)
101
+ return previous;
102
+ return withProjection({
103
+ snapshot: changedProjection(previous.snapshot, changes),
104
+ times: await changedTimes(previous.times, input),
105
+ workflows: await changedWorkflows(previous.workflows, input),
106
+ });
107
+ }
108
+ async function changedTimes(previous, input) {
109
+ const runs = new Map(input.changes.flatMap((change) => change.runs.map((row) => [row.runId, row.sequence])));
110
+ const carried = new Map(input.changes.flatMap((change) => change.events.map((event) => [event.sequence, event.recordedAt])));
111
+ const stored = await recordedTimes(input.connection, [
112
+ ...new Set([...runs.values()].filter((sequence) => !carried.has(sequence))),
113
+ ]);
114
+ const times = new Map(previous);
115
+ for (const [runId, sequence] of runs) {
116
+ const time = carried.get(sequence) ?? stored.get(sequence);
117
+ if (time === undefined)
118
+ times.delete(runId);
119
+ else
120
+ times.set(runId, time);
121
+ }
122
+ return times;
123
+ }
124
+ async function changedWorkflows(previous, input) {
125
+ const submissions = [
126
+ ...new Set(input.changes.flatMap((change) => change.details.flatMap((row) => row.submission === null || previous.has(row.submission)
127
+ ? []
128
+ : [row.submission]))),
129
+ ];
130
+ const stored = await storedWorkflows(input.connection, submissions);
131
+ return new Map([
132
+ ...previous,
133
+ ...submissions.map((submission) => [submission, stored.get(submission) ?? null]),
134
+ ]);
135
+ }
136
+ function withProjection(parts) {
137
+ return { ...parts, projection: projection(parts) };
138
+ }
139
+ function remembered(compute) {
140
+ let value;
141
+ return () => {
142
+ value ??= { current: compute() };
143
+ return value.current;
144
+ };
145
+ }
146
+ function projection(parts) {
147
+ const { snapshot, times, workflows } = parts;
148
+ const state = remembered(() => currentState(snapshot));
149
+ return {
150
+ snapshot,
151
+ state,
152
+ eligible: remembered(() => eligible({ snapshot, state: state() })),
153
+ operations: remembered(() => operationRecords(snapshot)),
154
+ sequences: remembered(() => sequences({ snapshot, times })),
155
+ workflowIds: (runIds) => workflowIds({ snapshot, workflows, runIds }),
156
+ };
157
+ }
158
+ function sequences(parts) {
159
+ const timed = parts.snapshot.runs.flatMap((row) => {
160
+ const time = parts.times.get(row.runId);
161
+ return time === undefined ? [] : [{ row, time }];
162
+ });
163
+ return {
164
+ latest: parts.snapshot.sequence,
165
+ runs: Object.fromEntries(timed.map(({ row }) => [row.runId, row.sequence])),
166
+ times: Object.fromEntries(timed.map(({ row, time }) => [row.runId, time])),
167
+ };
168
+ }
169
+ function workflowIds(parts) {
170
+ const wanted = parts.runIds === undefined ? null : new Set(parts.runIds);
171
+ return Object.fromEntries(parts.snapshot.details.flatMap((row) => {
172
+ const workflowId = row.submission === null ? null : parts.workflows.get(row.submission);
173
+ if (typeof workflowId !== "string")
174
+ return [];
175
+ return wanted === null || wanted.has(row.runId)
176
+ ? [[row.runId, workflowId]]
177
+ : [];
178
+ }));
179
+ }
180
+ export function operationRecords(snapshot) {
181
+ const deliveries = Map.groupBy(snapshot.deliveries, (row) => row.operationId);
182
+ return snapshot.outbox.map((row) => ({
183
+ operationId: row.operationId,
184
+ kind: intentKind(row.kind),
185
+ runId: row.runId,
186
+ createdSequence: row.createdSequence,
187
+ releasedSequence: row.releasedSequence,
188
+ deliveries: (deliveries.get(row.operationId) ?? []).map(({ effectId, outcome, receipt, purpose, epoch }) => ({
189
+ effectId,
190
+ outcome,
191
+ receipt,
192
+ purpose,
193
+ epoch,
194
+ })),
195
+ }));
196
+ }
197
+ function eligible(parts) {
198
+ return eligibleIntents({
199
+ ...parts.state,
200
+ effects: {
201
+ operations: Object.fromEntries(parts.snapshot.pending.map((operationId) => [
202
+ operationId,
203
+ { status: "dispatch-pending" },
204
+ ])),
205
+ },
206
+ });
207
+ }
@@ -1,4 +1,5 @@
1
1
  import { type ActorClass, type Database, type InstrumentationEvent, type SolidObjectsConfiguration, type SolidObjectsRuntime } from "solid-objects";
2
+ import { type DomainChange } from "../../storage/domain-changes.ts";
2
3
  import { type AuthorityTiming } from "./authority-settings.ts";
3
4
  import type { StrandingWatchdog } from "./authority-watchdog.ts";
4
5
  export interface FailedDelivery {
@@ -20,4 +21,8 @@ export declare function registeredRuntime(registration: {
20
21
  readonly database: Database;
21
22
  readonly actor: ActorClass;
22
23
  readonly settings: Partial<SolidObjectsConfiguration>;
24
+ readonly record: (recorded: {
25
+ readonly messageId: string;
26
+ readonly change: DomainChange;
27
+ }) => void;
23
28
  }): SolidObjectsRuntime;
@@ -1,5 +1,5 @@
1
1
  import { createRuntime, } from "solid-objects";
2
- import { persistChange } from "../../storage/domain-changes.js";
2
+ import { persistChange, } from "../../storage/domain-changes.js";
3
3
  import { persistAction } from "./authority-actor.js";
4
4
  import { brokerAuthorization } from "./authority-policy.js";
5
5
  import { librarySettings } from "./authority-settings.js";
@@ -26,9 +26,14 @@ export function runtimeSettings(parts) {
26
26
  };
27
27
  }
28
28
  export function registeredRuntime(registration) {
29
- const { database, actor, settings } = registration;
29
+ const { database, actor, settings, record } = registration;
30
30
  const runtime = createRuntime({ database, ...settings });
31
31
  runtime.register(actor);
32
- runtime.registerCommitAction(persistAction, (change, context) => persistChange(change, context.connection));
32
+ runtime.registerCommitAction(persistAction, async (change, context) => {
33
+ record({
34
+ messageId: context.messageId,
35
+ change: await persistChange(change, context.connection),
36
+ });
37
+ });
33
38
  return runtime;
34
39
  }
@@ -1,3 +1,4 @@
1
+ import { endedCommandWaitCheck } from "../../authority/command-waits.js";
1
2
  import { stopAttempts, } from "../../authority/stop-sequence.js";
2
3
  import { observingIntent, protectedIntent, } from "./delivery-intents.js";
3
4
  function present(state, planning) {
@@ -33,9 +34,14 @@ export function presentSubjects(state, planning) {
33
34
  }
34
35
  export function stopIntents(state, planning) {
35
36
  const { runId, stopping } = planning;
36
- if (state.runs[runId]?.status !== "cancelled")
37
- return [];
38
- const subjects = presentSubjects(state, planning);
37
+ const cancelled = state.runs[runId]?.status === "cancelled";
38
+ const subjects = presentSubjects(state, planning).flatMap((subject) => {
39
+ if (endedCommandWaitCheck(state.commandWaits ?? {}, subject.operationId))
40
+ return [
41
+ { ...subject, stopSequence: subject.stopSequence ?? state.sequence },
42
+ ];
43
+ return cancelled ? [subject] : [];
44
+ });
39
45
  return stopAttempts(subjects, {
40
46
  cancelledSequence: planning.cancelledNow[0] ?? stopping.cancelledSequence ?? 0,
41
47
  stopLimit: planning.stopLimit,
@@ -1,4 +1,5 @@
1
1
  import type { StopSubject } from "../../authority/stop-sequence.ts";
2
+ import type { CommandWaitsState } from "../../authority/command-waits.ts";
2
3
  import { type SqlConnection } from "../../storage/domain-rows.ts";
3
4
  export interface Stopping {
4
5
  readonly subjects: readonly StopSubject[];
@@ -10,6 +11,7 @@ export declare function turnStopping(connection: SqlConnection, scope: {
10
11
  readonly runId: string;
11
12
  readonly status: string | undefined;
12
13
  readonly kind: string;
14
+ readonly commandWaits?: CommandWaitsState;
13
15
  }): Promise<Stopping>;
14
16
  export declare function droppedRuns(connection: SqlConnection, rule: {
15
17
  readonly limit: number;
@@ -11,23 +11,36 @@ function attemptOf(row) {
11
11
  const closed = "NOT EXISTS (SELECT 1 FROM deliveries x WHERE x.operation_id = o.operation_id AND x.outcome IS NULL)";
12
12
  const reported = "EXISTS (SELECT 1 FROM stopped_operations s WHERE s.operation_id = o.operation_id)";
13
13
  const finalStatuses = "'refused', 'completed', 'failed', 'stopped', 'cancelled'";
14
+ const waitStopSequence = "(SELECT min(e.sequence) FROM command_waits w JOIN events e ON e.run_id = w.run_id WHERE w.check_operation = o.operation_id AND w.state != 'waiting' AND (e.type IN ('run.completed', 'run.failed', 'run.cancelled') OR (json_extract(e.payload, '$.waitName') = w.name AND (e.type = 'command-wait.expired' OR (e.type = 'command-wait.opened' AND round(unixepoch(e.recorded_at, 'subsec') * 1000) >= w.deadline)))))";
14
15
  export async function stopping(connection, runId) {
15
- const rows = await connection.all(`SELECT o.operation_id, o.kind, d.effect_id, coalesce(a.purpose, 'dispatch') AS purpose, d.outcome, d.staged_sequence, p.status, p.receipt, ${reported} AS stopped FROM outbox o JOIN deliveries d ON d.operation_id = o.operation_id LEFT JOIN dispatch_attempts a ON a.effect_id = d.effect_id LEFT JOIN operations p ON p.operation_id = o.operation_id WHERE o.run_id = ? AND ((coalesce(p.status, '') NOT IN (${finalStatuses}) AND NOT ${reported}) OR NOT ${closed}) ORDER BY d.staged_sequence, d.rowid`, [runId]);
16
+ const rows = await connection.all(`SELECT o.operation_id, o.kind, d.effect_id, coalesce(a.purpose, 'dispatch') AS purpose, d.outcome, d.staged_sequence, p.status, p.receipt, ${reported} AS stopped, ${waitStopSequence} AS stop_sequence FROM outbox o JOIN deliveries d ON d.operation_id = o.operation_id LEFT JOIN dispatch_attempts a ON a.effect_id = d.effect_id LEFT JOIN operations p ON p.operation_id = o.operation_id WHERE o.run_id = ? AND ((coalesce(p.status, '') NOT IN (${finalStatuses}) AND NOT ${reported}) OR NOT ${closed}) ORDER BY d.staged_sequence, d.rowid`, [runId]);
16
17
  const grouped = Map.groupBy(rows, (row) => text(row, "operation_id"));
17
18
  return {
18
- subjects: [...grouped].map(([operationId, attempts]) => ({
19
- operationId,
20
- kind: text(attempts[0], "kind"),
21
- attempts: attempts.map(attemptOf),
22
- status: optionalText(attempts[0], "status"),
23
- receipt: optionalText(attempts[0], "receipt"),
24
- stopped: integer(attempts[0], "stopped") === 1,
25
- })),
19
+ subjects: [...grouped].map(([operationId, attempts]) => {
20
+ const stopSequence = optionalInteger(attempts[0], "stop_sequence");
21
+ return {
22
+ operationId,
23
+ kind: text(attempts[0], "kind"),
24
+ attempts: attempts.map(attemptOf),
25
+ status: optionalText(attempts[0], "status"),
26
+ receipt: optionalText(attempts[0], "receipt"),
27
+ stopped: integer(attempts[0], "stopped") === 1,
28
+ ...(stopSequence === null ? {} : { stopSequence }),
29
+ };
30
+ }),
26
31
  cancelledSequence: optionalInteger(await connection.get("SELECT max(sequence) AS sequence FROM events WHERE run_id = ? AND type = 'run.cancelled'", [runId]), "sequence"),
27
32
  };
28
33
  }
29
34
  export function turnStopping(connection, scope) {
30
- const dropping = scope.status === "cancelled" || scope.kind === "cancel-run";
35
+ const endingWait = Object.values(scope.commandWaits ?? {}).some((wait) => wait.checkOperation !== null &&
36
+ (wait.state !== "waiting" ||
37
+ [
38
+ "expire-command-wait",
39
+ "open-command-wait",
40
+ "complete-run",
41
+ "fail-run",
42
+ ].includes(scope.kind)));
43
+ const dropping = scope.status === "cancelled" || scope.kind === "cancel-run" || endingWait;
31
44
  return dropping
32
45
  ? stopping(connection, scope.runId)
33
46
  : Promise.resolve(noStopping);
@@ -37,6 +50,6 @@ const last = "(SELECT d.outcome FROM deliveries d WHERE d.operation_id = o.opera
37
50
  const effectDue = `o.kind = 'protected-effect' AND p.status NOT IN (${finalStatuses}) AND (p.receipt IS NULL OR ?1 = 1 OR p.status IN ('recovering', 'blocked'))`;
38
51
  const runDue = `o.kind != 'protected-effect' AND NOT ${reported} AND ((${sent} AND ?1 = 1) OR (NOT ${sent} AND ${last} NOT IN ('withheld', 'blocked', 'absent')))`;
39
52
  export async function droppedRuns(connection, rule) {
40
- const rows = await connection.all(`SELECT r.run_id FROM runs r WHERE r.status = 'cancelled' AND EXISTS (SELECT 1 FROM outbox o LEFT JOIN operations p ON p.operation_id = o.operation_id WHERE o.run_id = r.run_id AND ${closed} AND ((${effectDue}) OR (${runDue}))) ORDER BY r.run_id LIMIT ?2`, [rule.stopSupported ? 1 : 0, rule.limit]);
53
+ const rows = await connection.all(`SELECT r.run_id FROM runs r WHERE EXISTS (SELECT 1 FROM outbox o LEFT JOIN operations p ON p.operation_id = o.operation_id WHERE o.run_id = r.run_id AND (r.status = 'cancelled' OR EXISTS (SELECT 1 FROM command_waits w WHERE w.run_id = r.run_id AND w.check_operation = o.operation_id AND w.state != 'waiting')) AND ${closed} AND ((${effectDue}) OR (${runDue}))) ORDER BY r.run_id LIMIT ?2`, [rule.stopSupported ? 1 : 0, rule.limit]);
41
54
  return rows.map((row) => text(row, "run_id"));
42
55
  }
@@ -11,6 +11,7 @@ export interface ChangeDraft {
11
11
  readonly released: readonly Row[];
12
12
  readonly closed: readonly Row[];
13
13
  readonly gates: readonly Row[];
14
+ readonly waits: readonly Row[];
14
15
  readonly details: readonly Row[];
15
16
  readonly documents: readonly Row[];
16
17
  readonly budgets: readonly Row[];
@@ -1,3 +1,4 @@
1
+ import { commandWaitRows } from "../command-wait-restore.js";
1
2
  import { evidenceOf } from "../../authority/operations.js";
2
3
  import { detailRecord, gateRecord } from "../decision-restore.js";
3
4
  import { effectRows } from "../effect-restore.js";
@@ -23,6 +24,7 @@ export function unchangedAt(sequence, closed) {
23
24
  released: [],
24
25
  closed,
25
26
  gates: [],
27
+ waits: [],
26
28
  details: [],
27
29
  documents: [],
28
30
  ...unchangedEffects,
@@ -78,6 +80,7 @@ export function draft(result, extra) {
78
80
  closed,
79
81
  documents,
80
82
  ...effectDraft(result, before),
83
+ waits: commandWaitRows(changedEntries(state.commandWaits ?? {}, before.commandWaits ?? {})).map(columns),
81
84
  gates: changed(state.gates, before.gates).map((gate) => columns(gateRecord(gate))),
82
85
  details: changed(state.details, before.details).map((details) => columns(detailRecord(details))),
83
86
  previous: before.sequence,
@@ -127,6 +130,7 @@ export function merged(first, later) {
127
130
  released: joined("released"),
128
131
  closed: joined("closed"),
129
132
  gates: joined("gates"),
133
+ waits: joined("waits"),
130
134
  details: joined("details"),
131
135
  documents: joined("documents"),
132
136
  budgets: joined("budgets"),
@@ -1,3 +1,4 @@
1
+ import { readCommandWaits } from "../command-wait-restore.js";
1
2
  import { parseCommand } from "../../authority/commands.js";
2
3
  import { readExecutionManifest } from "../../execution-pin.js";
3
4
  import { operationScope } from "../../operation-identity.js";
@@ -92,7 +93,7 @@ export async function recoverableIntents(connection) {
92
93
  return [...intents, ...(await uncertainEffects(connection))];
93
94
  }
94
95
  async function uncertainEffects(connection) {
95
- const rows = await connection.all("SELECT p.operation_id, p.run_id, r.workflow FROM operations p JOIN runs r ON r.run_id = p.run_id WHERE p.status IN ('recovering', 'blocked') AND r.status != 'cancelled' AND NOT EXISTS (SELECT 1 FROM deliveries d WHERE d.operation_id = p.operation_id AND d.outcome IS NULL) ORDER BY p.requested_sequence LIMIT ?", [recoveryBatch]);
96
+ const rows = await connection.all("SELECT p.operation_id, p.run_id, r.workflow FROM operations p JOIN runs r ON r.run_id = p.run_id WHERE p.status IN ('recovering', 'blocked') AND r.status != 'cancelled' AND NOT EXISTS (SELECT 1 FROM command_waits w WHERE w.check_operation = p.operation_id AND w.state != 'waiting') AND NOT EXISTS (SELECT 1 FROM deliveries d WHERE d.operation_id = p.operation_id AND d.outcome IS NULL) ORDER BY p.requested_sequence LIMIT ?", [recoveryBatch]);
96
97
  const intents = [];
97
98
  for (const row of rows)
98
99
  intents.push(await pendingEffect(connection, {
@@ -161,8 +162,10 @@ async function turnState(connection, scope) {
161
162
  sql: "SELECT operation_id, kind, run_id, created_sequence, released_sequence FROM outbox WHERE run_id = ? AND released_sequence IS NULL ORDER BY created_sequence, rowid",
162
163
  parameters: [runId],
163
164
  });
165
+ const waits = await readCommandWaits(connection, runId);
164
166
  return {
165
167
  state: {
168
+ ...waits,
166
169
  sequence,
167
170
  runs: { ...near.runs, ...(stored ? { [runId]: stored } : {}) },
168
171
  commands: recorded ? { [commandId]: recorded } : {},
@@ -178,6 +181,7 @@ async function turnState(connection, scope) {
178
181
  runId,
179
182
  status: stored?.status,
180
183
  kind: scope.kind,
184
+ ...waits,
181
185
  }),
182
186
  runId,
183
187
  created: recorded
@@ -1,3 +1,5 @@
1
+ import { commandWaitKey } from "../authority/command-waits.js";
2
+ import { documentDigest } from "./decision-restore.js";
1
3
  import { parseCommand } from "../authority/commands.js";
2
4
  import { executionCapabilities, readExecutionManifest, resumePinned, } from "../execution-pin.js";
3
5
  import { effectInputDigest } from "./effect-restore.js";
@@ -41,6 +43,61 @@ const commandEffect = (command, context) => {
41
43
  return { kind: "rejected", reason: "command-not-declared" };
42
44
  return unpinned(command, context);
43
45
  };
46
+ function waitMatches(command, wait) {
47
+ return (command.target === wait.command &&
48
+ command.maximumWaitSeconds === wait.maximumWaitSeconds &&
49
+ command.pollEverySeconds === wait.pollEverySeconds);
50
+ }
51
+ const commandWait = (command, context) => {
52
+ const pin = context.state.runs[command.runId]?.workflow ?? "";
53
+ const declaration = context.definitions.get(pin);
54
+ const name = String(command.waitName);
55
+ const waits = declaration?.commandWaits ?? {};
56
+ const wait = Object.hasOwn(waits, name) ? waits[name] : undefined;
57
+ if (wait === undefined || !waitMatches(command, wait))
58
+ return { kind: "rejected", reason: "command-wait-not-declared" };
59
+ return unpinned(command, context);
60
+ };
61
+ function declaredWaitCheck(command, context) {
62
+ const pin = context.state.runs[command.runId]?.workflow ?? "";
63
+ const declarations = context.definitions.get(pin)?.commandWaits ?? {};
64
+ const name = String(command.waitName);
65
+ const declaration = Object.hasOwn(declarations, name)
66
+ ? declarations[name]
67
+ : undefined;
68
+ const wait = context.state.commandWaits?.[commandWaitKey(command.runId, name)];
69
+ if (declaration === undefined || wait === undefined)
70
+ return undefined;
71
+ return waitMatches({
72
+ ...command,
73
+ target: wait.command,
74
+ maximumWaitSeconds: wait.maximumWaitSeconds,
75
+ pollEverySeconds: wait.pollEverySeconds,
76
+ }, declaration)
77
+ ? wait
78
+ : undefined;
79
+ }
80
+ const commandWaitCheck = (command, context) => {
81
+ const wait = declaredWaitCheck(command, context);
82
+ if (wait === undefined)
83
+ return { kind: "rejected", reason: "command-wait-not-declared" };
84
+ const computed = effectInputDigest({
85
+ capability: "command",
86
+ method: `run:${wait.command}`,
87
+ target: wait.command,
88
+ payload: wait.inputDigest,
89
+ });
90
+ return command.inputDigest === computed
91
+ ? unpinned(command, context)
92
+ : { kind: "rejected", reason: "input-digest-mismatch" };
93
+ };
94
+ const commandReport = (command, context) => {
95
+ const output = command.commandOutput;
96
+ if (output !== undefined &&
97
+ (command.worker !== "command" || documentDigest(output) !== command.result))
98
+ return { kind: "rejected", reason: "command-output-mismatch" };
99
+ return unpinned(command, context);
100
+ };
44
101
  const handoffEffect = (command, context) => {
45
102
  if (command.capability !== "handoff")
46
103
  return unpinned(command, context);
@@ -67,6 +124,9 @@ const effectRequest = (command, context) => {
67
124
  };
68
125
  const rules = {
69
126
  "request-effect": effectRequest,
127
+ "check-command-wait": commandWaitCheck,
128
+ "report-effect": commandReport,
129
+ "open-command-wait": commandWait,
70
130
  "admit-run": admission,
71
131
  "decide-gate": resumption,
72
132
  "resume-run": resumption,
@@ -1,3 +1,4 @@
1
+ import type { CommandWaitRow } from "../storage/command-wait-rows.ts";
1
2
  import type { AuthorityEvent, AuthorityState, EffectIntent, RunState } from "../authority/reducer.ts";
2
3
  import type { DecisionRows, DocumentRow } from "../storage/decision-rows.ts";
3
4
  import type { EffectRows } from "../storage/effect-rows.ts";
@@ -7,6 +8,7 @@ import type { ExecutionManifest } from "../workflow-admission.ts";
7
8
  export type RestoredSnapshot = AuthoritySnapshot & Partial<DecisionRows> & {
8
9
  readonly documents?: readonly DocumentRow[];
9
10
  readonly effects?: EffectRows;
11
+ readonly waits?: readonly CommandWaitRow[];
10
12
  };
11
13
  export type Restoration = {
12
14
  readonly kind: "restored";
@@ -1,3 +1,4 @@
1
+ import { commandWaitState, commandWaitRows, commandOutputsIntact, } from "./command-wait-restore.js";
1
2
  import { isDeepStrictEqual } from "node:util";
2
3
  import { parseEvent } from "../authority/events.js";
3
4
  import { rebuildHistory } from "../authority/history.js";
@@ -51,6 +52,7 @@ export function restore(snapshot) {
51
52
  isDeepStrictEqual(Object.fromEntries(snapshot.runs.map((row) => [row.runId, row.sequence])), history.runSequences),
52
53
  ],
53
54
  ["definitions", definitionsIntact(definitions, state)],
55
+ ...waitChecks(snapshot, { state, definitions, events }),
54
56
  ...decisionChecks(snapshot, state),
55
57
  ...(snapshot.effects === undefined
56
58
  ? []
@@ -68,6 +70,21 @@ export function restore(snapshot) {
68
70
  };
69
71
  return { kind: "restored", state, events, definitions };
70
72
  }
73
+ function waitChecks(snapshot, context) {
74
+ const { state, definitions } = context;
75
+ return [
76
+ ["command-outputs", commandOutputsIntact(context.events)],
77
+ ["command-waits", commandWaitsIntact(definitions, state)],
78
+ ...(snapshot.waits === undefined
79
+ ? []
80
+ : [
81
+ [
82
+ "waits",
83
+ isDeepStrictEqual(snapshot.waits, commandWaitRows(state.commandWaits ?? {})),
84
+ ],
85
+ ]),
86
+ ];
87
+ }
71
88
  function decisionChecks(snapshot, state) {
72
89
  const { gates, details, documents } = snapshot;
73
90
  if (gates === undefined || details === undefined || documents === undefined)
@@ -95,12 +112,29 @@ function definitionsIntact(definitions, state) {
95
112
  const used = new Set(Object.values(state.runs).map((run) => run.workflow));
96
113
  return ([...definitions].every(([pin, manifest]) => workflowPin(manifest) === pin && used.has(pin)) && [...used].every((pin) => definitions.has(pin)));
97
114
  }
115
+ function commandWaitsIntact(definitions, state) {
116
+ return Object.values(state.commandWaits ?? {}).every((wait) => {
117
+ const pin = state.runs[wait.runId]?.workflow ?? "";
118
+ const waits = definitions.get(pin)?.commandWaits ?? {};
119
+ const permission = Object.hasOwn(waits, wait.name)
120
+ ? waits[wait.name]
121
+ : undefined;
122
+ return (permission !== undefined &&
123
+ permission.command === wait.command &&
124
+ permission.maximumWaitSeconds === wait.maximumWaitSeconds &&
125
+ permission.pollEverySeconds === wait.pollEverySeconds);
126
+ });
127
+ }
98
128
  const projectionParts = ["sequence", "runs", "commands", "intents"];
99
129
  function text(error) {
100
130
  return String(Reflect.get(Object(error), "message"));
101
131
  }
102
132
  export function currentState(snapshot) {
103
- return { ...projectedState(snapshot), ...decisionState(snapshot) };
133
+ return {
134
+ ...projectedState(snapshot),
135
+ ...decisionState(snapshot),
136
+ ...commandWaitState(snapshot.waits),
137
+ };
104
138
  }
105
139
  export function projectedState(snapshot) {
106
140
  return {
@@ -0,0 +1,31 @@
1
+ import type { Database } from "solid-objects";
2
+ import { type CommandWaitsState } from "../authority/command-waits.ts";
3
+ import type { AuthorityEvent } from "../authority/transitions.ts";
4
+ import type { DispatchRead } from "../authority/dispatch-decision.ts";
5
+ import type { AuthorityState } from "../authority/transitions.ts";
6
+ import { type CommandWaitRow } from "../storage/command-wait-rows.ts";
7
+ import type { SqlConnection } from "../storage/domain-rows.ts";
8
+ export declare function commandWaitRows(waits: CommandWaitsState): readonly CommandWaitRow[];
9
+ export declare function commandWaitState(rows: readonly CommandWaitRow[]): {
10
+ readonly commandWaits?: CommandWaitsState;
11
+ };
12
+ export declare function readCommandWaits(connection: SqlConnection, runId: string): Promise<{
13
+ readonly commandWaits?: CommandWaitsState;
14
+ }>;
15
+ export declare function upgradeCommandWaits(database: Database, state: AuthorityState): Promise<void>;
16
+ export declare function upgradeCommandWaitChecks(database: Database): Promise<void>;
17
+ export declare function commandWaitDeadline(waits: CommandWaitsState, operationId: string): {
18
+ readonly commandDeadline?: string;
19
+ };
20
+ export declare function dispatchCommandWait(connection: SqlConnection, check: {
21
+ readonly runId: string;
22
+ readonly workflow: string;
23
+ readonly operationId: string;
24
+ }): Promise<Pick<DispatchRead, "commandWait">>;
25
+ export declare function commandOutputsIntact(events: readonly AuthorityEvent[]): boolean;
26
+ export declare function readCommandWaitDeadline(connection: SqlConnection, scope: {
27
+ readonly runId: string;
28
+ readonly operationId: string;
29
+ }): Promise<{
30
+ readonly commandDeadline?: string;
31
+ }>;