@humwick/server 0.130.1 → 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 (437) hide show
  1. package/README.md +5 -0
  2. package/docs/guides/tools-administration.md +4 -0
  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-B_rFEbnL.js → fleet-C7jQWbw2.js} +2 -2
  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 +8 -0
  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 +3 -3
  128. package/src/config-store-write.ts +13 -1
  129. package/src/config-store.ts +25 -14
  130. package/src/db/activity-async.ts +6 -1
  131. package/src/db/activity-write-ops.ts +12 -1
  132. package/src/db/activity.ts +1 -14
  133. package/src/db/agent-search.ts +1 -15
  134. package/src/db/agents-async.ts +52 -5
  135. package/src/db/agents-write-ops.ts +12 -3
  136. package/src/db/agents.ts +81 -54
  137. package/src/db/ci-incidents-async.ts +5 -5
  138. package/src/db/ci-incidents-write-ops.ts +2 -2
  139. package/src/db/command-row.ts +10 -2
  140. package/src/db/commands-list-query.ts +2 -0
  141. package/src/db/commands-spawn-read.ts +1 -1
  142. package/src/db/config-store-write-ops.ts +7 -2
  143. package/src/db/continuation-archives-async.ts +5 -0
  144. package/src/db/continuation-archives-write-ops.ts +7 -0
  145. package/src/db/continuations.ts +2 -2
  146. package/src/db/db-writer-worker.ts +8 -1
  147. package/src/db/deploy-events-async.ts +1 -0
  148. package/src/db/deploy-events-read-ops.ts +2 -1
  149. package/src/db/deploy-events.ts +35 -4
  150. package/src/db/hosts-registration.ts +10 -2
  151. package/src/db/hosts.ts +9 -6
  152. package/src/db/inbox.ts +0 -14
  153. package/src/db/index.ts +2 -0
  154. package/src/db/integrations-async.ts +8 -0
  155. package/src/db/integrations-ops.ts +15 -0
  156. package/src/db/integrations.ts +15 -21
  157. package/src/db/issue-lifecycle.ts +3 -4
  158. package/src/db/issue-refs-async.ts +20 -0
  159. package/src/db/issue-refs-write-ops.ts +29 -0
  160. package/src/db/issue-refs.ts +7 -8
  161. package/src/db/long-running-work.ts +2 -2
  162. package/src/db/main-connection-writes.ts +23 -2
  163. package/src/db/maintenance-job-reconcile.ts +61 -0
  164. package/src/db/maintenance-job-runs-async.ts +14 -0
  165. package/src/db/maintenance-job-write-ops.ts +9 -0
  166. package/src/db/mappers.ts +2 -14
  167. package/src/db/merge-lease.ts +1 -15
  168. package/src/db/migrations.ts +7 -15
  169. package/src/db/orchestrators.ts +4 -72
  170. package/src/db/pairs.ts +1 -13
  171. package/src/db/project-profile-instructions.ts +50 -0
  172. package/src/db/prompt-versions.ts +9 -2
  173. package/src/db/provider-config-history.ts +8 -3
  174. package/src/db/provider-model-seed.ts +34 -0
  175. package/src/db/read-op-marshaling-fixtures.ts +5 -0
  176. package/src/db/read-ops.ts +4 -0
  177. package/src/db/routing-policy.ts +2 -3
  178. package/src/db/schema.ts +9 -3
  179. package/src/db/spawn-delegation-state.ts +11 -4
  180. package/src/db/stats-async.ts +4 -3
  181. package/src/db/task-entity.ts +23 -2
  182. package/src/db/task-list.ts +3 -1
  183. package/src/db/tasks.ts +1 -17
  184. package/src/db/timer-ingress.ts +7 -1
  185. package/src/db/token-db-write-ops.ts +17 -0
  186. package/src/db/token-standing.ts +15 -11
  187. package/src/db/workflow-contact.ts +1 -1
  188. package/src/db/workflow-task-read-ops.ts +30 -0
  189. package/src/db/write-ops.ts +6 -0
  190. package/src/db/writer-gateway.ts +17 -11
  191. package/src/dispatch-availability.ts +10 -6
  192. package/src/ephemeral/{landing-ingress-proof-worker.ts → landing-ingress-proof.test-fixture.ts} +4 -2
  193. package/src/host/disk-alert.ts +121 -0
  194. package/src/lifecycle-manager.ts +3 -2
  195. package/src/maintenance/cd-watcher.ts +3 -3
  196. package/src/maintenance/critical-job-watchdog.ts +16 -17
  197. package/src/maintenance/index.ts +1 -0
  198. package/src/maintenance/jobs.ts +3 -2
  199. package/src/maintenance/scheduler.ts +84 -128
  200. package/src/maintenance/stalled-delivery-pager.ts +6 -5
  201. package/src/maintenance/workspaces.ts +1 -1
  202. package/src/managed-policy.ts +31 -27
  203. package/src/mcp/index.ts +5 -2
  204. package/src/mcp/output-descriptions.ts +8 -8
  205. package/src/mcp/presentation.ts +4 -4
  206. package/src/mcp/project-profile-instructions.ts +32 -0
  207. package/src/mcp/tool-groups.ts +1 -1
  208. package/src/mcp/tools-spawn.ts +4 -3
  209. package/src/mcp-agent-action.ts +1 -2
  210. package/src/mcp-issue-ref-tools.ts +12 -11
  211. package/src/mcp-maintenance-tools.ts +2 -2
  212. package/src/mcp-profile-trust-tools.ts +4 -4
  213. package/src/mcp-provider-catalog.ts +2 -2
  214. package/src/mcp-task-tools.ts +11 -5
  215. package/src/mcp-team-template-tools.ts +3 -3
  216. package/src/mcp-workflow-tools.ts +10 -3
  217. package/src/notification-types.ts +2 -1
  218. package/src/operator-alarm.ts +26 -11
  219. package/src/project-instructions.ts +4 -0
  220. package/src/project-profile-instruction-text.ts +6 -0
  221. package/src/project-profile-instructions.ts +20 -0
  222. package/src/provider-catalog-store.ts +3 -49
  223. package/src/provider-config-store.ts +12 -11
  224. package/src/provider-model-seed.ts +1 -18
  225. package/src/provider-quota-config-store.ts +5 -4
  226. package/src/provider-secrets.test-fixture.ts +3 -3
  227. package/src/recipe-runner.ts +2 -2
  228. package/src/repo-config-source.ts +10 -4
  229. package/src/routes/agent-profiles.ts +6 -6
  230. package/src/routes/agents-spawn.ts +1 -1
  231. package/src/routes/agents.ts +6 -6
  232. package/src/routes/commands-listing.ts +4 -0
  233. package/src/routes/commands.ts +2 -2
  234. package/src/routes/continuation-archives.ts +2 -2
  235. package/src/routes/index.ts +10 -1
  236. package/src/routes/integrations.ts +24 -50
  237. package/src/routes/issue-refs.ts +7 -7
  238. package/src/routes/orchestrator-bootstrap.ts +1 -1
  239. package/src/routes/orchestrator-host-registration.ts +2 -2
  240. package/src/routes/orchestrator-host-utilization.ts +3 -1
  241. package/src/routes/orchestrator-proxy.ts +2 -2
  242. package/src/routes/orchestrator.ts +2 -2
  243. package/src/routes/project-profile-instructions.ts +27 -0
  244. package/src/routes/project-trust.ts +2 -2
  245. package/src/routes/provider-config.ts +6 -6
  246. package/src/routes/provider-quota-config.ts +2 -2
  247. package/src/routes/settings.ts +5 -5
  248. package/src/routes/stats.ts +6 -6
  249. package/src/routes/task-http.ts +15 -1
  250. package/src/routes/task-issues.ts +11 -26
  251. package/src/routes/tasks.ts +22 -53
  252. package/src/routes/team-templates.ts +5 -5
  253. package/src/routes/teams-config.ts +2 -2
  254. package/src/routes/tokens.ts +12 -5
  255. package/src/routes/workflows.ts +20 -5
  256. package/src/routing-policy-normalize.ts +1 -2
  257. package/src/routing-step-defaults.ts +0 -2
  258. package/src/runner-token-mint-limit.ts +63 -0
  259. package/src/runtime-grant.ts +4 -2
  260. package/src/runtime-tokens.ts +39 -7
  261. package/src/security.ts +13 -20
  262. package/src/server-background.ts +1 -1
  263. package/src/server.ts +5 -2
  264. package/src/services/host-utilization.ts +23 -0
  265. package/src/services/idle-epics.ts +5 -6
  266. package/src/services/issue-lifecycle.ts +57 -61
  267. package/src/services/issue-refs.ts +9 -13
  268. package/src/services/kernel-timers-cutover-chaos-worker.ts +10 -7
  269. package/src/services/kernel-timers-ingress.ts +1 -1
  270. package/src/services/landed-ancestry.ts +19 -3
  271. package/src/services/memory-areas.ts +15 -51
  272. package/src/services/plan-ci-notice.ts +57 -0
  273. package/src/services/plan-ci-recovery.ts +2 -55
  274. package/src/services/plan-wake-digest.ts +1 -0
  275. package/src/services/profile-trust.ts +4 -3
  276. package/src/services/project-config-read.ts +70 -0
  277. package/src/services/project-profile-instructions.ts +54 -0
  278. package/src/services/provisioning-harness/budget-baseline.json +4 -4
  279. package/src/services/restart-spawn.ts +61 -49
  280. package/src/services/self-care-state.ts +2 -1
  281. package/src/services/self-effort.ts +5 -5
  282. package/src/services/send-message.ts +5 -1
  283. package/src/services/spawn-agent.ts +13 -2
  284. package/src/services/spawn-chain-inspector.ts +6 -13
  285. package/src/services/spawn-policy.ts +68 -0
  286. package/src/services/spawn-tree-cleanup.ts +2 -2
  287. package/src/services/task-dispatch.ts +3 -0
  288. package/src/services/task-state-reconcile.ts +6 -14
  289. package/src/services/team-templates.ts +9 -8
  290. package/src/services/transient-agent-reaper.ts +3 -1
  291. package/src/services/workflow-auto-start.ts +25 -13
  292. package/src/services/workflow-ci-main.ts +1 -1
  293. package/src/services/workflow-deferred-cleanup.ts +4 -2
  294. package/src/services/workflow-effect-driver.ts +2 -0
  295. package/src/services/workflow-effects.ts +18 -11
  296. package/src/services/workflow-land-notice.ts +10 -0
  297. package/src/services/workflow-pause-text.ts +43 -0
  298. package/src/services/workflow-reconcile.ts +13 -11
  299. package/src/services/workflow-run-view.ts +2 -0
  300. package/src/services/workflow-runs.ts +29 -13
  301. package/src/services/workflow-supervisor-state.ts +5 -0
  302. package/src/services/workflow-supervisor.ts +7 -3
  303. package/src/settings/registry.ts +1 -2
  304. package/src/spawn-command.ts +4 -4
  305. package/src/spawn-models.ts +10 -12
  306. package/src/spawn-placement.ts +1 -1
  307. package/src/spawn-targets.ts +1 -1
  308. package/src/startup-context-injection.ts +1 -0
  309. package/src/state-adoption/cd-cutover.ts +53 -7
  310. package/src/state-adoption/fleet-cutover.ts +35 -0
  311. package/src/state-adoption/fleet-plan.ts +36 -0
  312. package/src/state-adoption/index.ts +2 -2
  313. package/src/state-adoption/linux-plan.ts +2 -2
  314. package/src/state-adoption/linux.ts +2 -2
  315. package/src/state-adoption/macos-plan.ts +2 -2
  316. package/src/state-adoption/macos.ts +1 -1
  317. package/src/steward.ts +7 -6
  318. package/src/task-dispatch-live.ts +1 -1
  319. package/src/task-dispatcher.ts +1 -1
  320. package/src/teams-config-store.ts +4 -3
  321. package/src/token-db-write.ts +10 -0
  322. package/src/workspace-command-settle.ts +2 -1
  323. package/src/workspace-steward-wake.ts +2 -2
  324. package/node_modules/@humwick/kernel/LICENSE +0 -167
  325. package/node_modules/@humwick/kernel/async-entity-persistence.ts +0 -344
  326. package/node_modules/@humwick/kernel/async-event-log.ts +0 -107
  327. package/node_modules/@humwick/kernel/automations.pilot.ts +0 -147
  328. package/node_modules/@humwick/kernel/config.ts +0 -111
  329. package/node_modules/@humwick/kernel/contract.ts +0 -572
  330. package/node_modules/@humwick/kernel/control-authz.ts +0 -281
  331. package/node_modules/@humwick/kernel/control.ts +0 -645
  332. package/node_modules/@humwick/kernel/effects.ts +0 -493
  333. package/node_modules/@humwick/kernel/event-log.ts +0 -319
  334. package/node_modules/@humwick/kernel/http-transport.ts +0 -384
  335. package/node_modules/@humwick/kernel/identity-grammar.ts +0 -20
  336. package/node_modules/@humwick/kernel/identity.ts +0 -248
  337. package/node_modules/@humwick/kernel/index.ts +0 -28
  338. package/node_modules/@humwick/kernel/kernel-persistence.ts +0 -181
  339. package/node_modules/@humwick/kernel/landing-incident.ts +0 -306
  340. package/node_modules/@humwick/kernel/landing.ts +0 -953
  341. package/node_modules/@humwick/kernel/manifest-validation.ts +0 -460
  342. package/node_modules/@humwick/kernel/mcp-transport.ts +0 -109
  343. package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +0 -200
  344. package/node_modules/@humwick/kernel/message-authority.ts +0 -281
  345. package/node_modules/@humwick/kernel/module-host.ts +0 -722
  346. package/node_modules/@humwick/kernel/openapi-init.ts +0 -14
  347. package/node_modules/@humwick/kernel/package.json +0 -39
  348. package/node_modules/@humwick/kernel/persistence-schema.ts +0 -248
  349. package/node_modules/@humwick/kernel/persistence.ts +0 -510
  350. package/node_modules/@humwick/kernel/registry.ts +0 -60
  351. package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +0 -499
  352. package/node_modules/@humwick/kernel/sqlite-module-host.ts +0 -87
  353. package/node_modules/@humwick/kernel/timers-chaos-worker.ts +0 -186
  354. package/node_modules/@humwick/kernel/timers.pilot.ts +0 -1411
  355. package/node_modules/@humwick/kernel/workflow-activation.ts +0 -25
  356. package/node_modules/@humwick/kernel/workflow-backstop.ts +0 -40
  357. package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +0 -20
  358. package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +0 -213
  359. package/node_modules/@humwick/kernel/workflow-composition.ts +0 -231
  360. package/node_modules/@humwick/kernel/workflow-container.ts +0 -72
  361. package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +0 -42
  362. package/node_modules/@humwick/kernel/workflow-control.ts +0 -178
  363. package/node_modules/@humwick/kernel/workflow-definition.ts +0 -507
  364. package/node_modules/@humwick/kernel/workflow-dispatch.ts +0 -60
  365. package/node_modules/@humwick/kernel/workflow-foreach.ts +0 -167
  366. package/node_modules/@humwick/kernel/workflow-journal-codec.ts +0 -339
  367. package/node_modules/@humwick/kernel/workflow-journal.ts +0 -922
  368. package/node_modules/@humwick/kernel/workflow-lane.ts +0 -48
  369. package/node_modules/@humwick/kernel/workflow-lease-owner.ts +0 -60
  370. package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +0 -19
  371. package/node_modules/@humwick/kernel/workflow-runtime.ts +0 -938
  372. package/node_modules/@humwick/kernel/workflow-script.ts +0 -11
  373. package/node_modules/@humwick/kernel/workflow-state.ts +0 -299
  374. package/node_modules/@humwick/kernel/workflow-step-commit.ts +0 -195
  375. package/node_modules/@humwick/kernel/workflow-transitions.ts +0 -317
  376. package/node_modules/@humwick/kernel/workflow-values.ts +0 -153
  377. package/node_modules/@humwick/kernel/workflow.pilot.ts +0 -376
  378. package/node_modules/@humwick/offload/LICENSE +0 -167
  379. package/node_modules/@humwick/offload/README.md +0 -57
  380. package/node_modules/@humwick/offload/dist/async-runner.d.ts +0 -18
  381. package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +0 -1
  382. package/node_modules/@humwick/offload/dist/async-runner.js +0 -158
  383. package/node_modules/@humwick/offload/dist/async-runner.js.map +0 -1
  384. package/node_modules/@humwick/offload/dist/envelope.d.ts +0 -25
  385. package/node_modules/@humwick/offload/dist/envelope.d.ts.map +0 -1
  386. package/node_modules/@humwick/offload/dist/envelope.js +0 -80
  387. package/node_modules/@humwick/offload/dist/envelope.js.map +0 -1
  388. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +0 -10
  389. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +0 -1
  390. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +0 -59
  391. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +0 -1
  392. package/node_modules/@humwick/offload/dist/index.d.ts +0 -5
  393. package/node_modules/@humwick/offload/dist/index.d.ts.map +0 -1
  394. package/node_modules/@humwick/offload/dist/index.js +0 -5
  395. package/node_modules/@humwick/offload/dist/index.js.map +0 -1
  396. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +0 -62
  397. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +0 -1
  398. package/node_modules/@humwick/offload/dist/subprocess-runner.js +0 -295
  399. package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +0 -1
  400. package/node_modules/@humwick/offload/dist/types.d.ts +0 -111
  401. package/node_modules/@humwick/offload/dist/types.d.ts.map +0 -1
  402. package/node_modules/@humwick/offload/dist/types.js +0 -176
  403. package/node_modules/@humwick/offload/dist/types.js.map +0 -1
  404. package/node_modules/@humwick/offload/dist/worker-pool.d.ts +0 -23
  405. package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +0 -1
  406. package/node_modules/@humwick/offload/dist/worker-pool.js +0 -435
  407. package/node_modules/@humwick/offload/dist/worker-pool.js.map +0 -1
  408. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +0 -2
  409. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +0 -1
  410. package/node_modules/@humwick/offload/dist/worker-runtime.js +0 -27
  411. package/node_modules/@humwick/offload/dist/worker-runtime.js.map +0 -1
  412. package/node_modules/@humwick/offload/dist/worker.d.ts +0 -5
  413. package/node_modules/@humwick/offload/dist/worker.d.ts.map +0 -1
  414. package/node_modules/@humwick/offload/dist/worker.js +0 -1
  415. package/node_modules/@humwick/offload/dist/worker.js.map +0 -1
  416. package/node_modules/@humwick/offload/package.json +0 -42
  417. package/node_modules/@humwick/offload/src/async-runner.ts +0 -170
  418. package/node_modules/@humwick/offload/src/envelope.ts +0 -92
  419. package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +0 -49
  420. package/node_modules/@humwick/offload/src/index.ts +0 -4
  421. package/node_modules/@humwick/offload/src/subprocess-runner.ts +0 -334
  422. package/node_modules/@humwick/offload/src/types.ts +0 -235
  423. package/node_modules/@humwick/offload/src/worker-pool.ts +0 -463
  424. package/node_modules/@humwick/offload/src/worker-runtime.ts +0 -32
  425. package/node_modules/@humwick/offload/src/worker.ts +0 -5
  426. package/public/assets/agent-liveness-DyqxfB-t.js +0 -2
  427. package/public/assets/agent-profiles-BpI_jTxq.js +0 -2
  428. package/public/assets/index-B7ZerHLY.css +0 -2
  429. package/public/assets/index-BU3uWo2t.js +0 -15
  430. package/public/assets/projects-B_lmcqfO.js +0 -2
  431. package/public/assets/provider-display-DbLWpP3j.js +0 -2
  432. package/public/assets/run-card-CO_bguQf.js +0 -2
  433. package/public/assets/use-project-list-B-T2Bnnn.js +0 -2
  434. package/public/assets/versioned-prompt-editor-Diu8BIaM.js +0 -8
  435. package/runner/src/gate-run-lease-probe.ts +0 -38
  436. package/src/db/claude-catalog-reconcile.ts +0 -27
  437. package/src/routing-model-aliases.ts +0 -21
