@humwick/server 0.130.0 → 0.130.2

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 (486) hide show
  1. package/README.md +5 -0
  2. package/docs/guides/tools-administration.md +6 -1
  3. package/docs/guides/tools-coordination.md +1 -0
  4. package/docs/guides/tools-tasks.md +8 -0
  5. package/docs/guides/workflows.md +58 -0
  6. package/docs/openapi.json +414 -1
  7. package/package.json +8 -17
  8. package/public/assets/{MessageBubble-BWpoBjl4.js → MessageBubble-BWLqMGKG.js} +2 -2
  9. package/public/assets/{PlanGraphPanel-C0auck3n.js → PlanGraphPanel-3TlIQpjk.js} +2 -2
  10. package/public/assets/{TraceView-DBnOx7AQ.js → TraceView-Wimm0Czv.js} +2 -2
  11. package/public/assets/{VaultDecisionCard-0kp2oNBC.js → VaultDecisionCard-Bau_vNax.js} +2 -2
  12. package/public/assets/{activity-yLztXEBt.js → activity-CrSurn-r.js} +2 -2
  13. package/public/assets/{agent-lineage-BbJDR1dW.js → agent-lineage-CknAJKcb.js} +2 -2
  14. package/public/assets/agent-liveness-CaNwK0Zf.js +2 -0
  15. package/public/assets/{agent-name-CTZBrSD9.js → agent-name-DKbiIhBo.js} +2 -2
  16. package/public/assets/agent-profiles-DwF6EQ4R.js +2 -0
  17. package/public/assets/{agent-project-badge-9TqzUUYE.js → agent-project-badge-Diz4Sjjb.js} +2 -2
  18. package/public/assets/{agent-tools-D4n40pn8.js → agent-tools-CRhpRQza.js} +2 -2
  19. package/public/assets/{agent-type-icon-D6FUarOf.js → agent-type-icon-LrUEU3lB.js} +2 -2
  20. package/public/assets/{agents-BYDslkJc.js → agents-B-v66C-4.js} +2 -2
  21. package/public/assets/{analytics-B-XSbEMC.js → analytics-BqCb-LKi.js} +2 -2
  22. package/public/assets/{artifact-viewer-DdWIfV6M.js → artifact-viewer-kf4pGuAR.js} +2 -2
  23. package/public/assets/{artifacts-oBOqks-2.js → artifacts-D32zyw4Y.js} +2 -2
  24. package/public/assets/{artifacts-DPN1q4pA.js → artifacts-DJevXcvg.js} +2 -2
  25. package/public/assets/{automation-4KcRFXEu.js → automation-BpRajx_5.js} +2 -2
  26. package/public/assets/{backlog-Cw9xPTCF.js → backlog-D_UjTdzX.js} +2 -2
  27. package/public/assets/{busy-health-CTu32MZt.js → busy-health-N3VEh4ao.js} +2 -2
  28. package/public/assets/{channels-BiXzg1G9.js → channels-9jopungq.js} +2 -2
  29. package/public/assets/{chat-Ctk2qwvA.js → chat-CSXrTB3C.js} +2 -2
  30. package/public/assets/{code-preview-Bm1wWqAp.js → code-preview-p1NnmqK0.js} +2 -2
  31. package/public/assets/{connectors-C36ax4Vw.js → connectors-CZdqlDNL.js} +2 -2
  32. package/public/assets/{cosmos-Jj9LK2Wf.js → cosmos-BgdsE4dG.js} +2 -2
  33. package/public/assets/{crate-agent-H6S7-B9T.js → crate-agent-30aXTKva.js} +2 -2
  34. package/public/assets/{dependency-graph-Cd1E23wa.js → dependency-graph-B_jvw2GE.js} +2 -2
  35. package/public/assets/{deploys-Bwfq146q.js → deploys-DoN6bAb5.js} +2 -2
  36. package/public/assets/{display-CfO7rTuF.js → display-BrKWFGJz.js} +2 -2
  37. package/public/assets/{files-DhhZI2Ih.js → files-D6XEu8Ng.js} +2 -2
  38. package/public/assets/fleet-C7jQWbw2.js +2 -0
  39. package/public/assets/{fleet-metrics-BkHP-P3R.js → fleet-metrics-BiDs3MLx.js} +2 -2
  40. package/public/assets/{formatted-body-Cm1RrzmN.js → formatted-body-CQBwc1fb.js} +3 -3
  41. package/public/assets/{formatted-body-impl-DQiOuOD1.js → formatted-body-impl-DyG0WsQS.js} +2 -2
  42. package/public/assets/{host-providers-dialog-CHqwXTov.js → host-providers-dialog-DKaW31dN.js} +2 -2
  43. package/public/assets/index-CRHep3M9.js +15 -0
  44. package/public/assets/index-MYAIwYTX.css +2 -0
  45. package/public/assets/{insights-fH1EL-qZ.js → insights-CzrtkpOE.js} +2 -2
  46. package/public/assets/{integrations-C5Oix6ng.js → integrations-aVsUmigt.js} +2 -2
  47. package/public/assets/{issue-ref-chip-ODyondIa.js → issue-ref-chip-BnSlM_95.js} +2 -2
  48. package/public/assets/{log-viewer-Cvfy6PW7.js → log-viewer-qe7Ee47Z.js} +2 -2
  49. package/public/assets/{maintenance-D8CDH5wZ.js → maintenance-NfjqFZAk.js} +2 -2
  50. package/public/assets/{managed-agents-egllbpij.js → managed-agents-BiH0HgaE.js} +2 -2
  51. package/public/assets/{markdown-preview-CYSFgVgK.js → markdown-preview-BixaT3Xr.js} +3 -3
  52. package/public/assets/{markdown-preview-impl-BteVIjX4.js → markdown-preview-impl-DNGlWIS3.js} +2 -2
  53. package/public/assets/{memory-C8WmXsyr.js → memory-DNsCl6UX.js} +2 -2
  54. package/public/assets/{messages-BzQdqlO6.js → messages-DopU1L1X.js} +2 -2
  55. package/public/assets/{orchestrators-B1HxzkNw.js → orchestrators-Cq7ckLQT.js} +2 -2
  56. package/public/assets/{overview-DCUnI4Z7.js → overview-C1DCTzzV.js} +2 -2
  57. package/public/assets/{pairs-ftCd2XcD.js → pairs-DE_m6rrE.js} +2 -2
  58. package/public/assets/{profile-icon-B2zH8DMH.js → profile-icon-B3rfh77F.js} +2 -2
  59. package/public/assets/project-profile-instructions-DQWZE0kA.js +2 -0
  60. package/public/assets/projects-Br2Bd2h1.js +2 -0
  61. package/public/assets/{prompt-settings-Bftq9czX.js → prompt-settings-BKvFsvGD.js} +2 -2
  62. package/public/assets/provider-display-DsuWMvVL.js +2 -0
  63. package/public/assets/{registry-DWCU5c8q.js → registry-Cw38_6gW.js} +2 -2
  64. package/public/assets/run-card-CpKqRbhg.js +2 -0
  65. package/public/assets/scroll-text-BKXS1ahr.js +2 -0
  66. package/public/assets/{secrets-C7GIUATz.js → secrets-DxI-vzSq.js} +2 -2
  67. package/public/assets/{service-access-settings-C3aujqTG.js → service-access-settings-D9s_VJbd.js} +2 -2
  68. package/public/assets/{settings-C6sZ9eBa.js → settings-CKZTVGIx.js} +2 -2
  69. package/public/assets/{shipping-Bu8p1gEw.js → shipping-B9YtnwtG.js} +2 -2
  70. package/public/assets/{stats-V9Z-GxUd.js → stats-DyUBKyWI.js} +2 -2
  71. package/public/assets/{status-dot-CVv0iGHL.js → status-dot-DYqlEUhJ.js} +2 -2
  72. package/public/assets/{store-BvtFdlGX.js → store-DcClXLH0.js} +3 -3
  73. package/public/assets/{tasks-CXU75HRF.js → tasks-Bh7Chr4-.js} +2 -2
  74. package/public/assets/{teams-BPf976zb.js → teams-bowI7qVY.js} +2 -2
  75. package/public/assets/{terminal-viewer-DE9BOe-8.js → terminal-viewer-BgVLHdsU.js} +3 -3
  76. package/public/assets/{terminal-viewer-impl-D04pjmGh.js → terminal-viewer-impl-Dz22UPy6.js} +2 -2
  77. package/public/assets/token-estimate-Co-Jdphh.js +2 -0
  78. package/public/assets/{use-agent-terminal-B1yR456R.js → use-agent-terminal-DSQQ-iY7.js} +2 -2
  79. package/public/assets/{use-decisions-D-S4tpDf.js → use-decisions-samMQhQf.js} +2 -2
  80. package/public/assets/use-project-list-59LheVP7.js +2 -0
  81. package/public/assets/{use-selectors-CnrAk2YP.js → use-selectors-BZa25xEB.js} +2 -2
  82. package/public/assets/{user-avatar-nBFIcR4M.js → user-avatar-DHxlvUvV.js} +2 -2
  83. package/public/assets/versioned-prompt-editor-BmED5DnJ.js +8 -0
  84. package/public/assets/{work-queue-TCjJxeT-.js → work-queue-CSvpuNLz.js} +2 -2
  85. package/public/assets/{workflow-marker-Cf3Hf2fN.js → workflow-marker-DAZcj5qt.js} +2 -2
  86. package/public/assets/{workflows-CXQX20Qj.js → workflows-DdmxRSyg.js} +2 -2
  87. package/public/assets/{workspaces-qSYzKYMA.js → workspaces-BxeKfVuF.js} +2 -2
  88. package/public/cosmos.html +4 -4
  89. package/public/index.html +31 -29
  90. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  91. package/runner/plugins/claude/hooks/gate-run-guard.provisioned.mjs +31 -1
  92. package/runner/src/adapters/claude-effort.ts +2 -4
  93. package/runner/src/adapters/codex-launch-config.ts +4 -5
  94. package/runner/src/adapters/codex.ts +1 -1
  95. package/runner/src/adapters/opencode-provider.ts +7 -22
  96. package/runner/src/adapters/opencode.ts +4 -4
  97. package/runner/src/agent-runner/credentials.ts +42 -12
  98. package/runner/src/agent-runner/inbox.ts +27 -0
  99. package/runner/src/agent-runner/lifecycle-milestones.ts +0 -2
  100. package/runner/src/bin/claude.ts +4 -0
  101. package/runner/src/bin/codex.ts +4 -0
  102. package/runner/src/bin/opencode.ts +4 -0
  103. package/runner/src/index.ts +5 -2
  104. package/runner/src/provider-setup.ts +34 -0
  105. package/runner/src/providers.ts +2 -1
  106. package/runner/src/runner-core.ts +18 -23
  107. package/runner/src/spawn-input.ts +2 -1
  108. package/runner/src/test-run-dedup.ts +59 -56
  109. package/scripts/cd-config.ts +24 -3
  110. package/scripts/gate-run-lease.ts +10 -7
  111. package/scripts/gate.sh +5 -1
  112. package/src/branch-land-notice.ts +93 -0
  113. package/src/branch-landed.ts +7 -76
  114. package/src/bus.ts +19 -20
  115. package/src/cd-deploy-notify.ts +30 -2
  116. package/src/cd-host-lag-notify.ts +7 -6
  117. package/src/ci/incidents.ts +2 -2
  118. package/src/ci/monitor.ts +5 -8
  119. package/src/ci/root-missing.ts +2 -7
  120. package/src/ci/transition-notify.ts +7 -39
  121. package/src/cli/cd-pauses.ts +19 -0
  122. package/src/cli/orchestrator.ts +1 -1
  123. package/src/cli/state.ts +2 -19
  124. package/src/cli/task-edit.ts +22 -0
  125. package/src/cli/task.ts +3 -1
  126. package/src/cli/workflow.ts +8 -2
  127. package/src/commands-db.ts +10 -6
  128. package/src/commands-write.ts +8 -0
  129. package/src/config-store-write.ts +13 -1
  130. package/src/config-store.ts +25 -14
  131. package/src/db/activity-async.ts +6 -1
  132. package/src/db/activity-write-ops.ts +13 -1
  133. package/src/db/activity.ts +1 -14
  134. package/src/db/agent-lifecycle-write-ops.ts +1 -0
  135. package/src/db/agent-meta.ts +2 -2
  136. package/src/db/agent-ownership-write-ops.ts +4 -0
  137. package/src/db/agent-search.ts +1 -15
  138. package/src/db/agents-async.ts +58 -7
  139. package/src/db/agents-write-ops.ts +28 -5
  140. package/src/db/agents.ts +81 -54
  141. package/src/db/artifacts-write-ops.ts +8 -3
  142. package/src/db/bus-outbox-write-ops.ts +1 -0
  143. package/src/db/channels-write-ops.ts +4 -0
  144. package/src/db/ci-incidents-async.ts +5 -5
  145. package/src/db/ci-incidents-write-ops.ts +2 -2
  146. package/src/db/claude-api-budget-ops.ts +5 -5
  147. package/src/db/claude-app-oauth.ts +4 -4
  148. package/src/db/codex-reset-consumes-write-ops.ts +3 -0
  149. package/src/db/command-row.ts +10 -2
  150. package/src/db/commands-list-query.ts +2 -0
  151. package/src/db/commands-spawn-read.ts +1 -1
  152. package/src/db/commands-write-ops.ts +21 -0
  153. package/src/db/config-store-write-ops.ts +9 -2
  154. package/src/db/continuation-archives-async.ts +5 -0
  155. package/src/db/continuation-archives-write-ops.ts +9 -0
  156. package/src/db/continuations.ts +2 -2
  157. package/src/db/db-writer-worker.ts +8 -1
  158. package/src/db/delivery-write-ops.ts +5 -0
  159. package/src/db/deploy-events-async.ts +1 -0
  160. package/src/db/deploy-events-read-ops.ts +2 -1
  161. package/src/db/deploy-events.ts +35 -4
  162. package/src/db/hosts-registration.ts +10 -2
  163. package/src/db/hosts-write-ops.ts +14 -0
  164. package/src/db/hosts.ts +9 -6
  165. package/src/db/inbox-write-ops.ts +4 -0
  166. package/src/db/inbox.ts +0 -14
  167. package/src/db/index.ts +2 -0
  168. package/src/db/insights-write-ops.ts +1 -0
  169. package/src/db/integrations-async.ts +8 -0
  170. package/src/db/integrations-ops.ts +15 -0
  171. package/src/db/integrations.ts +15 -21
  172. package/src/db/issue-lifecycle.ts +3 -4
  173. package/src/db/issue-refs-async.ts +20 -0
  174. package/src/db/issue-refs-write-ops.ts +29 -0
  175. package/src/db/issue-refs.ts +7 -8
  176. package/src/db/long-running-work.ts +2 -2
  177. package/src/db/macos-privacy.ts +1 -1
  178. package/src/db/main-connection-writes.ts +23 -2
  179. package/src/db/maintenance-job-reconcile.ts +61 -0
  180. package/src/db/maintenance-job-runs-async.ts +14 -0
  181. package/src/db/maintenance-job-write-ops.ts +9 -0
  182. package/src/db/mappers.ts +2 -14
  183. package/src/db/merge-lease.ts +1 -15
  184. package/src/db/message-reads-write-ops.ts +3 -0
  185. package/src/db/message-write-ops.ts +4 -0
  186. package/src/db/migrations.ts +7 -15
  187. package/src/db/orchestrators-async.ts +7 -3
  188. package/src/db/orchestrators-write-ops.ts +7 -1
  189. package/src/db/orchestrators.ts +21 -77
  190. package/src/db/pairs.ts +1 -13
  191. package/src/db/project-profile-instructions.ts +50 -0
  192. package/src/db/prompt-versions-ops.ts +1 -0
  193. package/src/db/prompt-versions.ts +9 -2
  194. package/src/db/provider-catalog-write-ops.ts +4 -4
  195. package/src/db/provider-config-history.ts +8 -3
  196. package/src/db/provider-flap-messages-write-ops.ts +1 -0
  197. package/src/db/provider-model-seed.ts +34 -0
  198. package/src/db/provider-quota-lease-write-ops.ts +1 -0
  199. package/src/db/provider-quota-write-ops.ts +2 -0
  200. package/src/db/provisioning-write-ops.ts +9 -8
  201. package/src/db/read-op-marshaling-fixtures.ts +5 -0
  202. package/src/db/read-ops.ts +4 -0
  203. package/src/db/routing-policy.ts +3 -4
  204. package/src/db/schema.ts +9 -3
  205. package/src/db/secrets-broker-write-ops.ts +5 -5
  206. package/src/db/secrets-vault-write.ts +7 -7
  207. package/src/db/services-write-ops.ts +3 -0
  208. package/src/db/spawn-delegation-state.ts +11 -4
  209. package/src/db/spawn-tree-cleanup.ts +3 -0
  210. package/src/db/stats-async.ts +4 -3
  211. package/src/db/task-entity.ts +23 -2
  212. package/src/db/task-list.ts +3 -1
  213. package/src/db/tasks.ts +1 -17
  214. package/src/db/team-spawn-delegation.ts +1 -1
  215. package/src/db/teams-write-ops.ts +4 -4
  216. package/src/db/timer-cutover-write-ops.ts +7 -0
  217. package/src/db/timer-ingress-write-ops.ts +9 -0
  218. package/src/db/timer-ingress.ts +7 -1
  219. package/src/db/timer-operator-write-ops.ts +14 -0
  220. package/src/db/timer-transfer-write-ops.ts +4 -0
  221. package/src/db/timer-write-ops.ts +14 -0
  222. package/src/db/token-db-write-ops.ts +21 -0
  223. package/src/db/token-standing.ts +15 -11
  224. package/src/db/user-settings-write-ops.ts +1 -0
  225. package/src/db/vault-backup.ts +2 -2
  226. package/src/db/vault-expiry.ts +2 -2
  227. package/src/db/vault-health.ts +3 -3
  228. package/src/db/vault-token-ops.ts +8 -8
  229. package/src/db/web-tool-call-ops.ts +3 -3
  230. package/src/db/workflow-contact.ts +1 -1
  231. package/src/db/workflow-task-read-ops.ts +30 -0
  232. package/src/db/write-ops.ts +15 -1
  233. package/src/db/writer-fault-seam.ts +5 -2
  234. package/src/db/writer-gateway.ts +21 -12
  235. package/src/dispatch-availability.ts +10 -6
  236. package/src/ephemeral/{landing-ingress-proof-worker.ts → landing-ingress-proof.test-fixture.ts} +4 -2
  237. package/src/host/disk-alert.ts +121 -0
  238. package/src/lifecycle-manager.ts +3 -2
  239. package/src/maintenance/cd-watcher.ts +3 -3
  240. package/src/maintenance/critical-job-watchdog.ts +16 -17
  241. package/src/maintenance/index.ts +1 -0
  242. package/src/maintenance/jobs.ts +3 -2
  243. package/src/maintenance/scheduler.ts +84 -128
  244. package/src/maintenance/stalled-delivery-pager.ts +6 -5
  245. package/src/maintenance/workspaces.ts +1 -1
  246. package/src/managed-policy.ts +31 -27
  247. package/src/mcp/index.ts +5 -2
  248. package/src/mcp/output-descriptions.ts +8 -8
  249. package/src/mcp/presentation.ts +4 -4
  250. package/src/mcp/project-profile-instructions.ts +32 -0
  251. package/src/mcp/tool-groups.ts +1 -1
  252. package/src/mcp/tools-spawn.ts +4 -3
  253. package/src/mcp-agent-action.ts +1 -2
  254. package/src/mcp-host-tools.ts +1 -1
  255. package/src/mcp-issue-ref-tools.ts +12 -11
  256. package/src/mcp-maintenance-tools.ts +2 -2
  257. package/src/mcp-profile-trust-tools.ts +4 -4
  258. package/src/mcp-provider-catalog.ts +2 -2
  259. package/src/mcp-task-tools.ts +11 -5
  260. package/src/mcp-team-template-tools.ts +3 -3
  261. package/src/mcp-workflow-tools.ts +10 -3
  262. package/src/notification-types.ts +2 -1
  263. package/src/operator-alarm.ts +26 -11
  264. package/src/package-pack.ts +42 -39
  265. package/src/project-instructions.ts +4 -0
  266. package/src/project-profile-instruction-text.ts +6 -0
  267. package/src/project-profile-instructions.ts +20 -0
  268. package/src/provider-catalog-store.ts +3 -49
  269. package/src/provider-config-store.ts +12 -11
  270. package/src/provider-model-seed.ts +1 -18
  271. package/src/provider-quota-config-store.ts +5 -4
  272. package/src/provider-secrets.test-fixture.ts +3 -3
  273. package/src/recipe-runner.ts +2 -2
  274. package/src/repo-config-source.ts +10 -4
  275. package/src/routes/agent-profiles.ts +6 -6
  276. package/src/routes/agents-spawn.ts +1 -1
  277. package/src/routes/agents.ts +6 -6
  278. package/src/routes/commands-listing.ts +4 -0
  279. package/src/routes/commands.ts +2 -2
  280. package/src/routes/continuation-archives.ts +2 -2
  281. package/src/routes/host-resource-control.ts +4 -1
  282. package/src/routes/index.ts +10 -1
  283. package/src/routes/integrations.ts +24 -50
  284. package/src/routes/issue-refs.ts +7 -7
  285. package/src/routes/orchestrator-bootstrap.ts +3 -3
  286. package/src/routes/orchestrator-host-registration.ts +2 -2
  287. package/src/routes/orchestrator-host-utilization.ts +3 -1
  288. package/src/routes/orchestrator-proxy.ts +2 -2
  289. package/src/routes/orchestrator.ts +4 -4
  290. package/src/routes/project-profile-instructions.ts +27 -0
  291. package/src/routes/project-trust.ts +2 -2
  292. package/src/routes/provider-config.ts +9 -8
  293. package/src/routes/provider-quota-config.ts +2 -2
  294. package/src/routes/settings.ts +5 -5
  295. package/src/routes/stats.ts +6 -6
  296. package/src/routes/task-http.ts +15 -1
  297. package/src/routes/task-issues.ts +11 -26
  298. package/src/routes/tasks.ts +22 -53
  299. package/src/routes/team-templates.ts +5 -5
  300. package/src/routes/teams-config.ts +2 -2
  301. package/src/routes/tokens.ts +12 -5
  302. package/src/routes/workflows.ts +20 -5
  303. package/src/routing-policy-normalize.ts +1 -2
  304. package/src/routing-step-defaults.ts +0 -2
  305. package/src/runner-token-mint-limit.ts +63 -0
  306. package/src/runtime-grant.ts +4 -2
  307. package/src/runtime-tokens.ts +50 -9
  308. package/src/security.ts +13 -20
  309. package/src/server-background.ts +1 -1
  310. package/src/server.ts +5 -2
  311. package/src/services/host-utilization.ts +23 -0
  312. package/src/services/idle-epics.ts +5 -6
  313. package/src/services/issue-lifecycle.ts +57 -61
  314. package/src/services/issue-refs.ts +9 -13
  315. package/src/services/kernel-timers-cutover-chaos-worker.ts +12 -7
  316. package/src/services/kernel-timers-ingress.ts +1 -1
  317. package/src/services/kernel-timers-request-key-migration-chaos-worker.ts +4 -1
  318. package/src/services/landed-ancestry.ts +19 -3
  319. package/src/services/memory-areas.ts +15 -51
  320. package/src/services/plan-ci-notice.ts +57 -0
  321. package/src/services/plan-ci-recovery.ts +2 -55
  322. package/src/services/plan-wake-digest.ts +1 -0
  323. package/src/services/profile-trust.ts +4 -3
  324. package/src/services/project-config-read.ts +70 -0
  325. package/src/services/project-profile-instructions.ts +54 -0
  326. package/src/services/provisioning-harness/budget-baseline.json +4 -4
  327. package/src/services/restart-spawn.ts +61 -49
  328. package/src/services/self-care-state.ts +2 -1
  329. package/src/services/self-effort.ts +5 -5
  330. package/src/services/send-message.ts +5 -1
  331. package/src/services/spawn-agent.ts +13 -2
  332. package/src/services/spawn-chain-inspector.ts +6 -13
  333. package/src/services/spawn-policy.ts +68 -0
  334. package/src/services/spawn-tree-cleanup.ts +2 -2
  335. package/src/services/task-dispatch.ts +3 -0
  336. package/src/services/task-state-reconcile.ts +6 -14
  337. package/src/services/team-templates.ts +9 -8
  338. package/src/services/transient-agent-reaper.ts +3 -1
  339. package/src/services/workflow-auto-start.ts +25 -13
  340. package/src/services/workflow-ci-main.ts +1 -1
  341. package/src/services/workflow-deferred-cleanup.ts +4 -2
  342. package/src/services/workflow-effect-driver.ts +2 -0
  343. package/src/services/workflow-effects.ts +18 -11
  344. package/src/services/workflow-land-notice.ts +10 -0
  345. package/src/services/workflow-pause-text.ts +43 -0
  346. package/src/services/workflow-reconcile.ts +13 -11
  347. package/src/services/workflow-run-view.ts +2 -0
  348. package/src/services/workflow-runs.ts +29 -13
  349. package/src/services/workflow-supervisor-state.ts +5 -0
  350. package/src/services/workflow-supervisor.ts +7 -3
  351. package/src/settings/registry.ts +1 -2
  352. package/src/spawn-command.ts +4 -4
  353. package/src/spawn-models.ts +10 -12
  354. package/src/spawn-placement.ts +1 -1
  355. package/src/spawn-targets.ts +1 -1
  356. package/src/startup-context-injection.ts +1 -0
  357. package/src/state-adoption/cd-cutover.ts +53 -7
  358. package/src/state-adoption/fleet-cutover.ts +35 -0
  359. package/src/state-adoption/fleet-plan.ts +36 -0
  360. package/src/state-adoption/index.ts +2 -2
  361. package/src/state-adoption/linux-plan.ts +2 -2
  362. package/src/state-adoption/linux.ts +2 -2
  363. package/src/state-adoption/macos-plan.ts +2 -2
  364. package/src/state-adoption/macos.ts +1 -1
  365. package/src/steward.ts +7 -6
  366. package/src/task-dispatch-live.ts +1 -1
  367. package/src/task-dispatcher.ts +1 -1
  368. package/src/teams-config-store.ts +4 -3
  369. package/src/token-db-write.ts +10 -0
  370. package/src/workspace-command-settle.ts +2 -1
  371. package/src/workspace-steward-wake.ts +2 -2
  372. package/node_modules/@humwick/kernel/LICENSE +0 -167
  373. package/node_modules/@humwick/kernel/async-entity-persistence.ts +0 -344
  374. package/node_modules/@humwick/kernel/async-event-log.ts +0 -107
  375. package/node_modules/@humwick/kernel/automations.pilot.ts +0 -147
  376. package/node_modules/@humwick/kernel/config.ts +0 -111
  377. package/node_modules/@humwick/kernel/contract.ts +0 -572
  378. package/node_modules/@humwick/kernel/control-authz.ts +0 -281
  379. package/node_modules/@humwick/kernel/control.ts +0 -645
  380. package/node_modules/@humwick/kernel/effects.ts +0 -493
  381. package/node_modules/@humwick/kernel/event-log.ts +0 -319
  382. package/node_modules/@humwick/kernel/http-transport.ts +0 -384
  383. package/node_modules/@humwick/kernel/identity-grammar.ts +0 -20
  384. package/node_modules/@humwick/kernel/identity.ts +0 -248
  385. package/node_modules/@humwick/kernel/index.ts +0 -28
  386. package/node_modules/@humwick/kernel/kernel-persistence.ts +0 -181
  387. package/node_modules/@humwick/kernel/landing-incident.ts +0 -306
  388. package/node_modules/@humwick/kernel/landing.ts +0 -953
  389. package/node_modules/@humwick/kernel/manifest-validation.ts +0 -460
  390. package/node_modules/@humwick/kernel/mcp-transport.ts +0 -109
  391. package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +0 -200
  392. package/node_modules/@humwick/kernel/message-authority.ts +0 -281
  393. package/node_modules/@humwick/kernel/module-host.ts +0 -722
  394. package/node_modules/@humwick/kernel/openapi-init.ts +0 -14
  395. package/node_modules/@humwick/kernel/package.json +0 -47
  396. package/node_modules/@humwick/kernel/persistence-schema.ts +0 -248
  397. package/node_modules/@humwick/kernel/persistence.ts +0 -510
  398. package/node_modules/@humwick/kernel/registry.ts +0 -60
  399. package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +0 -499
  400. package/node_modules/@humwick/kernel/sqlite-module-host.ts +0 -87
  401. package/node_modules/@humwick/kernel/timers-chaos-worker.ts +0 -186
  402. package/node_modules/@humwick/kernel/timers.pilot.ts +0 -1411
  403. package/node_modules/@humwick/kernel/workflow-activation.ts +0 -25
  404. package/node_modules/@humwick/kernel/workflow-backstop.ts +0 -40
  405. package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +0 -20
  406. package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +0 -213
  407. package/node_modules/@humwick/kernel/workflow-composition.ts +0 -231
  408. package/node_modules/@humwick/kernel/workflow-container.ts +0 -72
  409. package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +0 -42
  410. package/node_modules/@humwick/kernel/workflow-control.ts +0 -178
  411. package/node_modules/@humwick/kernel/workflow-definition.ts +0 -507
  412. package/node_modules/@humwick/kernel/workflow-dispatch.ts +0 -60
  413. package/node_modules/@humwick/kernel/workflow-foreach.ts +0 -167
  414. package/node_modules/@humwick/kernel/workflow-journal-codec.ts +0 -339
  415. package/node_modules/@humwick/kernel/workflow-journal.ts +0 -922
  416. package/node_modules/@humwick/kernel/workflow-lane.ts +0 -48
  417. package/node_modules/@humwick/kernel/workflow-lease-owner.ts +0 -60
  418. package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +0 -19
  419. package/node_modules/@humwick/kernel/workflow-runtime.ts +0 -938
  420. package/node_modules/@humwick/kernel/workflow-script.ts +0 -11
  421. package/node_modules/@humwick/kernel/workflow-state.ts +0 -299
  422. package/node_modules/@humwick/kernel/workflow-step-commit.ts +0 -195
  423. package/node_modules/@humwick/kernel/workflow-transitions.ts +0 -317
  424. package/node_modules/@humwick/kernel/workflow-values.ts +0 -153
  425. package/node_modules/@humwick/kernel/workflow.pilot.ts +0 -376
  426. package/node_modules/@humwick/offload/LICENSE +0 -167
  427. package/node_modules/@humwick/offload/README.md +0 -57
  428. package/node_modules/@humwick/offload/dist/async-runner.d.ts +0 -18
  429. package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +0 -1
  430. package/node_modules/@humwick/offload/dist/async-runner.js +0 -158
  431. package/node_modules/@humwick/offload/dist/async-runner.js.map +0 -1
  432. package/node_modules/@humwick/offload/dist/envelope.d.ts +0 -25
  433. package/node_modules/@humwick/offload/dist/envelope.d.ts.map +0 -1
  434. package/node_modules/@humwick/offload/dist/envelope.js +0 -80
  435. package/node_modules/@humwick/offload/dist/envelope.js.map +0 -1
  436. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +0 -10
  437. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +0 -1
  438. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +0 -59
  439. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +0 -1
  440. package/node_modules/@humwick/offload/dist/index.d.ts +0 -5
  441. package/node_modules/@humwick/offload/dist/index.d.ts.map +0 -1
  442. package/node_modules/@humwick/offload/dist/index.js +0 -5
  443. package/node_modules/@humwick/offload/dist/index.js.map +0 -1
  444. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +0 -62
  445. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +0 -1
  446. package/node_modules/@humwick/offload/dist/subprocess-runner.js +0 -295
  447. package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +0 -1
  448. package/node_modules/@humwick/offload/dist/types.d.ts +0 -111
  449. package/node_modules/@humwick/offload/dist/types.d.ts.map +0 -1
  450. package/node_modules/@humwick/offload/dist/types.js +0 -176
  451. package/node_modules/@humwick/offload/dist/types.js.map +0 -1
  452. package/node_modules/@humwick/offload/dist/worker-pool.d.ts +0 -23
  453. package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +0 -1
  454. package/node_modules/@humwick/offload/dist/worker-pool.js +0 -435
  455. package/node_modules/@humwick/offload/dist/worker-pool.js.map +0 -1
  456. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +0 -2
  457. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +0 -1
  458. package/node_modules/@humwick/offload/dist/worker-runtime.js +0 -27
  459. package/node_modules/@humwick/offload/dist/worker-runtime.js.map +0 -1
  460. package/node_modules/@humwick/offload/dist/worker.d.ts +0 -5
  461. package/node_modules/@humwick/offload/dist/worker.d.ts.map +0 -1
  462. package/node_modules/@humwick/offload/dist/worker.js +0 -1
  463. package/node_modules/@humwick/offload/dist/worker.js.map +0 -1
  464. package/node_modules/@humwick/offload/package.json +0 -46
  465. package/node_modules/@humwick/offload/src/async-runner.ts +0 -170
  466. package/node_modules/@humwick/offload/src/envelope.ts +0 -92
  467. package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +0 -49
  468. package/node_modules/@humwick/offload/src/index.ts +0 -4
  469. package/node_modules/@humwick/offload/src/subprocess-runner.ts +0 -334
  470. package/node_modules/@humwick/offload/src/types.ts +0 -235
  471. package/node_modules/@humwick/offload/src/worker-pool.ts +0 -463
  472. package/node_modules/@humwick/offload/src/worker-runtime.ts +0 -32
  473. package/node_modules/@humwick/offload/src/worker.ts +0 -5
  474. package/public/assets/agent-liveness-DyqxfB-t.js +0 -2
  475. package/public/assets/agent-profiles-BpI_jTxq.js +0 -2
  476. package/public/assets/fleet-BL4UXS_k.js +0 -2
  477. package/public/assets/index-B7ZerHLY.css +0 -2
  478. package/public/assets/index-BK0WV6hF.js +0 -15
  479. package/public/assets/projects-B_lmcqfO.js +0 -2
  480. package/public/assets/provider-display-DbLWpP3j.js +0 -2
  481. package/public/assets/run-card-CO_bguQf.js +0 -2
  482. package/public/assets/use-project-list-B-T2Bnnn.js +0 -2
  483. package/public/assets/versioned-prompt-editor-Diu8BIaM.js +0 -8
  484. package/runner/src/gate-run-lease-probe.ts +0 -38
  485. package/src/db/claude-catalog-reconcile.ts +0 -27
  486. package/src/routing-model-aliases.ts +0 -21
