@humwick/server 0.130.1 → 0.130.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (811) hide show
  1. package/README.md +5 -0
  2. package/bundled/prompts/manifest.json +1 -0
  3. package/bundled/prompts/task.hydration.md +1 -1
  4. package/bundled/roles/builder.md +1 -1
  5. package/docs/guides/sub-coordinators.md +12 -5
  6. package/docs/guides/tools-administration.md +4 -0
  7. package/docs/guides/tools-coordination.md +2 -0
  8. package/docs/guides/tools-tasks.md +8 -0
  9. package/docs/guides/workflows.md +74 -0
  10. package/docs/openapi.json +486 -4
  11. package/package.json +10 -18
  12. package/public/assets/{MessageBubble-BWpoBjl4.js → MessageBubble-Bq9kr21N.js} +2 -2
  13. package/public/assets/{PlanGraphPanel-C0auck3n.js → PlanGraphPanel-Dqiroy06.js} +2 -2
  14. package/public/assets/{TraceView-DBnOx7AQ.js → TraceView-DYU962-H.js} +2 -2
  15. package/public/assets/{VaultDecisionCard-0kp2oNBC.js → VaultDecisionCard-DhPfzioE.js} +2 -2
  16. package/public/assets/{activity-yLztXEBt.js → activity-DzCcQTee.js} +2 -2
  17. package/public/assets/{agent-lineage-BbJDR1dW.js → agent-lineage-SgDt9i3T.js} +2 -2
  18. package/public/assets/agent-liveness-Bq-s73jW.js +2 -0
  19. package/public/assets/{agent-name-CTZBrSD9.js → agent-name-CcY3Aijy.js} +2 -2
  20. package/public/assets/agent-profiles-cxtRBn6O.js +2 -0
  21. package/public/assets/{agent-project-badge-9TqzUUYE.js → agent-project-badge-DGYbIhaI.js} +2 -2
  22. package/public/assets/{agent-tools-D4n40pn8.js → agent-tools-DHbftV0N.js} +2 -2
  23. package/public/assets/{agent-type-icon-D6FUarOf.js → agent-type-icon-CY113sp-.js} +2 -2
  24. package/public/assets/{agents-BYDslkJc.js → agents-BPzY7IlQ.js} +2 -2
  25. package/public/assets/{analytics-B-XSbEMC.js → analytics-DENFPv_a.js} +2 -2
  26. package/public/assets/{artifact-viewer-DdWIfV6M.js → artifact-viewer-KRvSc7sl.js} +2 -2
  27. package/public/assets/{artifacts-DPN1q4pA.js → artifacts-C9gSmL3s.js} +2 -2
  28. package/public/assets/{artifacts-oBOqks-2.js → artifacts-DT-HkI5O.js} +2 -2
  29. package/public/assets/{automation-4KcRFXEu.js → automation-BsmjQhKD.js} +2 -2
  30. package/public/assets/{backlog-Cw9xPTCF.js → backlog-8NYH_62t.js} +2 -2
  31. package/public/assets/{busy-health-CTu32MZt.js → busy-health-D3hwvYb4.js} +2 -2
  32. package/public/assets/{channels-BiXzg1G9.js → channels-DCJ5WL7O.js} +2 -2
  33. package/public/assets/{chat-Ctk2qwvA.js → chat-DAIl357U.js} +3 -3
  34. package/public/assets/{code-preview-Bm1wWqAp.js → code-preview-CzrZfr9U.js} +2 -2
  35. package/public/assets/{connectors-C36ax4Vw.js → connectors-xTxtlyou.js} +2 -2
  36. package/public/assets/{cosmos-Jj9LK2Wf.js → cosmos-BpebFLK2.js} +2 -2
  37. package/public/assets/{crate-agent-H6S7-B9T.js → crate-agent-BHvI5wXC.js} +2 -2
  38. package/public/assets/{dependency-graph-Cd1E23wa.js → dependency-graph-DGT0o3wJ.js} +2 -2
  39. package/public/assets/{deploys-Bwfq146q.js → deploys-hqWOHSHE.js} +2 -2
  40. package/public/assets/{display-CfO7rTuF.js → display-CDeH4I8T.js} +2 -2
  41. package/public/assets/{files-DhhZI2Ih.js → files-BTeGVmTg.js} +2 -2
  42. package/public/assets/{fleet-B_rFEbnL.js → fleet-DgXtNgCa.js} +2 -2
  43. package/public/assets/{fleet-metrics-BkHP-P3R.js → fleet-metrics-BHEt5yc_.js} +2 -2
  44. package/public/assets/{formatted-body-Cm1RrzmN.js → formatted-body-MLtodyPp.js} +3 -3
  45. package/public/assets/{formatted-body-impl-DQiOuOD1.js → formatted-body-impl-CAe664Z8.js} +2 -2
  46. package/public/assets/{host-providers-dialog-CHqwXTov.js → host-providers-dialog-mapF4Fj9.js} +2 -2
  47. package/public/assets/index-CqqgOI5R.js +15 -0
  48. package/public/assets/index-MYAIwYTX.css +2 -0
  49. package/public/assets/{insights-fH1EL-qZ.js → insights-DNyf7IzF.js} +2 -2
  50. package/public/assets/{integrations-C5Oix6ng.js → integrations-DlYOHq7T.js} +2 -2
  51. package/public/assets/{issue-ref-chip-ODyondIa.js → issue-ref-chip-CeWBNWn7.js} +2 -2
  52. package/public/assets/{log-viewer-Cvfy6PW7.js → log-viewer-Cib8nqdE.js} +2 -2
  53. package/public/assets/{maintenance-D8CDH5wZ.js → maintenance-DSn1cPm7.js} +2 -2
  54. package/public/assets/{managed-agents-egllbpij.js → managed-agents-C3CkM81m.js} +2 -2
  55. package/public/assets/{markdown-preview-CYSFgVgK.js → markdown-preview-W4VQQPAv.js} +3 -3
  56. package/public/assets/{markdown-preview-impl-BteVIjX4.js → markdown-preview-impl-CMsTdhjq.js} +2 -2
  57. package/public/assets/{memory-C8WmXsyr.js → memory-BV17qtO4.js} +2 -2
  58. package/public/assets/{messages-BzQdqlO6.js → messages-Bm_1AqSA.js} +2 -2
  59. package/public/assets/{orchestrators-B1HxzkNw.js → orchestrators-Dg-vfP09.js} +2 -2
  60. package/public/assets/{overview-DCUnI4Z7.js → overview-DcYCrvlr.js} +2 -2
  61. package/public/assets/{pairs-ftCd2XcD.js → pairs-8o9nuG3V.js} +2 -2
  62. package/public/assets/{profile-icon-B2zH8DMH.js → profile-icon-BsRyBMea.js} +2 -2
  63. package/public/assets/project-profile-instructions-DETKFn_E.js +2 -0
  64. package/public/assets/projects-M_m6AHws.js +2 -0
  65. package/public/assets/{prompt-settings-Bftq9czX.js → prompt-settings-C_mNCgeL.js} +2 -2
  66. package/public/assets/provider-display-BEeLXC_D.js +2 -0
  67. package/public/assets/{registry-DWCU5c8q.js → registry-DDHHHE5N.js} +2 -2
  68. package/public/assets/run-card-Dt4I4jaP.js +2 -0
  69. package/public/assets/scroll-text-BKXS1ahr.js +2 -0
  70. package/public/assets/{secrets-C7GIUATz.js → secrets-BkVwxI5O.js} +2 -2
  71. package/public/assets/{service-access-settings-C3aujqTG.js → service-access-settings-Crq-6a-B.js} +2 -2
  72. package/public/assets/{settings-C6sZ9eBa.js → settings-CYcavMFu.js} +2 -2
  73. package/public/assets/shipping-H6gzEeZB.js +2 -0
  74. package/public/assets/{stats-V9Z-GxUd.js → stats-BJVeWkpV.js} +2 -2
  75. package/public/assets/{status-dot-CVv0iGHL.js → status-dot-BcHyw1Vw.js} +2 -2
  76. package/public/assets/store-DKPDIZBe.js +9 -0
  77. package/public/assets/{tasks-CXU75HRF.js → tasks-qJjlQPpx.js} +2 -2
  78. package/public/assets/{teams-BPf976zb.js → teams-Cd5Sk1uX.js} +2 -2
  79. package/public/assets/{terminal-viewer-DE9BOe-8.js → terminal-viewer-CpY5_m7W.js} +3 -3
  80. package/public/assets/{terminal-viewer-impl-D04pjmGh.js → terminal-viewer-impl-DI_2llFR.js} +2 -2
  81. package/public/assets/token-estimate-Co-Jdphh.js +2 -0
  82. package/public/assets/{use-agent-terminal-B1yR456R.js → use-agent-terminal-0yJA_buM.js} +2 -2
  83. package/public/assets/{use-decisions-D-S4tpDf.js → use-decisions-uJXHf34W.js} +2 -2
  84. package/public/assets/use-project-list-CpWl6eHQ.js +2 -0
  85. package/public/assets/{use-selectors-CnrAk2YP.js → use-selectors-BdxBW8mL.js} +2 -2
  86. package/public/assets/{user-avatar-nBFIcR4M.js → user-avatar-3IndfpOc.js} +2 -2
  87. package/public/assets/versioned-prompt-editor-CIVg7XB1.js +8 -0
  88. package/public/assets/{work-queue-TCjJxeT-.js → work-queue-B9rnLwHZ.js} +2 -2
  89. package/public/assets/{workflow-marker-Cf3Hf2fN.js → workflow-marker-X8wxyhzC.js} +2 -2
  90. package/public/assets/{workflows-CXQX20Qj.js → workflows-o5YyMFW7.js} +2 -2
  91. package/public/assets/{workspaces-qSYzKYMA.js → workspaces-CzEGoAng.js} +2 -2
  92. package/public/cosmos.html +4 -4
  93. package/public/index.html +31 -29
  94. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  95. package/runner/plugins/claude/hooks/gate-run-guard.provisioned.mjs +31 -1
  96. package/runner/src/adapters/claude-effort.ts +2 -4
  97. package/runner/src/adapters/claude-headless.ts +1 -1
  98. package/runner/src/adapters/claude-launch-exit.ts +26 -0
  99. package/runner/src/adapters/claude-session-probe.ts +1 -1
  100. package/runner/src/adapters/claude-tmux.ts +38 -10
  101. package/runner/src/adapters/claude.ts +28 -75
  102. package/runner/src/adapters/codex-launch-config.ts +4 -5
  103. package/runner/src/adapters/codex.ts +1 -1
  104. package/runner/src/adapters/opencode-provider.ts +7 -22
  105. package/runner/src/adapters/opencode.ts +4 -4
  106. package/runner/src/adapters/provider-quota-harvest.ts +20 -71
  107. package/runner/src/agent-runner/credentials.ts +42 -12
  108. package/runner/src/agent-runner/inbox.ts +27 -0
  109. package/runner/src/agent-runner/lifecycle-milestones.ts +2 -4
  110. package/runner/src/agent-runner/liveness-exchange.ts +104 -0
  111. package/runner/src/bin/claude.ts +4 -0
  112. package/runner/src/bin/codex.ts +4 -0
  113. package/runner/src/bin/opencode.ts +4 -0
  114. package/runner/src/claude-system-prompt.ts +49 -0
  115. package/runner/src/index.ts +5 -2
  116. package/runner/src/launch-assembly.ts +14 -10
  117. package/runner/src/profile-home.ts +1 -1
  118. package/runner/src/project-instructions.ts +2 -2
  119. package/runner/src/provider-command.ts +18 -0
  120. package/runner/src/provider-setup.ts +35 -0
  121. package/runner/src/providers.ts +2 -1
  122. package/runner/src/runner-core.ts +32 -38
  123. package/runner/src/runner-helpers.ts +0 -8
  124. package/runner/src/session-scratch.ts +6 -2
  125. package/runner/src/spawn-input.ts +2 -1
  126. package/runner/src/test-run-dedup.ts +59 -56
  127. package/runner/test-support/provisioning-context.ts +4 -2
  128. package/runner/test-support/runner-harness.ts +2 -1
  129. package/scripts/cd-config.ts +8 -0
  130. package/scripts/gate-run-lease.ts +10 -7
  131. package/scripts/gate.sh +5 -1
  132. package/scripts/steward-policy-cleanup.ts +2 -2
  133. package/src/agent-authz.ts +2 -2
  134. package/src/agent-drive-arbiter.ts +3 -8
  135. package/src/agent-exit-cleanup-async.ts +2 -2
  136. package/src/agent-exit-cleanup.ts +23 -19
  137. package/src/agent-lifecycle-events.ts +112 -85
  138. package/src/agent-ref.ts +3 -2
  139. package/src/agent-status-events.ts +43 -14
  140. package/src/auth/password.ts +4 -3
  141. package/src/auth/session-refresh.ts +1 -1
  142. package/src/auth/session.test-fixture.ts +2 -2
  143. package/src/auth/session.ts +7 -7
  144. package/src/automations/crud.ts +8 -81
  145. package/src/automations/dispatch.ts +33 -58
  146. package/src/automations/index.ts +1 -1
  147. package/src/automations/persistence.ts +65 -0
  148. package/src/automations/precheck.ts +18 -16
  149. package/src/automations/read-async.ts +11 -0
  150. package/src/automations/reconcile.ts +30 -39
  151. package/src/automations/storage.ts +33 -10
  152. package/src/automations/write-async.ts +33 -0
  153. package/src/branch-land-notice.ts +93 -0
  154. package/src/branch-landed.ts +31 -98
  155. package/src/bus.ts +40 -39
  156. package/src/cd-deploy-notify.ts +32 -4
  157. package/src/cd-host-lag-notify.ts +7 -6
  158. package/src/ci/incidents.ts +2 -2
  159. package/src/ci/monitor.ts +5 -8
  160. package/src/ci/notifications.ts +4 -4
  161. package/src/ci/root-missing.ts +4 -9
  162. package/src/ci/transition-notify.ts +12 -44
  163. package/src/claude-app/oauth-http.ts +3 -3
  164. package/src/cli/cd-pauses.ts +19 -0
  165. package/src/cli/orchestrator.ts +1 -1
  166. package/src/cli/state.ts +2 -19
  167. package/src/cli/task-edit.ts +22 -0
  168. package/src/cli/task.ts +3 -1
  169. package/src/cli/workflow.ts +19 -4
  170. package/src/commands-db.ts +7 -7
  171. package/src/commands-write.ts +8 -0
  172. package/src/compaction-watch.ts +10 -8
  173. package/src/config-store-write.ts +13 -1
  174. package/src/config-store.ts +25 -14
  175. package/src/config.ts +4 -11
  176. package/src/connectors.ts +10 -8
  177. package/src/context-advisory.ts +22 -21
  178. package/src/db/activity-async.ts +6 -1
  179. package/src/db/activity-write-ops.ts +12 -1
  180. package/src/db/activity.ts +1 -14
  181. package/src/db/agent-action-write.ts +36 -0
  182. package/src/db/agent-control-async.ts +9 -0
  183. package/src/db/agent-control-write.ts +49 -0
  184. package/src/db/agent-effort-observe.ts +22 -0
  185. package/src/db/agent-meta.ts +14 -3
  186. package/src/db/agent-search.ts +1 -15
  187. package/src/db/agents-async.ts +59 -9
  188. package/src/db/agents-write-ops.ts +12 -3
  189. package/src/db/agents.ts +95 -65
  190. package/src/db/auth-identities.ts +2 -2
  191. package/src/db/automation-read-ops.ts +11 -0
  192. package/src/db/automation-write-ops.ts +46 -0
  193. package/src/db/blackboard.ts +2 -2
  194. package/src/db/browser-session-write.ts +32 -0
  195. package/src/db/bus-outbox-write-ops.ts +3 -1
  196. package/src/db/checkout-refresh-schema.ts +47 -0
  197. package/src/db/checkout-refresh.ts +5 -5
  198. package/src/db/ci-incidents-async.ts +5 -5
  199. package/src/db/ci-incidents-write-ops.ts +2 -2
  200. package/src/db/command-agent-profile-backfill.ts +39 -0
  201. package/src/db/command-agent-profiles.ts +5 -1
  202. package/src/db/command-retention.ts +126 -0
  203. package/src/db/command-row.ts +10 -2
  204. package/src/db/commands-list-query.ts +2 -0
  205. package/src/db/commands-planner-stats.test-fixture.ts +8 -0
  206. package/src/db/commands-read-ops.ts +16 -0
  207. package/src/db/commands-spawn-read.ts +1 -1
  208. package/src/db/config-store-write-ops.ts +7 -2
  209. package/src/db/connection.ts +1 -182
  210. package/src/db/context-carry-write.ts +74 -0
  211. package/src/db/continuation-archives-async.ts +5 -0
  212. package/src/db/continuation-archives-write-ops.ts +7 -0
  213. package/src/db/continuations.ts +6 -6
  214. package/src/db/db-error-registry.ts +12 -0
  215. package/src/db/db-writer-worker.ts +8 -1
  216. package/src/db/deploy-events-async.ts +1 -0
  217. package/src/db/deploy-events-read-ops.ts +2 -1
  218. package/src/db/deploy-events.ts +35 -4
  219. package/src/db/gateway.ts +26 -133
  220. package/src/db/history-retention-ops.ts +13 -0
  221. package/src/db/hosts-registration.ts +10 -2
  222. package/src/db/hosts.ts +9 -6
  223. package/src/db/inbox-async.ts +2 -2
  224. package/src/db/inbox-read-ops.ts +1 -1
  225. package/src/db/inbox.ts +9 -17
  226. package/src/db/index.ts +2 -0
  227. package/src/db/integrations-async.ts +8 -0
  228. package/src/db/integrations-ops.ts +15 -0
  229. package/src/db/integrations.ts +15 -21
  230. package/src/db/issue-associations.ts +2 -2
  231. package/src/db/issue-lifecycle.ts +3 -4
  232. package/src/db/issue-refs-async.ts +23 -0
  233. package/src/db/issue-refs-read-ops.ts +6 -0
  234. package/src/db/issue-refs-write-ops.ts +29 -0
  235. package/src/db/issue-refs.ts +15 -16
  236. package/src/db/kernel-event-log-schema.ts +2 -0
  237. package/src/db/land-boundaries.ts +51 -5
  238. package/src/db/land-gate-attestations.ts +2 -2
  239. package/src/db/landing-receipts.ts +2 -2
  240. package/src/db/lifecycle-context-write.ts +5 -0
  241. package/src/db/lifecycle-tail-async.ts +76 -0
  242. package/src/db/lifecycle-tail-write-ops.ts +62 -0
  243. package/src/db/lifecycle-write-window.ts +2 -1
  244. package/src/db/lineage-pair-dedupe-sql.ts +3 -3
  245. package/src/db/long-running-work.ts +2 -2
  246. package/src/db/main-connection-mode.ts +11 -0
  247. package/src/db/main-connection-writes.ts +41 -2
  248. package/src/db/maintenance-job-reconcile.ts +61 -0
  249. package/src/db/maintenance-job-runs-async.ts +14 -0
  250. package/src/db/maintenance-job-write-ops.ts +9 -0
  251. package/src/db/maintenance-pauses.ts +2 -2
  252. package/src/db/mappers.ts +2 -14
  253. package/src/db/merge-lease.ts +1 -15
  254. package/src/db/message-send.ts +4 -0
  255. package/src/db/messages.ts +31 -25
  256. package/src/db/migrations.ts +25 -16
  257. package/src/db/notification-write-ops.ts +19 -0
  258. package/src/db/operator-answer-write.ts +19 -0
  259. package/src/db/orchestrators.ts +14 -76
  260. package/src/db/pairs-async.ts +22 -1
  261. package/src/db/pairs-write-ops.ts +8 -3
  262. package/src/db/pairs.ts +42 -61
  263. package/src/db/plan-dispatch-schema.ts +5 -1
  264. package/src/db/plan-ready-notices.ts +43 -0
  265. package/src/db/plan-tools-include-migration.ts +36 -0
  266. package/src/db/plans-write-ops.ts +2 -0
  267. package/src/db/presence-buffer.ts +5 -0
  268. package/src/db/presence-loop.ts +18 -23
  269. package/src/db/presence-write.ts +0 -6
  270. package/src/db/project-entries-async.ts +14 -0
  271. package/src/db/project-entries-read-ops.ts +5 -1
  272. package/src/db/project-entries-write-ops.ts +8 -0
  273. package/src/db/project-entries.ts +15 -12
  274. package/src/db/project-journal.ts +2 -2
  275. package/src/db/project-profile-instructions.ts +50 -0
  276. package/src/db/project-trust-approvals.ts +2 -2
  277. package/src/db/prompt-templates.ts +4 -4
  278. package/src/db/prompt-versions.ts +9 -2
  279. package/src/db/provider-config-history.ts +8 -3
  280. package/src/db/provider-model-seed.ts +34 -0
  281. package/src/db/read-op-marshaling-fixtures.ts +23 -0
  282. package/src/db/read-ops.ts +8 -0
  283. package/src/db/retention.ts +13 -2
  284. package/src/db/review-publish-write.ts +8 -0
  285. package/src/db/routing-policy.ts +2 -3
  286. package/src/db/runtime-admin-async.ts +19 -0
  287. package/src/db/runtime-admin-write-ops.ts +36 -0
  288. package/src/db/runtime-tail-async.ts +288 -0
  289. package/src/db/runtime-tail-write-ops.ts +97 -0
  290. package/src/db/schema.ts +13 -4
  291. package/src/db/spawn-budget.ts +5 -0
  292. package/src/db/spawn-delegation-state.ts +11 -4
  293. package/src/db/spawn-delegation.ts +1 -11
  294. package/src/db/sql-audit-indexes.ts +3 -0
  295. package/src/db/stats-async.ts +9 -6
  296. package/src/db/task-entity.ts +28 -6
  297. package/src/db/task-list.ts +3 -1
  298. package/src/db/tasks.ts +1 -17
  299. package/src/db/team-claim-policy.ts +60 -0
  300. package/src/db/teams-async.ts +4 -0
  301. package/src/db/teams-write-ops.ts +4 -0
  302. package/src/db/terminal-events.ts +15 -0
  303. package/src/db/timer-cutover-bodies.ts +292 -0
  304. package/src/db/timer-cutover-core.ts +37 -278
  305. package/src/db/timer-cutover-state.ts +40 -0
  306. package/src/db/timer-cutover-write-ops.ts +1 -40
  307. package/src/db/timer-ingress.ts +7 -1
  308. package/src/db/token-db-write-ops.ts +19 -0
  309. package/src/db/token-standing-async.ts +12 -0
  310. package/src/db/token-standing.ts +21 -20
  311. package/src/db/token-usage-retention.ts +33 -0
  312. package/src/db/token-usage-write-ops.ts +1 -1
  313. package/src/db/token-usage.ts +4 -1
  314. package/src/db/user-invites-async.ts +13 -0
  315. package/src/db/user-invites.ts +3 -3
  316. package/src/db/users.ts +14 -6
  317. package/src/db/workflow-contact.ts +1 -1
  318. package/src/db/workflow-task-read-ops.ts +30 -0
  319. package/src/db/workspaces.ts +8 -5
  320. package/src/db/write-ops.ts +37 -12
  321. package/src/db/writer-fault-seam.ts +2 -9
  322. package/src/db/writer-fault-test-ops.ts +18 -0
  323. package/src/db/writer-gateway.ts +58 -56
  324. package/src/dev.ts +8 -3
  325. package/src/dispatch-availability.ts +10 -6
  326. package/src/ephemeral/{landing-ingress-proof-worker.ts → landing-ingress-proof.test-fixture.ts} +4 -2
  327. package/src/event-loop-lag.ts +4 -2
  328. package/src/event-loop-yield.ts +2 -2
  329. package/src/events.ts +10 -8
  330. package/src/host/disk-alert.ts +121 -0
  331. package/src/host-provider-store.ts +5 -4
  332. package/src/hub-metrics-report.ts +5 -1
  333. package/src/hub-metrics.ts +38 -18
  334. package/src/injection-sweep-pacing.ts +6 -8
  335. package/src/land-gate-notifications.ts +11 -10
  336. package/src/land-gate-queue-progress.ts +3 -3
  337. package/src/land-settle-dispatch.ts +52 -92
  338. package/src/lifecycle-context-pressure.ts +11 -10
  339. package/src/lifecycle-manager.ts +107 -100
  340. package/src/maintenance/breaker.ts +18 -16
  341. package/src/maintenance/cd-watcher.ts +3 -3
  342. package/src/maintenance/checkout-refresh.ts +18 -1
  343. package/src/maintenance/critical-job-watchdog.ts +19 -20
  344. package/src/maintenance/events.ts +3 -3
  345. package/src/maintenance/history-retention-job.ts +30 -0
  346. package/src/maintenance/index.ts +1 -0
  347. package/src/maintenance/jobs.ts +32 -69
  348. package/src/maintenance/long-running-work.ts +3 -1
  349. package/src/maintenance/offline-agent-prune-job.ts +28 -0
  350. package/src/maintenance/orphaned-sessions.ts +5 -4
  351. package/src/maintenance/pauses.ts +4 -10
  352. package/src/maintenance/plan-ready-notice-job.ts +71 -0
  353. package/src/maintenance/scheduler.ts +98 -137
  354. package/src/maintenance/stalled-delivery-pager.ts +10 -9
  355. package/src/maintenance/teams.ts +19 -30
  356. package/src/maintenance/workspaces.ts +18 -28
  357. package/src/managed-policy-escalation.ts +7 -7
  358. package/src/managed-policy.ts +32 -28
  359. package/src/managed-provider-gate.ts +5 -4
  360. package/src/managed-spawn-reservation.ts +5 -4
  361. package/src/mcp/index.ts +12 -9
  362. package/src/mcp/output-descriptions.ts +8 -8
  363. package/src/mcp/presentation.ts +6 -5
  364. package/src/mcp/project-profile-instructions.ts +32 -0
  365. package/src/mcp/runtime-output.ts +6 -1
  366. package/src/mcp/self-care-visibility.ts +2 -2
  367. package/src/mcp/spawn-targets-matrix.ts +10 -1
  368. package/src/mcp/tool-groups.ts +4 -3
  369. package/src/mcp/tool-roles.ts +81 -0
  370. package/src/mcp/tools-spawn.ts +4 -3
  371. package/src/mcp/tools-workspace.ts +1 -1
  372. package/src/mcp-agent-action.ts +1 -2
  373. package/src/mcp-blackboard-tools.ts +6 -5
  374. package/src/mcp-decision-tools.ts +7 -7
  375. package/src/mcp-egress-tools.ts +10 -9
  376. package/src/mcp-issue-activity-tools.ts +2 -2
  377. package/src/mcp-issue-association-tools.ts +4 -4
  378. package/src/mcp-issue-ref-tools.ts +13 -12
  379. package/src/mcp-maintenance-tools.ts +2 -2
  380. package/src/mcp-merge-tools.ts +5 -5
  381. package/src/mcp-notification-subscription-tools.ts +14 -15
  382. package/src/mcp-output-levels.test-fixture.ts +1 -1
  383. package/src/mcp-profile-trust-tools.ts +5 -5
  384. package/src/mcp-project-journal-tools.ts +4 -12
  385. package/src/mcp-project-tools.ts +17 -18
  386. package/src/mcp-provider-catalog.ts +2 -2
  387. package/src/mcp-routing-policy-tools.ts +3 -2
  388. package/src/mcp-task-tools.ts +11 -5
  389. package/src/mcp-team-template-tools.ts +3 -3
  390. package/src/mcp-team-tools.ts +6 -6
  391. package/src/mcp-workflow-tools.ts +13 -4
  392. package/src/memory-area-injection.ts +13 -12
  393. package/src/memory-boundary-capture.ts +6 -6
  394. package/src/memory-injection.ts +45 -43
  395. package/src/merge-pause-maintenance.ts +5 -4
  396. package/src/notification-router.ts +44 -17
  397. package/src/notification-types.ts +2 -1
  398. package/src/notify.ts +5 -5
  399. package/src/observed-reset-refresh.ts +4 -4
  400. package/src/operator-alarm.ts +23 -11
  401. package/src/project-ingest.ts +6 -6
  402. package/src/project-instructions.ts +12 -1
  403. package/src/project-profile-instruction-text.ts +6 -0
  404. package/src/project-profile-instructions.ts +20 -0
  405. package/src/project-trust.ts +12 -11
  406. package/src/prompt-resolver.ts +3 -3
  407. package/src/provider-catalog-store.ts +3 -49
  408. package/src/provider-config-store.ts +12 -11
  409. package/src/provider-model-seed.ts +1 -18
  410. package/src/provider-quota-async.ts +5 -7
  411. package/src/provider-quota-config-store.ts +5 -4
  412. package/src/provider-secrets.test-fixture.ts +3 -3
  413. package/src/provider-timeline-event.ts +4 -7
  414. package/src/quota-band-delivery.ts +2 -2
  415. package/src/recipe-runner.ts +6 -5
  416. package/src/repo-config-source.ts +10 -4
  417. package/src/retention-config-store.ts +4 -3
  418. package/src/reviewer-pipeline.ts +11 -10
  419. package/src/routes/_route.ts +4 -4
  420. package/src/routes/_shared.ts +13 -12
  421. package/src/routes/agent-effort.ts +5 -5
  422. package/src/routes/agent-heartbeat-quota.ts +23 -0
  423. package/src/routes/agent-keep.ts +1 -1
  424. package/src/routes/agent-profiles.ts +6 -6
  425. package/src/routes/agent-reporting.ts +1 -1
  426. package/src/routes/agent-self-effort.ts +5 -5
  427. package/src/routes/agent-sessions.ts +24 -24
  428. package/src/routes/agents-spawn.ts +2 -2
  429. package/src/routes/agents.ts +19 -121
  430. package/src/routes/auth.ts +14 -15
  431. package/src/routes/authority-audit.ts +2 -2
  432. package/src/routes/automations.ts +6 -5
  433. package/src/routes/channel-binding-delete.ts +1 -1
  434. package/src/routes/channel-output.ts +3 -3
  435. package/src/routes/claude-api-budget.ts +5 -5
  436. package/src/routes/commands-listing.ts +5 -1
  437. package/src/routes/commands.ts +31 -29
  438. package/src/routes/context-resets.ts +2 -2
  439. package/src/routes/continuation-archives.ts +2 -2
  440. package/src/routes/coordinator-teams.ts +1 -1
  441. package/src/routes/decisions.ts +2 -2
  442. package/src/routes/host-secret-files.ts +1 -1
  443. package/src/routes/hosts.ts +11 -11
  444. package/src/routes/inbox.ts +3 -2
  445. package/src/routes/index.ts +12 -2
  446. package/src/routes/integrations.ts +32 -56
  447. package/src/routes/issue-activity.ts +2 -2
  448. package/src/routes/issue-associations.ts +4 -4
  449. package/src/routes/issue-refs.ts +9 -9
  450. package/src/routes/kernel-timers-operator.ts +1 -1
  451. package/src/routes/memory.ts +1 -1
  452. package/src/routes/merges.ts +11 -11
  453. package/src/routes/messages.ts +23 -17
  454. package/src/routes/orchestrator-bootstrap.ts +2 -2
  455. package/src/routes/orchestrator-host-registration.ts +4 -2
  456. package/src/routes/orchestrator-host-utilization.ts +5 -3
  457. package/src/routes/orchestrator-proxy.ts +4 -4
  458. package/src/routes/orchestrator.ts +28 -19
  459. package/src/routes/pairs.ts +8 -8
  460. package/src/routes/project-profile-instructions.ts +27 -0
  461. package/src/routes/project-trust.ts +3 -3
  462. package/src/routes/projects.ts +16 -16
  463. package/src/routes/prompt-templates.ts +12 -12
  464. package/src/routes/provider-config.ts +6 -6
  465. package/src/routes/provider-quota-config.ts +2 -2
  466. package/src/routes/provider-quotas.ts +10 -10
  467. package/src/routes/run-command.ts +1 -1
  468. package/src/routes/secrets-backup.ts +2 -2
  469. package/src/routes/secrets-import.ts +1 -1
  470. package/src/routes/secrets-vault.ts +7 -7
  471. package/src/routes/settings.ts +6 -6
  472. package/src/routes/shipping.ts +9 -1
  473. package/src/routes/spawn-policy.ts +8 -7
  474. package/src/routes/sse.ts +2 -2
  475. package/src/routes/stats.ts +6 -6
  476. package/src/routes/task-http.ts +15 -1
  477. package/src/routes/task-issues.ts +11 -26
  478. package/src/routes/tasks.ts +22 -53
  479. package/src/routes/team-templates.ts +12 -12
  480. package/src/routes/teams-config.ts +2 -2
  481. package/src/routes/teams.ts +10 -10
  482. package/src/routes/tokens.ts +30 -22
  483. package/src/routes/users.ts +24 -24
  484. package/src/routes/viewer-read.ts +3 -3
  485. package/src/routes/web-tool-calls.ts +4 -4
  486. package/src/routes/workflows.ts +22 -7
  487. package/src/routes/workspaces.ts +17 -16
  488. package/src/routing-policy-normalize.ts +1 -2
  489. package/src/routing-policy-push.ts +6 -6
  490. package/src/routing-step-defaults.ts +0 -2
  491. package/src/runner-token-mint-limit.ts +63 -0
  492. package/src/runtime-grant.ts +21 -13
  493. package/src/runtime-tokens.ts +59 -35
  494. package/src/secrets-broker/config.ts +1 -1
  495. package/src/secrets-broker/service.ts +2 -2
  496. package/src/security.ts +16 -21
  497. package/src/self-resume-carry.ts +21 -110
  498. package/src/self-resume-supersede.ts +4 -3
  499. package/src/self-resume.ts +54 -66
  500. package/src/server-background.ts +2 -2
  501. package/src/server.ts +26 -24
  502. package/src/services/agent-action.ts +12 -31
  503. package/src/services/agent-effort.ts +7 -32
  504. package/src/services/agent-hang-watchdog-job.ts +2 -2
  505. package/src/services/agent-heartbeat.ts +166 -0
  506. package/src/services/agent-reporting.ts +1 -1
  507. package/src/services/browser-inventory.ts +3 -3
  508. package/src/services/browser-session-state.ts +3 -2
  509. package/src/services/browser-session.ts +21 -27
  510. package/src/services/bus-command-update.ts +9 -9
  511. package/src/services/checkout-refresh-state.ts +1 -0
  512. package/src/services/checkout-refresh.test-fixture.ts +3 -2
  513. package/src/services/command-host.ts +7 -2
  514. package/src/services/coordinator-digest.ts +3 -2
  515. package/src/services/dispatch-command.ts +12 -13
  516. package/src/services/effective-launch-driver.ts +14 -9
  517. package/src/services/host-changed-paths.test-fixture.ts +5 -6
  518. package/src/services/host-project-readiness.ts +2 -2
  519. package/src/services/host-providers.ts +1 -1
  520. package/src/services/host-utilization.ts +27 -4
  521. package/src/services/idle-agent-digest.ts +17 -12
  522. package/src/services/idle-epics.ts +9 -10
  523. package/src/services/interrupt-agent.ts +12 -9
  524. package/src/services/issue-activity.ts +3 -3
  525. package/src/services/issue-associations.ts +15 -18
  526. package/src/services/issue-lifecycle-comments.ts +3 -2
  527. package/src/services/issue-lifecycle.ts +69 -72
  528. package/src/services/issue-refs.ts +10 -15
  529. package/src/services/kernel-timers-cutover-chaos-worker.ts +11 -8
  530. package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
  531. package/src/services/kernel-timers-ingress.ts +4 -4
  532. package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
  533. package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
  534. package/src/services/kernel-timers-outbox.ts +1 -1
  535. package/src/services/kernel-timers-stage10-operator.ts +1 -1
  536. package/src/services/kernel-timers.ts +4 -3
  537. package/src/services/land-gate-placement.ts +11 -2
  538. package/src/services/land-issue-relations.ts +5 -4
  539. package/src/services/land-restart-retry.ts +7 -6
  540. package/src/services/landed-ancestry.ts +25 -8
  541. package/src/services/legacy-landing-host.ts +4 -1
  542. package/src/services/legacy-landing-recovery.ts +8 -6
  543. package/src/services/managed-running.ts +17 -17
  544. package/src/services/memory-areas.ts +17 -53
  545. package/src/services/merge-pause.ts +9 -15
  546. package/src/services/operator-decisions.ts +37 -41
  547. package/src/services/orchestrator-registration-recovery.ts +6 -2
  548. package/src/services/orchestrator-upgrade.ts +14 -12
  549. package/src/services/permission-decision.ts +4 -3
  550. package/src/services/plan-ci-notice.ts +57 -0
  551. package/src/services/plan-ci-recovery.ts +2 -55
  552. package/src/services/plan-conformance.ts +4 -4
  553. package/src/services/plan-dispatch-policy.ts +10 -3
  554. package/src/services/plan-dispatch.ts +35 -14
  555. package/src/services/plan-graph.ts +39 -261
  556. package/src/services/plan-import.ts +1 -1
  557. package/src/services/plan-live-bindings.ts +8 -37
  558. package/src/services/plan-task-create.ts +1 -1
  559. package/src/services/plan-terminal-refs.ts +2 -12
  560. package/src/services/plan-wake-digest.ts +9 -3
  561. package/src/services/profile-trust.ts +6 -5
  562. package/src/services/project-config-read.ts +70 -0
  563. package/src/services/project-profile-instructions.ts +54 -0
  564. package/src/services/project.ts +11 -10
  565. package/src/services/prompt-templates.ts +6 -5
  566. package/src/services/provider-hold-nudge.ts +9 -9
  567. package/src/services/provider-hold-record.ts +13 -12
  568. package/src/services/provider-state-audit.ts +19 -18
  569. package/src/services/provisioning-harness/budget-baseline.json +4 -4
  570. package/src/services/provisioning-harness/driver.ts +1 -1
  571. package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
  572. package/src/services/provisioning-harness/lifecycle.ts +24 -22
  573. package/src/services/provisioning-harness/prepare.ts +16 -17
  574. package/src/services/rate-limit-resume.ts +10 -9
  575. package/src/services/red-main-auto-revert.ts +7 -5
  576. package/src/services/red-main-controller-deps.ts +20 -3
  577. package/src/services/red-main-incident.ts +3 -1
  578. package/src/services/red-main-quarantine.ts +2 -3
  579. package/src/services/register-agent.ts +22 -22
  580. package/src/services/restart-project-role.ts +1 -1
  581. package/src/services/restart-spawn.ts +74 -61
  582. package/src/services/review-publish.ts +7 -24
  583. package/src/services/review-ref-reconcile.ts +29 -38
  584. package/src/services/runtime-dependency-alert.ts +27 -0
  585. package/src/services/scheduler-command.ts +11 -9
  586. package/src/services/scheduler.ts +5 -5
  587. package/src/services/self-care-state.ts +2 -1
  588. package/src/services/self-effort-grant.ts +5 -25
  589. package/src/services/self-effort.ts +5 -5
  590. package/src/services/send-message.ts +21 -34
  591. package/src/services/shutdown-agent.ts +17 -14
  592. package/src/services/spawn-agent.ts +25 -43
  593. package/src/services/spawn-chain-inspector.ts +7 -14
  594. package/src/services/spawn-cwd-placement.ts +21 -5
  595. package/src/services/spawn-issue-activity.ts +8 -6
  596. package/src/services/spawn-launch.ts +17 -1
  597. package/src/services/spawn-loss-escalation.ts +1 -1
  598. package/src/services/spawn-policy.ts +68 -0
  599. package/src/services/spawn-registration-watchdog.ts +2 -2
  600. package/src/services/spawn-repo-probe.ts +7 -5
  601. package/src/services/spawn-tree-cleanup.ts +8 -9
  602. package/src/services/steward-reaper.ts +4 -4
  603. package/src/services/task-context-hydration.ts +25 -1
  604. package/src/services/task-delivery.ts +20 -34
  605. package/src/services/task-dispatch.ts +3 -0
  606. package/src/services/task-entity.ts +40 -203
  607. package/src/services/task-issue-link.ts +13 -20
  608. package/src/services/task-issue-weave.ts +12 -11
  609. package/src/services/task-lifecycle.ts +27 -31
  610. package/src/services/task-repo-cwd.ts +37 -0
  611. package/src/services/task-review-hold.ts +4 -17
  612. package/src/services/task-semantic-events.ts +13 -11
  613. package/src/services/task-state-reconcile.ts +8 -17
  614. package/src/services/task-validation.ts +2 -2
  615. package/src/services/team-templates.ts +12 -11
  616. package/src/services/team.ts +31 -98
  617. package/src/services/terminal-provider-exit.ts +6 -5
  618. package/src/services/timer-message-fire-chaos-worker.ts +7 -3
  619. package/src/services/timer-message-fire-outbox.ts +1 -1
  620. package/src/services/transient-agent-reaper.ts +25 -20
  621. package/src/services/turn-reconcile-audit.ts +9 -9
  622. package/src/services/user-onboarding.ts +8 -6
  623. package/src/services/user-profile.ts +6 -9
  624. package/src/services/work-finality.ts +16 -4
  625. package/src/services/workflow-auto-start.ts +26 -13
  626. package/src/services/workflow-caller.ts +61 -4
  627. package/src/services/workflow-child-proof-worker.ts +1 -1
  628. package/src/services/workflow-ci-main.ts +1 -1
  629. package/src/services/workflow-control-proof-worker.ts +1 -1
  630. package/src/services/workflow-coordinator.ts +8 -2
  631. package/src/services/workflow-decisions.ts +5 -5
  632. package/src/services/workflow-deferred-cleanup.ts +5 -3
  633. package/src/services/workflow-effect-driver.ts +42 -17
  634. package/src/services/workflow-effects.ts +58 -37
  635. package/src/services/workflow-land-notice.ts +10 -0
  636. package/src/services/workflow-pause-text.ts +43 -0
  637. package/src/services/workflow-pins.ts +7 -4
  638. package/src/services/workflow-reconcile.ts +14 -11
  639. package/src/services/workflow-resume-pins.ts +3 -3
  640. package/src/services/workflow-routing-defaults.ts +18 -0
  641. package/src/services/workflow-run-view.ts +6 -3
  642. package/src/services/workflow-run.test-fixture.ts +1 -1
  643. package/src/services/workflow-runs.ts +61 -33
  644. package/src/services/workflow-supervisor-state.ts +26 -1
  645. package/src/services/workflow-supervisor.ts +11 -6
  646. package/src/services/workspace-recovery.ts +4 -3
  647. package/src/settings/registry.ts +1 -2
  648. package/src/spawn-admission.ts +10 -2
  649. package/src/spawn-command.ts +4 -4
  650. package/src/spawn-models.ts +10 -12
  651. package/src/spawn-placement.ts +28 -10
  652. package/src/spawn-targets.ts +9 -9
  653. package/src/sse-agent-status.ts +15 -6
  654. package/src/sse.ts +49 -24
  655. package/src/startup-context-injection.ts +62 -57
  656. package/src/state-adoption/cd-cutover.ts +53 -7
  657. package/src/state-adoption/fleet-cutover.ts +35 -0
  658. package/src/state-adoption/fleet-plan.ts +36 -0
  659. package/src/state-adoption/index.ts +2 -2
  660. package/src/state-adoption/linux-plan.ts +2 -2
  661. package/src/state-adoption/linux.ts +2 -2
  662. package/src/state-adoption/macos-plan.ts +2 -2
  663. package/src/state-adoption/macos.ts +1 -1
  664. package/src/steward-handoff.ts +4 -3
  665. package/src/steward-policy-cleanup.ts +9 -8
  666. package/src/steward.ts +9 -8
  667. package/src/task-dispatch-live.ts +6 -3
  668. package/src/task-dispatcher.ts +3 -3
  669. package/src/teams-config-store.ts +4 -3
  670. package/src/token-db-write.ts +10 -0
  671. package/src/token-db.ts +2 -2
  672. package/src/token-usage-async.ts +2 -4
  673. package/src/user-awaiting-reply.ts +5 -5
  674. package/src/workspace-actions.ts +24 -29
  675. package/src/workspace-auto-merge.ts +14 -12
  676. package/src/workspace-changed-paths-client.ts +16 -5
  677. package/src/workspace-cleanup-stale.ts +30 -7
  678. package/src/workspace-command-settle.ts +26 -17
  679. package/src/workspace-config.ts +1 -1
  680. package/src/workspace-conflict-resolver.ts +9 -7
  681. package/src/workspace-dead-owner-recovery.ts +5 -4
  682. package/src/workspace-escalation.ts +12 -11
  683. package/src/workspace-human-escalation.ts +3 -3
  684. package/src/workspace-land-gate-watchdog.ts +3 -3
  685. package/src/workspace-land-refusal.ts +20 -0
  686. package/src/workspace-land-stopped.ts +1 -0
  687. package/src/workspace-merge.ts +10 -26
  688. package/src/workspace-nothing-to-land-notice.ts +2 -2
  689. package/src/workspace-orphans.ts +18 -37
  690. package/src/workspace-pr-completion.ts +10 -8
  691. package/src/workspace-rekey.ts +3 -3
  692. package/src/workspace-steward-failure.ts +2 -2
  693. package/src/workspace-steward-wake.ts +4 -4
  694. package/node_modules/@humwick/kernel/LICENSE +0 -167
  695. package/node_modules/@humwick/kernel/async-entity-persistence.ts +0 -344
  696. package/node_modules/@humwick/kernel/async-event-log.ts +0 -107
  697. package/node_modules/@humwick/kernel/automations.pilot.ts +0 -147
  698. package/node_modules/@humwick/kernel/config.ts +0 -111
  699. package/node_modules/@humwick/kernel/contract.ts +0 -572
  700. package/node_modules/@humwick/kernel/control-authz.ts +0 -281
  701. package/node_modules/@humwick/kernel/control.ts +0 -645
  702. package/node_modules/@humwick/kernel/effects.ts +0 -493
  703. package/node_modules/@humwick/kernel/event-log.ts +0 -319
  704. package/node_modules/@humwick/kernel/http-transport.ts +0 -384
  705. package/node_modules/@humwick/kernel/identity-grammar.ts +0 -20
  706. package/node_modules/@humwick/kernel/identity.ts +0 -248
  707. package/node_modules/@humwick/kernel/index.ts +0 -28
  708. package/node_modules/@humwick/kernel/kernel-persistence.ts +0 -181
  709. package/node_modules/@humwick/kernel/landing-incident.ts +0 -306
  710. package/node_modules/@humwick/kernel/landing.ts +0 -953
  711. package/node_modules/@humwick/kernel/manifest-validation.ts +0 -460
  712. package/node_modules/@humwick/kernel/mcp-transport.ts +0 -109
  713. package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +0 -200
  714. package/node_modules/@humwick/kernel/message-authority.ts +0 -281
  715. package/node_modules/@humwick/kernel/module-host.ts +0 -722
  716. package/node_modules/@humwick/kernel/openapi-init.ts +0 -14
  717. package/node_modules/@humwick/kernel/package.json +0 -39
  718. package/node_modules/@humwick/kernel/persistence-schema.ts +0 -248
  719. package/node_modules/@humwick/kernel/persistence.ts +0 -510
  720. package/node_modules/@humwick/kernel/registry.ts +0 -60
  721. package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +0 -499
  722. package/node_modules/@humwick/kernel/sqlite-module-host.ts +0 -87
  723. package/node_modules/@humwick/kernel/timers-chaos-worker.ts +0 -186
  724. package/node_modules/@humwick/kernel/timers.pilot.ts +0 -1411
  725. package/node_modules/@humwick/kernel/workflow-activation.ts +0 -25
  726. package/node_modules/@humwick/kernel/workflow-backstop.ts +0 -40
  727. package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +0 -20
  728. package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +0 -213
  729. package/node_modules/@humwick/kernel/workflow-composition.ts +0 -231
  730. package/node_modules/@humwick/kernel/workflow-container.ts +0 -72
  731. package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +0 -42
  732. package/node_modules/@humwick/kernel/workflow-control.ts +0 -178
  733. package/node_modules/@humwick/kernel/workflow-definition.ts +0 -507
  734. package/node_modules/@humwick/kernel/workflow-dispatch.ts +0 -60
  735. package/node_modules/@humwick/kernel/workflow-foreach.ts +0 -167
  736. package/node_modules/@humwick/kernel/workflow-journal-codec.ts +0 -339
  737. package/node_modules/@humwick/kernel/workflow-journal.ts +0 -922
  738. package/node_modules/@humwick/kernel/workflow-lane.ts +0 -48
  739. package/node_modules/@humwick/kernel/workflow-lease-owner.ts +0 -60
  740. package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +0 -19
  741. package/node_modules/@humwick/kernel/workflow-runtime.ts +0 -938
  742. package/node_modules/@humwick/kernel/workflow-script.ts +0 -11
  743. package/node_modules/@humwick/kernel/workflow-state.ts +0 -299
  744. package/node_modules/@humwick/kernel/workflow-step-commit.ts +0 -195
  745. package/node_modules/@humwick/kernel/workflow-transitions.ts +0 -317
  746. package/node_modules/@humwick/kernel/workflow-values.ts +0 -153
  747. package/node_modules/@humwick/kernel/workflow.pilot.ts +0 -376
  748. package/node_modules/@humwick/offload/LICENSE +0 -167
  749. package/node_modules/@humwick/offload/README.md +0 -57
  750. package/node_modules/@humwick/offload/dist/async-runner.d.ts +0 -18
  751. package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +0 -1
  752. package/node_modules/@humwick/offload/dist/async-runner.js +0 -158
  753. package/node_modules/@humwick/offload/dist/async-runner.js.map +0 -1
  754. package/node_modules/@humwick/offload/dist/envelope.d.ts +0 -25
  755. package/node_modules/@humwick/offload/dist/envelope.d.ts.map +0 -1
  756. package/node_modules/@humwick/offload/dist/envelope.js +0 -80
  757. package/node_modules/@humwick/offload/dist/envelope.js.map +0 -1
  758. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +0 -10
  759. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +0 -1
  760. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +0 -59
  761. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +0 -1
  762. package/node_modules/@humwick/offload/dist/index.d.ts +0 -5
  763. package/node_modules/@humwick/offload/dist/index.d.ts.map +0 -1
  764. package/node_modules/@humwick/offload/dist/index.js +0 -5
  765. package/node_modules/@humwick/offload/dist/index.js.map +0 -1
  766. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +0 -62
  767. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +0 -1
  768. package/node_modules/@humwick/offload/dist/subprocess-runner.js +0 -295
  769. package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +0 -1
  770. package/node_modules/@humwick/offload/dist/types.d.ts +0 -111
  771. package/node_modules/@humwick/offload/dist/types.d.ts.map +0 -1
  772. package/node_modules/@humwick/offload/dist/types.js +0 -176
  773. package/node_modules/@humwick/offload/dist/types.js.map +0 -1
  774. package/node_modules/@humwick/offload/dist/worker-pool.d.ts +0 -23
  775. package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +0 -1
  776. package/node_modules/@humwick/offload/dist/worker-pool.js +0 -435
  777. package/node_modules/@humwick/offload/dist/worker-pool.js.map +0 -1
  778. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +0 -2
  779. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +0 -1
  780. package/node_modules/@humwick/offload/dist/worker-runtime.js +0 -27
  781. package/node_modules/@humwick/offload/dist/worker-runtime.js.map +0 -1
  782. package/node_modules/@humwick/offload/dist/worker.d.ts +0 -5
  783. package/node_modules/@humwick/offload/dist/worker.d.ts.map +0 -1
  784. package/node_modules/@humwick/offload/dist/worker.js +0 -1
  785. package/node_modules/@humwick/offload/dist/worker.js.map +0 -1
  786. package/node_modules/@humwick/offload/package.json +0 -42
  787. package/node_modules/@humwick/offload/src/async-runner.ts +0 -170
  788. package/node_modules/@humwick/offload/src/envelope.ts +0 -92
  789. package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +0 -49
  790. package/node_modules/@humwick/offload/src/index.ts +0 -4
  791. package/node_modules/@humwick/offload/src/subprocess-runner.ts +0 -334
  792. package/node_modules/@humwick/offload/src/types.ts +0 -235
  793. package/node_modules/@humwick/offload/src/worker-pool.ts +0 -463
  794. package/node_modules/@humwick/offload/src/worker-runtime.ts +0 -32
  795. package/node_modules/@humwick/offload/src/worker.ts +0 -5
  796. package/public/assets/agent-liveness-DyqxfB-t.js +0 -2
  797. package/public/assets/agent-profiles-BpI_jTxq.js +0 -2
  798. package/public/assets/index-B7ZerHLY.css +0 -2
  799. package/public/assets/index-BU3uWo2t.js +0 -15
  800. package/public/assets/projects-B_lmcqfO.js +0 -2
  801. package/public/assets/provider-display-DbLWpP3j.js +0 -2
  802. package/public/assets/run-card-CO_bguQf.js +0 -2
  803. package/public/assets/shipping-Bu8p1gEw.js +0 -2
  804. package/public/assets/store-BvtFdlGX.js +0 -9
  805. package/public/assets/use-project-list-B-T2Bnnn.js +0 -2
  806. package/public/assets/versioned-prompt-editor-Diu8BIaM.js +0 -8
  807. package/runner/src/agent-runner/http-liveness.ts +0 -85
  808. package/runner/src/gate-run-lease-probe.ts +0 -38
  809. package/src/db/claude-catalog-reconcile.ts +0 -27
  810. package/src/db/on-loop-write-transaction.ts +0 -34
  811. package/src/routing-model-aliases.ts +0 -21
