@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
@@ -2,8 +2,9 @@ import { registryToolName } from "@humwick/sdk";
2
2
  import { writeChildEnv } from "@humwick/sdk/env";
3
3
  import { deriveRunnerGrant, successorGrant } from "./runtime-grant";
4
4
  export { runnerSpawnGrantForAgent } from "./runtime-grant";
5
- import { TOKEN_RECOVERY_WINDOW_SECONDS, createToken, getToken, getTokenProfile, type CreateTokenInput } from "./token-db";
6
- import { handoffTokenAsync } from "./token-db-write";
5
+ import { TOKEN_RECOVERY_WINDOW_SECONDS, createToken, getToken, getTokenProfile, type CreateTokenInput, type HandoffTokenInput, type TokenHandoff } from "./token-db";
6
+ import { runnerTokenMintLimit } from "./runner-token-mint-limit";
7
+ import { createRunnerTokenAsync, createTokenAsync, handoffTokenAsync } from "./token-db-write";
7
8
  import { FLEET_SCOPE, verifyComponentTokenAllowExpired } from "./security";
8
9
  import { getAgentProfile, hostPinForProfile, spawnGrantForProfile, toolGroupsForProfile } from "./config-store";
9
10
  import { toolAllowlistForRunner } from "./mcp-tool-allowlist";
@@ -38,6 +39,22 @@ interface RuntimeTokenResult {
38
39
  record: TokenRecord;
39
40
  }
40
41
 