@@ -1,7 +1,7 @@
1
1
  import { parseWorkflowDecisionResponse } from "../services/workflow-decisions";
2
2
  // #3076 — the dashboard's workflow run reads: the run list (by status and project), one run, the stats
3
3
  // window and the workflows that ran, all from the workflow run records relay_workflow_get reads, with its visibility rule.
4
- // #3120 — the catalog. #3166 — cancel a run, as relay_workflow_cancel does.
4
+ // #3120 — the catalog. #3166 — cancel a run, as relay_workflow_cancel does. #3757 — pause and unpause one.
5
5
  import { OpError } from "@humwick/kernel";
6
6
  import { isRecord, WORKFLOW_STATS_WINDOWS, type WorkflowStatsWindow } from "@humwick/sdk";
7
7
  import { resolveProjectAsync, ValidationError } from "../db";
@@ -10,7 +10,7 @@ import { authContextFromRequest } from "../services/auth-context";
10
10
  import { ServiceAuthError } from "../services/errors";
11
11
  import { readWorkflowCatalog } from "../services/workflow-catalog";
12
12
  import { requireSpawnBudget } from "../spawn-budget";
13
- import { amendWorkflowRunBudget, cancelWorkflowRun, getWorkflowRun, readWorkflowRunBudget, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun } from "../services/workflow-runs";
13
+ import { amendWorkflowRunBudget, cancelWorkflowRun, getWorkflowRun, pauseWorkflowRun, readWorkflowRunBudget, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun } from "../services/workflow-runs";
14
14
  import { workflowDefinitions, workflowStats } from "../services/workflow-stats";