@@ -1,922 +0,0 @@
1
- import type { Database } from "bun:sqlite";
2
- import { Buffer } from "node:buffer";
3
- import type { Clock, JsonValue, WaitCondition, WorkflowStatusName } from "./contract";
4
-
5
- /**
6
- * Durable-workflow storage catalog (RFC 0001 / S4 §6), owned by the kernel.
7
- *
8
- * The step journal is deliberately independent of the scoped event log: bus
9
- * retention may discard observer events, while an open workflow's replay source
10
- * must live forever. This idempotent DDL is the single source of truth; `src/`
11
- * installs it through the strangler re-export in `src/db/workflow-journal-schema.ts`
12
- * so fresh and upgraded databases converge on exactly the same table and index
13
- * shapes. The kernel never imports `src/`.
14
- */
15
- export const WORKFLOW_JOURNAL_SCHEMA_SQL = `
16
- CREATE TABLE IF NOT EXISTS workflow_instances (
17
- id TEXT PRIMARY KEY CHECK (length(id) > 0),
18
- module_name TEXT NOT NULL CHECK (length(module_name) > 0),
19
- workflow_name TEXT NOT NULL CHECK (length(workflow_name) > 0),
20
- idempotency_key TEXT NOT NULL CHECK (length(idempotency_key) > 0),
21
- input TEXT NOT NULL CHECK (json_valid(input)),
22
- initial_state TEXT NOT NULL CHECK (json_valid(initial_state)),
23
- start_step TEXT NOT NULL CHECK (length(start_step) > 0),
24
- status TEXT NOT NULL DEFAULT 'pending'
25
- CHECK (status IN ('pending', 'running', 'sleeping', 'waiting', 'done', 'failed', 'canceled')),
26
- sleep_until INTEGER,
27
- wait_for TEXT CHECK (wait_for IS NULL OR (json_valid(wait_for) AND json_type(wait_for) = 'object')),
28
- output TEXT CHECK (output IS NULL OR json_valid(output)),
29
- failure TEXT CHECK (failure IS NULL OR json_valid(failure)),
30
- journal_cursor INTEGER NOT NULL DEFAULT 0 CHECK (journal_cursor >= 0),
31
- journal_pruned_through INTEGER NOT NULL DEFAULT 0 CHECK (journal_pruned_through >= 0),
32
- executor_lease_owner TEXT,
33
- executor_lease_token TEXT,
34
- executor_lease_version INTEGER NOT NULL DEFAULT 0 CHECK (executor_lease_version >= 0),
35
- executor_lease_acquired_at INTEGER,
36
- executor_lease_expires_at INTEGER,
37
- created_at INTEGER NOT NULL,
38
- updated_at INTEGER NOT NULL,
39
- closed_at INTEGER,
40
- retention_until INTEGER,
41
- -- Generic owner column (KAPI-1 / decision 3): the creator's actor, stamped from
42
- -- ctx.principal.actor at ctx.workflows.start. NULL for system/migrated instances (the
43
- -- accepted "creator-invisible" divergence). Unlocks a generic per-creator filter that
44
- -- lives only in module entity fields today.
45
- created_by TEXT,
46
- -- Pause control flag (KAPI-1, Option A): orthogonal to status, so the underlying
47
- -- pending/running/sleeping/waiting is preserved verbatim. When non-NULL the driver
48
- -- work-lists AND the lease-acquire predicate skip the instance, so a paused instance
49
- -- runs no further steps; resume clears it and the original deadline/condition fires.
50
- paused_at INTEGER,
51
- CHECK (journal_pruned_through <= journal_cursor),
52
- CHECK (
53
- (executor_lease_owner IS NULL AND executor_lease_token IS NULL
54
- AND executor_lease_acquired_at IS NULL AND executor_lease_expires_at IS NULL)
55
- OR
56
- (executor_lease_owner IS NOT NULL AND executor_lease_token IS NOT NULL
57
- AND executor_lease_acquired_at IS NOT NULL AND executor_lease_expires_at > executor_lease_acquired_at)
58
- ),
59
- UNIQUE(module_name, workflow_name, idempotency_key)
60
- );
61
- CREATE INDEX IF NOT EXISTS idx_workflow_instances_open
62
- ON workflow_instances(status, updated_at);
63
- CREATE INDEX IF NOT EXISTS idx_workflow_instances_timer
64
- ON workflow_instances(status, sleep_until) WHERE sleep_until IS NOT NULL;
65
- CREATE INDEX IF NOT EXISTS idx_workflow_instances_wait
66
- ON workflow_instances(status) WHERE wait_for IS NOT NULL;
67
- CREATE INDEX IF NOT EXISTS idx_workflow_instances_lease
68
- ON workflow_instances(status, executor_lease_expires_at);
69
- CREATE INDEX IF NOT EXISTS idx_workflow_instances_retention
70
- ON workflow_instances(status, retention_until) WHERE retention_until IS NOT NULL;
71
- CREATE INDEX IF NOT EXISTS idx_workflow_instances_creator
72
- ON workflow_instances(created_by, status);
73
- -- O(1) empty-path probe for the periodic journal prune (#3018): only closed instances that still
74
- -- hold journal rows live here, so drained history never slows the lock-free EXISTS.
75
- CREATE INDEX IF NOT EXISTS idx_workflow_instances_prunable
76
- ON workflow_instances(retention_until)
77
- WHERE retention_until IS NOT NULL AND status IN ('done', 'failed', 'canceled')
78
- AND journal_cursor > journal_pruned_through;
79
-
80
- CREATE TABLE IF NOT EXISTS workflow_step_journal (
81
- instance_id TEXT NOT NULL REFERENCES workflow_instances(id),
82
- seq INTEGER NOT NULL CHECK (seq > 0),
83
- step_index INTEGER NOT NULL CHECK (step_index >= 0),
84
- step_name TEXT NOT NULL CHECK (length(step_name) > 0),
85
- attempt INTEGER NOT NULL CHECK (attempt > 0),
86
- outcome TEXT NOT NULL CHECK (outcome IN ('completed', 'failed')),
87
- state_snapshot TEXT NOT NULL CHECK (json_valid(state_snapshot)),
88
- result TEXT NOT NULL CHECK (json_valid(result)),
89
- sleep_until INTEGER,
90
- wait_for TEXT CHECK (wait_for IS NULL OR (json_valid(wait_for) AND json_type(wait_for) = 'object')),
91
- started_at INTEGER NOT NULL,
92
- completed_at INTEGER NOT NULL CHECK (completed_at >= started_at),
93
- PRIMARY KEY (instance_id, seq)
94
- );
95
- CREATE UNIQUE INDEX IF NOT EXISTS idx_workflow_step_journal_attempt
96
- ON workflow_step_journal(instance_id, step_index, attempt);
97
- CREATE INDEX IF NOT EXISTS idx_workflow_step_journal_step
98
- ON workflow_step_journal(instance_id, step_index, outcome);
99
- `;
100
-
101
- /** How long a closed run's journal is kept before the prune job may delete it (#3018). */
102
- export const WORKFLOW_JOURNAL_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
103
-
104
- const WORKFLOW_JOURNAL_COLUMNS = `
105
- instance_id, seq, step_index, step_name, attempt, outcome, state_snapshot, result,
106
- sleep_until, wait_for, started_at, completed_at
107
- `;
108
-
109
- /** True when `column` already exists on `table` (via `PRAGMA table_info`). */
110
- function columnExists(db: Database, table: string, column: string): boolean {
111
- const cols = db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
112
- return cols.some((c) => c.name === column);
113
- }
114
-
115
- /**
116
- * Install the journal catalog on the given handle, rebuilding the original cascading
117
- * FK without data loss. Mirrors `ensureEventLogSchema(db)` (`event-log.ts`): the catalog
118
- * is kernel-owned and installed on the caller's shared connection.
119
- */
120
- export function ensureWorkflowJournalSchema(db: Database): void {
121
- const tableSql = (db.query(`SELECT sql FROM sqlite_master
122
- WHERE type = 'table' AND name = 'workflow_step_journal'`).get() as { sql?: string } | undefined)?.sql;
123
- const hasDeleteCascade = tableSql?.toUpperCase().includes("ON DELETE CASCADE") ?? false;
124
- const instancesExists = (db.query(`SELECT 1 FROM sqlite_master
125
- WHERE type = 'table' AND name = 'workflow_instances'`).get() as unknown) != null;
126
-
127
- db.transaction(() => {
128
- // Additive column migration for pre-existing databases (KAPI-1): a bare
129
- // `CREATE TABLE IF NOT EXISTS` cannot add a column to an already-present table, and the new
130
- // `idx_workflow_instances_creator` index in the DDL references `created_by`, so the columns
131
- // must land BEFORE the schema blob runs its index creation. A fresh database skips this — the
132
- // columns are already in the CREATE TABLE below.
133
- if (instancesExists) {
134
- if (!columnExists(db, "workflow_instances", "created_by")) {
135
- db.run("ALTER TABLE workflow_instances ADD COLUMN created_by TEXT");
136
- }
137
- if (!columnExists(db, "workflow_instances", "paused_at")) {
138
- db.run("ALTER TABLE workflow_instances ADD COLUMN paused_at INTEGER");
139
- }
140
- }
141
-
142
- if (hasDeleteCascade) {
143
- db.run("ALTER TABLE workflow_step_journal RENAME TO workflow_step_journal_cascade_legacy");
144
- // Explicit index names survive a table rename and would make the canonical
145
- // CREATE INDEX IF NOT EXISTS statements skip the replacement table.
146
- db.run("DROP INDEX IF EXISTS idx_workflow_step_journal_attempt");
147
- db.run("DROP INDEX IF EXISTS idx_workflow_step_journal_step");
148
- }
149
-
150
- db.run(WORKFLOW_JOURNAL_SCHEMA_SQL);
151
-
152
- if (hasDeleteCascade) {
153
- db.run(`INSERT INTO workflow_step_journal (${WORKFLOW_JOURNAL_COLUMNS})
154
- SELECT ${WORKFLOW_JOURNAL_COLUMNS} FROM workflow_step_journal_cascade_legacy`);
155
- db.run("DROP TABLE workflow_step_journal_cascade_legacy");
156
- }
157
-
158
- // #3018 backfill: runs closed before retention was stamped at close get the same window, so the
159
- // bounded prune job drains them. Probe first: an UPDATE takes the write lock even when it matches
160
- // nothing, and a persistence constructed under cross-process contention must not block on that.
161
- const unstamped = db.query(`SELECT EXISTS(SELECT 1 FROM workflow_instances
162
- WHERE status IN ('done', 'failed', 'canceled') AND retention_until IS NULL) AS n`).get() as { n: number };
163
- if (unstamped.n) {
164
- db.query(`UPDATE workflow_instances SET retention_until = COALESCE(closed_at, updated_at) + ?
165
- WHERE status IN ('done', 'failed', 'canceled') AND retention_until IS NULL`).run(WORKFLOW_JOURNAL_RETENTION_MS);
166
- }
167
- })();
168
- }
169
-
170
- /** The lease fields granted to an executor when it seeds/claims an instance (M0: seed only). */
171
- export interface WorkflowExecutorLease {
172
- readonly owner: string;
173
- readonly token: string;
174
- readonly acquiredAt: number;
175
- readonly expiresAt: number;
176
- readonly version?: number;
177
- }
178
-
179
- /** M2 acquire/reclaim inputs: who is claiming, with which fresh token, at what time and TTL. */
180
- export interface AcquireLeaseParams {
181
- readonly instanceId: string;
182
- readonly owner: string;
183
- readonly token: string;
184
- readonly now: number;
185
- readonly ttlMs: number;
186
- }
187
-
188
- /** M2 renew inputs: the holder's own token (its fence identity) and the fresh expiry window. */
189
- export interface RenewLeaseParams {
190
- readonly instanceId: string;
191
- readonly token: string;
192
- readonly now: number;
193
- readonly ttlMs: number;
194
- }
195
-
196
- /** The minimal instance seed M0 needs — the M1 `ctx.workflows.start` path will supersede this. */
197
- export interface WorkflowInstanceSeed {
198
- readonly id: string;
199
- readonly moduleName: string;
200
- readonly workflowName: string;
201
- readonly idempotencyKey: string;
202
- readonly input: JsonValue;
203
- readonly initialState: JsonValue;
204
- readonly startStep: string;
205
- readonly status?: WorkflowStatusName;
206
- readonly lease?: WorkflowExecutorLease;
207
- /** The creator's actor, stamped into `created_by` at `ctx.workflows.start`. Absent for
208
- * system/migrated seeds — those land `created_by = NULL` (accepted creator-invisible divergence). */
209
- readonly createdBy?: string | null;
210
- }
211
-
212
- /**
213
- * One step attempt's memoization payload: the state snapshot + serialized `StepResult` +
214
- * outcome, plus the transition (next status and any `sleepUntil`/`waitFor`) that advances
215
- * the owning instance. Written together with the instance cursor bump in one statement pair.
216
- */
217
- export interface WorkflowStepCommitRecord {
218
- readonly instanceId: string;
219
- readonly stepIndex: number;
220
- readonly stepName: string;
221
- readonly attempt: number;
222
- readonly outcome: "completed" | "failed";
223
- readonly state: JsonValue;
224
- readonly result: JsonValue;
225
- readonly nextStatus: WorkflowStatusName;
226
- readonly startedAt?: number;
227
- readonly sleepUntil?: number | null;
228
- readonly waitFor?: WaitCondition | null;
229
- readonly output?: JsonValue | null;
230
- readonly failure?: JsonValue | null;
231
- }
232
-
233
- /** Raw `workflow_instances` row shape (M0 exposes the raw row; strict codecs live in `src/`). */
234
- export interface WorkflowInstanceRow {
235
- id: string;
236
- module_name: string;
237
- workflow_name: string;
238
- idempotency_key: string;
239
- input: string;
240
- initial_state: string;
241
- start_step: string;
242
- status: string;
243
- sleep_until: number | null;
244
- wait_for: string | null;
245
- output: string | null;
246
- failure: string | null;
247
- journal_cursor: number;
248
- journal_pruned_through: number;
249
- executor_lease_owner: string | null;
250
- executor_lease_token: string | null;
251
- executor_lease_version: number;
252
- executor_lease_acquired_at: number | null;
253
- executor_lease_expires_at: number | null;
254
- created_at: number;
255
- updated_at: number;
256
- closed_at: number | null;
257
- retention_until: number | null;
258
- created_by: string | null;
259
- paused_at: number | null;
260
- }
261
-
262
- /** Raw `workflow_step_journal` row shape. */
263
- export interface WorkflowStepJournalRow {
264
- instance_id: string;
265
- seq: number;
266
- step_index: number;
267
- step_name: string;
268
- attempt: number;
269
- outcome: string;
270
- state_snapshot: string;
271
- result: string;
272
- sleep_until: number | null;
273
- wait_for: string | null;
274
- started_at: number;
275
- completed_at: number;
276
- }
277
-
278
- /** Raised when the executor lease/fence predicate fails inside the step-commit transaction. */
279
- export class WorkflowLeaseLostError extends Error {
280
- constructor(readonly instanceId: string, readonly leaseToken: string) {
281
- super(`workflow instance '${instanceId}' lease/fence check failed for token '${leaseToken}'`);
282
- this.name = "WorkflowLeaseLostError";
283
- }
284
- }
285
-
286
- /** Raised when a failed run cannot be reopened because retention already pruned (part of) its journal (#3018). */
287
- export class WorkflowHistoryExpiredError extends Error {
288
- constructor(readonly instanceId: string) {
289
- super(`workflow instance '${instanceId}' cannot be reopened: its journal history was pruned (history-expired)`);
290
- this.name = "WorkflowHistoryExpiredError";
291
- }
292
- }
293
-
294
- /**
295
- * Raised when a step memoization write collides with an already-journaled attempt — the
296
- * `workflow_step_journal` PRIMARY KEY `(instance_id, seq)` or the `(instance_id, step_index,
297
- * attempt)` unique index. This is the durability guardrail that a memoized step is never written
298
- * twice; the M0 review flagged that the underlying SqliteError should not escape untyped, so the
299
- * commit wraps the constraint abort in this error instead.
300
- */
301
- export class WorkflowStepConflictError extends Error {
302
- constructor(readonly instanceId: string, readonly seq: number, options?: ErrorOptions) {
303
- super(`workflow instance '${instanceId}' already has a journaled step at seq ${seq}`, options);
304
- this.name = "WorkflowStepConflictError";
305
- }
306
- }
307
-
308
- /** True for a SQLite UNIQUE/PRIMARY KEY constraint violation raised by the journal INSERT. */
309
- function isSqliteConstraintViolation(error: unknown): boolean {
310
- const code = (error as { code?: unknown } | null)?.code;
311
- if (typeof code === "string" && code.startsWith("SQLITE_CONSTRAINT")) return true;
312
- return error instanceof Error && /constraint failed/i.test(error.message);
313
- }
314
-
315
- /**
316
- * Raised when a lease op or the step-commit transaction cannot obtain SQLite's write lock within the
317
- * connection's `busy_timeout` — a real multi-process/multi-connection contention signal (`SQLITE_BUSY`
318
- * / `SQLITE_BUSY_SNAPSHOT`). It is DISTINCT from {@link WorkflowLeaseLostError}: a busy driver still
319
- * holds whatever lease it held (nothing was written — a busy on the COMMIT tx rolls the whole tx back),
320
- * so the runtime must back off and retry the tick, NEVER conclude it lost its lease. The M4 kill-9
321
- * chaos harness is what surfaces this under real cross-process contention; M0–M3's single-thread
322
- * `bun:sqlite` tests never could. Handled as a typed relinquish alongside the M2/M3 lease errors.
323
- */
324
- export class WorkflowContendedError extends Error {
325
- constructor(readonly instanceId: string, readonly operation: string, options?: ErrorOptions) {
326
- super(`workflow instance '${instanceId}' storage was contended (SQLITE_BUSY) during ${operation}`, options);
327
- this.name = "WorkflowContendedError";
328
- }
329
- }
330
-
331
- /** True for a SQLite write-lock contention (`SQLITE_BUSY`) surfaced under multi-connection load. */
332
- export function isSqliteBusy(error: unknown): boolean {
333
- const code = (error as { code?: unknown } | null)?.code;
334
- if (typeof code === "string" && code.startsWith("SQLITE_BUSY")) return true;
335
- return error instanceof Error && /database is locked|database table is locked/i.test(error.message);
336
- }
337
-
338
- /** Default `PRAGMA busy_timeout` (ms): under contention a writer BLOCKS-and-retries for this long
339
- * before surfacing `SQLITE_BUSY`, which is what makes most cross-process contention resolve without
340
- * a typed relinquish at all. Injectable via {@link WorkflowJournalOptions.busyTimeoutMs} so a test
341
- * can force immediate contention with `0`. */
342
- const DEFAULT_BUSY_TIMEOUT_MS = 5_000;
343
-
344
- export interface WorkflowJournalOptions {
345
- readonly db: Database;
346
- readonly clock: Clock;
347
- /** `PRAGMA busy_timeout` in ms for this connection; defaults to {@link DEFAULT_BUSY_TIMEOUT_MS}.
348
- * `null` leaves it to the connection's owner (a borrowed, shared handle). */
349
- readonly busyTimeoutMs?: number | null;
350
- /** Internal gateway-reader construction: catalog installation belongs to the writer op. */
351
- readonly installSchema?: boolean;
352
- }
353
-
354
- /** {@link WorkflowJournalPersistence.listInstances} filters — every field optional so the same op
355
- * serves an unfiltered admin sweep and a narrow `{module, status}` view alike (KAPI-1 / G-A). */
356
- export interface ListInstancesOptions {
357
- readonly module?: string;
358
- readonly workflow?: string;
359
- readonly status?: WorkflowStatusName;
360
- /** Keyed owner filter (KAPI-1 Wave 2 / G-M1). When set, only rows whose `created_by` equals it
361
- * are returned — the SQL equality also excludes NULL-owner (system/migrated) rows, so the control
362
- * module's non-admin owner-scoping is fail-closed AND a keyed lookup over the base's
363
- * `(created_by, status)` index rather than a ragged post-fetch filter. Omitted → no owner filter
364
- * (the admin cross-creator sweep). Additive: existing callers are unaffected. */
365
- readonly createdBy?: string;
366
- readonly cursor?: string;
367
- readonly limit?: number;
368
- }
369
-
370
- export interface WorkflowInstancePage {
371
- readonly items: readonly WorkflowInstanceRow[];
372
- readonly cursor?: string;
373
- }
374
-
375
- export const DEFAULT_INSTANCE_PAGE_SIZE = 50;
376
- export const MAX_INSTANCE_PAGE_SIZE = 200;
377
-
378
- function resolveInstancePageLimit(limit: number | undefined): number {
379
- const resolved = limit ?? DEFAULT_INSTANCE_PAGE_SIZE;
380
- if (!Number.isInteger(resolved) || resolved < 1 || resolved > MAX_INSTANCE_PAGE_SIZE) {
381
- throw new RangeError(`workflow instance list limit must be an integer from 1 to ${MAX_INSTANCE_PAGE_SIZE}`);
382
- }
383
- return resolved;
384
- }
385
-
386
- interface InstanceCursorPayload {
387
- readonly v: 1;
388
- readonly updatedAt: number;
389
- readonly id: string;
390
- }
391
-
392
- /** Opaque, unsigned `(updated_at, id)` continuation token. Unlike {@link EntityPersistence}'s
393
- * cursor this carries no HMAC: this accessor is the raw persistence-layer read (no transport
394
- * exposure yet — that's KAPI-1's later transport-wiring task), so tamper-resistance is not yet a
395
- * requirement; a malformed cursor fails closed with a clear error either way. */
396
- function encodeInstanceCursor(row: Pick<WorkflowInstanceRow, "updated_at" | "id">): string {
397
- const payload: InstanceCursorPayload = { v: 1, updatedAt: row.updated_at, id: row.id };
398
- return Buffer.from(JSON.stringify(payload)).toString("base64url");
399
- }
400
-
401
- function decodeInstanceCursor(cursor: string): InstanceCursorPayload {
402
- try {
403
- const payload = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")) as Partial<InstanceCursorPayload>;
404
- if (payload.v !== 1 || typeof payload.updatedAt !== "number" || typeof payload.id !== "string") throw new Error();
405
- return payload as InstanceCursorPayload;
406
- } catch {
407
- throw new TypeError("invalid workflow instance list cursor");
408
- }
409
- }
410
-
411
- /**
412
- * Kernel-owned durable-workflow storage, constructed like {@link EntityPersistence} /
413
- * {@link EventLogPersistence}: it installs its catalog on the injected handle at
414
- * construction and exposes the in-transaction primitives the step commit composes
415
- * (the lease fence and the memoization write), plus seed/read helpers for M0.
416
- */
417
- export class WorkflowJournalPersistence {
418
- readonly #db: Database;
419
- readonly #clock: Clock;
420
-
421
- constructor(options: WorkflowJournalOptions) {
422
- this.#db = options.db;
423
- this.#clock = options.clock;
424
- // The journal's FK (workflow_step_journal.instance_id → workflow_instances.id) is only
425
- // enforced when the handle honors foreign keys; enable it up front so a journal row can
426
- // never outlive its instance, regardless of construction order with EntityPersistence.
427
- this.#db.run("PRAGMA foreign_keys = ON");
428
- // Multi-process contention (M4): a contended writer BLOCKS for up to busy_timeout before SQLite
429
- // surfaces SQLITE_BUSY, so a generous window resolves nearly all cross-connection contention
430
- // transparently; whatever still surfaces is caught and typed as WorkflowContendedError. This is
431
- // per-connection, so every driver's handle sets it. WAL (writer + concurrent readers) is a
432
- // db-file property the on-disk caller sets; on `:memory:` the pragma is a harmless no-op.
433
- if (options.busyTimeoutMs !== null) {
434
- const busyTimeoutMs = options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS;
435
- this.#db.run(`PRAGMA busy_timeout = ${Math.max(0, Math.trunc(busyTimeoutMs))}`);
436
- }
437
- if (options.installSchema !== false) ensureWorkflowJournalSchema(this.#db);
438
- }
439
-
440
- /** Run a single-statement lease write, converting a `SQLITE_BUSY` write-lock contention into the
441
- * typed {@link WorkflowContendedError} so a contended tick backs off cleanly instead of leaking a
442
- * raw SqliteError that would abort the wakeup/recover loop. */
443
- #leaseWrite(operation: string, instanceId: string, run: () => number): number {
444
- try {
445
- return run();
446
- } catch (error) {
447
- if (isSqliteBusy(error)) throw new WorkflowContendedError(instanceId, operation, { cause: error });
448
- throw error;
449
- }
450
- }
451
-
452
- /** Insert a workflow instance (M0 seed path). Returns the stored row. */
453
- createInstance(seed: WorkflowInstanceSeed, now = this.#clock.now()): WorkflowInstanceRow {
454
- const lease = seed.lease;
455
- if (lease && !(lease.expiresAt > lease.acquiredAt)) {
456
- throw new RangeError("workflow lease expiresAt must be greater than acquiredAt");
457
- }
458
- this.#db.query(`INSERT INTO workflow_instances (
459
- id, module_name, workflow_name, idempotency_key, input, initial_state, start_step, status,
460
- executor_lease_owner, executor_lease_token, executor_lease_version,
461
- executor_lease_acquired_at, executor_lease_expires_at, created_at, updated_at, created_by
462
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
463
- seed.id,
464
- seed.moduleName,
465
- seed.workflowName,
466
- seed.idempotencyKey,
467
- JSON.stringify(seed.input),
468
- JSON.stringify(seed.initialState),
469
- seed.startStep,
470
- seed.status ?? "pending",
471
- lease?.owner ?? null,
472
- lease?.token ?? null,
473
- lease?.version ?? 0,
474
- lease?.acquiredAt ?? null,
475
- lease?.expiresAt ?? null,
476
- now,
477
- now,
478
- seed.createdBy ?? null,
479
- );
480
- return this.getInstance(seed.id)!;
481
- }
482
-
483
- /** The raw instance row, or `undefined` when absent. */
484
- getInstance(id: string): WorkflowInstanceRow | undefined {
485
- return this.#db.query("SELECT * FROM workflow_instances WHERE id = ?").get(id) as WorkflowInstanceRow | undefined;
486
- }
487
-
488
- /** The raw instance row for a `(module, workflow, idempotency_key)` identity, or `undefined`. */
489
- getInstanceByIdentity(moduleName: string, workflowName: string, idempotencyKey: string): WorkflowInstanceRow | undefined {
490
- return this.#db.query(`SELECT * FROM workflow_instances
491
- WHERE module_name = ? AND workflow_name = ? AND idempotency_key = ?`)
492
- .get(moduleName, workflowName, idempotencyKey) as WorkflowInstanceRow | undefined;
493
- }
494
-
495
- /**
496
- * Module-agnostic, cursor-paginated instance list (KAPI-1 / G-A): `{module?, workflow?, status?}`
497
- * filters over `workflow_instances`, ordered `(updated_at, id)` ascending so `idx_workflow_instances_open`
498
- * (or the plain `status` equality scan when `module`-only) serves the query and pagination is
499
- * stable even when several rows share an `updated_at` tick. Raw rows — no owner-scoping or
500
- * redaction; the control module applies `created_by` filtering and DTO gating on top.
501
- */
502
- listInstances(options: ListInstancesOptions = {}): WorkflowInstancePage {
503
- const limit = resolveInstancePageLimit(options.limit);
504
- const clauses: string[] = [];
505
- const bindings: Array<string | number> = [];
506
- if (options.module !== undefined) {
507
- clauses.push("module_name = ?");
508
- bindings.push(options.module);
509
- }
510
- if (options.workflow !== undefined) {
511
- clauses.push("workflow_name = ?");
512
- bindings.push(options.workflow);
513
- }
514
- if (options.status !== undefined) {
515
- clauses.push("status = ?");
516
- bindings.push(options.status);
517
- }
518
- if (options.createdBy !== undefined) {
519
- // Equality (not `IS`) so a NULL-owner row NEVER matches a non-admin owner filter — the
520
- // control module's fail-closed creator scoping (G-M1) rides this exclusion.
521
- clauses.push("created_by = ?");
522
- bindings.push(options.createdBy);
523
- }
524
- if (options.cursor !== undefined) {
525
- const { updatedAt, id } = decodeInstanceCursor(options.cursor);
526
- clauses.push("(updated_at > ? OR (updated_at = ? AND id > ?))");
527
- bindings.push(updatedAt, updatedAt, id);
528
- }
529
- const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
530
- bindings.push(limit + 1);
531
- const rows = this.#db.query(`SELECT * FROM workflow_instances ${where}
532
- ORDER BY updated_at ASC, id ASC LIMIT ?`).all(...bindings) as WorkflowInstanceRow[];
533
- const hasMore = rows.length > limit;
534
- const items = rows.slice(0, limit);
535
- return {
536
- items,
537
- ...(hasMore ? { cursor: encodeInstanceCursor(items[items.length - 1]!) } : {}),
538
- };
539
- }
540
-
541
- /**
542
- * Seed an instance idempotently on `UNIQUE(module_name, workflow_name, idempotency_key)`: the
543
- * first `start` for a key inserts the `pending` row; a later `start` with the same key is a
544
- * no-op insert that returns the SAME existing row (never a second run, no double-start). This is
545
- * the ratified `ctx.workflows.start` dedupe. Returns the stored row and whether it was created.
546
- */
547
- createInstanceIfAbsent(seed: WorkflowInstanceSeed, now = this.#clock.now()): { instance: WorkflowInstanceRow; created: boolean } {
548
- const lease = seed.lease;
549
- if (lease && !(lease.expiresAt > lease.acquiredAt)) {
550
- throw new RangeError("workflow lease expiresAt must be greater than acquiredAt");
551
- }
552
- const changes = this.#db.query(`INSERT INTO workflow_instances (
553
- id, module_name, workflow_name, idempotency_key, input, initial_state, start_step, status,
554
- executor_lease_owner, executor_lease_token, executor_lease_version,
555
- executor_lease_acquired_at, executor_lease_expires_at, created_at, updated_at, created_by
556
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
557
- ON CONFLICT(module_name, workflow_name, idempotency_key) DO NOTHING`).run(
558
- seed.id,
559
- seed.moduleName,
560
- seed.workflowName,
561
- seed.idempotencyKey,
562
- JSON.stringify(seed.input),
563
- JSON.stringify(seed.initialState),
564
- seed.startStep,
565
- seed.status ?? "pending",
566
- lease?.owner ?? null,
567
- lease?.token ?? null,
568
- lease?.version ?? 0,
569
- lease?.acquiredAt ?? null,
570
- lease?.expiresAt ?? null,
571
- now,
572
- now,
573
- seed.createdBy ?? null,
574
- ).changes;
575
- const instance = this.getInstanceByIdentity(seed.moduleName, seed.workflowName, seed.idempotencyKey);
576
- if (!instance) throw new Error("workflow instance insert did not produce a row");
577
- return { instance, created: changes === 1 };
578
- }
579
-
580
- /**
581
- * Transition an OPEN instance to the terminal `canceled` status. Returns `true` when it moved,
582
- * `false` when the instance is absent or already terminal. This is a control-plane transition,
583
- * not a step commit — it needs no journal memo row, and the fold projects the terminal status
584
- * straight from the instance row.
585
- */
586
- cancelInstance(id: string, now = this.#clock.now()): boolean {
587
- const changes = this.#db.query(`UPDATE workflow_instances
588
- SET status = 'canceled', updated_at = ?, closed_at = ?, retention_until = ?
589
- WHERE id = ? AND status IN ('pending', 'running', 'sleeping', 'waiting')`)
590
- .run(now, now, now + WORKFLOW_JOURNAL_RETENTION_MS, id).changes;
591
- return changes === 1;
592
- }
593
-
594
- /**
595
- * Transition an OPEN instance to the terminal `failed` status with a JSON failure payload.
596
- * Like {@link cancelInstance} this is a control-plane transition, not a step commit: M1 has no
597
- * retry driver (that is M3), so an unhandled step throw terminates the instance here. Returns
598
- * `true` when it moved, `false` when the instance is absent or already terminal.
599
- */
600
- failInstance(id: string, failure: JsonValue, now = this.#clock.now()): boolean {
601
- const changes = this.#db.query(`UPDATE workflow_instances
602
- SET status = 'failed', failure = ?, updated_at = ?, closed_at = ?, retention_until = ?
603
- WHERE id = ? AND status IN ('pending', 'running', 'sleeping', 'waiting')`)
604
- .run(JSON.stringify(failure), now, now, now + WORKFLOW_JOURNAL_RETENTION_MS, id).changes;
605
- return changes === 1;
606
- }
607
-
608
- /**
609
- * Pause an OPEN instance (KAPI-1 control primitive, Option A). Sets `paused_at` WITHOUT touching
610
- * `status`, so the underlying `pending`/`running`/`sleeping`/`waiting` — and any `sleep_until`/
611
- * `wait_for` deadline — is preserved verbatim. While `paused_at` is non-NULL the driver work-lists
612
- * ({@link listResumable}/{@link listDueSleepers}/{@link listWaiters}) AND the lease-acquire
613
- * predicate ({@link acquireLease}) skip the instance, so it runs no further steps. OPEN-guarded and
614
- * idempotent like {@link cancelInstance}: returns `true` when it newly paused, `false` when the
615
- * instance is absent, terminal, or already paused. This is a control-plane transition, not a step
616
- * commit — it needs no journal memo. Authorization (owner check / system twin) is the caller's job,
617
- * mirroring the timers `cancel`/`cancelMirror` split; the primitive itself is authz-free.
618
- */
619
- pauseInstance(id: string, now = this.#clock.now()): boolean {
620
- const changes = this.#db.query(`UPDATE workflow_instances
621
- SET paused_at = ?, updated_at = ?
622
- WHERE id = ? AND status IN ('pending', 'running', 'sleeping', 'waiting') AND paused_at IS NULL`)
623
- .run(now, now, id).changes;
624
- return changes === 1;
625
- }
626
-
627
- /**
628
- * Resume a paused instance: clear `paused_at` so the driver work-lists and lease-acquire predicate
629
- * see it again. The preserved `sleep_until`/`wait_for` then fires on the next tick — a due sleeper
630
- * fires immediately (late by design; durable, not real-time). Idempotent: returns `true` when it
631
- * cleared a pause, `false` when the instance was not paused (absent or never paused). Not
632
- * status-guarded — clearing the flag on a terminal instance is a harmless no-op (the work-lists
633
- * exclude terminal rows regardless), so cancel/terminal remains absorbing.
634
- */
635
- resumeInstance(id: string, now = this.#clock.now()): boolean {
636
- const changes = this.#db.query(`UPDATE workflow_instances
637
- SET paused_at = NULL, updated_at = ?
638
- WHERE id = ? AND paused_at IS NOT NULL`)
639
- .run(now, id).changes;
640
- return changes === 1;
641
- }
642
-
643
- /**
644
- * Re-open a `failed` instance so the runtime re-drives it from its last good journal cursor
645
- * (memoized steps replay via the fold; only the failed step re-runs). Sets `status = 'pending'`,
646
- * clears `failure` AND `closed_at`, and clears the executor lease (bumping the version so any stale
647
- * token is fenced off) so a driver can reclaim it. Guarded `WHERE status = 'failed'` and idempotent:
648
- * a second reopen on an already-open instance is a no-op (`false`). Like the other control
649
- * primitives it carries no authorization — that is the control module's job. Returns `true` when it
650
- * re-opened, `false` when the instance is absent or not `failed`.
651
- */
652
- reopenInstance(id: string, now = this.#clock.now()): boolean {
653
- // The pruned-floor guard lives in the UPDATE so a prune cannot slip between check and reopen.
654
- const changes = this.#db.query(`UPDATE workflow_instances SET
655
- status = 'pending', failure = NULL, closed_at = NULL, retention_until = NULL,
656
- executor_lease_owner = NULL, executor_lease_token = NULL,
657
- executor_lease_acquired_at = NULL, executor_lease_expires_at = NULL,
658
- executor_lease_version = executor_lease_version + 1, updated_at = ?
659
- WHERE id = ? AND status = 'failed' AND journal_pruned_through = 0`)
660
- .run(now, id).changes;
661
- if (changes === 1) return true;
662
- const row = this.#db.query("SELECT status, journal_pruned_through AS pruned FROM workflow_instances WHERE id = ?")
663
- .get(id) as { status: string; pruned: number } | undefined;
664
- // A pruned history cannot be replayed (resume would hit a gap): refuse loudly, keep the failed row.
665
- if (row?.status === "failed" && row.pruned > 0) throw new WorkflowHistoryExpiredError(id);
666
- return false;
667
- }
668
-
669
- /** All journal rows for an instance in commit order. */
670
- listSteps(instanceId: string): WorkflowStepJournalRow[] {
671
- return this.#db.query("SELECT * FROM workflow_step_journal WHERE instance_id = ? ORDER BY seq ASC")
672
- .all(instanceId) as WorkflowStepJournalRow[];
673
- }
674
-
675
- /**
676
- * (a) of the step commit: re-check the executor lease/fence by SQL predicate inside the
677
- * caller-owned transaction. Never trusts a pre-transaction read — the predicate re-evaluates
678
- * `executor_lease_token` and `executor_lease_expires_at` against the live row. A row that no
679
- * longer matches (reclaimed, fenced, or expired) yields `changes !== 1` and aborts.
680
- */
681
- fenceLeaseInTransaction(instanceId: string, leaseToken: string, now: number): void {
682
- if (!this.#db.inTransaction) {
683
- throw new Error("workflow lease fence must run inside a synchronous SQLite transaction");
684
- }
685
- const result = this.#db.query(`UPDATE workflow_instances SET updated_at = ?
686
- WHERE id = ? AND executor_lease_token = ? AND executor_lease_expires_at > ?`)
687
- .run(now, instanceId, leaseToken, now);
688
- if (result.changes !== 1) throw new WorkflowLeaseLostError(instanceId, leaseToken);
689
- }
690
-
691
- /**
692
- * M2 acquire / reclaim: atomically claim an OPEN instance IF it is currently unleased OR its
693
- * lease has expired (`expires_at <= now`). A live lease held by another driver blocks the claim
694
- * — this returns `false` and the contender must back off, never steal. The claim writes a fresh
695
- * unique token + expiry and bumps `executor_lease_version`, so any prior holder's token is
696
- * fenced off (its later commit/renew no longer matches). This one predicate is BOTH the
697
- * cold-start acquire and the crash-recovery reclaim of an expired lease: a single-writer guard
698
- * enforced in SQL, not by trusting a pre-read. Returns whether the caller now holds the lease.
699
- *
700
- * The `paused_at IS NULL` predicate here is the pause TOCTOU fix: a pause that lands AFTER a
701
- * work-list query ({@link listDueSleepers} et al.) already returned this id, but BEFORE the driver
702
- * acquires, still blocks the acquire — so a paused instance can never be leased and thus can never
703
- * fire "one more time." Gating only the work-lists would leave that race open; gating the acquire
704
- * (and the inner `#drive` loop) closes it.
705
- */
706
- acquireLease(params: AcquireLeaseParams): boolean {
707
- if (!(params.ttlMs > 0)) throw new RangeError("workflow lease ttlMs must be a positive number");
708
- const expiresAt = params.now + params.ttlMs;
709
- const changes = this.#leaseWrite("acquireLease", params.instanceId, () => this.#db.query(`UPDATE workflow_instances SET
710
- executor_lease_owner = ?, executor_lease_token = ?,
711
- executor_lease_acquired_at = ?, executor_lease_expires_at = ?,
712
- executor_lease_version = executor_lease_version + 1, updated_at = ?
713
- WHERE id = ? AND status IN ('pending', 'running', 'sleeping', 'waiting')
714
- AND paused_at IS NULL
715
- AND (executor_lease_token IS NULL OR executor_lease_expires_at <= ?)`)
716
- .run(params.owner, params.token, params.now, expiresAt, params.now, params.instanceId, params.now).changes);
717
- return changes === 1;
718
- }
719
-
720
- /**
721
- * M2 renew: the holding driver extends its expiry while it makes progress, so a long-running or
722
- * multi-step drive never lets the lease lapse under a competitor. Succeeds only while the caller
723
- * STILL holds the token (token identity is the fence): a reclaim by another driver rotated the
724
- * token, so a stale holder's renew returns `false` and it must relinquish. Returns whether the
725
- * lease was extended.
726
- */
727
- renewLease(params: RenewLeaseParams): boolean {
728
- if (!(params.ttlMs > 0)) throw new RangeError("workflow lease ttlMs must be a positive number");
729
- const expiresAt = params.now + params.ttlMs;
730
- // A renew that hits SQLITE_BUSY is NOT a lost lease — the holder still owns its (token-pinned)
731
- // lease, the extend UPDATE simply did not land. Surface it as WorkflowContendedError so the
732
- // driver backs off this tick keeping its lease, rather than mistaking contention for a fence-off.
733
- const changes = this.#leaseWrite("renewLease", params.instanceId, () => this.#db.query(`UPDATE workflow_instances SET
734
- executor_lease_expires_at = ?, updated_at = ?
735
- WHERE id = ? AND executor_lease_token = ?`)
736
- .run(expiresAt, params.now, params.instanceId, params.token).changes);
737
- return changes === 1;
738
- }
739
-
740
- /**
741
- * M2 release: relinquish the lease the caller holds so a later driver (or an M3 timer/wait
742
- * wakeup) can reacquire immediately without waiting out the TTL. Clears the lease columns and
743
- * bumps the version. A no-op (`false`) when the caller no longer holds the token — e.g. it was
744
- * already reclaimed. Never touches instance status; it is a lease-only relinquish.
745
- */
746
- releaseLease(instanceId: string, token: string, now = this.#clock.now()): boolean {
747
- // Release is best-effort cleanup called from a `finally`, so it must NEVER throw: a contended
748
- // release just leaves the lease to lapse via its TTL (recovery reclaims it then), which is safe.
749
- try {
750
- const changes = this.#db.query(`UPDATE workflow_instances SET
751
- executor_lease_owner = NULL, executor_lease_token = NULL,
752
- executor_lease_acquired_at = NULL, executor_lease_expires_at = NULL,
753
- executor_lease_version = executor_lease_version + 1, updated_at = ?
754
- WHERE id = ? AND executor_lease_token = ?`)
755
- .run(now, instanceId, token).changes;
756
- return changes === 1;
757
- } catch (error) {
758
- if (isSqliteBusy(error)) return false;
759
- throw error;
760
- }
761
- }
762
-
763
- /**
764
- * The crash-recovery work-list: ids of OPEN, actively-drivable instances (`pending`/`running`)
765
- * whose executor lease is absent or expired at `now`. A live-leased instance is excluded — a
766
- * slow-but-alive driver still owns it, and recovery must never double-run a step it is inside.
767
- * Parked `sleeping`/`waiting` instances are excluded too: their wakeup is the M3 timer/wait
768
- * driver's job, not a blind resume. Ordered oldest-touched first so recovery drains fairly.
769
- * Every work-list is scoped to ONE module: a driver must never scan another module's instances (#3006).
770
- */
771
- listResumable(moduleName: string, now: number): string[] {
772
- return (this.#db.query(`SELECT id FROM workflow_instances
773
- WHERE module_name = ? AND status IN ('pending', 'running') AND paused_at IS NULL
774
- AND (executor_lease_token IS NULL OR executor_lease_expires_at <= ?)
775
- ORDER BY updated_at ASC`).all(moduleName, now) as Array<{ id: string }>).map((row) => row.id);
776
- }
777
-
778
- /**
779
- * A SINGLE-QUERY, SINGLE-`now` classification of this module's residual OPEN work for a boot/OFF
780
- * reconcile (#1447 finding C, round-7). Returns BOTH signals the reconcile decision needs, computed in
781
- * ONE statement against ONE `now`, so they can NEVER disagree across the lease-expiry boundary:
782
- * • `reclaimableCount` — OPEN (`pending`/`running`, unpaused) instances that are RECLAIMABLE right now
783
- * — the exact predicate (and thus the count) of {@link listResumable}, module-scoped: lease absent
784
- * OR expired at `now` (`executor_lease_expires_at <= now`). A boot/OFF reconcile uses this to tell
785
- * "genuinely converged" from "recover() returned [] only because a CONTENDED acquire
786
- * ({@link WorkflowContendedError}) was swallowed to a no-op, even though the instance is drivable
787
- * NOW" — the latter needs a short retry, not a false convergence.
788
- * • `nextLiveLeaseExpiry` — the earliest executor-lease expiry among the REST (still-LIVE leases,
789
- * `executor_lease_token IS NOT NULL AND executor_lease_expires_at > now`) — instances a plain
790
- * {@link recover}/{@link listResumable} cannot yet reclaim. `undefined` when none. A crash before a
791
- * step's journal commit leaves such an instance holding the dead driver's lease; the OFF reconcile
792
- * schedules a bounded retry AT this expiry, when {@link listResumable} legitimately includes the
793
- * now-EXPIRED lease — instead of FORCE-CLEARING a lease it cannot prove is dead (round-4: a lease
794
- * held by a LIVE concurrent process, e.g. a restart-overlap or different-port-same-DB peer, must
795
- * never be cleared, or its step body double-drives, violating the M2 single-writer invariant).
796
- *
797
- * ── Why one query (round-6 → round-7) ──────────────────────────────────────────────────────────────
798
- * READ-ONLY (never mutates a lease) and module-scoped. Every OPEN instance is classified into EXACTLY
799
- * ONE bucket at `now`: reclaimable-now (`<= now`) XOR live-leased (`> now`). So
800
- * `reclaimableCount === 0 && nextLiveLeaseExpiry === undefined` means there is GENUINELY no open work —
801
- * never a boundary artifact. The prior two-read design (a `countReclaimable` and a `nextLiveLeaseExpiry`
802
- * each sampling the clock/DB independently) let a lease expire BETWEEN the reads: reclaimable-count at
803
- * E-1 = 0 AND live-lease at E = undefined → a FALSE convergence that stranded an acknowledged close
804
- * with the ticker OFF. One statement + one `now` bound closes that gap by construction.
805
- */
806
- snapshotReconcileResidual(moduleName: string, now: number): { reclaimableCount: number; nextLiveLeaseExpiry: number | undefined } {
807
- const row = this.#db.query(`SELECT
808
- SUM(CASE WHEN executor_lease_token IS NULL OR executor_lease_expires_at <= ? THEN 1 ELSE 0 END) AS reclaimable,
809
- MIN(CASE WHEN executor_lease_token IS NOT NULL AND executor_lease_expires_at > ? THEN executor_lease_expires_at ELSE NULL END) AS liveExpiry
810
- FROM workflow_instances
811
- WHERE module_name = ? AND status IN ('pending', 'running') AND paused_at IS NULL`)
812
- .get(now, now, moduleName) as { reclaimable: number | null; liveExpiry: number | null };
813
- return { reclaimableCount: row.reclaimable ?? 0, nextLiveLeaseExpiry: row.liveExpiry ?? undefined };
814
- }
815
-
816
- /**
817
- * The M3 timer work-list: ids of `sleeping` instances whose `sleep_until` is DUE (`<= now`) and
818
- * whose executor lease is absent or expired — the acquire-able, fire-able sleepers. This is the
819
- * SQL half of the "no instance is due but un-acquirable" gate: the same lease predicate as
820
- * {@link acquireLease}, so a row this returns can be claimed (unless a rival claims it first).
821
- * Both durable timers (`StepResult.sleepUntil`) and retry backoffs park as sleeping rows, so this
822
- * one predicate fires both. Ordered earliest-due first so the closest deadline fires first.
823
- */
824
- listDueSleepers(moduleName: string, now: number): string[] {
825
- return (this.#db.query(`SELECT id FROM workflow_instances
826
- WHERE module_name = ? AND status = 'sleeping' AND sleep_until IS NOT NULL AND sleep_until <= ? AND paused_at IS NULL
827
- AND (executor_lease_token IS NULL OR executor_lease_expires_at <= ?)
828
- ORDER BY sleep_until ASC`).all(moduleName, now, now) as Array<{ id: string }>).map((row) => row.id);
829
- }
830
-
831
- /**
832
- * The M3 wait work-list: ids of `waiting` instances whose executor lease is absent or expired.
833
- * Unlike a sleeper, a waiter's DUENESS (is its {@link WaitCondition} satisfied?) is not a pure
834
- * SQL predicate — it depends on events/commands the journal does not own — so this returns the
835
- * acquire-able candidates and the runtime applies the satisfaction predicate. Same lease gate as
836
- * {@link acquireLease} so a returned row is claimable. Ordered oldest-armed first for fairness.
837
- */
838
- listWaiters(moduleName: string, now: number): string[] {
839
- return (this.#db.query(`SELECT id FROM workflow_instances
840
- WHERE module_name = ? AND status = 'waiting' AND wait_for IS NOT NULL AND paused_at IS NULL
841
- AND (executor_lease_token IS NULL OR executor_lease_expires_at <= ?)
842
- ORDER BY updated_at ASC`).all(moduleName, now) as Array<{ id: string }>).map((row) => row.id);
843
- }
844
-
845
- /**
846
- * (d) of the step commit: insert the immutable memoization row at `journal_cursor + 1` and
847
- * advance the instance's status, cursor, and transition columns — one statement pair inside
848
- * the caller-owned transaction so the journal row and the cursor bump are never torn apart.
849
- */
850
- recordStepInTransaction(record: WorkflowStepCommitRecord, completedAt = this.#clock.now()): number {
851
- if (!this.#db.inTransaction) {
852
- throw new Error("workflow step journal must be written inside a synchronous SQLite transaction");
853
- }
854
- const cursorRow = this.#db.query("SELECT journal_cursor FROM workflow_instances WHERE id = ?")
855
- .get(record.instanceId) as { journal_cursor: number } | undefined;
856
- if (!cursorRow) throw new Error(`workflow instance '${record.instanceId}' does not exist`);
857
-
858
- const seq = cursorRow.journal_cursor + 1;
859
- const startedAt = record.startedAt ?? completedAt;
860
- // The INSTANCE schedule and the immutable JOURNAL-ROW schedule are decoupled. A completed step
861
- // records its own `sleepUntil`/`waitFor` on both. A FAILED attempt's row must carry neither
862
- // (the codec forbids a failed record from scheduling a timer/wait), yet the instance may still
863
- // need to park `sleeping` until a retry backoff elapses — so the row columns come from the
864
- // outcome-gated values while the instance columns come straight from the record.
865
- const instanceSleepUntil = record.sleepUntil ?? null;
866
- const instanceWaitForText = record.waitFor == null ? null : JSON.stringify(record.waitFor);
867
- const rowSleepUntil = record.outcome === "failed" ? null : instanceSleepUntil;
868
- const rowWaitForText = record.outcome === "failed" ? null : instanceWaitForText;
869
-
870
- try {
871
- this.#db.query(`INSERT INTO workflow_step_journal (
872
- instance_id, seq, step_index, step_name, attempt, outcome, state_snapshot, result,
873
- sleep_until, wait_for, started_at, completed_at
874
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
875
- record.instanceId,
876
- seq,
877
- record.stepIndex,
878
- record.stepName,
879
- record.attempt,
880
- record.outcome,
881
- JSON.stringify(record.state),
882
- JSON.stringify(record.result),
883
- rowSleepUntil,
884
- rowWaitForText,
885
- startedAt,
886
- completedAt,
887
- );
888
- } catch (error) {
889
- // A UNIQUE/PRIMARY KEY collision here means this step attempt is already memoized: surface
890
- // it as the typed conflict rather than leaking a raw SqliteError (M0 review nit). The throw
891
- // aborts the caller-owned transaction, rolling back the entity + event writes with it.
892
- if (isSqliteConstraintViolation(error)) throw new WorkflowStepConflictError(record.instanceId, seq, { cause: error });
893
- throw error;
894
- }
895
-
896
- const outputText = record.output == null ? null : JSON.stringify(record.output);
897
- const failureText = record.failure == null ? null : JSON.stringify(record.failure);
898
- this.#db.query(`UPDATE workflow_instances SET
899
- status = ?, journal_cursor = ?, sleep_until = ?, wait_for = ?, output = ?, failure = ?, updated_at = ?,
900
- closed_at = CASE WHEN ? IN ('done', 'failed', 'canceled') THEN ? ELSE closed_at END,
901
- retention_until = CASE WHEN ? IN ('done', 'failed', 'canceled') THEN ? ELSE retention_until END
902
- WHERE id = ?`).run(
903
- record.nextStatus,
904
- seq,
905
- instanceSleepUntil,
906
- instanceWaitForText,
907
- outputText,
908
- failureText,
909
- completedAt,
910
- record.nextStatus,
911
- completedAt,
912
- record.nextStatus,
913
- completedAt + WORKFLOW_JOURNAL_RETENTION_MS,
914
- record.instanceId,
915
- );
916
- return seq;
917
- }
918
- }
919
-
920
- export function createWorkflowJournalPersistence(options: WorkflowJournalOptions): WorkflowJournalPersistence {
921
- return new WorkflowJournalPersistence(options);
922
- }