42
+ /** A refused successor mint; `retryAfterSeconds` marks a per-subject rate limit (HTTP 429). */
43
+ export type RunnerTokenMintRefusal = { error: string; retryAfterSeconds?: number };
44
+
45
+ /** #3775 — the one way a runner token's successor is minted: within the subject's mint limit, charged per new token. */
46
+ export async function handoffRunnerTokenAsync(sub: string, input: HandoffTokenInput): Promise<TokenHandoff | null | { retryAfterSeconds: number }> {
47
+ const retryAfterSeconds = runnerTokenMintLimit.refusal(sub);
48
+ if (retryAfterSeconds !== undefined) return { retryAfterSeconds };
49
+ const handoff = await handoffTokenAsync(input);
50
+ if (handoff && !handoff.replayed) runnerTokenMintLimit.charge(sub);
51
+ return handoff;
52
+ }
53
+
54
+ function mintRateLimited(handoff: object | null): handoff is { retryAfterSeconds: number } {
55
+ return handoff !== null && "retryAfterSeconds" in handoff;
56
+ }
57
+
41
58
  function runnerTokenOwnedByOrchestratorHost(
42
59
  constraints: NonNullable<ReturnType<typeof verifyComponentTokenAllowExpired>>["constraints"],
43
60
  orchestratorId: string,
@@ -68,7 +85,7 @@ export async function reissueRunnerRuntimeToken(input: {
68
85
  expiredToken: string;
69
86
  orchestratorId: string;
70
87
  createdBy?: string;
71
- }): Promise<RuntimeTokenResult | { error: string }> {
88
+ }): Promise<RuntimeTokenResult | RunnerTokenMintRefusal> {
72
89
  const payload = verifyComponentTokenAllowExpired(input.expiredToken, { allowStaleRevoked: true });
73
90
  // #3133 — recovery hands off from the token's record, which holds its standing; a token without one recovers nothing.
74
91
  if (!payload?.jti) return { error: "invalid or revoked runner token" };
@@ -95,7 +112,8 @@ export async function reissueRunnerRuntimeToken(input: {
95
112
  constraints: payload.constraints,
96
113
  createdBy: input.createdBy ?? `remint:${payload.jti}`,
97
114
  };
98
- const handoff = await handoffTokenAsync({ jti: payload.jti, successor, recoverExpiredWithinSeconds: TOKEN_RECOVERY_WINDOW_SECONDS, createdBy: successor.createdBy });
115
+ const handoff = await handoffRunnerTokenAsync(payload.sub, { jti: payload.jti, successor, recoverExpiredWithinSeconds: TOKEN_RECOVERY_WINDOW_SECONDS, createdBy: successor.createdBy });
116
+ if (mintRateLimited(handoff)) return { error: "runner token mint rate limited", retryAfterSeconds: handoff.retryAfterSeconds };
99
117
  return handoff ?? { error: "invalid or revoked runner token" };
100
118
  }
101
119
 
@@ -122,7 +140,7 @@ function runnerGrantKey(scope: readonly string[] | undefined, constraints: Token
122
140
  * (current profile, lineage ceiling) and the presented token's launch identity. An identical grant is left
123
141
  * alone; otherwise the token is handed off like a renewal, so a lost response is recovered by a retry.
124
142
  */
125
- export async function remintRunnerRuntimeToken(auth: ComponentToken, createdBy: string): Promise<RunnerTokenRemint | { unchanged: true; jti: string } | { error: string }> {
143
+ export async function remintRunnerRuntimeToken(auth: ComponentToken, createdBy: string): Promise<RunnerTokenRemint | { unchanged: true; jti: string } | RunnerTokenMintRefusal> {
126
144
  const previous = auth.jti ? getToken(auth.jti) : null;
127
145
  const c = auth.constraints ?? {};
128
146
  const agentId = c.agents?.length === 1 ? c.agents[0] : undefined;
@@ -149,7 +167,8 @@ export async function remintRunnerRuntimeToken(auth: ComponentToken, createdBy:
149
167
  });
150
168
  if (token.constraints?.orchestrators?.includes("")) return { error: "the agent's host is unknown, so its host pin cannot be kept" };
151
169
  if (runnerGrantKey(previous.scope, previous.constraints) === runnerGrantKey(token.scope, token.constraints)) return { unchanged: true, jti: previous.jti };
152
- const handoff = await handoffTokenAsync({ jti: previous.jti, allowedProfileIds: ["provider-agent"], successor: token, createdBy });
170
+ const handoff = await handoffRunnerTokenAsync(auth.sub, { jti: previous.jti, allowedProfileIds: ["provider-agent"], successor: token, createdBy });
171
+ if (mintRateLimited(handoff)) return { error: "runner token mint rate limited", retryAfterSeconds: handoff.retryAfterSeconds };
153
172
  if (!handoff) return { error: "the presented token was already rotated" };
154
173
  const { record } = handoff;
155
174
  return {
@@ -171,7 +190,16 @@ export function issueOrchestratorRuntimeToken(input: {
171
190
  baseDir: string;
172
191
  createdBy?: string;
173
192
  }): RuntimeTokenResult {
174
- return createToken({
193
+ return createToken(orchestratorTokenSpec(input));
194
+ }
195
+
196
+ /** #1870 S6c — the mint on the writer thread, for the async routes. */
197
+ export function issueOrchestratorRuntimeTokenAsync(input: Parameters<typeof issueOrchestratorRuntimeToken>[0]): Promise<RuntimeTokenResult> {
198
+ return createTokenAsync(orchestratorTokenSpec(input));
199
+ }
200
+
201
+ function orchestratorTokenSpec(input: Parameters<typeof issueOrchestratorRuntimeToken>[0]): CreateTokenInput {
202
+ return {
175
203
  profileId: "orchestrator-host",
176
204
  sub: `orchestrator:${input.orchestratorId}`,
177
205
  role: "orchestrator",
@@ -184,7 +212,7 @@ export function issueOrchestratorRuntimeToken(input: {
184
212
  terminalWrite: true,
185
213
  },
186
214
  createdBy: input.createdBy ?? "runtime",
187
- });
215
+ };
188
216
  }
189
217
 
190
218
  export interface RunnerRuntimeTokenInput {
@@ -297,6 +325,7 @@ function runnerTokenSpec(input: RunnerRuntimeTokenInput): { token: CreateTokenIn
297
325
  : { kind: "root", ...(authority.kind === "delegated" ? { launcherScopes: authority.callerScopes } : {}), ...(profile ? { profile } : {}) } };
298
326
  }
299
327
 
328
+ /** Sync twin for in-process tests; every runtime launch path mints through the writer op below. */
300
329
  export function issueRunnerRuntimeToken(input: RunnerRuntimeTokenInput): RuntimeTokenResult {
301
330
  const spec = runnerTokenSpec(input);
302
331
  const issued = createToken(spec.token);
@@ -305,6 +334,11 @@ export function issueRunnerRuntimeToken(input: RunnerRuntimeTokenInput): Runtime
305
334
  return issued;
306
335
  }
307
336
 
337
+ export function issueRunnerRuntimeTokenAsync(input: RunnerRuntimeTokenInput): Promise<RuntimeTokenResult> {
338
+ const spec = runnerTokenSpec(input);
339
+ return createRunnerTokenAsync(spec.token, spec.launchFact ? { agentId: input.agentId, fact: spec.launchFact } : undefined);
340
+ }
341
+
308
342
  export function issueInteractiveRunnerRuntimeToken(input: {
309
343
  provider: string;
310
344
  cwd: string;
@@ -384,7 +418,14 @@ export function issueIntegrationRuntimeToken(input: {
384
418
  }
385
419
 
386
420
  export function runnerRuntimeTokenEnv(input: RunnerRuntimeTokenInput): Record<string, string> {
387
- const issued = issueRunnerRuntimeToken(input);
421
+ return runnerTokenEnv(input, issueRunnerRuntimeToken(input));
422
+ }
423
+
424
+ export async function runnerRuntimeTokenEnvAsync(input: RunnerRuntimeTokenInput): Promise<Record<string, string>> {
425
+ return runnerTokenEnv(input, await issueRunnerRuntimeTokenAsync(input));
426
+ }
427
+
428
+ function runnerTokenEnv(input: RunnerRuntimeTokenInput, issued: RuntimeTokenResult): Record<string, string> {
388
429
  return writeChildEnv({}, {
389
430
  AGENT_RELAY_TOKEN: issued.token,
390
431
  AGENT_RELAY_TOKEN_JTI: issued.record.jti,
package/src/security.ts CHANGED
@@ -233,6 +233,8 @@ export function isOrchestratorRegistrationAuthorized(req: Request): boolean {
233
233
  }
234
234
 
235
235
  const SHARED_UI_ROUTE_SCOPES: Record<string, string> = {
236
+ "/api/orchestrators/bootstrap": "token:write",
237
+ "/api/orchestrators/bootstrap/exchange": "token:write",
236
238
  "/api/claude-api-budget/summary": "agent:read",
237
239
  "/api/claude-api-budget": "system:admin",
238
240
  "/api/stats": "stats:read",
@@ -252,6 +254,15 @@ function sharedUiRouteScope(pathname: string, method: string): string | undefine
252
254
  if (/^\/api\/api-budgets\/[^/]+\/summary$/.test(pathname)) return "agent:read";
253
255
  if (/^\/api\/api-budgets\/[^/]+$/.test(pathname)) return "system:admin";
254
256
  if (/^\/api\/shipping\/incidents\/[^/]+\/abandon$/.test(pathname)) return "command:write";
257
+ if (pathname === "/api/teams/config") return method === "GET" ? "teams:read" : "settings:write:teams";
258
+ if (pathname.startsWith("/api/teams")) return method === "GET" ? "teams:read" : "teams:write";
259
+ if (pathname === "/api/decisions" && method === "POST") return "teams:write"; // #2914: filing = the relay_decision scope
260
+ if (pathname.startsWith("/api/decisions")) return method === "GET" ? "teams:read" : "message:send"; // #2885: answering = messaging the coordinator
261
+ if (pathname.startsWith("/api/blackboard")) return method === "GET" ? "blackboard:read" : "blackboard:write";
262
+ if (pathname.startsWith("/api/issue-associations")) return method === "GET" ? "issue:read" : "issue:write";
263
+ if (pathname.startsWith("/api/issue-activity")) return method === "GET" ? "issue:read" : "issue:write";
264
+ if (pathname.startsWith("/api/issue-lifecycle")) return method === "GET" ? "issue:read" : "issue:write";
265
+ if (pathname.startsWith("/api/issue-refs")) return method === "GET" ? "issue:read" : "issue:write";
255
266
  return Object.hasOwn(SHARED_UI_ROUTE_SCOPES, pathname) ? SHARED_UI_ROUTE_SCOPES[pathname] : undefined;
256
267
  }
257
268
 
@@ -266,8 +277,6 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
266
277
  if (pathname === "/api/route") return "agent:read";
267
278
  if (pathname === "/api/notifications/preview") return "notifications:read";
268
279
  if (pathname === "/api/mcp") return "mcp:use";
269
- if (pathname === "/api/orchestrators/bootstrap/exchange") return "token:write";
270
- if (pathname === "/api/orchestrators/bootstrap") return "token:write";
271
280
  if (pathname.match(/^\/api\/orchestrators\/[^/]+\/logs\//)) return "logs:read";
272
281
  if (pathname.match(/^\/api\/orchestrators\/[^/]+\/terminal\/[^/]+\/input$/)) return "terminal:write";
273
282
  if (pathname.match(/^\/api\/orchestrators\/[^/]+\/terminal\//)) return "terminal:read";
@@ -323,16 +332,8 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
323
332
  if (pathname.match(/^\/api\/provider-quotas\/[^/]+\/reset-fired$/)) return "quota:write";
324
333
  if (pathname === "/api/provider-quotas") return method === "GET" ? "agent:read" : "quota:write";
325
334
  if (pathname === "/api/routing-policy") return method === "GET" ? "agent:read" : "settings:write:routing";
326
- if (pathname === "/api/teams/config") return method === "GET" ? "teams:read" : "settings:write:teams";
327
335
  if (pathname === "/api/settings/prompts" || pathname.startsWith("/api/settings/prompts/")) return method === "GET" ? "agent:read" : "system:admin";
328
- if (pathname.startsWith("/api/teams")) return method === "GET" ? "teams:read" : "teams:write";
329
- if (pathname === "/api/decisions" && method === "POST") return "teams:write"; // #2914: filing = the relay_decision scope
330
- if (pathname.startsWith("/api/decisions")) return method === "GET" ? "teams:read" : "message:send"; // #2885: answering = messaging the coordinator
331
- if (pathname.startsWith("/api/blackboard")) return method === "GET" ? "blackboard:read" : "blackboard:write";
332
- if (pathname.startsWith("/api/issue-associations")) return method === "GET" ? "issue:read" : "issue:write";
333
- if (pathname.startsWith("/api/issue-activity")) return method === "GET" ? "issue:read" : "issue:write";
334
- if (pathname.startsWith("/api/issue-lifecycle")) return method === "GET" ? "issue:read" : "issue:write";
335
- if (pathname.startsWith("/api/issue-refs")) return method === "GET" ? "issue:read" : "issue:write";
336
+ if (/^\/api\/projects\/[^/]+\/profile-instructions(?:\/|$)/.test(pathname)) return method === "GET" ? "agent:read" : "agent:write";
336
337
  if (pathname.startsWith("/api/projects")) return method === "GET" ? "project:read" : "project:write";
337
338
  // Insights config (the feature toggle) stays admin-only via the default; only the
338
339
  // mechanical observation feed is writable by lower-privilege callers.
@@ -466,7 +467,6 @@ export function requiredComponentScopeFor(method: string, pathname: string): str
466
467
  if (pathname === "/api/routing-policy") {
467
468
  return method === "GET" ? "agent:read" : "settings:write:routing";
468
469
  }
469
- if (pathname === "/api/teams/config") return method === "GET" ? "teams:read" : "settings:write:teams";
470
470
  if (pathname === "/api/settings/prompts" || pathname.startsWith("/api/settings/prompts/")) {
471
471
  return method === "GET" ? "agent:read" : versionedPromptsEnabled() ? "agent:write" : "system:admin";
472
472
  }
@@ -477,16 +477,9 @@ export function requiredComponentScopeFor(method: string, pathname: string): str
477
477
  if (pathname.startsWith("/api/workflows")) return method === "GET" ? "task:read" : "task:write";
478
478
  if (pathname.startsWith("/api/orchestrators")) return method === "GET" ? "agent:read" : "command:write";
479
479
  if (pathname.startsWith("/api/plans")) return method === "GET" ? "plans:read" : "plans:write";
480
- if (pathname.startsWith("/api/teams")) return method === "GET" ? "teams:read" : "teams:write";
481
- if (pathname === "/api/decisions" && method === "POST") return "teams:write"; // #2914: filing = the relay_decision scope
482
- if (pathname.startsWith("/api/decisions")) return method === "GET" ? "teams:read" : "message:send"; // #2885: answering = messaging the coordinator
483
- if (pathname.startsWith("/api/blackboard")) return method === "GET" ? "blackboard:read" : "blackboard:write";
484
- if (pathname.startsWith("/api/issue-associations")) return method === "GET" ? "issue:read" : "issue:write";
485
- if (pathname.startsWith("/api/issue-activity")) return method === "GET" ? "issue:read" : "issue:write";
486
- if (pathname.startsWith("/api/issue-lifecycle")) return method === "GET" ? "issue:read" : "issue:write";
487
- if (pathname.startsWith("/api/issue-refs")) return method === "GET" ? "issue:read" : "issue:write";
488
480
  // Fail-closed default returns system:admin, so this case is load-bearing: without it
489
481
  // every /api/projects call from a scoped component token would 403-blackhole (#405).
482
+ if (/^\/api\/projects\/[^/]+\/profile-instructions(?:\/|$)/.test(pathname)) return method === "GET" ? "agent:read" : "agent:write";
490
483
  if (pathname.startsWith("/api/projects")) return method === "GET" ? "project:read" : "project:write";
491
484
  // The Runner posts the #184 context-gathering signal here (source:"server") via its
492
485
  // provider token. Without this case the path fell through to the system:admin default
@@ -19,6 +19,6 @@ export function startServerBackgroundJobs(): void {
19
19
  { name: "connectors", start: startConnectorStatusPoller },
20
20
  { name: "maintenance", start: startMaintenanceScheduler },
21
21
  { name: "steward-rekey-notices", start: async () => (await import("./workspace-rekey")).drainStewardRekeyNotices() },
22
- { name: "deploy-notice", start: () => { if (!isEphemeralMode()) notifyCdDeployLive(); } },
22
+ { name: "deploy-notice", start: async () => { if (!isEphemeralMode()) await notifyCdDeployLive(); } },
23
23
  ]);
24
24
  }
package/src/server.ts CHANGED
@@ -7,6 +7,8 @@ import { inviteLinkRedirect } from "./invite-link";
7
7
  import { claudeAppBoundary, CLAUDE_APP_PATH, isClaudeAppPath } from "./claude-app-auth";
8
8
  import { handleClaudeApp } from "./claude-app-gateway";
9
9
  import { handleClaudeAppOAuth } from "./claude-app/oauth-http";
10
+ import { installTimerIngressSchema } from "./db/timer-ingress";
11
+ import { beginMainWriteBootPhase, endMainWriteBootPhase } from "./db/main-connection-writes";
10
12
  import { markServerBound, timePostBindPhase } from "./post-bind-startup";
11
13
  import type { ServerWebSocket } from "bun";
12
14
  import { beginStartupPhase, enableStartupTiming, timeStartupPhase } from "./startup-timing";
@@ -24,7 +26,6 @@ import { reconcileAutomationRuns, runDueAutomationsAsync, type AutomationDispatc
24
26
  import { getLifecycleManager } from "./lifecycle-manager";
25
27
  import { reconcileStewardPolicyModels } from "./steward-policy-model-reconcile";
26
28
  import { getConfig, setConfig } from "./config-store";
27
- import { reconcileProviderModelSeed } from "./provider-model-seed";
28
29
  export { reconcileProviderModelSeed } from "./provider-model-seed";
29
30
  import { ensureEscalationChannelRegistered } from "./connectors";
30
31
  import { installNotificationSubscriptionRule } from "./notification-subscription-rules";
@@ -111,6 +112,7 @@ export async function startServer(): Promise<void> {
111
112
  startArtifactCheckoutLease();
112
113
 
113
114
  assertSafeNetworkConfig(HOST);
115
+ beginMainWriteBootPhase();
114
116
  timeStartupPhase("server.database", () => initDb(DB_PATH));
115
117
  // #1960 T2 — stand the gateway up EXPLICITLY here rather than leaving it to the lazy self-init
116
118
  // (`getGateway()` on first call, gateway.ts), which would race the rest of this boot sequence.
@@ -136,7 +138,6 @@ export async function startServer(): Promise<void> {
136
138
  installNotificationSubscriptionRule();
137
139
  startPlanLiveBindings();
138
140
  reconcileRelayVersion();
139
- if (!isEphemeralMode()) reconcileProviderModelSeed();
140
141
  // Register the ntfy escalation channel row from persisted connector config BEFORE the
141
142
  // warning check, so a correctly-configured relay doesn't spuriously warn while the async
142
143
  // channels-host connect is still in flight (#1510).
@@ -174,7 +175,9 @@ export async function startServer(): Promise<void> {
174
175
  }, AUTOMATION_INTERVAL_MS);
175
176
  }
176
177
 
178
+ installTimerIngressSchema();
177
179
  finishServices();
180
+ endMainWriteBootPhase();
178
181
  timeStartupPhase("server.bind", () => Bun.serve<RelaySocketData>({
179
182
  port: PORT,
180
183
  hostname: HOST,
@@ -11,10 +11,14 @@
11
11
  // 2. the §4.4 in-memory last-hour series, ONLY once the durable write landed, so the Monitor
12
12
  // chart can never show a point the Fleet card cannot corroborate;
13
13
  // 3. #2822 — the one operator page a gate-capacity transition owes, decided by that same write.
14
+ // 4. #3762 — the one low-disk page (ntfy + coordinator) a disk level change owes, same write.
14
15
  import type { HostDaemonSample, HostMachineSample } from "@humwick/sdk";
15
16
  import { recordHostUtilizationAsync } from "../db";
16
17
  import { recordHostUtilizationSample } from "../host/utilization-buffer";
17
18
  import { composeGateCapacityNotice, type GateCapacityAlert } from "../host/gate-capacity-alert";
19
+ import { composeDiskNotice, type DiskAlert } from "../host/disk-alert";
20
+ import { stewardFallbackTarget } from "../config";
21
+ import { notifyTarget } from "../maintenance/events";
18
22
  import { notifyWorkspaceHumanEscalation } from "../workspace-human-escalation";
19
23
 
20
24
  export interface RecordHostUtilizationInput {
@@ -52,9 +56,28 @@ export async function recordHostUtilization(input: RecordHostUtilizationInput):
52
56
  }
53
57
  recordHostUtilizationSample(input.hostId, input.machine, input.now ?? Date.now());
54
58
  if (recorded?.gateAlert) notifyGateCapacity(input.hostId, recorded.hostLabel ?? input.hostId, recorded.gateAlert, input.now ?? Date.now());
59
+ if (recorded?.diskAlert) notifyDisk(input.hostId, recorded.hostLabel ?? input.hostId, recorded.diskAlert);
55
60
  return true;
56
61
  }
57
62
 
63
+ /** Operator ntfy plus the steward/coordinator target — the same two recipients as the critical-job watchdog. */
64
+ function notifyDisk(hostId: string, label: string, alert: DiskAlert): void {
65
+ const notice = composeDiskNotice(hostId, label, alert);
66
+ const payload = { ...alert, kind: `host-disk.${alert.kind}`, hostId };
67
+ const human = notifyWorkspaceHumanEscalation({
68
+ subject: notice.subject,
69
+ body: notice.body,
70
+ priority: notice.priority,
71
+ tags: ["agent-relay", "host-disk"],
72
+ source: "host-disk",
73
+ payload,
74
+ idempotencyKey: notice.idempotencyKey,
75
+ });
76
+ const target = stewardFallbackTarget();
77
+ const routed = target ? notifyTarget("maintenance.degraded", {}, target, notice.subject, notice.body, payload, notice.idempotencyKey) : null;
78
+ console.warn(`[relay] ${notice.subject}${human.accepted || routed ? "" : " — nobody was notified (no ntfy channel or steward target)"}`);
79
+ }
80
+
58
81
  function notifyGateCapacity(hostId: string, label: string, alert: GateCapacityAlert, now: number): void {
59
82
  const notice = composeGateCapacityNotice(hostId, label, alert, now);
60
83
  const result = notifyWorkspaceHumanEscalation({
@@ -3,8 +3,8 @@ import { isTerminalTaskStatus } from "./task-entity";
3
3
  import { errMessage } from "@humwick/sdk";
4
4
  import { coordinatedTeamIds } from "../db/coordinator-seat";
5
5
  import { teamEpicWork } from "../db/team-epics";
6
- import { clearIssueTrackerNotFound, issueTrackerNotFoundCount, recordIssueTrackerNotFound, getIssueHierarchy, putIssueHierarchy } from "../db/long-running-work";
7
- import { updateIssueRef } from "../db/issue-refs";
6
+ import { issueTrackerNotFoundCount, getIssueHierarchy } from "../db/long-running-work";
7
+ import { putIssueHierarchyAsync, recordIssueRefLiveStatusAsync, recordIssueTrackerNotFoundAsync } from "../db/issue-refs-async";
8
8
  import { resolveIssueTrackerAdapter } from "../issue-tracker";
9
9
  import { GitHubApiError } from "../issue-tracker/github";
10
10
  import { routeNotification } from "../notification-router";
@@ -41,14 +41,13 @@ export async function refreshTeamEpicHierarchy(teamId: string, signal?: AbortSig
41
41
  const live = await adapter.getStatus(issue);
42
42
  if (signal?.aborted) break;
43
43
  statusLoaded = true;
44
- clearIssueTrackerNotFound(issue.id);
45
- updateIssueRef(issue.id, { status: live.status, title: live.title, closeReason: live.closeReason, statusSyncedAt: Date.now() });
44
+ await recordIssueRefLiveStatusAsync(issue.id, { status: live.status, title: live.title, closeReason: live.closeReason, statusSyncedAt: Date.now() });
46
45
  if (live.status !== "open") continue;
47
46
  const children = adapter.listSubIssues ? await adapter.listSubIssues(issue) : await adapter.listOpenSubIssues?.(issue);
48
- if (children) putIssueHierarchy(issue.id, children);
47
+ if (children) await putIssueHierarchyAsync(issue.id, children);
49
48
  } catch (error) {
50
49
  if (!statusLoaded && error instanceof GitHubApiError && error.statusCode === 404
51
- && recordIssueTrackerNotFound(issue.id) >= 2) {
50
+ && await recordIssueTrackerNotFoundAsync(issue.id) >= 2) {
52
51
  reportUnresolvable(teamId, issue);
53
52
  continue;
54
53
  }
@@ -8,9 +8,9 @@ import { listEntitiesForIssue, listIssuesForEntity } from "../db/issue-associati
8
8
  import {
9
9
  getSuccessfulIssueLifecycleAction,
10
10
  listSuccessfulCloseActionsForCommit,
11
- upsertIssueLifecycleAction,
12
11
  } from "../db/issue-lifecycle";
13
- import { getIssueRef, resolveIssueRefByExternalKey, resolveIssueRefFromString, updateIssueRef } from "../db/issue-refs";
12
+ import { getIssueRef } from "../db/issue-refs";
13
+ import { resolveIssueRefByExternalKeyAsync, resolveIssueRefFromStringAsync, updateIssueRefAsync, upsertIssueLifecycleActionAsync } from "../db/issue-refs-async";
14
14
  import { associateIssue } from "../db/issue-associations";
15
15
  import { resolveIssueTrackerAdapter } from "../issue-tracker";
16
16
  import { defaultIssueRepo } from "../config";
@@ -141,42 +141,53 @@ const ISSUE_REF_AT_START_RE = /^(https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z
141
141
  const REF_SEPARATOR_RE = /^(?:\s*,\s*|\s+(?:and|&)\s+)/i;
142
142
 
143
143
  // A run of issue refs ("#1, #2 and owner/repo#3") starting at the head of `segment`.
144
- // Bare refs resolve against `fallbackDefaultRepo`; unresolvable ones are skipped so a
145
- // land never fails here.
146
- function collectRefRun(segment: string, fallbackDefaultRepo: string | undefined, into: IssueRef[]): void {
144
+ function refRun(segment: string): string[] {
145
+ const refs: string[] = [];
147
146
  let rest = segment;
148
147
  while (rest.trim()) {
149
148
  rest = rest.trimStart();
150
149
  const refMatch = rest.match(ISSUE_REF_AT_START_RE);
151
150
  if (!refMatch) break;
152
- try {
153
- into.push(resolveIssueRefFromString({ ref: refMatch[1]!, defaultRepo: fallbackDefaultRepo }));
154
- } catch {
155
- // Invalid or bare-without-default refs are ignored; the land itself must never fail here.
156
- }
151
+ refs.push(refMatch[1]!);
157
152
  rest = rest.slice(refMatch[0].length);
158
153
  const separator = rest.match(REF_SEPARATOR_RE);
159
154
  if (!separator) break;
160
155
  rest = rest.slice(separator[0].length);
161
156
  }
157
+ return refs;
162
158
  }
163
159
 
164
- export function parseClosingIssueRefs(text: string | undefined, fallbackDefaultRepo = defaultIssueRepo()): IssueRef[] {
165
- if (!text) return [];
160
+ // Bare refs resolve against `fallbackDefaultRepo`; unresolvable ones are skipped so a land never fails here.
161
+ async function resolveRefRuns(segments: Iterable<string>, fallbackDefaultRepo: string | undefined): Promise<IssueRef[]> {
166
162
  const refs: IssueRef[] = [];
167
- for (const match of text.matchAll(CLOSING_KEYWORD_RE)) collectRefRun(match[1] ?? "", fallbackDefaultRepo, refs);
163
+ for (const segment of segments) {
164
+ for (const ref of refRun(segment)) {
165
+ try {
166
+ refs.push(await resolveIssueRefFromStringAsync({ ref, defaultRepo: fallbackDefaultRepo }));
167
+ } catch {
168
+ // Invalid or bare-without-default refs are ignored; the land itself must never fail here.
169
+ }
170
+ }
171
+ }
168
172
  return issueRefMap(refs);
169
173
  }
170
174
 
175
+ export function parseClosingIssueRefsAsync(text: string | undefined, fallbackDefaultRepo = defaultIssueRepo()): Promise<IssueRef[]> {
176
+ return resolveRefRuns(text ? [...text.matchAll(CLOSING_KEYWORD_RE)].map((match) => match[1] ?? "") : [], fallbackDefaultRepo);
177
+ }
178
+
171
179
  // Parenthesized backlink groups: the trailing `(#NNN)` convention agents (and a squash merge)
172
180
  // stamp on a subject without a closing keyword. Non-issue parens (conventional-commit scopes
173
181
  // like `fix(tokens)`, notes like `(see also)`) carry no `#` and fall out.
174
182
  const BACKLINK_GROUP_RE = /\(([^()]*#[0-9]+[^()]*)\)/g;
175
183
 
176
- export function parseBacklinkIssueRefs(text: string | undefined, fallbackDefaultRepo = defaultIssueRepo()): IssueRef[] {
177
- if (!text) return [];
184
+ export function parseBacklinkIssueRefsAsync(text: string | undefined, fallbackDefaultRepo = defaultIssueRepo()): Promise<IssueRef[]> {
185
+ return resolveRefRuns(text ? [...text.matchAll(BACKLINK_GROUP_RE)].map((group) => group[1] ?? "") : [], fallbackDefaultRepo);
186
+ }
187
+
188
+ async function eachSubject(subjects: string[], parse: (text: string) => Promise<IssueRef[]>): Promise<IssueRef[]> {
178
189
  const refs: IssueRef[] = [];
179
- for (const group of text.matchAll(BACKLINK_GROUP_RE)) collectRefRun(group[1] ?? "", fallbackDefaultRepo, refs);
190
+ for (const subject of subjects) refs.push(...await parse(subject));
180
191
  return issueRefMap(refs);
181
192
  }
182
193
 
@@ -190,13 +201,13 @@ async function resolveIssueRefsForLand(input: LandedIssueLifecycleInput, links:
190
201
  const associated = issueRefMap(links.flatMap((link) => link.issues.filter((issue) => issue.relation === "delivers").map((issue) => issue.ref)));
191
202
  const fallbackDefaultRepo = await landDefaultRepo(input.workspace);
192
203
  const subjects = landedSubjectTexts(input);
193
- const keyworded = issueRefMap(subjects.flatMap((subject) => parseClosingIssueRefs(subject, fallbackDefaultRepo)));
204
+ const keyworded = await eachSubject(subjects, (subject) => parseClosingIssueRefsAsync(subject, fallbackDefaultRepo));
194
205
  // A commit declaration is authored at the change boundary, unlike a Task association
195
206
  // which can legitimately be historical or span several work items. It is the sole close
196
207
  // target when present (#1694).
197
208
  if (keyworded.length > 0) return { source: "commit", refs: keyworded, declared: keyworded };
198
209
  if (!input.prLand && links.some((link) => link.buildLike)) {
199
- const backlinks = issueRefMap(subjects.flatMap((subject) => parseBacklinkIssueRefs(subject, fallbackDefaultRepo)))
210
+ const backlinks = (await eachSubject(subjects, (subject) => parseBacklinkIssueRefsAsync(subject, fallbackDefaultRepo)))
200
211
  .filter((ref) => canCloseBacklink(ref, links));
201
212
  if (backlinks.length) return { source: "backlink", refs: backlinks, declared: backlinks };
202
213
  }
@@ -348,7 +359,7 @@ async function reconcileClosedIssueRefBeforeSkip(issueRef: IssueRef, opts: Issue
348
359
  // LOW-4 (#2520 review) — thread the live closeReason through: a completed close observed
349
360
  // here was dropped, so the cached ref stayed reason-less and could never count as a
350
361
  // completed close. updateIssueRef preserves the cached reason when live carries none.
351
- issueRef: updateIssueRef(issueRef.id, { status, title: live.title, closeReason: live.closeReason, statusSyncedAt: Date.now() }) ?? issueRef,
362
+ issueRef: await updateIssueRefAsync(issueRef.id, { status, title: live.title, closeReason: live.closeReason, statusSyncedAt: Date.now() }) ?? issueRef,
352
363
  liveStatus: live.status,
353
364
  };
354
365
  } catch (error) {
@@ -406,7 +417,7 @@ export async function closeIssueRefForLand(
406
417
  logFailure(opts, `issue lifecycle epic-guard title lookup returned no title for ${latest.id} on ${key}`);
407
418
  return { issueRef: latest, action: "skipped", reason: "title-unavailable", landKey: key };
408
419
  }
409
- latest = updateIssueRef(latest.id, { title, statusSyncedAt: Date.now() }) ?? latest;
420
+ latest = await updateIssueRefAsync(latest.id, { title, statusSyncedAt: Date.now() }) ?? latest;
410
421
  } catch (error) {
411
422
  logFailure(opts, `issue lifecycle epic-guard title lookup failed for ${latest.id} on ${key}: ${errMessage(error)}`);
412
423
  return { issueRef: latest, action: "skipped", reason: "title-unavailable", landKey: key };
@@ -423,22 +434,25 @@ export async function closeIssueRefForLand(
423
434
  return { issueRef: latest, action: "skipped", reason: "epic-issue", landKey: key };
424
435
  }
425
436
 
437
+ const closeAction = {
438
+ issueRefId: latest.id,
439
+ action: "close" as const,
440
+ landKey: key,
441
+ workspaceId: input.workspace.id,
442
+ branch: input.workspace.branch,
443
+ baseRef: input.workspace.baseRef ?? "main",
444
+ commitSha: input.mergedSha,
445
+ commitSubject: input.subject,
446
+ agentId: input.workspace.ownerAgentId,
447
+ source,
448
+ };
426
449
  let openSubIssues: IssueTrackerIssueSummary[];
427
450
  try {
428
451
  openSubIssues = await listOpenSubIssuesForLand(latest, opts);
429
452
  } catch (error) {
430
453
  const message = `sub-issue lookup failed: ${errMessage(error)}`;
431
- upsertIssueLifecycleAction({
432
- issueRefId: latest.id,
433
- action: "close",
434
- landKey: key,
435
- workspaceId: input.workspace.id,
436
- branch: input.workspace.branch,
437
- baseRef: input.workspace.baseRef ?? "main",
438
- commitSha: input.mergedSha,
439
- commitSubject: input.subject,
440
- agentId: input.workspace.ownerAgentId,
441
- source,
454
+ await upsertIssueLifecycleActionAsync({
455
+ ...closeAction,
442
456
  status: "failed",
443
457
  error: message,
444
458
  });
@@ -458,18 +472,9 @@ export async function closeIssueRefForLand(
458
472
  const comment = closeAuditComment(input);
459
473
  try {
460
474
  await adapterFor(latest, opts).close(latest, { comment, labels: ["fixed"], closeReason: "completed" });
461
- const closed = updateIssueRef(latest.id, { status: "closed", closeReason: "completed", statusSyncedAt: Date.now() }) ?? latest;
462
- upsertIssueLifecycleAction({
463
- issueRefId: latest.id,
464
- action: "close",
465
- landKey: key,
466
- workspaceId: input.workspace.id,
467
- branch: input.workspace.branch,
468
- baseRef: input.workspace.baseRef ?? "main",
469
- commitSha: input.mergedSha,
470
- commitSubject: input.subject,
471
- agentId: input.workspace.ownerAgentId,
472
- source,
475
+ const closed = await updateIssueRefAsync(latest.id, { status: "closed", closeReason: "completed", statusSyncedAt: Date.now() }) ?? latest;
476
+ await upsertIssueLifecycleActionAsync({
477
+ ...closeAction,
473
478
  comment,
474
479
  status: "succeeded",
475
480
  });
@@ -494,17 +499,8 @@ export async function closeIssueRefForLand(
494
499
  return { issueRef: closed, action: "closed", source, landKey: key };
495
500
  } catch (error) {
496
501
  const message = errMessage(error);
497
- upsertIssueLifecycleAction({
498
- issueRefId: latest.id,
499
- action: "close",
500
- landKey: key,
501
- workspaceId: input.workspace.id,
502
- branch: input.workspace.branch,
503
- baseRef: input.workspace.baseRef ?? "main",
504
- commitSha: input.mergedSha,
505
- commitSubject: input.subject,
506
- agentId: input.workspace.ownerAgentId,
507
- source,
502
+ await upsertIssueLifecycleActionAsync({
503
+ ...closeAction,
508
504
  comment,
509
505
  status: "failed",
510
506
  error: message,
@@ -564,15 +560,15 @@ export async function handleLandedIssueLifecycle(input: LandedIssueLifecycleInpu
564
560
  return { source: resolved.source, closed, reopened, skipped: [], mismatches };
565
561
  }
566
562
 
567
- function resolveReopenRef(input: ReopenIssueLifecycleInput): IssueRef {
563
+ async function resolveReopenRef(input: ReopenIssueLifecycleInput): Promise<IssueRef> {
568
564
  if (input.issueRefId) {
569
565
  const issueRef = getIssueRef(input.issueRefId);
570
566
  if (!issueRef) throw new Error(`issue ref ${input.issueRefId} not found`);
571
567
  return issueRef;
572
568
  }
573
- if (input.ref) return resolveIssueRefFromString({ ref: input.ref, defaultRepo: input.defaultRepo ?? input.repo, provider: input.provider, title: input.title });
569
+ if (input.ref) return resolveIssueRefFromStringAsync({ ref: input.ref, defaultRepo: input.defaultRepo ?? input.repo, provider: input.provider, title: input.title });
574
570
  if (input.provider && input.repo && input.externalKey) {
575
- return resolveIssueRefByExternalKey({
571
+ return resolveIssueRefByExternalKeyAsync({
576
572
  provider: input.provider,
577
573
  repo: input.repo,
578
574
  externalKey: input.externalKey,
@@ -584,14 +580,14 @@ function resolveReopenRef(input: ReopenIssueLifecycleInput): IssueRef {
584
580
  }
585
581
 
586
582
  export async function reopenIssueLifecycle(input: ReopenIssueLifecycleInput, opts: IssueLifecycleOptions = {}): Promise<ReopenIssueLifecycleResult> {
587
- const issueRef = resolveReopenRef(input);
583
+ const issueRef = await resolveReopenRef(input);
588
584
  const key = manualReopenKey(issueRef);
589
585
  if (issueRef.status === "open") return { issueRef, action: "skipped", reason: "already-open", landKey: key };
590
586
  const comment = input.comment ?? "Reopened by Humwick issue lifecycle.";
591
587
  try {
592
588
  await adapterFor(issueRef, opts).reopen(issueRef, { comment });
593
- const opened = updateIssueRef(issueRef.id, { status: "open", statusSyncedAt: Date.now() }) ?? issueRef;
594
- upsertIssueLifecycleAction({
589
+ const opened = await updateIssueRefAsync(issueRef.id, { status: "open", statusSyncedAt: Date.now() }) ?? issueRef;
590
+ await upsertIssueLifecycleActionAsync({
595
591
  issueRefId: issueRef.id,
596
592
  action: "reopen",
597
593
  landKey: key,
@@ -604,7 +600,7 @@ export async function reopenIssueLifecycle(input: ReopenIssueLifecycleInput, opt
604
600
  return { issueRef: opened, action: "reopened", landKey: key };
605
601
  } catch (error) {
606
602
  const message = errMessage(error);
607
- upsertIssueLifecycleAction({
603
+ await upsertIssueLifecycleActionAsync({
608
604
  issueRefId: issueRef.id,
609
605
  action: "reopen",
610
606
  landKey: key,
@@ -1,11 +1,7 @@
1
+ import { deleteIssueRefAsync, resolveIssueRefByExternalKeyAsync, resolveIssueRefFromStringAsync, updateIssueRefAsync, upsertIssueRefAsync } from "../db/issue-refs-async";
1
2
  import {
2
- deleteIssueRef,
3
3
  getIssueRef,
4
4
  listIssueRefs,
5
- resolveIssueRefByExternalKey,
6
- resolveIssueRefFromString,
7
- updateIssueRef,
8
- upsertIssueRef,
9
5
  type ListIssueRefsOptions,
10
6
  type ResolveIssueRefByExternalKeyInput,
11
7
  type ResolveIssueRefFromStringInput,
@@ -15,20 +11,20 @@ import {
15
11
 
16
12
  export { getIssueRef, listIssueRefs, type ListIssueRefsOptions };
17
13
 
18
- export function upsertIssueRefMapping(input: UpsertIssueRefInput) {
19
- return upsertIssueRef(input);
14
+ export function upsertIssueRefMappingAsync(input: UpsertIssueRefInput) {
15
+ return upsertIssueRefAsync(input);
20
16
  }
21
17
 
22
- export function resolveIssueRefMapping(input: ResolveIssueRefByExternalKeyInput | ResolveIssueRefFromStringInput) {
23
- return "ref" in input ? resolveIssueRefFromString(input) : resolveIssueRefByExternalKey(input);
18
+ export function resolveIssueRefMappingAsync(input: ResolveIssueRefByExternalKeyInput | ResolveIssueRefFromStringInput) {
19
+ return "ref" in input ? resolveIssueRefFromStringAsync(input) : resolveIssueRefByExternalKeyAsync(input);
24
20
  }
25
21
 
26
- export function patchIssueRefMapping(id: string, input: UpdateIssueRefInput) {
27
- return updateIssueRef(id, input);
22
+ export function patchIssueRefMappingAsync(id: string, input: UpdateIssueRefInput) {
23
+ return updateIssueRefAsync(id, input);
28
24
  }
29
25
 
30
- export function deleteIssueRefMapping(id: string) {
31
- return deleteIssueRef(id);
26
+ export function deleteIssueRefMappingAsync(id: string) {
27
+ return deleteIssueRefAsync(id);
32
28
  }
33
29
 
34
30
  // --- Caller-supplied issue-ref normalization (#2214/#1930) ---------------------------------