15
15
  import { cleanString, taskIdField } from "../validation";
16
16
  import { error, json, parseBody, type Handler } from "./_shared";
@@ -83,7 +83,22 @@ export const postWorkflowRunCancelRoute: Handler = async (req, params) => {
83
83
  }
84
84
  };
85
85
 
86
- function budgetRouteError(e: unknown): Response {
86
+ /** #3757 — pause (a `reason` is required) or unpause a run, as relay_workflow_resume's `pause` does; the dashboard's buttons use these. */
87
+ function workflowRunPauseRoute(pause: boolean): Handler {
88
+ return async (req, params) => {
89
+ const parsed = await parseBody<unknown>(req);
90
+ if (!parsed.ok) return error(parsed.error, parsed.status);
91
+ if (parsed.body !== null && !isRecord(parsed.body)) return error("body must be a JSON object", 400);
92
+ try {
93
+ const reason = cleanString(parsed.body?.reason, "reason", { max: 2000 });
94
+ return json(await pauseWorkflowRun({ runId: params.runId!, pause, ...(reason ? { reason } : {}) }, authContextFromRequest(req)));
95
+ } catch (e) { return runRouteError(e); }
96
+ };
97
+ }
98
+ export const postWorkflowRunPauseRoute = workflowRunPauseRoute(true);
99
+ export const postWorkflowRunUnpauseRoute = workflowRunPauseRoute(false);
100
+
101
+ function runRouteError(e: unknown): Response {
87
102
  if (e instanceof McpNotFoundError) return error(e.message, 404);
88
103
  if (e instanceof ServiceAuthError) return error(e.message, 403);
89
104
  if (e instanceof ValidationError) return error(e.message, 400);
@@ -91,7 +106,7 @@ function budgetRouteError(e: unknown): Response {
91
106
  }
92
107
 
93
108
  export const getWorkflowRunBudgetRoute: Handler = async (req, params) => {
94
- try { return json(await readWorkflowRunBudget(params.runId!, authContextFromRequest(req))); } catch (e) { return budgetRouteError(e); }
109
+ try { return json(await readWorkflowRunBudget(params.runId!, authContextFromRequest(req))); } catch (e) { return runRouteError(e); }
95
110
  };
96
111
 
97
112
  export const patchWorkflowRunBudgetRoute: Handler = async (req, params) => {
@@ -100,7 +115,7 @@ export const patchWorkflowRunBudgetRoute: Handler = async (req, params) => {
100
115
  try {
101
116
  const { tokenCap } = requireSpawnBudget(parsed.body);
102
117
  return json(await amendWorkflowRunBudget({ runId: params.runId!, tokenCap }, authContextFromRequest(req)));
103
- } catch (e) { return budgetRouteError(e); }
118
+ } catch (e) { return runRouteError(e); }
104
119
  };
105
120
 
106
121
  export const postWorkflowRunResumeRoute: Handler = async (req, params) => {
@@ -1,5 +1,4 @@
1
1
  import { ValidationError } from "@humwick/sdk";
2
- import { currentDispatchModels } from "./routing-model-aliases";
3
2
  import { normalizeStepRoutingDefaults } from "./routing-step-defaults";
4
3
  import {
5
4
  isRecord,
@@ -199,7 +198,7 @@ export function normalizeDispatchPolicy(raw: unknown): DispatchPolicy {
199
198
  */
200
199
  /** Pure projection used by reader-gateway callers that already captured the policy snapshot. */
201
200
  export function dispatchPolicyFor(policy: RoutingPolicy): DispatchPolicy {
202
- return policy.dispatch ? currentDispatchModels(policy.dispatch) : DEFAULT_DISPATCH_POLICY;
201
+ return policy.dispatch ?? DEFAULT_DISPATCH_POLICY;
203
202
  }
204
203
 
205
204
  const MAX_RULES = 100;
@@ -1,5 +1,4 @@
1
1
  import { cleanString, isRecord, isSpawnProvider, ValidationError, type StepRoutingDefaults, type StepRoutingTarget } from "@humwick/sdk";
2
- import { VALID_EFFORTS } from "@humwick/sdk/provider-catalog";
3
2
  import { WORKFLOW_PIN_FIELDS } from "@humwick/kernel/workflow-definition";
4
3
 
5
4
  export function normalizeStepRoutingDefaults(raw: unknown): StepRoutingDefaults {
@@ -25,7 +24,6 @@ function normalizeRoles(raw: unknown, where: string): Record<string, StepRouting
25
24
  for (const key of Object.keys(target)) if (!(WORKFLOW_PIN_FIELDS as readonly string[]).includes(key)) throw new ValidationError(`${where}.${role}.${key} is not a routing field`);
26
25
  const fields = Object.fromEntries(Object.entries(target).map(([key, value]) => [key, cleanString(value, `${where}.${role}.${key}`, { required: true, max: 200 })]));
27
26
  if (fields.provider !== undefined && !isSpawnProvider(fields.provider)) throw new ValidationError(`${where}.${role}.provider is not a provider`);
28
- if (fields.effort !== undefined && !(VALID_EFFORTS as readonly string[]).includes(fields.effort)) throw new ValidationError(`${where}.${role}.effort must be ${VALID_EFFORTS.join(", ")}`);
29
27
  return [role, fields as StepRoutingTarget];
30
28
  }));
31
29
  }
@@ -0,0 +1,63 @@
1
+ // #3775 — per-subject cap on runner token successor mints (renew, orchestrator re-mint, boundary
2
+ // re-derive). A runner stuck re-minting after 401 minted 700-1,400 tokens/min and overloaded the
3
+ // hub writer; this refuses the excess before any token row is written. A spawn subject
4
+ // (`runner:spawn:<id>`) keeps one subject across its whole token lineage, so it is the key. Only a
5
+ // real mint is charged: a replayed handoff (retry or concurrent caller) writes nothing.
6
+
7
+ /** At most this many new tokens per subject in any rolling window. */
8
+ export const RUNNER_TOKEN_MINTS_PER_WINDOW = 3;
9
+ export const RUNNER_TOKEN_MINT_WINDOW_MS = 60_000;
10
+ const MAX_TRACKED_SUBJECTS = 10_000;
11
+
12
+ export class RunnerTokenMintLimit {
13
+ /** Recent mint times per subject, oldest first, at most RUNNER_TOKEN_MINTS_PER_WINDOW long. */
14
+ private readonly mints = new Map<string, number[]>();
15
+ /** Subjects currently refused, so a trip logs once rather than once per refused mint. */
16
+ private readonly tripped = new Set<string>();
17
+ private readonly now: () => number;
18
+ private readonly log: (message: string) => void;
19
+
20
+ constructor(options: { now?: () => number; log?: (message: string) => void } = {}) {
21
+ this.now = options.now ?? Date.now;
22
+ this.log = options.log ?? console.warn;
23
+ }
24
+
25
+ /** Seconds `sub` must wait before its next mint, or undefined when it may mint now. Charges nothing. */
26
+ refusal(sub: string): number | undefined {
27
+ const recent = this.recent(sub);
28
+ if (recent.length < RUNNER_TOKEN_MINTS_PER_WINDOW) {
29
+ this.tripped.delete(sub);
30
+ return undefined;
31
+ }
32
+ if (!this.tripped.has(sub)) {
33
+ if (this.tripped.size >= MAX_TRACKED_SUBJECTS) this.tripped.clear();
34
+ this.tripped.add(sub);
35
+ this.log(`[token-mint] rate limit tripped for ${sub}: ${RUNNER_TOKEN_MINTS_PER_WINDOW} tokens minted in the last ${RUNNER_TOKEN_MINT_WINDOW_MS / 1000}s; refusing until the window frees`);
36
+ }
37
+ return Math.max(1, Math.ceil((recent[0]! + RUNNER_TOKEN_MINT_WINDOW_MS - this.now()) / 1000));
38
+ }
39
+
40
+ /** Record one new token minted for `sub`. */
41
+ charge(sub: string): void {
42
+ const recent = this.recent(sub);
43
+ if (!this.mints.has(sub) && this.mints.size >= MAX_TRACKED_SUBJECTS) this.mints.delete(this.mints.keys().next().value!);
44
+ recent.push(this.now());
45
+ if (recent.length > RUNNER_TOKEN_MINTS_PER_WINDOW) recent.shift();
46
+ this.mints.set(sub, recent);
47
+ }
48
+
49
+ reset(): void {
50
+ this.mints.clear();
51
+ this.tripped.clear();
52
+ }
53
+
54
+ private recent(sub: string): number[] {
55
+ const since = this.now() - RUNNER_TOKEN_MINT_WINDOW_MS;
56
+ const recent = (this.mints.get(sub) ?? []).filter((at) => at > since);
57
+ if (recent.length) this.mints.set(sub, recent);
58
+ else this.mints.delete(sub);
59
+ return recent;
60
+ }
61
+ }
62
+
63
+ export const runnerTokenMintLimit = new RunnerTokenMintLimit();
@@ -24,7 +24,9 @@ const SPAWN_SCOPES = ["command:spawn", "command:shutdown"] as const;
24
24
  const FLEET_OPS_PROFILES = new Set(["coordinator", "ops-assistant"]);
25
25
  // #1925 H10 — fleet visibility is coordinator-facing. Keep this a fixed trusted-profile allowlist,
26
26
  // like command:fleet, rather than a user-editable profile setting that could widen worker tokens.
27
- const HOST_READ_PROFILES = new Set(["coordinator"]);
27
+ const HOST_WRITE_PROFILES = new Set(["coordinator"]);
28
+ // #3772 — the reboot runbook reads resource_control held/waiter counts from /api/hosts; ops profiles read, never write.
29
+ const HOST_READ_PROFILES = new Set([...HOST_WRITE_PROFILES, "ops-assistant", "sec-ops"]);
28
30
  // #3073 — coordinator-only groups (not in WORKER_TOOL_GROUPS) a coordinator always gets, even when its stored override predates them.
29
31
  const COORDINATOR_ONLY_TOOL_GROUPS = ["decisions", "workflows", "project", "routing"] as const;
30
32
  // #3327 — the coordinator edits routing preferences, like Edin in the dashboard.
@@ -109,7 +111,7 @@ export function deriveRunnerGrant(profile: string | undefined, authority: Deriva
109
111
  ...nonTrustedBaseline(agentInitiated),
110
112
  ...(fleetOps ? [FLEET_SCOPE] : []),
111
113
  ...(hostsRead ? ["hosts:read"] : []),
112
- ...(!agentInitiated && Boolean(profile && HOST_READ_PROFILES.has(profile)) && callerMayMintHostsScope(callerScopes, "hosts:write") ? ["hosts:write"] : []),
114
+ ...(!agentInitiated && Boolean(profile && HOST_WRITE_PROFILES.has(profile)) && callerMayMintHostsScope(callerScopes, "hosts:write") ? ["hosts:write"] : []),
113
115
  ...(grantsSecretsScope(profile, toolGroups, callerScopes) ? [SECRETS_WRITE_SCOPE] : []),
114
116
  ...(!agentInitiated && (COORDINATOR_PROFILE_NAMES as readonly string[]).includes(profile ?? "") && (callerScopes === undefined || hasScope(callerScopes, ROUTING_WRITE_SCOPE)) ? [ROUTING_WRITE_SCOPE] : []),
115
117
  ...(grant.canSpawn ? SPAWN_SCOPES.filter(scope => !agentInitiated || callerScopes === undefined || hasScope(callerScopes, scope)) : []),
@@ -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 { createTokenAsync, 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 {
@@ -306,6 +325,7 @@ function runnerTokenSpec(input: RunnerRuntimeTokenInput): { token: CreateTokenIn
306
325
  : { kind: "root", ...(authority.kind === "delegated" ? { launcherScopes: authority.callerScopes } : {}), ...(profile ? { profile } : {}) } };
307
326
  }
308
327
 
328
+ /** Sync twin for in-process tests; every runtime launch path mints through the writer op below. */
309
329
  export function issueRunnerRuntimeToken(input: RunnerRuntimeTokenInput): RuntimeTokenResult {
310
330
  const spec = runnerTokenSpec(input);
311
331
  const issued = createToken(spec.token);
@@ -314,6 +334,11 @@ export function issueRunnerRuntimeToken(input: RunnerRuntimeTokenInput): Runtime
314
334
  return issued;
315
335
  }
316
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
+
317
342
  export function issueInteractiveRunnerRuntimeToken(input: {
318
343
  provider: string;
319
344
  cwd: string;
@@ -393,7 +418,14 @@ export function issueIntegrationRuntimeToken(input: {
393
418
  }
394
419
 
395
420
  export function runnerRuntimeTokenEnv(input: RunnerRuntimeTokenInput): Record<string, string> {
396
- 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> {
397
429
  return writeChildEnv({}, {
398
430
  AGENT_RELAY_TOKEN: issued.token,
399
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
  }