@humwick/server 0.130.2 → 0.130.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (587) hide show
  1. package/bundled/prompts/manifest.json +1 -0
  2. package/bundled/prompts/task.hydration.md +1 -1
  3. package/bundled/roles/builder.md +1 -1
  4. package/docs/guides/sub-coordinators.md +12 -5
  5. package/docs/guides/tools-coordination.md +1 -0
  6. package/docs/guides/workflows.md +16 -0
  7. package/docs/openapi.json +175 -4
  8. package/package.json +8 -7
  9. package/public/assets/{MessageBubble-BWLqMGKG.js → MessageBubble-BOkmEvgZ.js} +2 -2
  10. package/public/assets/{PlanGraphPanel-3TlIQpjk.js → PlanGraphPanel-BkaDJp0r.js} +2 -2
  11. package/public/assets/{TraceView-Wimm0Czv.js → TraceView-B99Uu2yT.js} +2 -2
  12. package/public/assets/{VaultDecisionCard-Bau_vNax.js → VaultDecisionCard-DUIfTTFF.js} +2 -2
  13. package/public/assets/{activity-CrSurn-r.js → activity-ZIX0N1El.js} +2 -2
  14. package/public/assets/{agent-lineage-CknAJKcb.js → agent-lineage-kq9TPYFw.js} +2 -2
  15. package/public/assets/agent-liveness-DSQY1Aa_.js +2 -0
  16. package/public/assets/{agent-name-DKbiIhBo.js → agent-name-C_7F4DFR.js} +2 -2
  17. package/public/assets/{agent-profiles-DwF6EQ4R.js → agent-profiles-BNOcrGcY.js} +2 -2
  18. package/public/assets/{agent-project-badge-Diz4Sjjb.js → agent-project-badge-B6yAh70a.js} +2 -2
  19. package/public/assets/{agent-tools-CRhpRQza.js → agent-tools-CcrhmOzF.js} +2 -2
  20. package/public/assets/{agent-type-icon-LrUEU3lB.js → agent-type-icon-CXSLrKiq.js} +2 -2
  21. package/public/assets/{agents-B-v66C-4.js → agents-DIZG97_X.js} +2 -2
  22. package/public/assets/{analytics-BqCb-LKi.js → analytics-rasmoPjR.js} +2 -2
  23. package/public/assets/{artifact-viewer-kf4pGuAR.js → artifact-viewer-tBpTrEPb.js} +2 -2
  24. package/public/assets/{artifacts-DJevXcvg.js → artifacts-BNkrynP1.js} +2 -2
  25. package/public/assets/{artifacts-D32zyw4Y.js → artifacts-Bjq-ZyBA.js} +2 -2
  26. package/public/assets/{automation-BpRajx_5.js → automation-Cx422L3L.js} +2 -2
  27. package/public/assets/{backlog-D_UjTdzX.js → backlog-RuFQ8PUu.js} +2 -2
  28. package/public/assets/{busy-health-N3VEh4ao.js → busy-health-BvNQ9g5x.js} +2 -2
  29. package/public/assets/{channels-9jopungq.js → channels-DNbKAS0I.js} +2 -2
  30. package/public/assets/{chat-CSXrTB3C.js → chat-Bp3Fx73h.js} +3 -3
  31. package/public/assets/{code-preview-p1NnmqK0.js → code-preview-KIRZA6cz.js} +2 -2
  32. package/public/assets/{connectors-CZdqlDNL.js → connectors-k7vO4vvc.js} +2 -2
  33. package/public/assets/{cosmos-BgdsE4dG.js → cosmos-BuVoq1aw.js} +2 -2
  34. package/public/assets/{crate-agent-30aXTKva.js → crate-agent-irqlt4NH.js} +2 -2
  35. package/public/assets/{dependency-graph-B_jvw2GE.js → dependency-graph-D_rurNBz.js} +2 -2
  36. package/public/assets/{deploys-DoN6bAb5.js → deploys-yzZqIpxE.js} +2 -2
  37. package/public/assets/{display-BrKWFGJz.js → display-BemJG1f_.js} +2 -2
  38. package/public/assets/{files-D6XEu8Ng.js → files-DXWcrxOz.js} +2 -2
  39. package/public/assets/{fleet-C7jQWbw2.js → fleet-TEuOCZXE.js} +2 -2
  40. package/public/assets/{fleet-metrics-BiDs3MLx.js → fleet-metrics-BGoDAFI-.js} +2 -2
  41. package/public/assets/{formatted-body-impl-DyG0WsQS.js → formatted-body-impl-BB9eq9nN.js} +2 -2
  42. package/public/assets/{formatted-body-CQBwc1fb.js → formatted-body-qy2nTD_1.js} +3 -3
  43. package/public/assets/{host-providers-dialog-DKaW31dN.js → host-providers-dialog-BGPHxuwT.js} +2 -2
  44. package/public/assets/{index-CRHep3M9.js → index-BC5XZc0z.js} +5 -5
  45. package/public/assets/{index-MYAIwYTX.css → index-D8Rp9pz2.css} +1 -1
  46. package/public/assets/{insights-CzrtkpOE.js → insights-C-KQG0h-.js} +2 -2
  47. package/public/assets/{integrations-aVsUmigt.js → integrations-ClPRPyKU.js} +2 -2
  48. package/public/assets/{issue-ref-chip-BnSlM_95.js → issue-ref-chip-DbH8J9Ak.js} +2 -2
  49. package/public/assets/{log-viewer-qe7Ee47Z.js → log-viewer-PH4zVn24.js} +2 -2
  50. package/public/assets/{maintenance-NfjqFZAk.js → maintenance-5lV43uTL.js} +2 -2
  51. package/public/assets/{managed-agents-BiH0HgaE.js → managed-agents-BdgU35aX.js} +2 -2
  52. package/public/assets/{markdown-preview-BixaT3Xr.js → markdown-preview-DNT4OUwQ.js} +3 -3
  53. package/public/assets/{markdown-preview-impl-DNGlWIS3.js → markdown-preview-impl-CnCcPy0A.js} +2 -2
  54. package/public/assets/{memory-DNsCl6UX.js → memory--GgWH1gH.js} +2 -2
  55. package/public/assets/{messages-DopU1L1X.js → messages-CzFl_97O.js} +2 -2
  56. package/public/assets/{orchestrators-Cq7ckLQT.js → orchestrators-CVvJVkzv.js} +2 -2
  57. package/public/assets/overview-DsrZvE86.js +2 -0
  58. package/public/assets/{pairs-DE_m6rrE.js → pairs-D2d0d2py.js} +2 -2
  59. package/public/assets/{profile-icon-B3rfh77F.js → profile-icon-_SMuSbYF.js} +2 -2
  60. package/public/assets/{project-profile-instructions-DQWZE0kA.js → project-profile-instructions-Apn15y43.js} +2 -2
  61. package/public/assets/{projects-Br2Bd2h1.js → projects-D3B6kto5.js} +2 -2
  62. package/public/assets/{prompt-settings-BKvFsvGD.js → prompt-settings-DnPB6Ez6.js} +2 -2
  63. package/public/assets/provider-display-bWwiuedV.js +2 -0
  64. package/public/assets/{registry-Cw38_6gW.js → registry-D790cIhN.js} +2 -2
  65. package/public/assets/run-card-BCro-Odj.js +2 -0
  66. package/public/assets/{secrets-DxI-vzSq.js → secrets-CVXvfvbU.js} +2 -2
  67. package/public/assets/{service-access-settings-D9s_VJbd.js → service-access-settings-D3Xxqb9w.js} +2 -2
  68. package/public/assets/{settings-CKZTVGIx.js → settings-CoUC7Jci.js} +2 -2
  69. package/public/assets/shipping-D_Lp9n_J.js +2 -0
  70. package/public/assets/{stats-DyUBKyWI.js → stats-CXiNjB9S.js} +2 -2
  71. package/public/assets/{status-dot-DYqlEUhJ.js → status-dot-C703YIy1.js} +2 -2
  72. package/public/assets/store-BgDALGIX.js +9 -0
  73. package/public/assets/{tasks-Bh7Chr4-.js → tasks-DO0WpJCx.js} +2 -2
  74. package/public/assets/{teams-bowI7qVY.js → teams-FGkdi_tL.js} +2 -2
  75. package/public/assets/{terminal-viewer-BgVLHdsU.js → terminal-viewer-Dh8dSC7e.js} +3 -3
  76. package/public/assets/{terminal-viewer-impl-Dz22UPy6.js → terminal-viewer-impl-B5Zriip3.js} +2 -2
  77. package/public/assets/{use-agent-terminal-DSQQ-iY7.js → use-agent-terminal-CRUVDvLC.js} +2 -2
  78. package/public/assets/{use-decisions-samMQhQf.js → use-decisions-CoaqRh4l.js} +2 -2
  79. package/public/assets/{use-project-list-59LheVP7.js → use-project-list-X-BgT53X.js} +2 -2
  80. package/public/assets/{use-selectors-BZa25xEB.js → use-selectors-0X_UZkk-.js} +2 -2
  81. package/public/assets/{user-avatar-DHxlvUvV.js → user-avatar-CkLhVBGu.js} +2 -2
  82. package/public/assets/{versioned-prompt-editor-BmED5DnJ.js → versioned-prompt-editor-BkTi7eim.js} +2 -2
  83. package/public/assets/{work-queue-CSvpuNLz.js → work-queue-DDOLEyKo.js} +2 -2
  84. package/public/assets/{workflow-marker-DAZcj5qt.js → workflow-marker-CzOsB8BM.js} +2 -2
  85. package/public/assets/{workflows-DdmxRSyg.js → workflows-DJilvmRM.js} +2 -2
  86. package/public/assets/{workspaces-BxeKfVuF.js → workspaces-CHcfbLR3.js} +2 -2
  87. package/public/cosmos.html +4 -4
  88. package/public/index.html +29 -29
  89. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  90. package/runner/src/adapters/claude-api.ts +1 -1
  91. package/runner/src/adapters/claude-headless.ts +1 -1
  92. package/runner/src/adapters/claude-launch-exit.ts +26 -0
  93. package/runner/src/adapters/claude-session-probe.ts +1 -1
  94. package/runner/src/adapters/claude-tmux.ts +38 -10
  95. package/runner/src/adapters/claude.ts +28 -75
  96. package/runner/src/adapters/provider-quota-harvest.ts +20 -71
  97. package/runner/src/agent-runner/lifecycle-milestones.ts +2 -2
  98. package/runner/src/agent-runner/liveness-exchange.ts +104 -0
  99. package/runner/src/claude-system-prompt.ts +49 -0
  100. package/runner/src/launch-assembly.ts +14 -10
  101. package/runner/src/profile-home.ts +1 -1
  102. package/runner/src/project-instructions.ts +2 -2
  103. package/runner/src/provider-command.ts +18 -0
  104. package/runner/src/provider-setup.ts +3 -2
  105. package/runner/src/runner-core.ts +14 -15
  106. package/runner/src/runner-helpers.ts +0 -8
  107. package/runner/src/session-scratch.ts +6 -2
  108. package/runner/test-support/provisioning-context.ts +4 -2
  109. package/runner/test-support/runner-harness.ts +2 -1
  110. package/scripts/steward-policy-cleanup.ts +2 -2
  111. package/src/agent-authz.ts +2 -2
  112. package/src/agent-drive-arbiter.ts +3 -8
  113. package/src/agent-exit-cleanup-async.ts +2 -2
  114. package/src/agent-exit-cleanup.ts +23 -19
  115. package/src/agent-lifecycle-events.ts +112 -85
  116. package/src/agent-ref.ts +3 -2
  117. package/src/agent-status-events.ts +43 -14
  118. package/src/auth/login-link.ts +35 -0
  119. package/src/auth/password.ts +4 -3
  120. package/src/auth/session-refresh.ts +1 -1
  121. package/src/auth/session.test-fixture.ts +2 -2
  122. package/src/auth/session.ts +7 -7
  123. package/src/automations/crud.ts +8 -81
  124. package/src/automations/dispatch.ts +33 -58
  125. package/src/automations/index.ts +1 -1
  126. package/src/automations/persistence.ts +65 -0
  127. package/src/automations/precheck.ts +18 -16
  128. package/src/automations/read-async.ts +11 -0
  129. package/src/automations/reconcile.ts +30 -39
  130. package/src/automations/storage.ts +33 -10
  131. package/src/automations/write-async.ts +33 -0
  132. package/src/branch-land-notice.ts +3 -3
  133. package/src/branch-landed.ts +25 -23
  134. package/src/bus.ts +21 -19
  135. package/src/cd-deploy-notify.ts +2 -2
  136. package/src/ci/notifications.ts +4 -4
  137. package/src/ci/root-missing.ts +3 -3
  138. package/src/ci/transition-notify.ts +5 -5
  139. package/src/claude-app/oauth-http.ts +3 -3
  140. package/src/cli/index.ts +7 -1
  141. package/src/cli/init.ts +53 -0
  142. package/src/cli/orchestrator.ts +1 -7
  143. package/src/cli/setup.ts +6 -4
  144. package/src/cli/workflow.ts +12 -3
  145. package/src/commands-db.ts +4 -4
  146. package/src/commands-write.ts +8 -0
  147. package/src/compaction-watch.ts +10 -8
  148. package/src/config-store-write.ts +2 -2
  149. package/src/config.ts +4 -11
  150. package/src/connectors.ts +10 -8
  151. package/src/context-advisory.ts +22 -21
  152. package/src/db/agent-action-write.ts +36 -0
  153. package/src/db/agent-control-async.ts +9 -0
  154. package/src/db/agent-control-write.ts +49 -0
  155. package/src/db/agent-effort-observe.ts +22 -0
  156. package/src/db/agent-meta.ts +14 -3
  157. package/src/db/agents-async.ts +8 -5
  158. package/src/db/agents.ts +15 -12
  159. package/src/db/auth-identities.ts +2 -2
  160. package/src/db/automation-read-ops.ts +11 -0
  161. package/src/db/automation-write-ops.ts +46 -0
  162. package/src/db/blackboard.ts +2 -2
  163. package/src/db/browser-session-write.ts +32 -0
  164. package/src/db/bus-outbox-write-ops.ts +3 -1
  165. package/src/db/checkout-refresh-schema.ts +47 -0
  166. package/src/db/checkout-refresh.ts +5 -5
  167. package/src/db/command-agent-profile-backfill.ts +39 -0
  168. package/src/db/command-agent-profiles.ts +5 -1
  169. package/src/db/command-retention.ts +126 -0
  170. package/src/db/commands-planner-stats.test-fixture.ts +8 -0
  171. package/src/db/commands-read-ops.ts +16 -0
  172. package/src/db/connection.ts +1 -182
  173. package/src/db/context-carry-write.ts +74 -0
  174. package/src/db/continuations.ts +4 -4
  175. package/src/db/db-error-registry.ts +12 -0
  176. package/src/db/gateway.ts +26 -133
  177. package/src/db/history-retention-ops.ts +13 -0
  178. package/src/db/inbox-async.ts +2 -2
  179. package/src/db/inbox-read-ops.ts +1 -1
  180. package/src/db/inbox.ts +9 -3
  181. package/src/db/issue-associations.ts +2 -2
  182. package/src/db/issue-refs-async.ts +4 -1
  183. package/src/db/issue-refs-read-ops.ts +6 -0
  184. package/src/db/issue-refs.ts +8 -8
  185. package/src/db/kernel-event-log-schema.ts +2 -0
  186. package/src/db/land-boundaries.ts +51 -5
  187. package/src/db/land-gate-attestations.ts +2 -2
  188. package/src/db/landing-receipts.ts +2 -2
  189. package/src/db/lifecycle-context-write.ts +5 -0
  190. package/src/db/lifecycle-tail-async.ts +76 -0
  191. package/src/db/lifecycle-tail-write-ops.ts +62 -0
  192. package/src/db/lifecycle-write-window.ts +2 -1
  193. package/src/db/lineage-pair-dedupe-sql.ts +3 -3
  194. package/src/db/main-connection-mode.ts +11 -0
  195. package/src/db/main-connection-writes.ts +20 -2
  196. package/src/db/maintenance-job-runs-async.ts +2 -2
  197. package/src/db/maintenance-job-write-ops.ts +1 -1
  198. package/src/db/maintenance-pauses.ts +2 -2
  199. package/src/db/message-send.ts +4 -0
  200. package/src/db/messages.ts +31 -25
  201. package/src/db/migrations.ts +18 -1
  202. package/src/db/notification-write-ops.ts +19 -0
  203. package/src/db/operator-answer-write.ts +19 -0
  204. package/src/db/orchestrators.ts +10 -4
  205. package/src/db/pairs-async.ts +22 -1
  206. package/src/db/pairs-write-ops.ts +8 -3
  207. package/src/db/pairs.ts +41 -48
  208. package/src/db/plan-dispatch-schema.ts +5 -1
  209. package/src/db/plan-ready-notices.ts +43 -0
  210. package/src/db/plan-tools-include-migration.ts +36 -0
  211. package/src/db/plans-write-ops.ts +2 -0
  212. package/src/db/presence-buffer.ts +5 -0
  213. package/src/db/presence-loop.ts +18 -23
  214. package/src/db/presence-write.ts +0 -6
  215. package/src/db/project-entries-async.ts +14 -0
  216. package/src/db/project-entries-read-ops.ts +5 -1
  217. package/src/db/project-entries-write-ops.ts +8 -0
  218. package/src/db/project-entries.ts +15 -12
  219. package/src/db/project-journal.ts +2 -2
  220. package/src/db/project-trust-approvals.ts +2 -2
  221. package/src/db/prompt-templates.ts +4 -4
  222. package/src/db/read-op-marshaling-fixtures.ts +18 -0
  223. package/src/db/read-ops.ts +4 -0
  224. package/src/db/retention.ts +13 -2
  225. package/src/db/review-publish-write.ts +8 -0
  226. package/src/db/runtime-admin-async.ts +19 -0
  227. package/src/db/runtime-admin-write-ops.ts +36 -0
  228. package/src/db/runtime-tail-async.ts +288 -0
  229. package/src/db/runtime-tail-write-ops.ts +97 -0
  230. package/src/db/schema.ts +4 -1
  231. package/src/db/spawn-budget.ts +5 -0
  232. package/src/db/spawn-delegation.ts +3 -12
  233. package/src/db/sql-audit-indexes.ts +3 -0
  234. package/src/db/stats-async.ts +5 -3
  235. package/src/db/task-entity.ts +5 -4
  236. package/src/db/team-claim-policy.ts +60 -0
  237. package/src/db/teams-async.ts +4 -0
  238. package/src/db/teams-write-ops.ts +4 -0
  239. package/src/db/terminal-events.ts +15 -0
  240. package/src/db/timer-cutover-bodies.ts +292 -0
  241. package/src/db/timer-cutover-core.ts +37 -278
  242. package/src/db/timer-cutover-state.ts +40 -0
  243. package/src/db/timer-cutover-write-ops.ts +1 -40
  244. package/src/db/token-db-write-ops.ts +2 -0
  245. package/src/db/token-standing-async.ts +12 -0
  246. package/src/db/token-standing.ts +6 -9
  247. package/src/db/token-usage-retention.ts +33 -0
  248. package/src/db/token-usage-write-ops.ts +1 -1
  249. package/src/db/token-usage.ts +4 -1
  250. package/src/db/user-invites-async.ts +13 -0
  251. package/src/db/user-invites.ts +3 -3
  252. package/src/db/users.ts +14 -6
  253. package/src/db/workflow-execution.ts +3 -3
  254. package/src/db/workspaces.ts +8 -5
  255. package/src/db/write-ops.ts +31 -12
  256. package/src/db/writer-fault-seam.ts +2 -9
  257. package/src/db/writer-fault-test-ops.ts +18 -0
  258. package/src/db/writer-gateway.ts +42 -46
  259. package/src/dev.ts +12 -16
  260. package/src/event-loop-lag.ts +4 -2
  261. package/src/event-loop-yield.ts +2 -2
  262. package/src/events.ts +10 -8
  263. package/src/host-provider-store.ts +5 -4
  264. package/src/hub-metrics-report.ts +5 -1
  265. package/src/hub-metrics.ts +38 -18
  266. package/src/init.ts +299 -0
  267. package/src/injection-sweep-pacing.ts +6 -8
  268. package/src/invite-link.ts +16 -9
  269. package/src/land-gate-notifications.ts +11 -10
  270. package/src/land-gate-queue-progress.ts +3 -3
  271. package/src/land-settle-dispatch.ts +52 -92
  272. package/src/lifecycle-context-pressure.ts +11 -10
  273. package/src/lifecycle-manager.ts +104 -98
  274. package/src/maintenance/breaker.ts +18 -16
  275. package/src/maintenance/checkout-refresh.ts +18 -1
  276. package/src/maintenance/critical-job-watchdog.ts +3 -3
  277. package/src/maintenance/events.ts +3 -3
  278. package/src/maintenance/history-retention-job.ts +30 -0
  279. package/src/maintenance/jobs.ts +29 -67
  280. package/src/maintenance/long-running-work.ts +3 -1
  281. package/src/maintenance/offline-agent-prune-job.ts +28 -0
  282. package/src/maintenance/orphaned-sessions.ts +5 -4
  283. package/src/maintenance/pauses.ts +4 -10
  284. package/src/maintenance/plan-ready-notice-job.ts +71 -0
  285. package/src/maintenance/scheduler.ts +18 -13
  286. package/src/maintenance/stalled-delivery-pager.ts +4 -4
  287. package/src/maintenance/teams.ts +19 -30
  288. package/src/maintenance/workspaces.ts +17 -27
  289. package/src/managed-policy-escalation.ts +7 -7
  290. package/src/managed-policy.ts +3 -3
  291. package/src/managed-provider-gate.ts +5 -4
  292. package/src/managed-spawn-reservation.ts +5 -4
  293. package/src/mcp/index.ts +8 -8
  294. package/src/mcp/output-descriptions.ts +1 -1
  295. package/src/mcp/presentation.ts +2 -1
  296. package/src/mcp/runtime-output.ts +6 -1
  297. package/src/mcp/self-care-visibility.ts +2 -2
  298. package/src/mcp/spawn-targets-matrix.ts +10 -1
  299. package/src/mcp/tool-groups.ts +3 -2
  300. package/src/mcp/tool-roles.ts +81 -0
  301. package/src/mcp/tools-workspace.ts +1 -1
  302. package/src/mcp-blackboard-tools.ts +6 -5
  303. package/src/mcp-decision-tools.ts +7 -7
  304. package/src/mcp-egress-tools.ts +10 -9
  305. package/src/mcp-issue-activity-tools.ts +2 -2
  306. package/src/mcp-issue-association-tools.ts +4 -4
  307. package/src/mcp-issue-ref-tools.ts +3 -3
  308. package/src/mcp-merge-tools.ts +5 -5
  309. package/src/mcp-notification-subscription-tools.ts +14 -15
  310. package/src/mcp-output-levels.test-fixture.ts +1 -1
  311. package/src/mcp-profile-trust-tools.ts +1 -1
  312. package/src/mcp-project-journal-tools.ts +4 -12
  313. package/src/mcp-project-tools.ts +17 -18
  314. package/src/mcp-routing-policy-tools.ts +3 -2
  315. package/src/mcp-team-tools.ts +6 -6
  316. package/src/mcp-workflow-tools.ts +5 -3
  317. package/src/memory-area-injection.ts +13 -12
  318. package/src/memory-boundary-capture.ts +6 -6
  319. package/src/memory-injection.ts +45 -43
  320. package/src/merge-pause-maintenance.ts +5 -4
  321. package/src/notification-router.ts +44 -17
  322. package/src/notify.ts +5 -5
  323. package/src/observed-reset-refresh.ts +4 -4
  324. package/src/operator-alarm.ts +2 -5
  325. package/src/project-ingest.ts +6 -6
  326. package/src/project-instructions.ts +8 -1
  327. package/src/project-trust.ts +12 -11
  328. package/src/prompt-resolver.ts +3 -3
  329. package/src/provider-quota-async.ts +5 -7
  330. package/src/provider-timeline-event.ts +4 -7
  331. package/src/quota-band-delivery.ts +2 -2
  332. package/src/recipe-runner.ts +4 -3
  333. package/src/retention-config-store.ts +4 -3
  334. package/src/reviewer-pipeline.ts +11 -10
  335. package/src/routes/_route.ts +4 -4
  336. package/src/routes/_shared.ts +13 -12
  337. package/src/routes/agent-effort.ts +5 -5
  338. package/src/routes/agent-heartbeat-quota.ts +23 -0
  339. package/src/routes/agent-keep.ts +1 -1
  340. package/src/routes/agent-reporting.ts +1 -1
  341. package/src/routes/agent-self-effort.ts +5 -5
  342. package/src/routes/agent-sessions.ts +24 -24
  343. package/src/routes/agents-spawn.ts +1 -1
  344. package/src/routes/agents.ts +17 -119
  345. package/src/routes/auth.ts +44 -17
  346. package/src/routes/authority-audit.ts +2 -2
  347. package/src/routes/automations.ts +6 -5
  348. package/src/routes/channel-binding-delete.ts +1 -1
  349. package/src/routes/channel-output.ts +3 -3
  350. package/src/routes/claude-api-budget.ts +5 -5
  351. package/src/routes/commands-listing.ts +1 -1
  352. package/src/routes/commands.ts +29 -27
  353. package/src/routes/context-resets.ts +2 -2
  354. package/src/routes/coordinator-teams.ts +1 -1
  355. package/src/routes/decisions.ts +2 -2
  356. package/src/routes/host-providers.ts +18 -0
  357. package/src/routes/host-secret-files.ts +1 -1
  358. package/src/routes/hosts.ts +11 -11
  359. package/src/routes/inbox.ts +3 -2
  360. package/src/routes/index.ts +7 -3
  361. package/src/routes/integrations.ts +9 -7
  362. package/src/routes/issue-activity.ts +2 -2
  363. package/src/routes/issue-associations.ts +4 -4
  364. package/src/routes/issue-refs.ts +3 -3
  365. package/src/routes/kernel-timers-operator.ts +1 -1
  366. package/src/routes/memory.ts +1 -1
  367. package/src/routes/merges.ts +11 -11
  368. package/src/routes/messages.ts +23 -17
  369. package/src/routes/orchestrator-bootstrap.ts +3 -1
  370. package/src/routes/orchestrator-host-registration.ts +2 -0
  371. package/src/routes/orchestrator-host-utilization.ts +2 -2
  372. package/src/routes/orchestrator-proxy.ts +2 -2
  373. package/src/routes/orchestrator.ts +27 -18
  374. package/src/routes/pairs.ts +8 -8
  375. package/src/routes/project-profile-instructions.ts +1 -1
  376. package/src/routes/project-trust.ts +1 -1
  377. package/src/routes/projects.ts +16 -16
  378. package/src/routes/prompt-templates.ts +12 -12
  379. package/src/routes/provider-quotas.ts +10 -10
  380. package/src/routes/run-command.ts +1 -1
  381. package/src/routes/secrets-backup.ts +4 -5
  382. package/src/routes/secrets-import.ts +3 -4
  383. package/src/routes/secrets-vault.ts +12 -14
  384. package/src/routes/settings.ts +1 -1
  385. package/src/routes/shipping.ts +9 -1
  386. package/src/routes/spawn-policy.ts +8 -7
  387. package/src/routes/sse.ts +2 -2
  388. package/src/routes/team-templates.ts +7 -7
  389. package/src/routes/teams.ts +10 -10
  390. package/src/routes/tokens.ts +18 -17
  391. package/src/routes/users.ts +24 -24
  392. package/src/routes/viewer-read.ts +3 -3
  393. package/src/routes/web-tool-calls.ts +4 -4
  394. package/src/routes/workflows.ts +2 -2
  395. package/src/routes/workspaces.ts +17 -16
  396. package/src/routing-policy-push.ts +6 -6
  397. package/src/runtime-grant.ts +17 -11
  398. package/src/runtime-install.ts +30 -0
  399. package/src/runtime-tokens.ts +20 -28
  400. package/src/secrets-broker/config.ts +1 -1
  401. package/src/secrets-broker/service.ts +2 -2
  402. package/src/security.ts +3 -1
  403. package/src/self-resume-carry.ts +21 -110
  404. package/src/self-resume-supersede.ts +4 -3
  405. package/src/self-resume.ts +54 -66
  406. package/src/server-background.ts +1 -1
  407. package/src/server.ts +25 -26
  408. package/src/services/agent-action.ts +12 -31
  409. package/src/services/agent-effort.ts +7 -32
  410. package/src/services/agent-hang-watchdog-job.ts +2 -2
  411. package/src/services/agent-heartbeat.ts +166 -0
  412. package/src/services/agent-reporting.ts +1 -1
  413. package/src/services/auth-context.ts +6 -0
  414. package/src/services/browser-inventory.ts +3 -3
  415. package/src/services/browser-session-state.ts +3 -2
  416. package/src/services/browser-session.ts +21 -27
  417. package/src/services/bus-command-update.ts +9 -9
  418. package/src/services/checkout-refresh-state.ts +1 -0
  419. package/src/services/checkout-refresh.test-fixture.ts +3 -2
  420. package/src/services/command-host.ts +7 -2
  421. package/src/services/coordinator-digest.ts +3 -2
  422. package/src/services/dispatch-command.ts +12 -13
  423. package/src/services/effective-launch-driver.ts +14 -9
  424. package/src/services/host-changed-paths.test-fixture.ts +5 -6
  425. package/src/services/host-project-readiness.ts +2 -2
  426. package/src/services/host-providers.ts +1 -1
  427. package/src/services/host-utilization.ts +9 -9
  428. package/src/services/idle-agent-digest.ts +17 -12
  429. package/src/services/idle-epics.ts +4 -4
  430. package/src/services/interrupt-agent.ts +12 -9
  431. package/src/services/issue-activity.ts +3 -3
  432. package/src/services/issue-associations.ts +15 -18
  433. package/src/services/issue-lifecycle-comments.ts +3 -2
  434. package/src/services/issue-lifecycle.ts +14 -13
  435. package/src/services/issue-refs.ts +4 -5
  436. package/src/services/kernel-timers-cutover-chaos-worker.ts +1 -1
  437. package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
  438. package/src/services/kernel-timers-ingress.ts +3 -3
  439. package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
  440. package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
  441. package/src/services/kernel-timers-outbox.ts +1 -1
  442. package/src/services/kernel-timers-stage10-operator.ts +1 -1
  443. package/src/services/kernel-timers.ts +4 -3
  444. package/src/services/land-gate-placement.ts +11 -2
  445. package/src/services/land-issue-relations.ts +5 -4
  446. package/src/services/land-restart-retry.ts +7 -6
  447. package/src/services/landed-ancestry.ts +6 -5
  448. package/src/services/legacy-landing-host.ts +4 -1
  449. package/src/services/legacy-landing-recovery.ts +8 -6
  450. package/src/services/managed-running.ts +17 -17
  451. package/src/services/memory-areas.ts +3 -3
  452. package/src/services/merge-pause.ts +9 -15
  453. package/src/services/operator-decisions.ts +37 -41
  454. package/src/services/orchestrator-registration-recovery.ts +6 -2
  455. package/src/services/orchestrator-upgrade.ts +14 -12
  456. package/src/services/permission-decision.ts +4 -3
  457. package/src/services/plan-ci-notice.ts +1 -1
  458. package/src/services/plan-conformance.ts +4 -4
  459. package/src/services/plan-dispatch-policy.ts +10 -3
  460. package/src/services/plan-dispatch.ts +35 -14
  461. package/src/services/plan-graph.ts +39 -261
  462. package/src/services/plan-import.ts +1 -1
  463. package/src/services/plan-live-bindings.ts +8 -37
  464. package/src/services/plan-task-create.ts +1 -1
  465. package/src/services/plan-terminal-refs.ts +2 -12
  466. package/src/services/plan-wake-digest.ts +8 -3
  467. package/src/services/profile-trust.ts +2 -2
  468. package/src/services/project.ts +11 -10
  469. package/src/services/prompt-templates.ts +6 -5
  470. package/src/services/provider-api-key.ts +73 -0
  471. package/src/services/provider-hold-nudge.ts +9 -9
  472. package/src/services/provider-hold-record.ts +13 -12
  473. package/src/services/provider-state-audit.ts +19 -18
  474. package/src/services/provisioning-harness/driver.ts +1 -1
  475. package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
  476. package/src/services/provisioning-harness/lifecycle.ts +24 -22
  477. package/src/services/provisioning-harness/prepare.ts +16 -17
  478. package/src/services/rate-limit-resume.ts +10 -9
  479. package/src/services/red-main-auto-revert.ts +7 -5
  480. package/src/services/red-main-controller-deps.ts +20 -3
  481. package/src/services/red-main-incident.ts +3 -1
  482. package/src/services/red-main-quarantine.ts +2 -3
  483. package/src/services/register-agent.ts +22 -22
  484. package/src/services/restart-project-role.ts +1 -1
  485. package/src/services/restart-spawn.ts +17 -16
  486. package/src/services/review-publish.ts +7 -24
  487. package/src/services/review-ref-reconcile.ts +29 -38
  488. package/src/services/runtime-dependency-alert.ts +27 -0
  489. package/src/services/scheduler-command.ts +11 -9
  490. package/src/services/scheduler.ts +5 -5
  491. package/src/services/self-effort-grant.ts +5 -25
  492. package/src/services/send-message.ts +16 -33
  493. package/src/services/shutdown-agent.ts +17 -14
  494. package/src/services/spawn-agent.ts +12 -41
  495. package/src/services/spawn-chain-inspector.ts +1 -1
  496. package/src/services/spawn-cwd-placement.ts +21 -5
  497. package/src/services/spawn-issue-activity.ts +8 -6
  498. package/src/services/spawn-launch.ts +17 -1
  499. package/src/services/spawn-loss-escalation.ts +1 -1
  500. package/src/services/spawn-registration-watchdog.ts +2 -2
  501. package/src/services/spawn-repo-probe.ts +7 -5
  502. package/src/services/spawn-tree-cleanup.ts +7 -8
  503. package/src/services/steward-reaper.ts +4 -4
  504. package/src/services/task-context-hydration.ts +25 -1
  505. package/src/services/task-delivery.ts +20 -34
  506. package/src/services/task-entity.ts +40 -203
  507. package/src/services/task-issue-link.ts +13 -20
  508. package/src/services/task-issue-weave.ts +12 -11
  509. package/src/services/task-lifecycle.ts +27 -31
  510. package/src/services/task-repo-cwd.ts +37 -0
  511. package/src/services/task-review-hold.ts +4 -17
  512. package/src/services/task-semantic-events.ts +13 -11
  513. package/src/services/task-state-reconcile.ts +3 -4
  514. package/src/services/task-validation.ts +2 -2
  515. package/src/services/team-templates.ts +5 -5
  516. package/src/services/team.ts +31 -98
  517. package/src/services/terminal-provider-exit.ts +6 -5
  518. package/src/services/timer-message-fire-chaos-worker.ts +7 -3
  519. package/src/services/timer-message-fire-outbox.ts +1 -1
  520. package/src/services/transient-agent-reaper.ts +22 -19
  521. package/src/services/turn-reconcile-audit.ts +9 -9
  522. package/src/services/user-onboarding.ts +8 -6
  523. package/src/services/user-profile.ts +6 -9
  524. package/src/services/work-finality.ts +16 -4
  525. package/src/services/workflow-auto-start.ts +2 -1
  526. package/src/services/workflow-caller.ts +61 -4
  527. package/src/services/workflow-child-proof-worker.ts +1 -1
  528. package/src/services/workflow-control-proof-worker.ts +1 -1
  529. package/src/services/workflow-coordinator.ts +8 -2
  530. package/src/services/workflow-decisions.ts +5 -5
  531. package/src/services/workflow-deferred-cleanup.ts +1 -1
  532. package/src/services/workflow-effect-driver.ts +42 -19
  533. package/src/services/workflow-effects.ts +41 -27
  534. package/src/services/workflow-pins.ts +7 -4
  535. package/src/services/workflow-reconcile.ts +1 -0
  536. package/src/services/workflow-resume-pins.ts +3 -3
  537. package/src/services/workflow-routing-defaults.ts +18 -0
  538. package/src/services/workflow-run-view.ts +4 -3
  539. package/src/services/workflow-run.test-fixture.ts +1 -1
  540. package/src/services/workflow-runs.ts +34 -22
  541. package/src/services/workflow-supervisor-state.ts +21 -1
  542. package/src/services/workflow-supervisor.ts +4 -3
  543. package/src/services/workspace-recovery.ts +4 -3
  544. package/src/setup.ts +47 -8
  545. package/src/spawn-admission.ts +10 -2
  546. package/src/spawn-placement.ts +27 -9
  547. package/src/spawn-targets.ts +8 -8
  548. package/src/sse-agent-status.ts +15 -6
  549. package/src/sse.ts +49 -24
  550. package/src/startup-context-injection.ts +61 -57
  551. package/src/steward-handoff.ts +4 -3
  552. package/src/steward-policy-cleanup.ts +9 -8
  553. package/src/steward.ts +2 -2
  554. package/src/task-dispatch-live.ts +5 -2
  555. package/src/task-dispatcher.ts +2 -2
  556. package/src/token-db.ts +2 -2
  557. package/src/token-usage-async.ts +2 -4
  558. package/src/user-awaiting-reply.ts +5 -5
  559. package/src/utils.ts +7 -0
  560. package/src/workspace-actions.ts +24 -29
  561. package/src/workspace-auto-merge.ts +14 -12
  562. package/src/workspace-changed-paths-client.ts +16 -5
  563. package/src/workspace-cleanup-stale.ts +30 -7
  564. package/src/workspace-command-settle.ts +24 -16
  565. package/src/workspace-config.ts +1 -1
  566. package/src/workspace-conflict-resolver.ts +9 -7
  567. package/src/workspace-dead-owner-recovery.ts +5 -4
  568. package/src/workspace-escalation.ts +12 -11
  569. package/src/workspace-human-escalation.ts +3 -3
  570. package/src/workspace-land-gate-watchdog.ts +3 -3
  571. package/src/workspace-land-refusal.ts +20 -0
  572. package/src/workspace-land-stopped.ts +1 -0
  573. package/src/workspace-merge.ts +10 -26
  574. package/src/workspace-nothing-to-land-notice.ts +2 -2
  575. package/src/workspace-orphans.ts +18 -37
  576. package/src/workspace-pr-completion.ts +10 -8
  577. package/src/workspace-rekey.ts +3 -3
  578. package/src/workspace-steward-failure.ts +2 -2
  579. package/src/workspace-steward-wake.ts +2 -2
  580. package/public/assets/agent-liveness-CaNwK0Zf.js +0 -2
  581. package/public/assets/overview-C1DCTzzV.js +0 -2
  582. package/public/assets/provider-display-DsuWMvVL.js +0 -2
  583. package/public/assets/run-card-CpKqRbhg.js +0 -2
  584. package/public/assets/shipping-B9YtnwtG.js +0 -2
  585. package/public/assets/store-DcClXLH0.js +0 -9
  586. package/runner/src/agent-runner/http-liveness.ts +0 -85
  587. package/src/db/on-loop-write-transaction.ts +0 -34
