@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
@@ -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) ---------------------------------
@@ -24,6 +24,8 @@ const releaseFile = process.env.CUTOVER_CHAOS_RELEASE_FILE;
24
24
  const resultFile = process.env.CUTOVER_CHAOS_RESULT_FILE;
25
25
  const owner = process.env.CUTOVER_CHAOS_OWNER ?? `worker-${process.pid}`;
26
26
  const leaseMs = Number(process.env.CUTOVER_CHAOS_LEASE_MS ?? "400");
27
+ let now = Number(required("CUTOVER_CHAOS_NOW"));
28
+ const clock = () => now;
27
29
 
28
30
  function required(name: string): string {
29
31
  const value = relayEnv(process.env, name);
@@ -60,7 +62,7 @@ function timerInput(idSuffix = String(process.pid)): CreateScheduledTimerRowInpu
60
62
  createdByKind: "system",
61
63
  createdByScopes: ["message:send"],
62
64
  deliveryFrom: "system",
63
- nextRunAt: Date.now() + 100_000,
65
+ nextRunAt: now + 100_000,
64
66
  scheduleKind: "once",
65
67
  scheduleDef: { afterMs: 100_000 },
66
68
  body: `cutover-${idSuffix}`,
@@ -87,6 +89,7 @@ async function main(): Promise<void> {
87
89
  };
88
90
  const c = new TimerMessageCutoverCoordinator({
89
91
  ownerId: owner,
92
+ now: clock,
90
93
  leaseMs,
91
94
  tickMs: 25,
92
95
  quiesceTimeoutMs: 5_000,
@@ -94,7 +97,7 @@ async function main(): Promise<void> {
94
97
  faults,
95
98
  });
96
99
  await c.requestQuiesce();
97
- if (boundary === "after-renewal") await Bun.sleep(Math.ceil(leaseMs * 0.75));
100
+ if (boundary === "after-renewal") now += Math.ceil(leaseMs * 0.75);
98
101
  await c.tick();
99
102
  await c.tick();
100
103
  await c.tick();
@@ -112,7 +115,7 @@ async function main(): Promise<void> {
112
115
  const ticks = phaseTicks[boundary];
113
116
  if (ticks === undefined) throw new Error(`unknown deadline-watch phase ${boundary}`);
114
117
  const c = new TimerMessageCutoverCoordinator({
115
- ownerId: owner, leaseMs: 10_000, tickMs: 25, quiesceTimeoutMs: 8_000, worstCaseDrainTransferMs: 10_000,
118
+ ownerId: owner, now: clock, leaseMs: 10_000, tickMs: 25, quiesceTimeoutMs: 8_000, worstCaseDrainTransferMs: 10_000,
116
119
  });
117
120
  await c.requestQuiesce();
118
121
  for (let i = 0; i < ticks; i++) await c.tick();
@@ -124,14 +127,14 @@ async function main(): Promise<void> {
124
127
 
125
128
  if (command === "claim-bound") {
126
129
  const c = new TimerMessageCutoverCoordinator({
127
- ownerId: owner, leaseMs: 1_000, tickMs: 25, quiesceTimeoutMs: 300, worstCaseDrainTransferMs: 1_000,
130
+ ownerId: owner, now: clock, leaseMs: 1_000, tickMs: 25, quiesceTimeoutMs: 300, worstCaseDrainTransferMs: 1_000,
128
131
  });
129
132
  await c.requestQuiesce();
130
133
  await c.tick();
131
134
  await c.tick();
132
135
  const drain = await c.tick();
133
136
  if (boundary === "held") {
134
- await Bun.sleep(340);
137
+ now += 340;
135
138
  await c.tick();
136
139
  await c.tick();
137
140
  }
@@ -141,7 +144,7 @@ async function main(): Promise<void> {
141
144
 
142
145
  if (command === "recover") {
143
146
  const c = new TimerMessageCutoverCoordinator({
144
- ownerId: owner, leaseMs, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
147
+ ownerId: owner, now: clock, leaseMs, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
145
148
  });
146
149
  const outcomes = [];
147
150
  for (let i = 0; i < 5; i++) outcomes.push(await c.tick());
@@ -152,7 +155,7 @@ async function main(): Promise<void> {
152
155
  if (command === "close-gate") {
153
156
  marker();
154
157
  const c = new TimerMessageCutoverCoordinator({
155
- ownerId: owner, leaseMs: 5_000, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
158
+ ownerId: owner, now: clock, leaseMs: 5_000, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
156
159
  });
157
160
  const acquired = await c.requestQuiesce();
158
161
  const outcome = acquired ? await c.tick() : null;
@@ -322,7 +322,7 @@ export class TimerIngressRejectedError extends Error {
322
322
  }
323
323
 
324
324
  export function createTimerIngress(options: TimerIngressOptions): TimerIngress {
325
- installTimerIngressSchema();
325
+ installTimerIngressSchema({ ifNotInstalled: true });
326
326
  const apply = options.apply;
327
327
  const ownerId = options.ownerId ?? `${process.pid}-${randomUUID()}`;
328
328
  const now = options.now ?? Date.now;
@@ -1,3 +1,5 @@
1
+ import { repoDefaultBranch } from "@humwick/sdk/land-gates";
2
+ import { withWorkspaceTaskOwnerRoles } from "../task-owner-notices";
1
3
  // #1713 — the coordinator's "landed" signal, derived from GIT ANCESTRY rather than a command's
2
4
  // reported status. The observed bug: a worker delivers `complete`, the merge executor stalls, and
3
5
  // the coordinator is nonetheless told the work is "complete"/"landed" while `origin/main` is
@@ -113,7 +115,8 @@ const realVerifier: AncestryVerifier = async (repoRoot, sha, targetRef) => {
113
115
  // source of truth; a fetch failure/timeout is swallowed — the checks below then run against
114
116
  // whatever refs are already local, preserving the prior fail-closed behavior exactly.
115
117
  const remoteBranch = ref.startsWith("origin/") ? ref.slice("origin/".length) : ref;
116
- if (remoteBranch) await gitStatus(root, ["fetch", "--quiet", "origin", remoteBranch], GIT_FETCH_TIMEOUT_MS);
118
+ const fetched = remoteBranch ? await gitStatus(root, ["fetch", "--quiet", "origin", `+refs/heads/${remoteBranch}:refs/remotes/origin/${remoteBranch}`], GIT_FETCH_TIMEOUT_MS) : null;
119
+ if (ref.startsWith("origin/") && fetched !== 0) return "unresolvable";
117
120
  // The sha itself must resolve to a commit in this repo, else ancestry is unknowable (not a
118
121
  // negative). `--verify --quiet ^{commit}` distinguishes "sha absent" from "sha present but not
119
122
  // on the ref".
@@ -246,6 +249,18 @@ export async function verifyLandedAncestry(repoRoot: string | undefined, sha: st
246
249
  }
247
250
  }
248
251
 
252
+ /** Notice proof requires the freshly fetched remote; a local-only main never qualifies (#3761). */
253
+ export async function verifyPublishedLand(repoRoot: string | undefined, sha: string | undefined, baseRef?: string): Promise<LandedAncestryVerdict> {
254
+ if (!repoRoot || !sha?.trim()) return "unresolvable";
255
+ try {
256
+ const base = baseRef ?? await repoDefaultBranch(async args => {
257
+ const result = await gitOutput(repoRoot, args);
258
+ return { ok: result?.exitCode === 0, stdout: result?.stdout ?? "", unavailable: !result };
259
+ });
260
+ return base ? await verifyLandedAncestry(repoRoot, sha, base.startsWith("origin/") ? base : `origin/${base}`) : "unresolvable";
261
+ } catch { return "unresolvable"; }
262
+ }
263
+
249
264
  // #2718 — the landing host's own fresh-fetch ancestry check (`landVerifiedSha`) is ref-backed proof
250
265
  // that works wherever the repo lives; the hub's check needs a checkout of the same remote on its own
251
266
  // disk. Without a matching host verdict, fall back to the hub check (an absent path never confirms).
@@ -366,6 +381,7 @@ export function notifyLandUnconfirmed(input: LandUnconfirmedInput): void {
366
381
  const { subject, body } = landUnconfirmedText(input);
367
382
 
368
383
  const payload = {
384
+ verified: false,
369
385
  kind: "workspace.land-unconfirmed",
370
386
  notification: "land-unconfirmed",
371
387
  landOutcome: kind,
@@ -397,7 +413,7 @@ export function notifyLandUnconfirmed(input: LandUnconfirmedInput): void {
397
413
  metadata: { source: "server", maintenanceJobId: "workspace-land-unconfirmed", workspaceId: workspace.id, mergeCommandId: input.commandId, landOutcome: kind },
398
414
  });
399
415
 
400
- const recipients = [...new Set([owner, coordinator, fallback].filter((r): r is string => Boolean(r)))];
416
+ const recipients = withWorkspaceTaskOwnerRoles([owner, coordinator, fallback], workspace);
401
417
  if (recipients.length === 0) return;
402
418
  routeNotification({
403
419
  type: "workspace.land_unconfirmed",
@@ -407,7 +423,7 @@ export function notifyLandUnconfirmed(input: LandUnconfirmedInput): void {
407
423
  forceDelivery: true,
408
424
  message: {
409
425
  subject,
410
- body,
426
+ body: `${body} verified: false.`,
411
427
  payload,
412
428
  idempotencyKey: kind === "merge-failed" ? landFailureEventKey(workspace, input.commandId)
413
429
  : `land-unconfirmed:${workspace.id}:${input.commandId ?? "nocmd"}:${kind}`,
@@ -2,15 +2,13 @@
2
2
  // (relay_project_write_entry, relay_store_memory, the project-entry REST routes) resolves the
3
3
  // vocabulary and the stored value here, so the closed vocabulary cannot drift between them.
4
4
 
5
- import { AREAS_FILE, SYSTEM_AREAS, SYSTEM_AREA_DESCRIPTIONS, areasAtCommit, injectsAtStartup, isSystemArea, readAreasAtDefaultBranch, type AreaDefinition, type AreasAtCommit } from "@humwick/sdk/areas";
6
- import { errMessage, stringValue } from "@humwick/sdk";
7
- import { countActivePinnedEntriesInArea, getAgent, rootsForProject, ValidationError } from "../db";
8
- import { localProjectGit, readHostProjectFiles } from "../project-roles";
9
- import { hostIsRelayHost } from "../relay-host-locality";
5
+ import { AREAS_FILE, SYSTEM_AREAS, SYSTEM_AREA_DESCRIPTIONS, areasAtCommit, injectsAtStartup, isSystemArea, type AreaDefinition, type AreasConfig } from "@humwick/sdk/areas";
6
+ import { stringValue } from "@humwick/sdk";
7
+ import { countActivePinnedEntriesInArea, getAgent, ValidationError } from "../db";
10
8
  import type { Project, ProjectEntry } from "../types";
11
9
  import type { AuthContext } from "./auth-context";
12
10
  import { callerHost, callerProjectId } from "./memory-identity";
13
- import { resolveProjectFilesHost } from "./spawn-project-role";
11
+ import { readProjectConfig, readProjectConfigFirst, type ConfigLookup, type ProjectCheckout } from "./project-config-read";
14
12
 
15
13
  export const MAX_ENTRY_AREAS = 4;
16
14
  const PROJECT_WIDE = "project-wide";
@@ -47,42 +45,15 @@ export function areaCallerFromCtx(ctx: AuthContext): AreaCaller {
47
45
  }
48
46
 
49
47
  /** A checkout to read areas.json from: a cwd on a host (a hostname or a registered orchestrator). */
50
- export interface AreasCheckout {
51
- cwd: string;
52
- host?: string;
53
- orchestratorId?: string;
54
- }
55
-
56
- export type AreasLookup = AreasAtCommit | { status: "unreadable"; error: string };
48
+ export type AreasCheckout = ProjectCheckout;
57
49
 
58
- const loggedProblems = new Set<string>();
50
+ export type AreasLookup = ConfigLookup<AreasConfig>;
59
51
 
60
- function logProblemOnce(key: string, problem: string): void {
61
- if (loggedProblems.has(key)) return;
62
- if (loggedProblems.size >= 1000) loggedProblems.clear();
63
- loggedProblems.add(key);
64
- console.warn(`[memory-areas] ${AREAS_FILE} for ${key}: ${problem}; accepting system areas only`);
65
- }
52
+ const ACCEPTING_SYSTEM_AREAS = "accepting system areas only";
66
53
 
67
- /** #3635 — the areas.json at the checkout's default branch (origin/<default>, fetched first), read
68
- * on the checkout's host through the #3043 project-files reader. Never a working tree and never
69
- * cached, so a stale checkout or an old negative answer cannot hide a file that landed. */
70
- export async function readAreasConfig(checkout: AreasCheckout): Promise<AreasLookup> {
71
- const where = `${checkout.orchestratorId ?? checkout.host ?? "local"}:${checkout.cwd}`;
72
- try {
73
- const lookup = !checkout.orchestratorId && hostIsRelayHost(checkout.host)
74
- ? await readAreasAtDefaultBranch(localProjectGit(checkout.cwd))
75
- : areasAtCommit(await readHostProjectFiles(
76
- resolveProjectFilesHost({ subject: AREAS_FILE, cwd: checkout.cwd, orchestratorId: checkout.orchestratorId, preferHost: checkout.host }),
77
- checkout.cwd, { paths: [AREAS_FILE] }, AREAS_FILE,
78
- ));
79
- if (lookup.status === "invalid") logProblemOnce(`${where}@${lookup.sha}`, `invalid: ${lookup.error}`);
80
- return lookup;
81
- } catch (err) {
82
- const error = errMessage(err);
83
- logProblemOnce(where, `unreadable: ${error}`);
84
- return { status: "unreadable", error };
85
- }
54
+ /** #3635 — the areas.json at the checkout's default branch, through the shared project-config reader. */
55
+ export function readAreasConfig(checkout: AreasCheckout): Promise<AreasLookup> {
56
+ return readProjectConfig(checkout, AREAS_FILE, areasAtCommit, ACCEPTING_SYSTEM_AREAS);
86
57
  }
87
58
 
88
59
  function invalidDetail(lookup: Extract<AreasLookup, { status: "invalid" }>): string {
@@ -94,18 +65,11 @@ function invalidDetail(lookup: Extract<AreasLookup, { status: "invalid" }>): str
94
65
  * answers. Every other cell (base/provider/host) takes system areas only, optionally. */
95
66
  export async function resolveAreaVocabulary(anchor: Project, caller: AreaCaller | undefined): Promise<AreaVocabulary> {
96
67
  if (anchor.scope !== "project") return { pathAreas: [], required: false, pathAreasMissing: "not-project" };
97
- const checkouts: AreasCheckout[] = [
98
- ...(caller?.cwd && caller.projectId === anchor.id ? [{ cwd: caller.cwd, host: caller.host }] : []),
99
- ...rootsForProject(anchor.id).map((root) => ({ cwd: root.rootPath, ...(root.orchestratorId ? { orchestratorId: root.orchestratorId } : {}) })),
100
- ];
101
- const failures: string[] = [];
102
- for (const checkout of checkouts) {
103
- const lookup = await readAreasConfig(checkout);
104
- if (lookup.status === "found") return { pathAreas: lookup.config.areas, required: true };
105
- if (lookup.status === "absent") return { pathAreas: [], required: false, pathAreasMissing: "undeclared" };
106
- if (lookup.status === "invalid") return { pathAreas: [], required: false, pathAreasMissing: "invalid", pathAreasDetail: invalidDetail(lookup) };
107
- failures.push(lookup.error);
108
- }
68
+ const preferred = caller?.cwd && caller.projectId === anchor.id ? [{ cwd: caller.cwd, host: caller.host }] : [];
69
+ const { lookup, failures } = await readProjectConfigFirst(anchor.id, preferred, AREAS_FILE, areasAtCommit, ACCEPTING_SYSTEM_AREAS);
70
+ if (lookup?.status === "found") return { pathAreas: lookup.config.areas, required: true };
71
+ if (lookup?.status === "absent") return { pathAreas: [], required: false, pathAreasMissing: "undeclared" };
72
+ if (lookup?.status === "invalid") return { pathAreas: [], required: false, pathAreasMissing: "invalid", pathAreasDetail: invalidDetail(lookup) };
109
73
  return { pathAreas: [], required: false, pathAreasMissing: "unreadable", pathAreasDetail: failures.join("; ") || "the project has no checkout to read it from" };
110
74
  }
111
75
 
@@ -0,0 +1,57 @@
1
+ // #3716 — workflow effects read CI episodes without depending on workflow launch or recovery.
2
+ import { ValidationError } from "../db/connection";
3
+ import { computeCiFailureFingerprint } from "../ci/incidents";
4
+ import type { CiShaObservation } from "../db/ci-incidents";
5
+ import type { StoredPlanCiBlock } from "../db/plan-ci-blocks";
6
+ import { getGateway } from "../db/gateway";
7
+ import { getPlanGraphProjectionAsync } from "../db/plan-graph-async";
8
+ import { planNoticeTarget } from "../db/plan-completion";
9
+ import { notifySystemMessage } from "../notify";
10
+ import type { WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
11
+
12
+ /** Same watch and landed SHA, or the same failing jobs: an unrelated red main must not quiet this failure. */
13
+ export async function correlatedCiIncidents(rows: Array<{ workflow: string; row?: CiShaObservation }>): Promise<string[]> {
14
+ const ids: string[] = [];
15
+ for (const { workflow, row } of rows) {
16
+ if (!row) return [];
17
+ const incident = await getGateway().read("ciIncidents.activeForWatch", { watchKey: row.watchKey });
18
+ if (!incident) return [];
19
+ if (incident.openedHeadSha !== row.headSha) {
20
+ if (!row.failingJobs?.length) return [];
21
+ const fingerprint = computeCiFailureFingerprint({ workflow, failingRuns: [{ id: "sha", status: "completed", jobs: row.failingJobs.map((job) => ({ id: job.name, name: job.name })) }], conclusion: "failure" });
22
+ if (fingerprint !== incident.fingerprint) return [];
23
+ }
24
+ ids.push(incident.id);
25
+ }
26
+ return [...new Set(ids)].sort();
27
+ }
28
+
29
+ /** Return a quiet receipt only after both the blocked attempt and the actionable plan notice are durable. */
30
+ export async function planCiNotice(run: WorkflowRunRecord, payload: Record<string, unknown>, result = false): Promise<Record<string, unknown> | undefined> {
31
+ const owner = await getGateway().read("planDispatch.owner", { taskId: run.taskId });
32
+ if (!owner || owner.audit) return undefined;
33
+ let block = await getGateway().read("planDispatch.ci.block", { runId: run.id });
34
+ const incidentIds = Array.isArray(payload.incidentIds) ? payload.incidentIds.filter((id): id is string => typeof id === "string") : [];
35
+ if (!result && incidentIds.length && typeof payload.landedSha === "string") {
36
+ try {
37
+ block = await getGateway().write("planDispatch.ci.block", { planId: owner.planId, taskId: run.taskId, runId: run.id, sha: payload.landedSha, incidentIds, waitKey: run.escalation?.waitKey ?? "" }) as unknown as StoredPlanCiBlock;
38
+ } catch (error) {
39
+ // A superseded attempt must still send its normal CI notice (#3660).
40
+ if (error instanceof ValidationError && error.message === "CI blocker no longer owns its plan slot") return undefined;
41
+ throw error;
42
+ }
43
+ }
44
+ if (!block || (result && (block.retriedAt !== undefined || block.waitKey !== run.escalation?.waitKey)) || (!result && !incidentIds.length)) return undefined;
45
+ const plan = await getPlanGraphProjectionAsync({ planId: owner.planId });
46
+ const to = plan && planNoticeTarget(plan);
47
+ if (!to) return undefined;
48
+ for (const incidentId of block.incidentIds) {
49
+ notifySystemMessage(to, {
50
+ subject: `Plan ${plan.title ?? plan.id}: CI blocked`,
51
+ body: `Plan ${plan.id}: T${run.taskId}, run ${run.id}, landed SHA ${block.sha}, is blocked by CI incident ${incidentId}. Fix the incident; Relay automatically retries each blocked run's exact landed SHA after clearance. Slots and dependents wait. relay_plan_get shows every blocked Task and run.`,
52
+ idempotencyKey: `plan:${plan.id}:ci:${incidentId}`, replyExpected: false,
53
+ payload: { kind: "plan.ci-blocked", planId: plan.id, incidentId, taskId: run.taskId, runId: run.id, landedSha: block.sha },
54
+ });
55
+ }
56
+ return { quiet: "plan-ci-episode", planId: owner.planId, incidentIds: block.incidentIds };
57
+ }
@@ -1,62 +1,9 @@
1
- // #3660 — correlate only known CI episodes; clearance reuses the original run's CI resume path.
2
- import { ValidationError } from "../db/connection";
3
- import { computeCiFailureFingerprint } from "../ci/incidents";
4
- import type { CiShaObservation } from "../db/ci-incidents";
1
+ // #3660 — clearance reuses the original run's CI resume path.
2
+ import type { WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
5
3
  import type { StoredPlanCiBlock } from "../db/plan-ci-blocks";
6
4
  import { getGateway } from "../db/gateway";
7
- import { getPlanGraphProjectionAsync } from "../db/plan-graph-async";
8
- import { planNoticeTarget } from "../db/plan-completion";
9
- import { notifySystemMessage } from "../notify";
10
- import type { WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
11
5
  import { resumeWorkflowRun } from "./workflow-runs";
12
6
 
13
- /** Same watch and landed SHA, or the same failing jobs: an unrelated red main must not quiet this failure. */
14
- export async function correlatedCiIncidents(rows: Array<{ workflow: string; row?: CiShaObservation }>): Promise<string[]> {
15
- const ids: string[] = [];
16
- for (const { workflow, row } of rows) {
17
- if (!row) return [];
18
- const incident = await getGateway().read("ciIncidents.activeForWatch", { watchKey: row.watchKey });
19
- if (!incident) return [];
20
- if (incident.openedHeadSha !== row.headSha) {
21
- if (!row.failingJobs?.length) return [];
22
- const fingerprint = computeCiFailureFingerprint({ workflow, failingRuns: [{ id: "sha", status: "completed", jobs: row.failingJobs.map((job) => ({ id: job.name, name: job.name })) }], conclusion: "failure" });
23
- if (fingerprint !== incident.fingerprint) return [];
24
- }
25
- ids.push(incident.id);
26
- }
27
- return [...new Set(ids)].sort();
28
- }
29
-
30
- /** Return a quiet receipt only after both the blocked attempt and the actionable plan notice are durable. */
31
- export async function planCiNotice(run: WorkflowRunRecord, payload: Record<string, unknown>, result = false): Promise<Record<string, unknown> | undefined> {
32
- const owner = await getGateway().read("planDispatch.owner", { taskId: run.taskId });
33
- if (!owner || owner.audit) return undefined;
34
- let block = await getGateway().read("planDispatch.ci.block", { runId: run.id });
35
- const incidentIds = Array.isArray(payload.incidentIds) ? payload.incidentIds.filter((id): id is string => typeof id === "string") : [];
36
- if (!result && incidentIds.length && typeof payload.landedSha === "string") {
37
- try {
38
- block = await getGateway().write("planDispatch.ci.block", { planId: owner.planId, taskId: run.taskId, runId: run.id, sha: payload.landedSha, incidentIds, waitKey: run.escalation?.waitKey ?? "" }) as unknown as StoredPlanCiBlock;
39
- } catch (error) {
40
- // A superseded attempt must still send its normal CI notice (#3660).
41
- if (error instanceof ValidationError && error.message === "CI blocker no longer owns its plan slot") return undefined;
42
- throw error;
43
- }
44
- }
45
- if (!block || (result && (block.retriedAt !== undefined || block.waitKey !== run.escalation?.waitKey)) || (!result && !incidentIds.length)) return undefined;
46
- const plan = await getPlanGraphProjectionAsync({ planId: owner.planId });
47
- const to = plan && planNoticeTarget(plan);
48
- if (!to) return undefined;
49
- for (const incidentId of block.incidentIds) {
50
- notifySystemMessage(to, {
51
- subject: `Plan ${plan.title ?? plan.id}: CI blocked`,
52
- body: `Plan ${plan.id}: T${run.taskId}, run ${run.id}, landed SHA ${block.sha}, is blocked by CI incident ${incidentId}. Fix the incident; Relay automatically retries each blocked run's exact landed SHA after clearance. Slots and dependents wait. relay_plan_get shows every blocked Task and run.`,
53
- idempotencyKey: `plan:${plan.id}:ci:${incidentId}`, replyExpected: false,
54
- payload: { kind: "plan.ci-blocked", planId: plan.id, incidentId, taskId: run.taskId, runId: run.id, landedSha: block.sha },
55
- });
56
- }
57
- return { quiet: "plan-ci-episode", planId: owner.planId, incidentIds: block.incidentIds };
58
- }
59
-
60
7
  /** A kernel resume note is the durable receipt if the process dies before marking the plan's row. */
61
8
  export async function recoverPlanCiBlock(run: WorkflowRunRecord): Promise<StoredPlanCiBlock | null> {
62
9
  const block = await getGateway().read("planDispatch.ci.block", { runId: run.id });