@@ -77,13 +77,20 @@ export function spawnDelegationState(agentId: string, launchProfile?: string) {
77
77
  participating: Boolean((ancestors?.length !== 1 || launchProfile === "sub-coordinator")
78
78
  && (grant || ownTeam?.parentTeamId || (rootTeam && hasPrivateTeams(rootTeam.id)))), enabled: grant?.enabled === true,
79
79
  grant, projectId: ancestors && ancestors.length > 1 ? delegatedProjectId(agentId) ?? projectIdForAgent(agent) : rootTeam?.projectId,
80
- displaced: (ancestors ?? [agentId]).some(id => {
81
- const ancestor = getAgent(id);
82
- return isDisplacedSeat(id, ancestor?.teamId ?? seatTeam(id)?.id);
83
- }),
80
+ displaced: displacedThroughDelegation(ancestors ?? [agentId]),
84
81
  };
85
82
  }
86
83
 
84
+ /** The agent's own seat was claimed, or that of an ancestor it reaches only through delegated launches. An
85
+ * ordinary child is inherited by the claimant and keeps its standing (#3773). */
86
+ function displacedThroughDelegation(ancestors: readonly string[]): boolean {
87
+ for (const id of ancestors) {
88
+ if (isDisplacedSeat(id, getAgent(id)?.teamId ?? seatTeam(id)?.id)) return true;
89
+ if (!delegatedLaunch(id)) return false;
90
+ }
91
+ return false;
92
+ }
93
+
87
94
  /** Ordinary tokens avoid ancestry reads; private-team linkage survives grant removal. */