package/src/db/schema.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { COMMAND_RETENTION_SCHEMA } from "./command-retention";
2
+ import { TOKEN_USAGE_RETENTION_SCHEMA } from "./token-usage-retention";
1
3
  import { DURABLE_PAYLOAD_SCHEMA } from "./durable-payloads"; import { PLAN_IMPORT_SCHEMA_SQL } from "./plan-import-schema";
2
4
  import { PROJECT_ROOTS_SCHEMA } from "./project-root-schema";
3
5
  import { seedBuiltInTokenProfiles } from "../token-db";
@@ -964,7 +966,6 @@ export function initDb(path: string = "agent-relay.db"): Database {
964
966
  shutdown_requested_at INTEGER
965
967
  );
966
968
  CREATE INDEX IF NOT EXISTS idx_automation_runs_automation ON automation_runs(automation_id, created_at);
967
- CREATE INDEX IF NOT EXISTS idx_automation_runs_status ON automation_runs(status);
968
969
  CREATE INDEX IF NOT EXISTS idx_automation_runs_task ON automation_runs(task_id);
969
970
 
970
971
  CREATE TABLE IF NOT EXISTS task_events (
@@ -1264,6 +1265,8 @@ export function initDb(path: string = "agent-relay.db"): Database {
1264
1265
  ${PLAN_IMPORT_SCHEMA_SQL};
1265
1266
  ${IDLE_DIGEST_SCHEMA_SQL};
1266
1267
  ${TASK_TOKEN_TOTALS_SCHEMA};
1268
+ ${TOKEN_USAGE_RETENTION_SCHEMA};
1269
+ ${COMMAND_RETENTION_SCHEMA};
1267
1270
  ${LONG_RUNNING_WORK_SCHEMA};
1268
1271
  ${SHIPPING_STATS_SCHEMA};
1269
1272
  ${OPERATOR_ACTIONS_SCHEMA};
@@ -45,6 +45,11 @@ function observedTokens(agentIds: string[]): ObservedSubtreeSpend {
45
45
  COALESCE(SUM(usage_status IS NOT NULL), 0) AS unknownReports
46
46
  FROM session_token_usage WHERE agent_id IN (SELECT value FROM json_each(?))`)
47
47
  .get(ids) as { tokens: number; unknownReports: number };
48
+ const retired = getDb().query(`SELECT COALESCE(SUM(tokens), 0) AS tokens,
49
+ COALESCE(SUM(unknown_reports), 0) AS unknownReports FROM retired_token_usage_totals
50
+ WHERE agent_id IN (SELECT value FROM json_each(?))`).get(ids) as { tokens: number; unknownReports: number };
51
+ row.tokens += retired.tokens;
52
+ row.unknownReports += retired.unknownReports;
48
53
  const live = getDb().query(`SELECT COALESCE(SUM(status = 'busy'), 0) AS busyAgents,
49
54
  COALESCE(SUM(CASE WHEN json_valid(meta) THEN MAX(0, COALESCE(json_extract(meta, '$.unreportedTokenUsage'), 0)) ELSE 0 END), 0) AS unreportedReports
50
55
  FROM agents WHERE id IN (SELECT value FROM json_each(?))`)
@@ -5,12 +5,11 @@ import { getPersistedAgentRuntimeCard as getAgent } from "./agent-card-read";
5
5
  import { getDb, ValidationError } from "./connection";
6
6
  import { runnerSpawnGrantForAgent } from "../runtime-grant";
7
7
  import { optionalNonNegativeInt } from "../validation";
8
- import { sameHostIdentity } from "../utils";
9
- import { getOrchestrator } from "./orchestrators";
10
8
  import { stringValue } from "@humwick/sdk";
11
9
  import type { CreateCommandInput } from "../types";
12
10
  import { getLaunchFact } from "./launch-facts";
13
11
  import { spawnProcessGoneSql } from "./spawn-process-evidence";
12
+ import { WORKFLOW_EXECUTIONS_SQL } from "./workflow-execution";
14
13
 
15
14
  /** Read-only preflight runs before project or Task creation; the writer repeats it. */
16
15
  export function assertDelegatedSpawnTarget(input: CreateCommandInput) {
@@ -37,15 +36,7 @@ export function assertDelegatedSpawnTarget(input: CreateCommandInput) {
37
36
  return !agent || agent.status === "offline" || agent.meta?.lifecycleAction === "shutting-down";
38
37
  })) throw new ValidationError("SPAWN_SUBTREE_STOPPING: caller or ancestor has stopped");
39
38
  const projectId = delegatedProjectTarget(input, state);
40
- const parentHost = stringValue(getAgent(parentId)?.machine);
41
- const targetHost = stringValue(input.params?.orchestratorId);
42
- if (parentHost && targetHost) {
43
- // #3650: machine is the raw os.hostname() ("WBOX"); orchestrator identity is configured ("wbox"/"wbox-1").
44
- const target = getOrchestrator(targetHost);
45
- const sameHost = sameHostIdentity(parentHost, targetHost)
46
- || (target && [target.id, target.hostname, target.agentId].some(identity => sameHostIdentity(identity, parentHost)));
47
- if (!sameHost) throw new ValidationError("SPAWN_PROJECT_MISMATCH: delegated launches stay on their parent's host");
48
- }
39
+ // #3769 — placement owns the host; delegatedProjectTarget checks its registered project checkout.
49
40
  if (state.depth === 1 && profile === "sub-coordinator") throw new ValidationError("SPAWN_DEPTH_EXCEEDED: sub-coordinators cannot delegate another coordinator");
50
41
  const workflow = Boolean(input.params?.workflowRunId);
51
42
  const grant = runnerSpawnGrantForAgent(parentId);
@@ -63,7 +54,7 @@ export function assertDelegatedSpawnAdmission(input: CreateCommandInput, now: nu
63
54
  if (input.type !== "agent.spawn") return;
64
55
  const workflowRunId = stringValue(input.params?.workflowRunId);
65
56
  const run = workflowRunId && getDb().query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'kernel_entity__workflow__run'").get()
66
- ? getDb().query("SELECT callerAgentId, coordinator FROM kernel_entity__workflow__run WHERE scope = 'local' AND id = ?")
57
+ ? getDb().query(`SELECT callerAgentId, coordinator FROM ${WORKFLOW_EXECUTIONS_SQL} WHERE scope = 'local' AND id = ?`)
67
58
  .get(workflowRunId) as { callerAgentId: string | null; coordinator: string } | null : null;
68
59
  const origin = run?.callerAgentId ?? run?.coordinator;
69
60
  if (origin && participatesInSpawnDelegation(origin) && input.params?.spawnedBy !== origin) {
@@ -44,6 +44,9 @@ export const SQL_AUDIT_INDEXES = [
44
44
  `CREATE INDEX IF NOT EXISTS idx_task_events_land_workspace ON task_events(json_extract(metadata, '$.workspaceId'))
45
45
  WHERE type = 'deliverable' AND json_extract(metadata, '$.land.requested') = 1`,
46
46
  LAND_WITHDRAWN_INDEX_SQL,
47
+ // #3779 spawnRequestId -> Task resolution (every register/land/deliver auth check): a full task_events scan blocked the loop.
48
+ `CREATE INDEX IF NOT EXISTS idx_task_events_spawn_request ON task_events(json_extract(metadata, '$.spawnRequestId'))
49
+ WHERE type = 'agent.spawn_requested'`,
47
50
  `CREATE INDEX IF NOT EXISTS idx_task_events_land_bound_workspace ON task_events(${LAND_WORKSPACE_EXPRESSION})
48
51
  WHERE type = 'deliverable' AND json_extract(metadata, '$.land.requested') = 1`,
49
52
  `CREATE INDEX IF NOT EXISTS idx_task_events_deliverable_workspace ON task_events(${LAND_WORKSPACE_EXPRESSION}, task_id)
@@ -117,7 +117,9 @@ export function buildDbGatewayCheck(stats: DbGatewayStats): HealthCheck {
117
117
  const detail = [
118
118
  `writer=${stats.writerBackend}`,
119
119
  `reader=${stats.readerBackend}`,
120
- `onLoopOps=${specs.length - offLoop}; writerThreadOps=${offLoop}`,
120
+ // #3798 — the op split counts executed writes; the registered op definitions are named as such.
121
+ `onLoopOps=${stats.writer.onLoopOps}; writerThreadOps=${stats.writer.writerThreadOps}`,
122
+ `opDefinitions onLoop=${specs.length - offLoop} offLoop=${offLoop}`,
121
123
  `mainConnectionWrites1h=${JSON.stringify(writes)}`,
122
124
  ...(wedged ? [`writer wedged: op ${wedged.op} running ${Math.round(wedged.runningMs / 1000)}s`] : []),
123
125
  ...(shedCount > 0 ? [`${shedCount} reader shed(s)`] : []),
@@ -128,8 +130,8 @@ export function buildDbGatewayCheck(stats: DbGatewayStats): HealthCheck {
128
130
  deployRole: "load",
129
131
  detail,
130
132
  metrics: {
131
- onLoopOps: specs.length - offLoop,
132
- writerThreadOps: offLoop,
133
+ onLoopOps: stats.writer.onLoopOps,
134
+ writerThreadOps: stats.writer.writerThreadOps,
133
135
  mainConnectionWrites1h: Object.values(writes).reduce((sum, count) => sum + count, 0),
134
136
  mainConnectionBootWrites: mainConnectionBootWrites(),
135
137
  writerQueued: stats.writer.queued,
@@ -1085,14 +1085,15 @@ export function deliverableTaskIdsByOwnerAgent(agentId: string): number[] {
1085
1085
  // event (recorded BEFORE the child registers) carries the spawnRequestId. The landing worker's
1086
1086
  // `meta.spawnRequestId` resolves back through it to the Task. Returns every matching task id so a
1087
1087
  // reused spawnRequestId can't silently drop a link.
1088
+ export const TASK_IDS_BY_SPAWN_REQUEST_SQL = `SELECT DISTINCT te.task_id AS id FROM task_events te INDEXED BY idx_task_events_spawn_request
1089
+ JOIN tasks t ON t.id = te.task_id AND t.kind = '${DELIVERABLE_TASK_KIND}'
1090
+ WHERE te.type = 'agent.spawn_requested' AND json_extract(te.metadata, '$.spawnRequestId') = ?
1091
+ ORDER BY te.task_id ASC`;
1088
1092
  export function deliverableTaskIdsBySpawnRequestId(spawnRequestId: string): number[] {
1089
1093
  const cleaned = spawnRequestId?.trim();
1090
1094
  if (!cleaned) return [];
1091
1095
  return (getDb()
1092
- .query(`SELECT DISTINCT te.task_id AS id FROM task_events te
1093
- JOIN tasks t ON t.id = te.task_id AND t.kind = '${DELIVERABLE_TASK_KIND}'
1094
- WHERE te.type = 'agent.spawn_requested' AND json_extract(te.metadata, '$.spawnRequestId') = ?
1095
- ORDER BY te.task_id ASC`)
1096
+ .query(TASK_IDS_BY_SPAWN_REQUEST_SQL)
1096
1097
  .all(cleaned) as Array<{ id: number }>)
1097
1098
  .map((r) => r.id);
1098
1099
  }
@@ -0,0 +1,60 @@
1
+ import { STALE_TTL_MS } from "../config";
2
+ import { getAgent } from "./agents";
3
+ import { countLiveTeamMembers, getTeam, claimTeamWithAdoption, isClaimableTeam } from "./teams";
4
+ import type { Team } from "../types";
5
+ export class TeamAuthError extends Error {}
6
+ export class TeamNotFoundError extends Error {}
7
+ function lineageRootForAgent(agentId: string): string | undefined {
8
+ let current = getAgent(agentId);
9
+ const seen = new Set<string>();
10
+ while (current) {
11
+ if (seen.has(current.id)) return undefined;
12
+ seen.add(current.id);
13
+ if (!current.spawnedBy) return current.id;
14
+ current = getAgent(current.spawnedBy);
15
+ }
16
+ return undefined;
17
+ }
18
+
19
+ function coordinatorIsLive(team: Team): boolean {
20
+ if (!team.coordinatorId) return false;
21
+ const coordinator = getAgent(team.coordinatorId);
22
+ if (!coordinator || coordinator.status === "offline") return false;
23
+ if (coordinator.teamId === team.id) return countLiveTeamMembers(team.id, Date.now() - STALE_TTL_MS) > 0;
24
+ return coordinator.lastSeen > Date.now() - STALE_TTL_MS;
25
+ }
26
+
27
+ /** How a claim was authorized — carried into the audit trail so an orphan takeover is
28
+ * distinguishable from a routine same-lineage reclaim. */
29
+ export type TeamClaimMode = "admin" | "same-lineage" | "orphan-takeover";
30
+
31
+ // #1454 — live owners require same-lineage authority; dead owners permit orphan takeover.
32
+
33
+ function assertCanClaimTeam(admin: boolean, team: Team, callerAgentId: string): TeamClaimMode {
34
+ const caller = getAgent(callerAgentId);
35
+ const callerTeam = caller?.teamId ? getTeam(caller.teamId) : null;
36
+ if (!admin && callerTeam?.parentTeamId && team.id !== callerTeam.id) {
37
+ throw new TeamAuthError("a sub-coordinator may claim only its own private team");
38
+ }
39
+ const callerRoot = lineageRootForAgent(callerAgentId);
40
+ const teamRoot = team.rootCoordinatorId ?? team.coordinatorId;
41
+ // Same-lineage reclaim is the routine fast path (a replacement spawned under the original
42
+ // founder) and stays allowed regardless of owner liveness.
43
+ if (callerRoot && teamRoot && callerRoot === teamRoot) return "same-lineage";
44
+ if (admin) return "admin";
45
+ // #1454 orphan takeover: a replacement coordinator of a DIFFERENT lineage may adopt the team only
46
+ // when its owning coordinator is not live (offline, or stale past the heartbeat TTL). This is the
47
+ // sole security boundary for the cross-lineage path — a healthy owner is never displaced.
48
+ if (!coordinatorIsLive(team)) return "orphan-takeover";
49
+ throw new TeamAuthError(`caller ${callerAgentId} cannot claim team ${team.id}`);
50
+ }
51
+
52
+
53
+ export function claimAuthorizedTeam(teamId: string, agentId: string, admin: boolean) {
54
+ if (!isClaimableTeam(getTeam(teamId))) throw new TeamNotFoundError(`active team ${teamId} not found`);
55
+ let captured: { mode: TeamClaimMode; priorCoordinatorId: string | null } | undefined;
56
+ const result = claimTeamWithAdoption(teamId, agentId, team => {
57
+ captured = { mode: assertCanClaimTeam(admin, team, agentId), priorCoordinatorId: team.coordinatorId ?? null };
58
+ });
59
+ return { ...result, ...captured! };
60
+ }
@@ -56,3 +56,7 @@ export function setTeamBriefAsync(input: Parameters<typeof setTeamBrief>[0]): Pr
56
56
  export function dissolveTeamAsync(teamId: string, input?: Parameters<typeof dissolveTeam>[1], requestId: string = randomUUID()): Promise<ReturnType<typeof dissolveTeam>> {
57
57
  return getGateway().write("teams.dissolve", { teamId, input }, { idempotencyKey: requestId }) as unknown as Promise<ReturnType<typeof dissolveTeam>>;
58
58
  }
59
+
60
+ export function ensureTeamAsync(agentId: string, opts: { projectId?: string } = {}): Promise<NonNullable<ReturnType<typeof getTeam>>> {
61
+ return write("teams.ensure", { agentId, id: `team_${randomUUID()}`, ...opts });
62
+ }
@@ -1,3 +1,5 @@
1
+ import { claimAuthorizedTeam } from "./team-claim-policy";
2
+ import { ensureTeamInternal } from "./teams";
1
3
  // Team writer operations. Guards that were formerly adjacent sync statements now execute inside
2
4
  // the writer transaction; async callers submit a main-thread generated id and never rely on worker
3
5
  // process-local state.
@@ -10,6 +12,8 @@ import {
10
12
  import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
11
13
 
12
14
  export const TEAMS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
15
+ "teams.ensure": { offLoop: true, idempotent: true, fn: (a: { agentId: string; id: string; projectId?: string }, ctx) => ensureTeamInternal(a.agentId, a.id, ctx.now, false, a.projectId ?? null) },
16
+ "teams.claimAuthorized": { offLoop: true, idempotent: true, fn: (a: { teamId: string; agentId: string; admin: boolean }) => claimAuthorizedTeam(a.teamId, a.agentId, a.admin) },
13
17
  "teams.zeroMembers.set": { offLoop: true, idempotent: true, fn: (a: { teamId: string; ts: number }) => setTeamZeroMembersSince(a.teamId, a.ts) },
14
18
  "teams.zeroMembers.clear": { offLoop: true, idempotent: true, fn: (a: { teamId: string }, ctx) => clearTeamZeroMembersSince(a.teamId, ctx.now) },
15
19
  "teams.brief.set": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof setTeamBrief>[0], ctx) => setTeamBrief(a, ctx.now) },
@@ -111,3 +111,18 @@ export function clearAgentNotice(child: Pick<AgentCard, "id" | "epoch" | "meta">
111
111
  getDb().query("DELETE FROM agent_exit_notices WHERE agent_id = ? AND epoch = ? AND spawn_request_id = ? AND kind = ?")
112
112
  .run(child.id, child.epoch, stringValue(child.meta?.spawnRequestId) ?? "", kind);
113
113
  }
114
+
115
+ export interface AgentNoticeSlot {
116
+ child: Pick<AgentCard, "id" | "epoch" | "meta">;
117
+ kind: string;
118
+ }
119
+
120
+ /** Called inside notifications.agentNotice, so failure rolls back the slot and all sends. */
121
+ export function insertAgentNoticeSlot({ child, kind }: AgentNoticeSlot, now: number): boolean {
122
+ if (kind === "agent.ready") {
123
+ getDb().query("DELETE FROM agent_exit_notices WHERE agent_id = ? AND kind = 'agent.ready' AND NOT (epoch = ? AND spawn_request_id = ?)")
124
+ .run(child.id, child.epoch, stringValue(child.meta?.spawnRequestId) ?? "");
125
+ }
126
+ return getDb().query("INSERT OR IGNORE INTO agent_exit_notices (agent_id, epoch, spawn_request_id, kind, created_at) VALUES (?, ?, ?, ?, ?)")
127
+ .run(child.id, child.epoch, stringValue(child.meta?.spawnRequestId) ?? "", kind, now).changes > 0;
128
+ }
@@ -0,0 +1,292 @@
1
+ import { getDb, currentWriteTransactionContext } from "./connection";
2
+ import { clearExpiredLegacyMessageClaimsInTx, timerMessageDeadlineSnapshot } from "./timer-message-cutover";
3
+ import { normalizePhase, readCutover, appendJournal } from "./timer-cutover-state";
4
+ import type { KernelTimersDesiredMode, TimerMessageBackend, TimerMessageCutoverRow } from "./timer-message-flip";
5
+ import type { TimerMessageCutoverPhase, TimerMessageCutoverJournalEntry, TimerMessageDeadlineGuard } from "./timer-cutover-core";
6
+ import type { WriterOpSpec } from "./write-ops";
7
+
8
+ export interface CutoverWriterState {
9
+ ownerId: string; epoch?: number; leaseMs: number; quiesceTimeoutMs: number; worstCaseMs: number;
10
+ authoritativeActivation: boolean; operationLeaseFence?: { requestId: string; ownerId: string };
11
+ }
12
+ export interface CutoverStepArgs {
13
+ abort: { now: number; reason: string };
14
+ activate: { now: number; epoch: number };
15
+ completeLegacy: { now: number; epoch: number };
16
+ completeTransfer: { now: number; epoch: number; mode: KernelTimersDesiredMode; expectedBackend: TimerMessageBackend };
17
+ reverse: { now: number; epoch: number; targetMode: KernelTimersDesiredMode; sourceObservedMode: KernelTimersDesiredMode };
18
+ clearClaims: { now: number };
19
+ acquire: { now: number; create: boolean };
20
+ renew: { now: number; epoch: number };
21
+ closeGate: { epoch: number; now: number };
22
+ transition: { epoch: number; phase: TimerMessageCutoverPhase; reason: string; now: number };
23
+ finishAbort: { epoch: number; reason: string; now: number };
24
+ }
25
+ export interface CutoverStepResults {
26
+ abort: boolean;
27
+ activate: void;
28
+ completeLegacy: void;
29
+ completeTransfer: void;
30
+ reverse: void;
31
+ clearClaims: number;
32
+ acquire: { acquiredEpoch?: number };
33
+ renew: boolean;
34
+ closeGate: boolean;
35
+ transition: void;
36
+ finishAbort: void;
37
+ }
38
+ function owned(state: CutoverWriterState, epoch: number, now: number): TimerMessageCutoverRow {
39
+ const row = readCutover();
40
+ if (!row || row.epoch !== epoch || row.leaseOwner !== state.ownerId || (row.leaseExpiresAt ?? 0) <= now) {
41
+ throw new Error(`cutover epoch ${epoch} is not owned by ${state.ownerId}`);
42
+ }
43
+ return row;
44
+ }
45
+ function deadlineGuard(state: CutoverWriterState, at: number): TimerMessageDeadlineGuard {
46
+ const deadlines = timerMessageDeadlineSnapshot();
47
+ const slackMs = deadlines.earliestDeadline === null ? null : deadlines.earliestDeadline - at;
48
+ return { safe: slackMs === null || slackMs >= state.worstCaseMs, slackMs, requiredMs: state.worstCaseMs, deadlines };
49
+ }
50
+ const steps = {
51
+ abort: (state: CutoverWriterState, { now, reason }: CutoverStepArgs["abort"]) => {
52
+ const row = readCutover();
53
+ if (!row || normalizePhase(row.phase) === "idle") return false;
54
+ return getDb().query(`UPDATE timer_message_cutover
55
+ SET abort_requested = 1, phase_journal = ?, updated_at = ? WHERE id = 1`)
56
+ .run(appendJournal(row, { epoch: row.epoch, phase: "aborting", at: now, reason }), now).changes === 1;
57
+
58
+ },
59
+ activate: (state: CutoverWriterState, { now, epoch }: CutoverStepArgs["activate"]) => {
60
+ const row = owned(state, epoch, now);
61
+ if (normalizePhase(row.phase) !== "quiesced" || row.messageMutationGate !== "closed") {
62
+ throw new Error("ingress routing activation requires the live quiesced gate owner");
63
+ }
64
+ const changed = getDb().query(`UPDATE timer_message_cutover SET ingress_policy = 'ingress',
65
+ phase_journal = ?, updated_at = ?
66
+ WHERE id = 1 AND epoch = ? AND lease_owner = ? AND lease_expires_at > ?
67
+ AND phase = 'quiesced' AND message_mutation_gate = 'closed'`)
68
+ .run(appendJournal(row, { epoch, phase: "quiesced", at: now, reason: "authoritative ingress routing activated under fence" }),
69
+ now, epoch, state.ownerId, now);
70
+ if (changed.changes !== 1) throw new Error("cutover lease lost before ingress routing activation");
71
+
72
+ },
73
+ completeLegacy: (state: CutoverWriterState, { now, epoch }: CutoverStepArgs["completeLegacy"]) => {
74
+ const row = owned(state, epoch, now);
75
+ if (normalizePhase(row.phase) !== "quiesced" || row.messageMutationGate !== "closed" || row.ingressPolicy !== "ingress") {
76
+ throw new Error("G1 completion requires fenced authoritative ingress routing");
77
+ }
78
+ const changed = getDb().query(`UPDATE timer_message_cutover SET
79
+ protocol_version = MAX(COALESCE(protocol_version, 1), 2),
80
+ phase = 'idle', lease_owner = NULL, lease_expires_at = NULL,
81
+ quiesce_started_at = NULL, quiesce_deadline = NULL,
82
+ message_mutation_gate = 'open', gate_epoch = NULL, abort_requested = 0,
83
+ phase_journal = ?, updated_at = ?
84
+ WHERE id = 1 AND epoch = ? AND lease_owner = ? AND lease_expires_at > ?
85
+ AND phase = 'quiesced' AND message_mutation_gate = 'closed' AND ingress_policy = 'ingress'`)
86
+ .run(appendJournal(row, { epoch, phase: "idle", at: now, reason: "G1 proven; gate reopened under legacy authority" }),
87
+ now, epoch, state.ownerId, now);
88
+ if (changed.changes !== 1) throw new Error("cutover lease lost before G1 gate reopen");
89
+
90
+ },
91
+ completeTransfer: (state: CutoverWriterState, { now, epoch, mode, expectedBackend }: CutoverStepArgs["completeTransfer"]) => {
92
+ const row = owned(state, epoch, now);
93
+ if (normalizePhase(row.phase) !== "quiesced" || row.messageMutationGate !== "closed"
94
+ || row.ingressPolicy !== "ingress") {
95
+ throw new Error("authority-transfer completion requires the live quiesced ingress gate owner");
96
+ }
97
+ // Only ACTIVE (scheduled) rows must converge on the target backend — terminal rows never
98
+ // dispatch, so their backend is irrelevant and must not block completion (an ordinary kernel-mode
99
+ // cancel terminalizes a kernel-owned row that a reverse would otherwise be unable to move).
100
+ const wrongOwners = (getDb().query(`SELECT count(*) AS n FROM timer_message_authority
101
+ WHERE lifecycle='scheduled' AND backend != ?`).get(expectedBackend) as { n: number }).n;
102
+ if (wrongOwners !== 0) throw new Error(`${wrongOwners} scheduled MESSAGE owners are not ${expectedBackend}`);
103
+ const expectedFrozen = expectedBackend === "kernel" ? 1 : 0;
104
+ const projectionMismatch = (getDb().query(`SELECT count(*) AS n FROM timer_message_authority a
105
+ JOIN scheduled_timers s ON s.id=a.timer_id
106
+ WHERE a.lifecycle='scheduled' AND (s.command IS NOT NULL OR s.message_authority_frozen != ?)`)
107
+ .get(expectedFrozen) as { n: number }).n;
108
+ if (projectionMismatch !== 0) throw new Error(`${projectionMismatch} MESSAGE compatibility projections disagree with ${mode}`);
109
+ const changed = getDb().query(`UPDATE timer_message_cutover SET
110
+ desired_mode = ?, observed_mode = ?, phase = 'idle', lease_owner = NULL,
111
+ lease_expires_at = NULL, quiesce_started_at = NULL, quiesce_deadline = NULL,
112
+ message_mutation_gate = 'open', gate_epoch = NULL, abort_requested = 0,
113
+ phase_journal = ?, updated_at = ?
114
+ WHERE id=1 AND epoch=? AND lease_owner=? AND lease_expires_at>?
115
+ AND phase='quiesced' AND message_mutation_gate='closed' AND ingress_policy='ingress'`)
116
+ .run(mode, mode, appendJournal(row, {
117
+ epoch, phase: "idle", at: now,
118
+ reason: `${mode} per-row ownership proven; MESSAGE mutation gate reopened`,
119
+ }), now, epoch, state.ownerId, now);
120
+ if (changed.changes !== 1) throw new Error("cutover lease lost before authority-transfer completion");
121
+
122
+ },
123
+ reverse: (state: CutoverWriterState, { now, epoch, targetMode, sourceObservedMode }: CutoverStepArgs["reverse"]) => {
124
+ const row = owned(state, epoch, now);
125
+ const changed = getDb().query(`UPDATE timer_message_cutover SET desired_mode=?,
126
+ phase_journal=?, updated_at=?
127
+ WHERE id=1 AND epoch=? AND lease_owner=? AND lease_expires_at>?
128
+ AND phase='quiesced' AND message_mutation_gate='closed' AND observed_mode=?`)
129
+ .run(targetMode, appendJournal(row, {
130
+ epoch, phase: "quiesced", at: now,
131
+ reason: `${targetMode} rollback desired; kernel decision commits quiesced for reverse projection`,
132
+ }), now, epoch, state.ownerId, now, sourceObservedMode);
133
+ if (changed.changes !== 1) throw new Error(`reverse transfer requires observed ${sourceObservedMode} under the live fence`);
134
+
135
+ },
136
+ clearClaims: (state: CutoverWriterState, { now }: CutoverStepArgs["clearClaims"]) => {return clearExpiredLegacyMessageClaimsInTx(currentWriteTransactionContext()!, now);
137
+ },
138
+ acquire: (state: CutoverWriterState, { now, create }: CutoverStepArgs["acquire"]) => {let acquiredEpoch: number | undefined; let takeover = false;
139
+ (() => {
140
+ // Stage-10 pins the resolved leg before it asks this independent coordinator to close the
141
+ // global gate. Check that pin's operation lease under THIS acquisition transaction: if an
142
+ // abort won after pin but before requestQuiesce, it cannot be followed by a stale gate close.
143
+ const fence = state.operationLeaseFence;
144
+ if (fence) {
145
+ const live = getDb().query(`SELECT 1 FROM timer_message_cutover_operation
146
+ WHERE id=1 AND request_id=? AND state='running' AND abort_requested=0
147
+ AND lease_owner=? AND lease_expires_at > ?`)
148
+ .get(fence.requestId, fence.ownerId, now);
149
+ if (!live) return;
150
+ }
151
+ const row = readCutover();
152
+ if (!row) {
153
+ if (!create) return;
154
+ acquiredEpoch = 1;
155
+ const journal: TimerMessageCutoverJournalEntry[] = [{ epoch: 1, phase: "leased", at: now, reason: "initial lease acquired" }];
156
+ getDb().query(`INSERT INTO timer_message_cutover
157
+ (id, protocol_version, epoch, desired_mode, observed_mode, phase, lease_owner, lease_expires_at,
158
+ quiesce_started_at, quiesce_deadline, last_completed_timer, ingress_policy,
159
+ message_mutation_gate, gate_epoch, abort_requested, phase_journal, updated_at)
160
+ VALUES (1, 1, 1, 'shadow', 'shadow', 'leased', ?, ?, NULL, NULL, NULL, 'legacy', 'open', NULL, 0, ?, ?)`)
161
+ .run(state.ownerId, now + state.leaseMs, JSON.stringify(journal), now);
162
+ return;
163
+ }
164
+ const rowPhase = normalizePhase(row.phase);
165
+ const liveForeign = row.leaseOwner !== null && row.leaseOwner !== state.ownerId && (row.leaseExpiresAt ?? 0) > now;
166
+ if (liveForeign) return;
167
+ if (row.leaseOwner === state.ownerId && (row.leaseExpiresAt ?? 0) > now && state.epoch === row.epoch) {
168
+ acquiredEpoch = row.epoch;
169
+ return;
170
+ }
171
+ const nextEpoch = row.epoch + 1;
172
+ takeover = row.leaseOwner !== null || rowPhase !== "idle" || row.messageMutationGate === "closed";
173
+ const nextPhase: TimerMessageCutoverPhase = rowPhase === "idle" ? "leased" : rowPhase;
174
+ const entry: TimerMessageCutoverJournalEntry = {
175
+ epoch: nextEpoch,
176
+ phase: takeover ? "takeover" : "leased",
177
+ at: now,
178
+ reason: takeover ? `expired epoch ${row.epoch} taken over in phase ${rowPhase}` : "lease acquired",
179
+ };
180
+ const result = getDb().query(`UPDATE timer_message_cutover SET
181
+ protocol_version = COALESCE(protocol_version, 1), epoch = ?, phase = ?, lease_owner = ?, lease_expires_at = ?,
182
+ gate_epoch = CASE WHEN message_mutation_gate = 'closed' THEN ? ELSE NULL END,
183
+ phase_journal = ?, updated_at = ?
184
+ WHERE id = 1 AND (lease_owner IS NULL OR lease_expires_at <= ? OR lease_owner = ?)`)
185
+ .run(nextEpoch, nextPhase, state.ownerId, now + state.leaseMs, nextEpoch,
186
+ appendJournal(row, entry), now, now, state.ownerId);
187
+ if (result.changes === 1) acquiredEpoch = nextEpoch;
188
+ })();
189
+ return { acquiredEpoch };
190
+ },
191
+ renew: (state: CutoverWriterState, { now, epoch }: CutoverStepArgs["renew"]) => {
192
+ const row = readCutover();
193
+ if (!row || row.epoch !== epoch || row.leaseOwner !== state.ownerId || (row.leaseExpiresAt ?? 0) <= now) return false;
194
+ const deadlineCap = row.messageMutationGate === "closed" && row.quiesceDeadline !== null
195
+ ? row.quiesceDeadline
196
+ : Number.MAX_SAFE_INTEGER;
197
+ const nextExpiry = Math.min(now + state.leaseMs, deadlineCap);
198
+ return getDb().query(`UPDATE timer_message_cutover SET lease_expires_at = ?, phase_journal = ?, updated_at = ?
199
+ WHERE id = 1 AND epoch = ? AND lease_owner = ? AND lease_expires_at > ?`)
200
+ .run(nextExpiry, appendJournal(row, { epoch, phase: "renewed", at: now }), now,
201
+ epoch, state.ownerId, now).changes === 1;
202
+
203
+ },
204
+ closeGate: (state: CutoverWriterState, { epoch, now }: CutoverStepArgs["closeGate"]) => {
205
+ const row = owned(state, epoch, now);
206
+ // `requestQuiesce` acquires the coordinator lease before the following tick closes the gate.
207
+ // Recheck the Stage-10 operation lease in this close transaction: an abort (or expiry) that
208
+ // won in that interval must leave this independent coordinator open and unleased, never
209
+ // permit stale authority to close the MESSAGE gate after the abort's release observed it open.
210
+ const fence = state.operationLeaseFence;
211
+ if (fence) {
212
+ const live = getDb().query(`SELECT 1 FROM timer_message_cutover_operation
213
+ WHERE id=1 AND request_id=? AND state='running' AND abort_requested=0
214
+ AND lease_owner=? AND lease_expires_at > ?`)
215
+ .get(fence.requestId, fence.ownerId, now);
216
+ if (!live) {
217
+ const released = getDb().query(`UPDATE timer_message_cutover SET
218
+ phase = 'idle', lease_owner = NULL, lease_expires_at = NULL,
219
+ quiesce_started_at = NULL, quiesce_deadline = NULL,
220
+ message_mutation_gate = 'open', gate_epoch = NULL, abort_requested = 0,
221
+ desired_mode = observed_mode, phase_journal = ?, updated_at = ?
222
+ WHERE id=1 AND epoch=? AND lease_owner=? AND lease_expires_at>? AND phase='leased'`)
223
+ .run(appendJournal(row, {
224
+ epoch, phase: "idle", at: now,
225
+ reason: "Stage-10 operation lease no longer authorizes MESSAGE gate close",
226
+ }), now, epoch, state.ownerId, now);
227
+ if (released.changes !== 1) throw new Error("cutover lease lost while releasing stale Stage-10 gate request");
228
+ state.epoch = undefined;
229
+ return false;
230
+ }
231
+ }
232
+ // Re-read all three deadline sources under the SAME writer lock that closes the gate. A
233
+ // deadline mutation that won the race is therefore observed here and refuses entry; one that
234
+ // loses cannot commit until after the closed marker and is caught by the next continuous tick.
235
+ const guard = deadlineGuard(state, now);
236
+ if (!guard.safe) {
237
+ const changed = getDb().query(`UPDATE timer_message_cutover SET phase = 'aborting', abort_requested = 1,
238
+ phase_journal = ?, updated_at = ? WHERE id = 1 AND epoch = ? AND lease_owner = ? AND phase = 'leased'`)
239
+ .run(appendJournal(row, {
240
+ epoch, phase: "aborting", at: now,
241
+ reason: `deadline guard refused atomic gate close (slack=${guard.slackMs}, required=${guard.requiredMs})`,
242
+ }), now, epoch, state.ownerId);
243
+ if (changed.changes !== 1) throw new Error("cutover lease lost while refusing gate close");
244
+ return false;
245
+ }
246
+ const deadline = Math.min(
247
+ now + state.quiesceTimeoutMs,
248
+ guard.deadlines.earliestDeadline === null ? Number.MAX_SAFE_INTEGER : guard.deadlines.earliestDeadline - 1,
249
+ );
250
+ const changed = getDb().query(`UPDATE timer_message_cutover SET phase = 'quiescing',
251
+ message_mutation_gate = 'closed', gate_epoch = ?, quiesce_started_at = ?, quiesce_deadline = ?,
252
+ ingress_policy = CASE WHEN ? THEN 'activating' ELSE ingress_policy END,
253
+ phase_journal = ?, updated_at = ?
254
+ WHERE id = 1 AND epoch = ? AND lease_owner = ? AND phase = 'leased'`)
255
+ .run(epoch, now, deadline, state.authoritativeActivation ? 1 : 0,
256
+ appendJournal(row, { epoch, phase: "quiescing", at: now, reason: "MESSAGE mutation gate closed" }),
257
+ now, epoch, state.ownerId);
258
+ if (changed.changes !== 1) throw new Error("cutover lease lost before gate close");
259
+ return true;
260
+
261
+ },
262
+ transition: (state: CutoverWriterState, { epoch, phase, reason, now }: CutoverStepArgs["transition"]) => {
263
+ const row = owned(state, epoch, now);
264
+ const changed = getDb().query(`UPDATE timer_message_cutover SET phase = ?, phase_journal = ?, updated_at = ?
265
+ WHERE id = 1 AND epoch = ? AND lease_owner = ? AND lease_expires_at > ?`)
266
+ .run(phase, appendJournal(row, { epoch, phase, at: now, reason }), now, epoch, state.ownerId, now);
267
+ if (changed.changes !== 1) throw new Error(`cutover lease lost before phase ${phase}`);
268
+
269
+ },
270
+ finishAbort: (state: CutoverWriterState, { epoch, reason, now }: CutoverStepArgs["finishAbort"]) => {
271
+ const row = owned(state, epoch, now);
272
+ const changed = getDb().query(`UPDATE timer_message_cutover SET
273
+ phase = 'idle', lease_owner = NULL, lease_expires_at = NULL,
274
+ quiesce_started_at = NULL, quiesce_deadline = NULL,
275
+ message_mutation_gate = 'open', gate_epoch = NULL, abort_requested = 0,
276
+ phase_journal = ?, updated_at = ?
277
+ WHERE id = 1 AND epoch = ? AND lease_owner = ? AND lease_expires_at > ?`)
278
+ .run(appendJournal(row, { epoch, phase: "idle", at: now, reason: `gate reopened: ${reason}` }),
279
+ now, epoch, state.ownerId, now);
280
+ if (changed.changes !== 1) throw new Error("cutover lease lost before abort release");
281
+
282
+ },
283
+ };
284
+ export const TIMER_CUTOVER_COORDINATOR_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
285
+ "timerCutover.coordinatorStep": { offLoop: true, idempotent: false,
286
+ fn: (input: { step: keyof CutoverStepArgs; state: CutoverWriterState; args: CutoverStepArgs[keyof CutoverStepArgs] }) => {
287
+ const epoch = input.state.epoch;
288
+ const body = steps[input.step] as (state: CutoverWriterState, args: typeof input.args) => unknown;
289
+ const value = body(input.state, input.args);
290
+ return { value: value ?? null, ...(input.state.epoch !== epoch ? { clearEpoch: true } : {}) };
291
+ } },
292
+ });