88
95
  export function participatesInSpawnDelegation(agentId: string): boolean {
89
96
  return Boolean(getDb().query(`WITH RECURSIVE lineage(id, depth) AS (
@@ -35,12 +35,14 @@ export const SPAWN_TREE_CLEANUP_READ_OPS = {
35
35
 
36
36
  export const SPAWN_TREE_CLEANUP_WRITE_OPS = {
37
37
  "spawnTree.markTransferred": {
38
+ offLoop: true,
38
39
  idempotent: true,
39
40
  fn: ({ id }: { id: string }): void => {
40
41
  getDb().query("UPDATE spawn_tree_stops SET cleanup_effect_id = id WHERE id = ?").run(id);
41
42
  },
42
43
  },
43
44
  "spawnTree.stopGoneCallers": {
45
+ offLoop: true,
44
46
  idempotent: true,
45
47
  fn: (_args: undefined, ctx: { now: number }): void => {
46
48
  const rows = getDb().query(`WITH launches AS MATERIALIZED (
@@ -70,6 +72,7 @@ export const SPAWN_TREE_CLEANUP_WRITE_OPS = {
70
72
  },
71
73
  },
72
74
  "spawnTree.prepareCleanup": {
75
+ offLoop: true,
73
76
  idempotent: true,
74
77
  fn: ({ agentId }: { agentId: string }, ctx: { now: number }): void => {
75
78
  const ids = spawnTreeParentIds(agentId).filter(id => delegatedLaunch(id));
@@ -1,4 +1,4 @@
1
- import { mainConnectionWrites } from "./main-connection-writes";
1
+ import { mainConnectionBootWrites, mainConnectionWrites } from "./main-connection-writes";
2
2
  import { allWriterOps } from "./write-ops";
3
3
  import { assertMainThreadState } from "./main-thread-state";
4
4
  // Async reader-pool facades for DB-only stats. Health is a deliberate documented deferral: its
@@ -105,7 +105,7 @@ async function probeDatabaseLiveness(): Promise<HealthCheck> {
105
105
  * `readerBackend`/`writerBackend` plus reader-pool depth/in-flight/shed counts unconditionally (the
106
106
  * in-process backend's zeroed pool numbers ARE the "report that plainly" answer, not a special case).
107
107
  */
108
- function buildDbGatewayCheck(stats: DbGatewayStats): HealthCheck {
108
+ export function buildDbGatewayCheck(stats: DbGatewayStats): HealthCheck {
109
109
  const reader = stats.reader;
110
110
  const readerActive = reader.backend !== "uninitialized";
111
111
  const wedged = stats.writer.wedged;
@@ -119,7 +119,7 @@ function buildDbGatewayCheck(stats: DbGatewayStats): HealthCheck {
119
119
  `reader=${stats.readerBackend}`,
120
120
  `onLoopOps=${specs.length - offLoop}; writerThreadOps=${offLoop}`,
121
121
  `mainConnectionWrites1h=${JSON.stringify(writes)}`,
122
- ...(wedged ? ["writer wedged"] : []),
122
+ ...(wedged ? [`writer wedged: op ${wedged.op} running ${Math.round(wedged.runningMs / 1000)}s`] : []),
123
123
  ...(shedCount > 0 ? [`${shedCount} reader shed(s)`] : []),
124
124
  ].join("; ");
125
125
  return {
@@ -131,6 +131,7 @@ function buildDbGatewayCheck(stats: DbGatewayStats): HealthCheck {
131
131
  onLoopOps: specs.length - offLoop,
132
132
  writerThreadOps: offLoop,
133
133
  mainConnectionWrites1h: Object.values(writes).reduce((sum, count) => sum + count, 0),
134
+ mainConnectionBootWrites: mainConnectionBootWrites(),
134
135
  writerQueued: stats.writer.queued,
135
136
  writerBusy: stats.writer.busy,
136
137
  writerHighWater: stats.writer.highWater,
@@ -37,6 +37,7 @@ import {
37
37
  type TaskStatus,
38
38
  } from "../types";
39
39
  import { parseJson } from "../utils";
40
+ import { repoRelativePath } from "@humwick/sdk/spawn-policy";
40
41
  import { cleanEnum, cleanStringArray, optionalEnum } from "../validation";
41
42
  import { getDb, writeTransaction, ValidationError } from "./connection.ts";
42
43
  import { ownerForNewTask } from "./task-owner.ts";
@@ -108,6 +109,16 @@ function cleanTags(value: unknown, field = "tags"): string[] | undefined {
108
109
  return cleanStringArray(value, field, { itemMax: 60, maxItems: 30 });
109
110
  }
110
111
 
112
+ /** #3736 — repo-relative, normalized; a path that leaves the repo is refused. Stored null when empty. */
113
+ function taskPathsColumn(value: unknown): string | null {
114
+ const paths = (cleanStringArray(value, "paths", { itemMax: 300, maxItems: 50 }) ?? []).map((path, i) => {
115
+ const rel = repoRelativePath(path);
116
+ if (rel === undefined) throw new ValidationError(`paths[${i}] must be a repo-relative path inside the repo`);
117
+ return rel;
118
+ });
119
+ return paths.length ? JSON.stringify([...new Set(paths)]) : null;
120
+ }
121
+
111
122
  // #1535 WS3 — backlog lifecycle state. Optional (like priority): absent stays undefined so a
112
123
  // non-backlog task's column is NULL; a present-but-invalid value throws rather than silently
113
124
  // dropping. The backlog→promoted flip is a distinct writer (setTaskBacklogState in db/backlog.ts).
@@ -196,6 +207,7 @@ export interface CreateDeliverableTaskInput {
196
207
  complexity?: TaskComplexity;
197
208
  context?: string;
198
209
  terminalStage?: Task["terminalStage"];
210
+ onDeploy?: string;
199
211
  doneWhen?: string; branch?: string;
200
212
  teamId?: string;
201
213
  projectId?: string;
@@ -206,6 +218,7 @@ export interface CreateDeliverableTaskInput {
206
218
  latestSteer?: string;
207
219
  dispatch?: TaskDispatch;
208
220
  tags?: string[];
221
+ paths?: string[];
209
222
  priority?: TaskPriority;
210
223
  /** #1535 WS3 — seed the task directly into the backlog (`backlog`) so it shows up in the
211
224
  * prioritized backlog query until promoted. Absent ⇒ not backlog-managed. */
@@ -225,6 +238,7 @@ export function createDeliverableTaskBody(input: CreateDeliverableTaskInput, now
225
238
  const intent = requireText(input.intent, "intent", 1_000_000);
226
239
  const dispatch = cleanTaskDispatch(input.dispatch);
227
240
  const complexity = cleanTaskComplexity(input.complexity ?? dispatch?.complexity, "complexity") ?? DEFAULT_TASK_COMPLEXITY;
241
+ const onDeploy = optionalText(input.onDeploy, "onDeploy", 500);
228
242
  const context = optionalText(input.context, "context", 1_000_000);
229
243
  const branch = optionalText(input.branch, "branch", 500);
230
244
  const taskProject = cleanTaskProject(input);
@@ -237,8 +251,8 @@ export function createDeliverableTaskBody(input: CreateDeliverableTaskInput, now
237
251
  INSERT INTO tasks (
238
252
  source, title, body, severity, status, target, occurrence_count, metadata,
239
253
  created_at, updated_at, last_seen_at,
240
- kind, intent, complexity, context, latest_steer, deliverable_stage, done_when, branch, team_id, project_id, repo, created_by, dispatch, tags, priority, backlog_state, owner
241
- ) VALUES (?, ?, ?, 'info', 'open', ?, 1, '{}', ?, ?, ?, '${DELIVERABLE_TASK_KIND}', ?, ?, ?, ?, 'none', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
254
+ kind, intent, complexity, context, latest_steer, deliverable_stage, done_when, branch, team_id, project_id, repo, created_by, dispatch, tags, paths, priority, backlog_state, owner, on_deploy
255
+ ) VALUES (?, ?, ?, 'info', 'open', ?, 1, '{}', ?, ?, ?, '${DELIVERABLE_TASK_KIND}', ?, ?, ?, ?, 'none', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
242
256
  `).run(
243
257
  optionalText(input.source, "source", 200) ?? "coordinator",
244
258
  title,
@@ -254,9 +268,11 @@ export function createDeliverableTaskBody(input: CreateDeliverableTaskInput, now
254
268
  createdBy,
255
269
  dispatch ? JSON.stringify(dispatch) : null,
256
270
  tags.length ? JSON.stringify(tags) : null,
271
+ taskPathsColumn(input.paths),
257
272
  priority ?? null,
258
273
  backlogState ?? null,
259
274
  ownerForNewTask(createdBy, teamId),
275
+ onDeploy ?? null,
260
276
  );
261
277
  const id = Number(result.lastInsertRowid);
262
278
  if (input.terminalStage) getDb().query("UPDATE tasks SET terminal_stage = ? WHERE id = ?").run(input.terminalStage, id);
@@ -282,6 +298,7 @@ export interface UpdateDeliverableTaskInput {
282
298
  complexity?: TaskComplexity;
283
299
  context?: string;
284
300
  latestSteer?: string;
301
+ onDeploy?: string;
285
302
  doneWhen?: string;
286
303
  dispatch?: TaskDispatch;
287
304
  teamId?: string;
@@ -290,6 +307,8 @@ export interface UpdateDeliverableTaskInput {
290
307
  actor?: string;
291
308
  /** Absent ⇒ untouched. `[]` (or `null`) ⇒ clear all tags. */
292
309
  tags?: string[] | null;
310
+ /** Absent ⇒ untouched. `[]` (or `null`) ⇒ clear all paths. */
311
+ paths?: string[] | null;
293
312
  /** Absent ⇒ untouched. `null` ⇒ clear (priority has no default, unlike complexity). */
294
313
  priority?: TaskPriority | null;
295
314
  }
@@ -309,6 +328,7 @@ export function updateDeliverableTaskBody(id: number, input: UpdateDeliverableTa
309
328
  if (input.complexity !== undefined) push("complexity", cleanTaskComplexity(input.complexity) ?? null);
310
329
  if (input.context !== undefined) push("context", optionalText(input.context, "context", 1_000_000) ?? null);
311
330
  if (input.latestSteer !== undefined) push("latest_steer", optionalText(input.latestSteer, "latestSteer", 1_000_000) ?? null);
331
+ if (input.onDeploy !== undefined) push("on_deploy", optionalText(input.onDeploy, "onDeploy", 500) ?? null);
312
332
  if (input.doneWhen !== undefined) push("done_when", optionalText(input.doneWhen, "doneWhen", 1_000_000) ?? null);
313
333
  if (input.teamId !== undefined) push("team_id", optionalText(input.teamId, "teamId", 200) ?? null);
314
334
  if (input.projectId !== undefined || input.repo !== undefined) {
@@ -321,6 +341,7 @@ export function updateDeliverableTaskBody(id: number, input: UpdateDeliverableTa
321
341
  const tags = input.tags === null ? [] : (cleanTags(input.tags) ?? []);
322
342
  push("tags", tags.length ? JSON.stringify(tags) : null);
323
343
  }
344
+ if (input.paths !== undefined) push("paths", taskPathsColumn(input.paths ?? []));
324
345
  if (input.terminalStage !== undefined) push("terminal_stage", input.terminalStage === null ? null : cleanTaskTerminalStage(input.terminalStage));
325
346
  if (input.priority !== undefined) push("priority", input.priority === null ? null : requireTaskPriority(input.priority));
326
347
  if (sets.length > 0) {
@@ -41,7 +41,8 @@ function buildDeliverableTasksWhere(filter: ListDeliverableTasksFilter): { claus
41
41
  }
42
42
  if (filter.staleOwnerGone) conditions.push(STALE_TASK_OWNER_SQL);
43
43
  if (filter.status) { conditions.push("status = ?"); params.push(filter.status); }
44
- if (filter.deliverableStage) { conditions.push("deliverable_stage = ?"); params.push(filter.deliverableStage); }
44
+ if (filter.deliverableStage === "awaiting-deploy") conditions.push("deliverable_stage IN ('landed-on-main', 'deploying')");
45
+ else if (filter.deliverableStage) { conditions.push("deliverable_stage = ?"); params.push(filter.deliverableStage); }
45
46
  if (filter.teamId) { conditions.push("team_id = ?"); params.push(filter.teamId); }
46
47
  if (filter.createdBy) { conditions.push("created_by = ?"); params.push(filter.createdBy); }
47
48
  if (filter.ownerAgentId) { conditions.push("owner_agent_id = ?"); params.push(filter.ownerAgentId); }
@@ -87,6 +88,7 @@ function taskListItem(task: Task): TaskListItem {
87
88
  status: task.status,
88
89
  kind: task.kind,
89
90
  ...(task.validationItems ? { validationItems: task.validationItems } : {}),
91
+ ...(task.onDeploy ? { onDeploy: task.onDeploy } : {}),
90
92
  ...(task.terminalStage ? { terminalStage: task.terminalStage } : {}),
91
93
  ...(task.completedAt ? { completedAt: task.completedAt } : {}),
92
94
  ...(task.staleOwnerGone ? { staleOwnerGone: true } : {}),
package/src/db/tasks.ts CHANGED
@@ -1,19 +1,5 @@
1
1
  import { taskVisibilitySql, type ViewerScope } from "./viewer-scope";
2
- import { randomUUID } from "node:crypto";
3
- import { isRecord, stringValue, isMechanicalMessageKind } from "@humwick/sdk";
4
- import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "../config.ts";
5
- import { parseJson } from "../utils";
6
- import { isLiveIsolatedWorkspace } from "../workspace-phase";
7
- import {
8
- CONTRACT_REQUIREMENTS,
9
- contractCompatibility,
10
- parseRuntimeCapabilities,
11
- parseRuntimeContracts,
12
- parseRuntimePackage,
13
- type RuntimeContracts,
14
- } from "../contracts";
15
- import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
16
- import { matchAgents } from "../agent-ref";
2
+ import { CLAIM_LEASE_MS } from "../config";
17
3
  import { getAgent, validateAgentSession } from "./agents.ts";
18
4
  import { messageTaskClosed } from "./claim-sql.ts";
19
5
  import { ClaimError, getDb, writeTransaction } from "./connection.ts";
@@ -51,7 +37,6 @@ export function taskMessageBody(task: Pick<Task, "id" | "source" | "severity" |
51
37
  return lines.join("\n");
52
38
  }
53
39
 
54
-
55
40
  export function getTask(id: number): Task | null {
56
41
  const row = getDb().query(`${TASK_SELECT} WHERE id = ?`).get(id) as any;
57
42
  return row ? rowToTask(row) : null;
@@ -85,7 +70,6 @@ export function listTasks(filter?: { viewer?: ViewerScope | null; status?: strin
85
70
  return (getDb().query(`${TASK_SELECT} ${where} ORDER BY updated_at DESC LIMIT ?`).all(...params) as any[]).map(rowToTask);
86
71
  }
87
72
 
88
-
89
73
  /** Deliverable stages meaning the work already left the worker's hands (#2036): a Task sitting in
90
74
  * `in_progress` while its land gate runs is not a stalled worker. Excluded IN SQL, not after the
91
75
  * fact — these rows carry the OLDEST claims, so a post-LIMIT filter would let them consume the
@@ -45,5 +45,5 @@ export function setTeamSpawnDelegationBody(args: SetTeamDelegationArgs, now: num
45
45
  }
46
46
 
47
47
  export const TEAM_SPAWN_DELEGATION_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = {
48
- "teams.spawnDelegation.set": { idempotent: true, fn: (args: SetTeamDelegationArgs, ctx) => setTeamSpawnDelegationBody(args, ctx.now) },
48
+ "teams.spawnDelegation.set": { offLoop: true, idempotent: true, fn: (args: SetTeamDelegationArgs, ctx) => setTeamSpawnDelegationBody(args, ctx.now) },
49
49
  };
@@ -10,8 +10,8 @@ import {
10
10
  import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
11
11
 
12
12
  export const TEAMS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
13
- "teams.zeroMembers.set": { idempotent: true, fn: (a: { teamId: string; ts: number }) => setTeamZeroMembersSince(a.teamId, a.ts) },
14
- "teams.zeroMembers.clear": { idempotent: true, fn: (a: { teamId: string }, ctx) => clearTeamZeroMembersSince(a.teamId, ctx.now) },
15
- "teams.brief.set": { idempotent: true, fn: (a: Parameters<typeof setTeamBrief>[0], ctx) => setTeamBrief(a, ctx.now) },
16
- "teams.dissolve": { idempotent: false, fn: (a: { teamId: string; input?: Parameters<typeof dissolveTeam>[1] }, ctx) => dissolveTeam(a.teamId, a.input, ctx.now) },
13
+ "teams.zeroMembers.set": { offLoop: true, idempotent: true, fn: (a: { teamId: string; ts: number }) => setTeamZeroMembersSince(a.teamId, a.ts) },
14
+ "teams.zeroMembers.clear": { offLoop: true, idempotent: true, fn: (a: { teamId: string }, ctx) => clearTeamZeroMembersSince(a.teamId, ctx.now) },
15
+ "teams.brief.set": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof setTeamBrief>[0], ctx) => setTeamBrief(a, ctx.now) },
16
+ "teams.dissolve": { offLoop: true, idempotent: false, fn: (a: { teamId: string; input?: Parameters<typeof dissolveTeam>[1] }, ctx) => dissolveTeam(a.teamId, a.input, ctx.now) },
17
17
  });
@@ -242,6 +242,7 @@ function rebaseBootstrapEpoch(args: {
242
242
 
243
243
  export const TIMER_CUTOVER_WRITE_OPS = {
244
244
  "timers.cutover.releaseInterruptedTransfer": {
245
+ offLoop: true,
245
246
  idempotent: true,
246
247
  fn: (args: { now?: number } | undefined, ctx: WriterOpContext) =>
247
248
  releaseInterruptedCutoverTransfer(args?.now ?? ctx.now),
@@ -249,26 +250,32 @@ export const TIMER_CUTOVER_WRITE_OPS = {
249
250
  // A kernel batch has already durably changed timer state by the time these run. Never shed the
250
251
  // resume cursor/journal acknowledgement or a restart will repeat a committed side effect.
251
252
  "timers.cutoverOperation.commitBackfillBatch": {
253
+ offLoop: true,
252
254
  idempotent: false,
253
255
  fn: (args: Parameters<typeof commitBackfillBatch>[0]) => commitBackfillBatch(args),
254
256
  },
255
257
  "timers.cutoverOperation.commitBootstrapBatch": {
258
+ offLoop: true,
256
259
  idempotent: false,
257
260
  fn: (args: Parameters<typeof commitBootstrapBatch>[0]) => commitBootstrapBatch(args),
258
261
  },
259
262
  "timers.cutoverOperation.commitTransferBatch": {
263
+ offLoop: true,
260
264
  idempotent: false,
261
265
  fn: (args: Parameters<typeof commitTransferBatch>[0]) => commitTransferBatch(args),
262
266
  },
263
267
  "timers.cutoverOperation.appendJournal": {
268
+ offLoop: true,
264
269
  idempotent: false,
265
270
  fn: (args: Parameters<typeof appendOperationJournal>[0]) => appendOperationJournal(args),
266
271
  },
267
272
  "timers.cutoverOperation.complete": {
273
+ offLoop: true,
268
274
  idempotent: false,
269
275
  fn: (args: Parameters<typeof completeOperation>[0]) => completeOperation(args),
270
276
  },
271
277
  "timers.cutoverOperation.rebaseBootstrapEpoch": {
278
+ offLoop: true,
272
279
  idempotent: false,
273
280
  fn: (args: Parameters<typeof rebaseBootstrapEpoch>[0]) => rebaseBootstrapEpoch(args),
274
281
  },
@@ -78,12 +78,15 @@ export interface AcceptTimerIngressBootstrapOpArgs {
78
78
 
79
79
  export const TIMER_INGRESS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
80
80
  "timerIngress.acceptCreate": {
81
+ offLoop: true,
81
82
  idempotent: true,
82
83
  fn: (args: AcceptTimerIngressCreateOpArgs) =>
83
84
  acceptTimerIngressCreateInTx({ source: args.source, storedKey: args.storedKey, at: args.at }),
84
85
  },
85
86
 
86
87
  "timerIngress.acceptCancel": {
88
+
89
+ offLoop: true,
87
90
  idempotent: true,
88
91
  fn: (args: AcceptTimerIngressCancelOpArgs) =>
89
92
  acceptTimerIngressCancelInTx({
@@ -95,6 +98,8 @@ export const TIMER_INGRESS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = O
95
98
  },
96
99
 
97
100
  "timerIngress.acceptBootstrapCreate": {
101
+
102
+ offLoop: true,
98
103
  idempotent: true,
99
104
  fn: (args: AcceptTimerIngressBootstrapOpArgs) =>
100
105
  acceptTimerIngressBootstrapCreateInTx({
@@ -107,6 +112,7 @@ export const TIMER_INGRESS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = O
107
112
 
108
113
  /** Collapse one unsettled intent to `terminal` (supersede / tombstone / malformed quarantine). */
109
114
  "timerIngress.settleTerminal": {
115
+ offLoop: true,
110
116
  idempotent: true,
111
117
  fn: (args: { seq: number; reason: TimerIngressTerminalReason; error?: string; ownerId: string; at: number }) =>
112
118
  settleTimerIngressTerminal({
@@ -120,6 +126,7 @@ export const TIMER_INGRESS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = O
120
126
 
121
127
  /** CAS-claim an intent for apply (state + lease). Returns false when another applier holds it. */
122
128
  "timerIngress.claim": {
129
+ offLoop: true,
123
130
  idempotent: true,
124
131
  fn: (args: { seq: number; ownerId: string; leaseMs: number; at: number }) =>
125
132
  claimTimerIngressIntent({ seq: args.seq, ownerId: args.ownerId, leaseMs: args.leaseMs, at: args.at }),
@@ -127,6 +134,7 @@ export const TIMER_INGRESS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = O
127
134
 
128
135
  /** Settle a successfully applied intent and advance the terminal-inclusive watermark. */
129
136
  "timerIngress.settleApplied": {
137
+ offLoop: true,
130
138
  idempotent: true,
131
139
  fn: (args: { seq: number; ownerId: string; at: number }) => {
132
140
  settleTimerIngressApplied({ seq: args.seq, ownerId: args.ownerId, at: args.at });
@@ -136,6 +144,7 @@ export const TIMER_INGRESS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = O
136
144
 
137
145
  /** Drop our lease after a failed apply so the next pass retries immediately, keeping the error. */
138
146
  "timerIngress.releaseAfterFailure": {
147
+ offLoop: true,
139
148
  idempotent: true,
140
149
  fn: (args: { seq: number; error: string; ownerId: string }) => {
141
150
  releaseTimerIngressLeaseAfterFailure({ seq: args.seq, error: args.error, ownerId: args.ownerId });
@@ -104,13 +104,18 @@ function rowToIntent(row: IntentDbRow): TimerIngressIntentRow {
104
104
  };
105
105
  }
106
106
 
107
+ const installedOn = new WeakSet<object>();
108
+
107
109
  /** Idempotent, crash-safe schema install (one transaction; re-running over existing data is a
108
110
  * no-op). Bootstrap DDL — it runs ONCE at startup as its own committed statement, never lazily
109
111
  * inside an op, so a rolled-back op can never undo the schema it ran against (§4 allowlist). */
110
112
  export function installTimerIngressSchema(
111
- options: { requestKeyMigrationFaults?: TimerIngressRequestKeyMigrationFaults } = {},
113
+ options: { requestKeyMigrationFaults?: TimerIngressRequestKeyMigrationFaults; ifNotInstalled?: boolean } = {},
112
114
  ): void {
113
115
  const db = getDb();
116
+ // #1870 S7: `startServer` installs it before bind (the boot exception); `createTimerIngress` passes
117
+ // `ifNotInstalled` so its later call runs no DDL on the main connection. Explicit calls always install.
118
+ if (options.ifNotInstalled && installedOn.has(db)) return;
114
119
  db.transaction(() => {
115
120
  // AUTOINCREMENT is deliberate: `seq` must be monotone and NEVER reused (the watermark's meaning
116
121
  // depends on it — and it doubles as the per-timer apply FENCE), even if settled rows are pruned.
@@ -165,6 +170,7 @@ export function installTimerIngressSchema(
165
170
  db.run("INSERT OR IGNORE INTO timer_ingress_watermark (id, applied_seq, updated_at) VALUES (1, 0, 0)");
166
171
  options.requestKeyMigrationFaults?.beforeCommit?.();
167
172
  }).immediate();
173
+ installedOn.add(db);
168
174
  }
169
175
 
170
176
  // ── Reads ───────────────────────────────────────────────────────────────────────────────────────
@@ -39,14 +39,17 @@ import {
39
39
  export const TIMER_OPERATOR_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
40
40
  // --- Dedup-by-construction: idempotent, no key required. ------------------------------------------
41
41
  "timerOperator.acceptCommand": {
42
+ offLoop: true,
42
43
  idempotent: true,
43
44
  fn: (args: AcceptAuditedCommandArgs, ctx: WriterOpContext) => acceptAuditedCommandBody(args, ctx.now),
44
45
  },
45
46
  "timerOperator.ensureOperation": {
47
+ offLoop: true,
46
48
  idempotent: true,
47
49
  fn: (args: Omit<EnsureOperationArgs, "now">, ctx: WriterOpContext) => ensureOperationBody({ ...args, now: ctx.now }),
48
50
  },
49
51
  "timerOperator.resetTerminal": {
52
+ offLoop: true,
50
53
  // An occupied terminal slot deletes a row; replaying its body with `acceptAlreadyCleared: false`
51
54
  // throws. The facade therefore supplies a durable request-scoped key for retry replay.
52
55
  idempotent: false,
@@ -54,48 +57,59 @@ export const TIMER_OPERATOR_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> =
54
57
  resetTerminalOperationBody(args.requestId, args.acceptAlreadyCleared),
55
58
  },
56
59
  "timerOperator.finishAbort": {
60
+ offLoop: true,
57
61
  idempotent: true,
58
62
  fn: (_args: Record<string, never> | undefined, ctx: WriterOpContext) => finishAbortBody(ctx.now),
59
63
  },
60
64
  "timerOperator.pinTransferLeg": {
65
+ offLoop: true,
61
66
  idempotent: true,
62
67
  fn: (args: Omit<PinTransferLegArgs, "now">, ctx: WriterOpContext) => pinTransferLegBody({ ...args, now: ctx.now }),
63
68
  },
64
69
  "timerOperator.finishTransfer": {
70
+ offLoop: true,
65
71
  idempotent: true,
66
72
  fn: (args: Omit<FinishTransferArgs, "now">, ctx: WriterOpContext) => finishTransferBody({ ...args, now: ctx.now }),
67
73
  },
68
74
 
69
75
  // --- No in-body dedup guard: idempotent false, facade supplies a per-call key. --------------------
70
76
  "timerOperator.claimOperation": {
77
+ offLoop: true,
71
78
  idempotent: false,
72
79
  fn: (args: Omit<ClaimOperationArgs, "now">, ctx: WriterOpContext) => claimOperationBody({ ...args, now: ctx.now }),
73
80
  },
74
81
  "timerOperator.commitBackfillBatch": {
82
+ offLoop: true,
75
83
  idempotent: false,
76
84
  fn: (args: Omit<CommitBackfillBatchArgs, "at">, ctx: WriterOpContext) => commitBackfillBatchBody({ ...args, at: ctx.now }),
77
85
  },
78
86
  "timerOperator.epochRebase": {
87
+ offLoop: true,
79
88
  idempotent: false,
80
89
  fn: (args: Omit<EpochRebaseArgs, "at">, ctx: WriterOpContext) => epochRebaseBody({ ...args, at: ctx.now }),
81
90
  },
82
91
  "timerOperator.commitBootstrapBatch": {
92
+ offLoop: true,
83
93
  idempotent: false,
84
94
  fn: (args: Omit<CommitBootstrapBatchArgs, "at">, ctx: WriterOpContext) => commitBootstrapBatchBody({ ...args, at: ctx.now }),
85
95
  },
86
96
  "timerOperator.completeStage9": {
97
+ offLoop: true,
87
98
  idempotent: false,
88
99
  fn: (args: Omit<CompleteStage9Args, "at">, ctx: WriterOpContext) => completeStage9Body({ ...args, at: ctx.now }),
89
100
  },
90
101
  "timerOperator.beginAbort": {
102
+ offLoop: true,
91
103
  idempotent: false,
92
104
  fn: (args: Omit<BeginAbortArgs, "now">, ctx: WriterOpContext) => beginAbortBody({ ...args, now: ctx.now }),
93
105
  },
94
106
  "timerOperator.recordG1Transfer": {
107
+ offLoop: true,
95
108
  idempotent: false,
96
109
  fn: (args: Omit<RecordG1TransferArgs, "now">, ctx: WriterOpContext) => recordG1TransferBody({ ...args, now: ctx.now }),
97
110
  },
98
111
  "timerOperator.commitTransferBatch": {
112
+ offLoop: true,
99
113
  idempotent: false,
100
114
  fn: (args: Omit<CommitTransferBatchArgs, "at">, ctx: WriterOpContext) => commitTransferBatchBody({ ...args, at: ctx.now }),
101
115
  },
@@ -21,18 +21,22 @@ function withNow(args: TransferArgs, ctx: WriterOpContext): TimerMessageTransfer
21
21
  * the same durable journal row or is refused; it cannot move ownership twice. */
22
22
  export const TIMER_TRANSFER_WRITE_OPS = {
23
23
  "timers.transfer.prepareForward": {
24
+ offLoop: true,
24
25
  idempotent: true,
25
26
  fn: (args: TransferArgs, ctx: WriterOpContext) => prepareForwardTimerMessageTransfer(withNow(args, ctx)),
26
27
  },
27
28
  "timers.transfer.prepareReverse": {
29
+ offLoop: true,
28
30
  idempotent: true,
29
31
  fn: (args: TransferArgs, ctx: WriterOpContext) => prepareReverseTimerMessageTransfer(withNow(args, ctx)),
30
32
  },
31
33
  "timers.transfer.commitForward": {
34
+ offLoop: true,
32
35
  idempotent: true,
33
36
  fn: (args: TransferArgs, ctx: WriterOpContext) => commitForwardTimerMessageTransfer(withNow(args, ctx)),
34
37
  },
35
38
  "timers.transfer.commitReverse": {
39
+ offLoop: true,
36
40
  idempotent: true,
37
41
  fn: (args: TransferArgs, ctx: WriterOpContext) => commitReverseTimerMessageTransfer(withNow(args, ctx)),
38
42
  },
@@ -136,6 +136,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
136
136
  * `row.id` as that key: it is unique per logical create by construction and stable across a retry.
137
137
  */
138
138
  "timers.create": {
139
+ offLoop: true,
139
140
  idempotent: false,
140
141
  fn: (args: CreateTimerOpArgs, ctx: WriterOpContext) =>
141
142
  insertScheduledTimerWithinQuota(args.row, args.caps, ctx.now, QUOTA_FAULTS, args.route ?? "direct"),
@@ -143,6 +144,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
143
144
 
144
145
  /** CAS claim of a due timer. Re-applying either re-wins the same lease or returns null — idempotent. */
145
146
  "timers.claim": {
147
+ offLoop: true,
146
148
  idempotent: true,
147
149
  fn: (args: ClaimTimerOpArgs, ctx: WriterOpContext) =>
148
150
  claimScheduledTimer(args.id, args.owner, args.now ?? ctx.now, args.leaseMs),
@@ -155,6 +157,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
155
157
  * matching replay writes the identical row.
156
158
  */
157
159
  "timers.completeRun": {
160
+ offLoop: true,
158
161
  idempotent: true,
159
162
  fn: (args: CompleteTimerRunOpArgs, ctx: WriterOpContext) =>
160
163
  completeScheduledTimerRun({ timer: args.timer, messageId: args.messageId, now: args.now ?? ctx.now }),
@@ -162,6 +165,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
162
165
 
163
166
  /** Same claim-predicate shape as `completeRun`, so a replay is a no-op — idempotent. */
164
167
  "timers.recordFailure": {
168
+ offLoop: true,
165
169
  idempotent: true,
166
170
  fn: (args: RecordTimerFailureOpArgs, ctx: WriterOpContext) =>
167
171
  recordScheduledTimerFailure({
@@ -175,6 +179,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
175
179
 
176
180
  /** `WHERE enabled = 1` makes the second cancel a no-op that returns the same row — idempotent. */
177
181
  "timers.cancel": {
182
+ offLoop: true,
178
183
  idempotent: true,
179
184
  fn: (args: { id: string; route?: TimerMessageMutationRoute }) =>
180
185
  cancelScheduledTimer(args.id, args.route ?? "direct"),
@@ -182,6 +187,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
182
187
 
183
188
  /** Unconditional UPDATE to a fixed end state — re-applying reaches the same state. */
184
189
  "timers.disable": {
190
+ offLoop: true,
185
191
  idempotent: true,
186
192
  fn: (args: { id: string; reason: string }) => disableScheduledTimer(args.id, args.reason),
187
193
  },
@@ -189,6 +195,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
189
195
  /** UPDATE to a caller-supplied absolute `nextRunAt` — replaying writes the same instant, so it is
190
196
  * the same end state. Idempotent. */
191
197
  "timers.deferRun": {
198
+ offLoop: true,
192
199
  idempotent: true,
193
200
  fn: (args: { id: string; nextRunAt: number }) => deferScheduledTimerRun(args.id, args.nextRunAt),
194
201
  },
@@ -196,6 +203,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
196
203
  /** Read-modify-write of the command JSON, folded into the op. Writing the same commandId twice is
197
204
  * the same end state — idempotent. */
198
205
  "timers.setCommandId": {
206
+ offLoop: true,
199
207
  idempotent: true,
200
208
  fn: (args: { timerId: string; commandId: string }) => setScheduledTimerCommandId(args.timerId, args.commandId),
201
209
  },
@@ -203,6 +211,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
203
211
  /** Read-modify-write merge of a command patch, folded into the op. Re-merging the same patch onto
204
212
  * the already-merged row is the same end state — idempotent. */
205
213
  "timers.updateCommand": {
214
+ offLoop: true,
206
215
  idempotent: true,
207
216
  fn: (args: UpdateTimerCommandOpArgs) => updateScheduledTimerCommand(args.timerId, args.patch),
208
217
  },
@@ -214,6 +223,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
214
223
  * decide the same run twice.
215
224
  */
216
225
  "timers.decideFire": {
226
+ offLoop: true,
217
227
  idempotent: true,
218
228
  fn: (args: { input: Omit<DecideLegacyTimerMessageFireInput, "now">; now?: number }, ctx: WriterOpContext) =>
219
229
  decideLegacyTimerMessageFire({ ...args.input, now: args.now ?? ctx.now }, FIRE_DECISION_FAULTS),
@@ -221,6 +231,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
221
231
 
222
232
  /** Returns `already-sent` on replay rather than re-marking — idempotent. */
223
233
  "timers.markFireSent": {
234
+ offLoop: true,
224
235
  idempotent: true,
225
236
  fn: (args: { timerId: string; run: number; messageId: number }, ctx: WriterOpContext) =>
226
237
  markTimerMessageFireSent(args.timerId, args.run, args.messageId, ctx.now),
@@ -232,6 +243,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
232
243
  * therefore requires a key; the facade passes one derived from the failing attempt.
233
244
  */
234
245
  "timers.recordFireSendFailure": {
246
+ offLoop: true,
235
247
  idempotent: false,
236
248
  fn: (args: RecordFireSendFailureOpArgs, ctx: WriterOpContext) =>
237
249
  recordTimerMessageFireSendFailure({
@@ -246,6 +258,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
246
258
 
247
259
  /** DELETE of a transient failure record — re-applying deletes nothing. Idempotent. */
248
260
  "timers.clearFireSendFailure": {
261
+ offLoop: true,
249
262
  idempotent: true,
250
263
  fn: (args: { timerId: string; run: number }) => {
251
264
  clearTimerMessageFireSendFailure(args.timerId, args.run);
@@ -255,6 +268,7 @@ export const TIMER_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.fr
255
268
 
256
269
  /** Monotone fence advance (`applied_fence < fence` guard) — re-applying is a no-op. Idempotent. */
257
270
  "timers.markAuthorityApplied": {
271
+ offLoop: true,
258
272
  idempotent: true,
259
273
  fn: (args: { timerId: string; fence: number }, ctx: WriterOpContext) => {
260
274
  markLegacyMessageAuthorityApplied(args.timerId, args.fence, ctx.now);