@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,953 +0,0 @@
1
- import { z } from "zod";
2
- import { landingIncidentEntities, landingIncidentOps } from "./landing-incident";
3
- import { type EntityStore, type JsonValue, type ModuleCtx, type StepResult, OpError, defineModule } from "./contract";
4
-
5
- export const LANDING_STATES = [
6
- "controller_wait",
7
- "queued",
8
- "supersession_wait",
9
- "lane_wait",
10
- "authorization_required",
11
- "preparing",
12
- "gating",
13
- "gate_failed",
14
- "publish_ready",
15
- "publishing",
16
- "verifying",
17
- "cleanup",
18
- "receipt_ready",
19
- "external_retire_pending",
20
- "retry_wait",
21
- "reconciliation_required",
22
- "supersede_pending",
23
- "cancel_pending",
24
- "landed",
25
- "superseded",
26
- "canceled",
27
- "failed",
28
- ] as const;
29
-
30
- const landingStateSchema = z.enum(LANDING_STATES);
31
- const terminalProcessProofSchema = z.enum(["never_started", "completed", "process_tree_dead"]);
32
-
33
- const workspaceHeadSchema = z.object({
34
- id: z.string(),
35
- workspaceId: z.string(),
36
- currentRunId: z.string(),
37
- generation: z.number().int(),
38
- authorizationEventId: z.number().int(),
39
- updatedAt: z.number().int(),
40
- });
41
-
42
- const runSchema = z.object({
43
- id: z.string(),
44
- workspaceId: z.string(),
45
- laneKey: z.string().optional(),
46
- generation: z.number().int(),
47
- authorizationEventId: z.number().int(),
48
- workflowInstanceId: z.string().optional(),
49
- sourceRef: z.string(),
50
- sourceHeadSha: z.string(),
51
- repoRoot: z.string(),
52
- remoteIdentity: z.string(),
53
- baseRef: z.string(),
54
- strategy: z.string(),
55
- gateSnapshot: z.string(),
56
- requesterSnapshot: z.string(),
57
- baseHeadSha: z.string().optional(),
58
- candidateHeadSha: z.string().optional(),
59
- candidateTreeSha: z.string().optional(),
60
- pushRemote: z.string().optional(),
61
- selectedOrchestratorId: z.string().optional(),
62
- selectedCapabilityDigest: z.string().optional(),
63
- selectedSafetyDigest: z.string().optional(),
64
- candidateWorktreePath: z.string().optional(),
65
- executionTreeHandle: z.string().optional(),
66
- controllerVersion: z.number().int().optional(),
67
- controllerToken: z.string().optional(),
68
- laneToken: z.string().optional(),
69
- gateTreeSha: z.string().optional(),
70
- gateConfigDigest: z.string().optional(),
71
- gateAttestation: z.string().optional(),
72
- receiptId: z.string().optional(),
73
- receiptTargetSha: z.string().optional(),
74
- receiptVerifiedAt: z.number().int().optional(),
75
- receiptVerificationGeneration: z.number().int().optional(),
76
- receiptKind: z.enum(["kernel", "external"]).optional(),
77
- state: landingStateSchema,
78
- stateVersion: z.number().int(),
79
- stateSince: z.number().int(),
80
- queuedAt: z.number().int(),
81
- attempt: z.number().int(),
82
- phaseOrdinal: z.number().int(),
83
- commandGeneration: z.number().int(),
84
- // Optional for migration safety: Phase 3 rows predate this dedicated legacy projection clock.
85
- // commandGeneration remains reserved for the Phase 4 Kernel command fence.
86
- legacyProgressSequence: z.number().int().optional(),
87
- commandId: z.string().optional(),
88
- lastDryRunCommandId: z.string().optional(),
89
- nextRetryAt: z.number().int().optional(),
90
- transientFailures: z.number().int(),
91
- gateFailures: z.number().int(),
92
- pushRaces: z.number().int(),
93
- executorRecoveries: z.number().int(),
94
- blockerCode: z.string().optional(),
95
- blockerDetail: z.string().optional(),
96
- blockedAt: z.number().int().optional(),
97
- supersededBy: z.string().optional(),
98
- landedSha: z.string().optional(),
99
- landedAt: z.number().int().optional(),
100
- terminalProcessProof: terminalProcessProofSchema.optional(),
101
- legacyCommandId: z.string().optional(),
102
- legacyStatus: z.string().optional(),
103
- shadowVerdict: z.enum(["parity", "mismatch"]).optional(),
104
- shadowSummaryDigest: z.string().optional(),
105
- lastComparisonDigest: z.string().optional(),
106
- lastComparisonVersion: z.number().int().optional(),
107
- consecutiveStaleComparisons: z.number().int().optional(),
108
- shadowDetail: z.string().optional(),
109
- durableExitKind: z.enum(["command_wait", "event_wait", "sleep", "supervisor_reconcile", "explicit_action"]).optional(),
110
- observationGeneration: z.number().int().optional(),
111
- lastObservedAt: z.number().int().optional(),
112
- nextObservationAt: z.number().int().optional(),
113
- lastObservationDigest: z.string().optional(),
114
- observationRepeatCount: z.number().int().optional(),
115
- predictedOutcome: z.enum(["none", "external_retire", "reconciliation_required"]).optional(),
116
- lastReconciledAt: z.number().int().optional(),
117
- createdAt: z.number().int(),
118
- updatedAt: z.number().int(),
119
- });
120
-
121
- const laneSchema = z.object({
122
- id: z.string(),
123
- laneKey: z.string(),
124
- repoRoot: z.string(),
125
- remoteIdentity: z.string(),
126
- baseRef: z.string(),
127
- identityDigest: z.string(),
128
- currentRunId: z.string().optional(),
129
- retiringRunId: z.string().optional(),
130
- laneToken: z.string().optional(),
131
- leaseExpiresAt: z.number().int().optional(),
132
- activeCommandId: z.string().optional(),
133
- activeExecutionGeneration: z.number().int().optional(),
134
- drainEpochId: z.string().optional(),
135
- lastTerminalProcessProof: terminalProcessProofSchema.optional(),
136
- updatedAt: z.number().int(),
137
- });
138
-
139
- const noticeSchema = z.object({
140
- id: z.string(),
141
- runId: z.string(),
142
- stateVersion: z.number().int(),
143
- audience: z.string(),
144
- kind: z.string(),
145
- status: z.enum(["pending", "sent"]),
146
- payload: z.string(),
147
- createdAt: z.number().int(),
148
- sentAt: z.number().int().optional(),
149
- });
150
-
151
- const effectSchema = z.object({
152
- id: z.string(),
153
- runId: z.string(),
154
- stateVersion: z.number().int(),
155
- kind: z.string(),
156
- status: z.enum(["pending", "completed"]),
157
- payload: z.string(),
158
- createdAt: z.number().int(),
159
- completedAt: z.number().int().optional(),
160
- deliveryKey: z.string().optional(),
161
- claimedAt: z.number().int().optional(),
162
- attempts: z.number().int().optional(),
163
- });
164
-
165
- const comparisonSchema = z.object({
166
- id: z.string(),
167
- runId: z.string(),
168
- generation: z.number().int(),
169
- comparisonVersion: z.number().int(),
170
- verdict: z.enum(["parity", "mismatch", "stale"]),
171
- legacyState: z.string(),
172
- shadowState: landingStateSchema,
173
- summaryDigest: z.string(),
174
- detail: z.string(),
175
- createdAt: z.number().int(),
176
- });
177
-
178
- const targetObservationSchema = z.object({
179
- id: z.string(),
180
- runId: z.string(),
181
- generation: z.number().int(),
182
- observationGeneration: z.number().int(),
183
- sourceHeadSha: z.string(),
184
- candidateHeadSha: z.string().optional(),
185
- targetRef: z.string(),
186
- targetHeadSha: z.string().optional(),
187
- sourceContained: z.boolean().optional(),
188
- candidateContained: z.boolean().optional(),
189
- containmentMethod: z.string().optional(),
190
- status: z.enum(["positive", "negative", "unknown"]),
191
- observedAt: z.number().int(),
192
- });
193
-
194
- const receiptSchema = z.object({
195
- id: z.string(),
196
- runId: z.string(),
197
- workspaceId: z.string(),
198
- generation: z.number().int(),
199
- laneKey: z.string(),
200
- kind: z.enum(["kernel", "external"]),
201
- sourceRef: z.string(),
202
- sourceHeadSha: z.string(),
203
- targetRef: z.string(),
204
- targetHeadSha: z.string(),
205
- candidateHeadSha: z.string().optional(),
206
- gateAttestation: z.string().optional(),
207
- verificationGeneration: z.number().int(),
208
- verifiedAt: z.number().int(),
209
- createdAt: z.number().int(),
210
- });
211
-
212
- type LandingStores = {
213
- workspaceHead: EntityStore<{ schema: typeof workspaceHeadSchema }>;
214
- run: EntityStore<{ schema: typeof runSchema }>;
215
- lane: EntityStore<{ schema: typeof laneSchema }>;
216
- notice: EntityStore<{ schema: typeof noticeSchema }>;
217
- effect: EntityStore<{ schema: typeof effectSchema }>;
218
- targetObservation: EntityStore<{ schema: typeof targetObservationSchema }>;
219
- comparison: EntityStore<{ schema: typeof comparisonSchema }>;
220
- receipt: EntityStore<{ schema: typeof receiptSchema }>;
221
- };
222
-
223
- function stores(ctx: ModuleCtx): LandingStores {
224
- return ctx.entities as unknown as LandingStores;
225
- }
226
-
227
- export function landingRunId(workspaceId: string, authorizationEventId: number): string {
228
- return `land:${workspaceId}:event:${authorizationEventId}`;
229
- }
230
-
231
- const frozenIntentSchema = z.object({
232
- repoRoot: z.string().min(1),
233
- remoteIdentity: z.string().min(1),
234
- baseRef: z.string().min(1),
235
- strategy: z.string().min(1),
236
- gateSnapshot: z.record(z.string(), z.unknown()),
237
- requester: z.object({ actor: z.string().min(1), agentId: z.string().optional() }),
238
- });
239
-
240
- const admissionInputSchema = z.object({
241
- authorizationEventId: z.number().int().positive(),
242
- workspaceId: z.string().min(1),
243
- laneKey: z.string().min(1).optional(),
244
- sourceRef: z.string().min(1),
245
- sourceHeadSha: z.string().min(1),
246
- queuedAt: z.number().int(),
247
- frozenIntent: frozenIntentSchema,
248
- });
249
-
250
- const dryRunTupleSchema = z.object({
251
- runId: z.string(), generation: z.number().int(), commandGeneration: z.number().int(),
252
- attempt: z.number().int(), phaseOrdinal: z.number().int(), stateVersion: z.number().int(),
253
- });
254
-
255
- const dryRunCommandResultInputSchema = z.object({
256
- commandId: z.string().min(1),
257
- type: z.enum(["landing.prepare", "landing.gate", "landing.publish", "landing.verify", "landing.observe-target", "landing.cleanup"]),
258
- tuple: dryRunTupleSchema,
259
- terminalProcessProof: z.literal("completed"),
260
- result: z.record(z.string(), z.unknown()),
261
- });
262
-
263
- const admissionOutputSchema = z.object({
264
- runId: z.string(),
265
- generation: z.number().int(),
266
- state: landingStateSchema,
267
- current: z.boolean(),
268
- workflowInstanceId: z.string().optional(),
269
- });
270
-
271
- const observationInputSchema = z.object({
272
- runId: z.string(),
273
- expectedGeneration: z.number().int(),
274
- expectedStateVersion: z.number().int(),
275
- observationGeneration: z.number().int().positive(),
276
- factDigest: z.string().min(1).max(200),
277
- sourceHeadSha: z.string(),
278
- candidateHeadSha: z.string().optional(),
279
- targetRef: z.string(),
280
- targetHeadSha: z.string().optional(),
281
- sourceContained: z.boolean().optional(),
282
- candidateContained: z.boolean().optional(),
283
- containmentMethod: z.string().optional(),
284
- status: z.enum(["positive", "negative", "unknown"]),
285
- });
286
-
287
- const finalizeControlInputSchema = z.object({
288
- runId: z.string(), expectedGeneration: z.number().int(), expectedStateVersion: z.number().int(),
289
- commandId: z.string(), terminalProcessProof: terminalProcessProofSchema,
290
- });
291
-
292
- const landingHostCommandInputSchema = z.object({
293
- type: z.enum(["landing.prepare", "landing.gate", "landing.publish", "landing.verify", "landing.cleanup"]),
294
- params: z.record(z.string(), z.unknown()),
295
- });
296
-
297
- const LANDING_ADMIN_GRANT = "landing:admin" as const;
298
-
299
- type ControllerWaitState = { runId: string; commandId?: string };
300
- type LandingState = z.output<typeof landingStateSchema>;
301
- type LandingControlAction = "cancel" | "fail" | "supersede" | "retry" | "reconcile";
302
-
303
- const NONTERMINAL_LANDING_STATES = LANDING_STATES.filter(
304
- (state): state is Exclude<LandingState, "landed" | "superseded" | "canceled" | "failed"> =>
305
- state !== "landed" && state !== "superseded" && state !== "canceled" && state !== "failed",
306
- );
307
-
308
- export type LandingDurableExit = "command_wait" | "event_wait" | "sleep" | "supervisor_reconcile" | "explicit_action";
309
-
310
- // Persisted exit labels retained for historical landing command/control compatibility.
311
- export const LANDING_DURABLE_EXITS = Object.freeze({
312
- controller_wait: "event_wait",
313
- queued: "supervisor_reconcile",
314
- supersession_wait: "event_wait",
315
- lane_wait: "event_wait",
316
- authorization_required: "sleep",
317
- preparing: "supervisor_reconcile",
318
- gating: "supervisor_reconcile",
319
- gate_failed: "explicit_action",
320
- publish_ready: "supervisor_reconcile",
321
- publishing: "supervisor_reconcile",
322
- verifying: "supervisor_reconcile",
323
- cleanup: "supervisor_reconcile",
324
- receipt_ready: "supervisor_reconcile",
325
- external_retire_pending: "supervisor_reconcile",
326
- retry_wait: "sleep",
327
- reconciliation_required: "explicit_action",
328
- supersede_pending: "supervisor_reconcile",
329
- cancel_pending: "supervisor_reconcile",
330
- } satisfies Record<(typeof NONTERMINAL_LANDING_STATES)[number], LandingDurableExit>);
331
-
332
- const LANDING_OBSERVATION_BASE_MS = 30_000;
333
- const LANDING_OBSERVATION_MAX_MS = 30 * 60_000;
334
-
335
- function repeatedObservationDelayMs(repeats: number): number {
336
- return Math.min(LANDING_OBSERVATION_MAX_MS, LANDING_OBSERVATION_BASE_MS * 2 ** Math.min(6, repeats));
337
- }
338
-
339
- function commandForRun(row: z.output<typeof runSchema>): z.input<typeof landingHostCommandInputSchema> | undefined {
340
- if (!row.laneKey || !row.baseHeadSha || !row.controllerToken || !row.laneToken || !row.selectedOrchestratorId
341
- || !row.selectedCapabilityDigest || !row.selectedSafetyDigest || !row.executionTreeHandle) return undefined;
342
- const common = { workspaceId: row.workspaceId, laneKey: row.laneKey, repoRoot: row.repoRoot,
343
- sourceRef: row.sourceRef, sourceHeadSha: row.sourceHeadSha, baseRef: row.baseRef,
344
- baseHeadSha: row.baseHeadSha, laneToken: row.laneToken,
345
- runTuple: { runId: row.id, generation: row.generation, commandGeneration: row.commandGeneration,
346
- attempt: row.attempt, phaseOrdinal: row.phaseOrdinal, stateVersion: row.stateVersion },
347
- selectedHost: { orchestratorId: row.selectedOrchestratorId,
348
- capabilityDigest: row.selectedCapabilityDigest, safetyDigest: row.selectedSafetyDigest },
349
- executionTreeHandle: row.executionTreeHandle };
350
- if (row.state === "preparing" && row.candidateWorktreePath) return {
351
- type: "landing.prepare", params: { ...common, candidateWorktreePath: row.candidateWorktreePath },
352
- };
353
- if (row.state === "gating" && row.candidateWorktreePath && row.candidateHeadSha) return {
354
- type: "landing.gate", params: { ...common, candidateWorktreePath: row.candidateWorktreePath,
355
- candidateHeadSha: row.candidateHeadSha, gates: [] },
356
- };
357
- if (row.state === "publish_ready" && (row.observationGeneration ?? 0) > 0
358
- && row.candidateWorktreePath && row.candidateHeadSha
359
- && row.pushRemote && row.gateTreeSha && row.gateConfigDigest && row.gateAttestation) return {
360
- type: "landing.publish", params: { ...common, candidateWorktreePath: row.candidateWorktreePath,
361
- pushRemote: row.pushRemote, candidateHeadSha: row.candidateHeadSha, gateTreeSha: row.gateTreeSha,
362
- gateConfigDigest: row.gateConfigDigest, gateAttestationDigest: row.gateAttestation,
363
- observationGeneration: row.observationGeneration ?? 0, expectedRestartEpoch: null,
364
- pushAttemptId: `push:${row.id}:${row.commandGeneration}`,
365
- timing: { pushTimeoutMs: 300_000, reapTimeoutMs: 60_000, reportTimeoutMs: 30_000,
366
- refreshedLaneLeaseMs: 15 * 60_000 } },
367
- };
368
- if (row.state === "verifying" && row.pushRemote) return { type: "landing.verify",
369
- params: { ...common, pushRemote: row.pushRemote,
370
- ...(row.candidateHeadSha ? { candidateHeadSha: row.candidateHeadSha } : {}) } };
371
- if (row.state === "cleanup" && row.candidateWorktreePath) return { type: "landing.cleanup",
372
- params: { ...common, candidateWorktreePath: row.candidateWorktreePath,
373
- ...(row.candidateHeadSha ? { candidateHeadSha: row.candidateHeadSha } : {}) } };
374
- return undefined;
375
- }
376
-
377
- const LANDING_CONTROL_TRANSITIONS = {
378
- cancel: { allowedFrom: new Set<LandingState>(NONTERMINAL_LANDING_STATES), target: "canceled", workflow: "cancel" },
379
- fail: { allowedFrom: new Set<LandingState>(NONTERMINAL_LANDING_STATES), target: "failed", workflow: "fail" },
380
- supersede: { allowedFrom: new Set<LandingState>(NONTERMINAL_LANDING_STATES), target: "superseded", workflow: "cancel" },
381
- retry: { allowedFrom: new Set<LandingState>(["failed", "gate_failed", "retry_wait", "authorization_required"]), target: "controller_wait", workflow: "reopen" },
382
- reconcile: { allowedFrom: new Set<LandingState>(["reconciliation_required"]), target: "controller_wait", workflow: "reopen" },
383
- } as const satisfies Record<LandingControlAction, {
384
- allowedFrom: ReadonlySet<LandingState>;
385
- target: LandingState;
386
- workflow: "cancel" | "fail" | "reopen";
387
- }>;
388
-
389
- function landingControlTransition(action: LandingControlAction, source: LandingState) {
390
- const transition = LANDING_CONTROL_TRANSITIONS[action];
391
- return transition.allowedFrom.has(source) ? transition : undefined;
392
- }
393
-
394
- async function stageWorkflowControl(
395
- ctx: ModuleCtx,
396
- operation: "cancel" | "fail" | "reopen",
397
- workflowInstanceId: string | undefined,
398
- failure?: JsonValue,
399
- ): Promise<void> {
400
- if (!workflowInstanceId) throw new OpError("workflow_control_unavailable", 500, "landing run has no enlisted workflow instance");
401
- if (!ctx.workflows.control) throw new OpError("workflow_control_unavailable", 500, "enlisted workflow controls are unavailable");
402
- const workflow = await ctx.workflows.status(workflowInstanceId);
403
- if (!workflow) throw new OpError("workflow_control_unavailable", 500,
404
- `landing workflow instance '${workflowInstanceId}' was not found`);
405
- // Terminal workflows are absorbing. For cancel/fail the desired convergence has already happened,
406
- // even when its exact terminal status differs; staging another control would be refused during
407
- // opCommit and roll the landing entity/event/comparison transaction back forever. Reopen remains
408
- // strict because it is the one operation whose desired result is a live workflow.
409
- if (operation !== "reopen" && ["done", "failed", "canceled"].includes(workflow.status)) return;
410
- await ctx.workflows.control(operation, workflowInstanceId, failure);
411
- }
412
-
413
- async function transitionRun(
414
- ctx: ModuleCtx,
415
- input: { runId: string; expectedGeneration: number; expectedStateVersion: number; blockerCode?: string; blockerDetail?: string },
416
- action: LandingControlAction,
417
- ) {
418
- const store = stores(ctx).run;
419
- const row = await store.get(input.runId);
420
- if (!row) throw new OpError("not_found", 404, `landing run '${input.runId}' was not found`);
421
- if (row.generation !== input.expectedGeneration || row.stateVersion !== input.expectedStateVersion) {
422
- throw new OpError("conflict", 409, `landing run '${input.runId}' fence mismatch`);
423
- }
424
- const transition = landingControlTransition(action, row.state);
425
- if (!transition) {
426
- throw new OpError(
427
- "invalid_state_transition",
428
- 409,
429
- `landing run '${input.runId}' cannot ${action} from state '${row.state}'`,
430
- );
431
- }
432
- const now = ctx.clock.now();
433
- const restart = action === "retry" || action === "reconcile";
434
- if (restart && row.commandId && !row.terminalProcessProof) {
435
- throw new OpError(
436
- "retirement_required",
437
- 409,
438
- `landing run '${input.runId}' cannot ${action} while command '${row.commandId}' is bound; cancel the landing to retire the active command before starting recovery`,
439
- );
440
- }
441
- const retirementPending = Boolean(row.commandId) && !restart;
442
- const target: LandingState = retirementPending
443
- ? action === "supersede" ? "supersede_pending" : "cancel_pending"
444
- : restart ? "queued" : transition.target;
445
- const updated = await store.update(input.runId, {
446
- state: target,
447
- stateVersion: row.stateVersion + 1,
448
- stateSince: now,
449
- updatedAt: now,
450
- ...(restart ? { attempt: row.attempt + 1, commandGeneration: row.commandGeneration + 1,
451
- phaseOrdinal: 0, commandId: undefined, candidateHeadSha: undefined, candidateTreeSha: undefined,
452
- gateTreeSha: undefined, gateConfigDigest: undefined, gateAttestation: undefined,
453
- receiptId: undefined, receiptTargetSha: undefined, receiptVerifiedAt: undefined,
454
- receiptVerificationGeneration: undefined, receiptKind: undefined,
455
- laneToken: undefined, selectedOrchestratorId: undefined, selectedCapabilityDigest: undefined,
456
- selectedSafetyDigest: undefined, candidateWorktreePath: undefined, executionTreeHandle: undefined,
457
- blockerCode: undefined, blockerDetail: undefined, blockedAt: undefined } : {}),
458
- ...(input.blockerCode ? { blockerCode: input.blockerCode, blockerDetail: input.blockerDetail, blockedAt: now } : {}),
459
- });
460
- if (!retirementPending && row.laneKey) {
461
- const lane = await stores(ctx).lane.get(row.laneKey);
462
- if (lane?.currentRunId === row.id) await stores(ctx).lane.update(lane.id, {
463
- currentRunId: undefined, retiringRunId: undefined, laneToken: undefined,
464
- leaseExpiresAt: undefined, activeCommandId: undefined, activeExecutionGeneration: undefined,
465
- lastTerminalProcessProof: row.terminalProcessProof, updatedAt: now,
466
- });
467
- }
468
- const workflow = restart && row.workflowInstanceId
469
- ? await ctx.workflows.status(row.workflowInstanceId) : undefined;
470
- if (!retirementPending && (!restart
471
- || (workflow !== undefined && !["pending", "running", "waiting", "sleeping"].includes(workflow.status)))) {
472
- await stageWorkflowControl(ctx, transition.workflow, row.workflowInstanceId,
473
- input.blockerCode ? { code: input.blockerCode, detail: input.blockerDetail ?? "" } : undefined);
474
- }
475
- await ctx.events.emit("landing.run_transitioned", { runId: row.id, state: target, stateVersion: updated.stateVersion });
476
- if (restart || retirementPending) {
477
- await ctx.events.emit("landing.controller_activated", { runId: row.id, correlationId: row.id });
478
- }
479
- return updated;
480
- }
481
-
482
- export const landing = defineModule({
483
- id: "landing",
484
- title: "Durable landing controller",
485
- description: "Kernel-owned durable landing authorization and controller state.",
486
- entities: {
487
- workspaceHead: { schema: workspaceHeadSchema, indexes: ["authorizationEventId"] },
488
- run: { schema: runSchema, indexes: ["workspaceId", "authorizationEventId", "state", "blockerCode", "shadowVerdict", ["workspaceId", "state"]] },
489
- lane: { schema: laneSchema, indexes: ["identityDigest", "currentRunId", "retiringRunId"] },
490
- notice: { schema: noticeSchema, indexes: ["runId", "status", ["runId", "stateVersion"]] },
491
- effect: { schema: effectSchema, indexes: ["runId", "status", ["runId", "stateVersion"]] },
492
- targetObservation: { schema: targetObservationSchema, indexes: ["runId", ["runId", "observationGeneration"]] },
493
- comparison: { schema: comparisonSchema, indexes: ["runId", "verdict", ["runId", "comparisonVersion"]] },
494
- receipt: { schema: receiptSchema, indexes: ["runId", "workspaceId", ["laneKey", "createdAt"]] },
495
- ...landingIncidentEntities,
496
- },
497
- events: {
498
- "landing.authorization_admitted": z.object({ runId: z.string(), authorizationEventId: z.number().int(), current: z.boolean() }),
499
- "landing.run_transitioned": z.object({ runId: z.string(), state: landingStateSchema, stateVersion: z.number().int() }),
500
- "landing.controller_activated": z.object({ runId: z.string(), correlationId: z.string() }),
501
- "landing.target_observed": z.object({ runId: z.string(), status: z.enum(["positive", "negative", "unknown"]), observationGeneration: z.number().int() }),
502
- "landing.receipt_finalized": z.object({ runId: z.string(), receiptId: z.string(), targetHeadSha: z.string(), kind: z.enum(["kernel", "external"]) }),
503
- },
504
- commands: {
505
- execute: {
506
- input: landingHostCommandInputSchema,
507
- result: z.record(z.string(), z.unknown()),
508
- target: "host-agent",
509
- grant: "command:landing",
510
- idempotencyKey: (input) => {
511
- const tuple = input.params.runTuple as Record<string, unknown> | undefined;
512
- return `${input.type}:${String(tuple?.runId)}:${String(tuple?.generation)}:${String(tuple?.commandGeneration)}`;
513
- },
514
- timeoutMs: 15 * 60_000,
515
- whenDegraded: "queue",
516
- },
517
- },
518
- workflows: {
519
- controller: {
520
- input: z.object({ runId: z.string() }),
521
- state: z.object({ runId: z.string(), commandId: z.string().optional() }),
522
- init: (input): ControllerWaitState => ({ runId: input.runId }),
523
- start: "controller_wait",
524
- steps: {
525
- controller_wait: {
526
- run: async (ctx, state: ControllerWaitState): Promise<StepResult<ControllerWaitState>> => {
527
- const run = await stores(ctx).run.get(state.runId);
528
- if (run && ["landed", "superseded", "canceled", "failed"].includes(run.state)) {
529
- return { state, next: "done", output: { runId: state.runId, state: run.state } };
530
- }
531
- const command = run ? commandForRun(run) : undefined;
532
- if (run && command && run.selectedOrchestratorId) {
533
- const issued = await ctx.commands.issue("execute", command, { host: run.selectedOrchestratorId });
534
- return { state: { ...state, commandId: issued.id }, next: "command_wait",
535
- waitFor: { kind: "command", commandId: issued.id } };
536
- }
537
- return { state, next: "controller_wait", waitFor: { kind: "event", topic: "landing.controller_activated", correlationId: state.runId } };
538
- },
539
- },
540
- command_wait: {
541
- run: async (_ctx, state: ControllerWaitState): Promise<StepResult<ControllerWaitState>> => ({
542
- state: { runId: state.runId }, next: "controller_wait",
543
- }),
544
- },
545
- },
546
- },
547
- },
548
- ops: {
549
- ...landingIncidentOps,
550
- admitAuthorization: {
551
- kind: "mutation",
552
- idempotent: true,
553
- grant: LANDING_ADMIN_GRANT,
554
- summary: "Admit one immutable landing authorization in event order.",
555
- expose: { http: false, mcp: false, cli: false },
556
- emits: ["landing.authorization_admitted", "landing.run_transitioned"],
557
- input: admissionInputSchema,
558
- output: admissionOutputSchema,
559
- handler: async (ctx, input) => {
560
- const entity = stores(ctx);
561
- const runId = landingRunId(input.workspaceId, input.authorizationEventId);
562
- const existing = await entity.run.get(runId);
563
- if (existing) {
564
- const head = await entity.workspaceHead.get(input.workspaceId);
565
- return {
566
- runId,
567
- generation: existing.generation,
568
- state: existing.state,
569
- current: head?.currentRunId === runId,
570
- ...(existing.workflowInstanceId ? { workflowInstanceId: existing.workflowInstanceId } : {}),
571
- };
572
- }
573
-
574
- const head = await entity.workspaceHead.get(input.workspaceId);
575
- const isCurrent = !head || input.authorizationEventId > head.authorizationEventId;
576
- const now = ctx.clock.now();
577
- if (!isCurrent) {
578
- await entity.run.create({
579
- id: runId,
580
- workspaceId: input.workspaceId,
581
- laneKey: input.laneKey,
582
- generation: input.authorizationEventId,
583
- authorizationEventId: input.authorizationEventId,
584
- sourceRef: input.sourceRef,
585
- sourceHeadSha: input.sourceHeadSha,
586
- repoRoot: input.frozenIntent.repoRoot,
587
- remoteIdentity: input.frozenIntent.remoteIdentity,
588
- baseRef: input.frozenIntent.baseRef,
589
- strategy: input.frozenIntent.strategy,
590
- gateSnapshot: JSON.stringify(input.frozenIntent.gateSnapshot),
591
- requesterSnapshot: JSON.stringify(input.frozenIntent.requester),
592
- state: "superseded",
593
- stateVersion: 0,
594
- stateSince: now,
595
- queuedAt: input.queuedAt,
596
- attempt: 0,
597
- phaseOrdinal: 0,
598
- commandGeneration: 0,
599
- legacyProgressSequence: 0,
600
- transientFailures: 0,
601
- gateFailures: 0,
602
- pushRaces: 0,
603
- executorRecoveries: 0,
604
- supersededBy: head!.currentRunId,
605
- terminalProcessProof: "never_started",
606
- createdAt: now,
607
- updatedAt: now,
608
- });
609
- await ctx.events.emit("landing.authorization_admitted", { runId, authorizationEventId: input.authorizationEventId, current: false });
610
- return { runId, generation: input.authorizationEventId, state: "superseded", current: false };
611
- }
612
-
613
- const workflow = await ctx.workflows.start("controller", { runId }, { idempotencyKey: runId, enlist: "op-commit" });
614
- await entity.run.create({
615
- id: runId,
616
- workspaceId: input.workspaceId,
617
- laneKey: input.laneKey,
618
- generation: input.authorizationEventId,
619
- authorizationEventId: input.authorizationEventId,
620
- workflowInstanceId: workflow.id,
621
- sourceRef: input.sourceRef,
622
- sourceHeadSha: input.sourceHeadSha,
623
- repoRoot: input.frozenIntent.repoRoot,
624
- remoteIdentity: input.frozenIntent.remoteIdentity,
625
- baseRef: input.frozenIntent.baseRef,
626
- strategy: input.frozenIntent.strategy,
627
- gateSnapshot: JSON.stringify(input.frozenIntent.gateSnapshot),
628
- requesterSnapshot: JSON.stringify(input.frozenIntent.requester),
629
- state: "controller_wait",
630
- stateVersion: 0,
631
- stateSince: now,
632
- queuedAt: input.queuedAt,
633
- attempt: 0,
634
- phaseOrdinal: 0,
635
- commandGeneration: 0,
636
- legacyProgressSequence: 0,
637
- transientFailures: 0,
638
- gateFailures: 0,
639
- pushRaces: 0,
640
- executorRecoveries: 0,
641
- createdAt: now,
642
- updatedAt: now,
643
- });
644
- if (head) {
645
- const prior = await entity.run.get(head.currentRunId);
646
- const supersession = prior ? landingControlTransition("supersede", prior.state) : undefined;
647
- if (prior && supersession) {
648
- await entity.run.update(prior.id, {
649
- state: supersession.target,
650
- stateVersion: prior.stateVersion + 1,
651
- stateSince: now,
652
- supersededBy: runId,
653
- terminalProcessProof: "never_started",
654
- updatedAt: now,
655
- });
656
- await stageWorkflowControl(ctx, supersession.workflow, prior.workflowInstanceId);
657
- await ctx.events.emit("landing.run_transitioned", { runId: prior.id, state: supersession.target, stateVersion: prior.stateVersion + 1 });
658
- }
659
- await entity.workspaceHead.update(input.workspaceId, {
660
- currentRunId: runId,
661
- generation: input.authorizationEventId,
662
- authorizationEventId: input.authorizationEventId,
663
- updatedAt: now,
664
- });
665
- } else {
666
- await entity.workspaceHead.create({
667
- id: input.workspaceId,
668
- workspaceId: input.workspaceId,
669
- currentRunId: runId,
670
- generation: input.authorizationEventId,
671
- authorizationEventId: input.authorizationEventId,
672
- updatedAt: now,
673
- });
674
- }
675
- await ctx.events.emit("landing.authorization_admitted", { runId, authorizationEventId: input.authorizationEventId, current: true });
676
- return { runId, generation: input.authorizationEventId, state: "controller_wait", current: true, workflowInstanceId: workflow.id };
677
- },
678
- },
679
- acceptDryRunCommandResult: {
680
- kind: "mutation", idempotent: true, grant: LANDING_ADMIN_GRANT,
681
- summary: "Atomically commit one fenced terminal landing command attestation into the current run.",
682
- expose: { http: false, mcp: false, cli: false }, emits: ["landing.run_transitioned", "landing.controller_activated"],
683
- input: dryRunCommandResultInputSchema, output: runSchema,
684
- handler: async (ctx, input) => {
685
- const entity = stores(ctx);
686
- const row = await entity.run.get(input.tuple.runId);
687
- if (!row) throw new OpError("not_found", 404, `landing run '${input.tuple.runId}' was not found`);
688
- if (row.lastDryRunCommandId === input.commandId) return row;
689
- const tuple = input.tuple;
690
- if (row.generation !== tuple.generation || row.commandGeneration !== tuple.commandGeneration
691
- || row.attempt !== tuple.attempt || row.phaseOrdinal !== tuple.phaseOrdinal
692
- || row.stateVersion !== tuple.stateVersion) {
693
- throw new OpError("conflict", 409, `landing run '${row.id}' command tuple is stale`);
694
- }
695
- const result = input.result;
696
- const status = typeof result.status === "string" ? result.status : "";
697
- const required = (name: string): string => {
698
- const value = result[name];
699
- if (typeof value !== "string" || !value) throw new OpError("invalid_input", 400, `${input.type} result.${name} is required`);
700
- return value;
701
- };
702
- const now = ctx.clock.now();
703
- let state: LandingState = row.state;
704
- const patch: Record<string, string | number | undefined> = {
705
- lastDryRunCommandId: input.commandId,
706
- commandId: undefined,
707
- terminalProcessProof: input.terminalProcessProof,
708
- commandGeneration: row.commandGeneration + 1,
709
- phaseOrdinal: row.phaseOrdinal + 1,
710
- updatedAt: now,
711
- };
712
- if (input.type === "landing.prepare") {
713
- if (status === "prepared") {
714
- patch.baseHeadSha = required("baseHeadSha");
715
- patch.candidateHeadSha = required("candidateHeadSha");
716
- patch.candidateTreeSha = required("candidateTreeSha");
717
- patch.pushRemote = required("pushRemote");
718
- state = "gating";
719
- } else state = status === "source-drift" ? "authorization_required" : "retry_wait";
720
- } else if (input.type === "landing.gate") {
721
- if (status === "passed") {
722
- patch.candidateHeadSha = required("candidateHeadSha");
723
- patch.gateTreeSha = required("gateTreeSha");
724
- patch.gateConfigDigest = required("gateConfigDigest");
725
- patch.gateAttestation = required("gateAttestationDigest");
726
- state = "publish_ready";
727
- } else if (status === "red") {
728
- patch.gateFailures = row.gateFailures + 1;
729
- state = "gate_failed";
730
- } else {
731
- patch.transientFailures = row.transientFailures + 1;
732
- state = "retry_wait";
733
- }
734
- } else if (input.type === "landing.publish") {
735
- if (status === "published" || status === "ambiguous") state = "verifying";
736
- else if (status === "base-race") {
737
- patch.pushRaces = row.pushRaces + 1;
738
- state = "retry_wait";
739
- } else state = "authorization_required";
740
- } else if (input.type === "landing.verify") {
741
- if (status === "contained") {
742
- const targetHeadSha = required("targetHeadSha");
743
- if (!row.candidateHeadSha || targetHeadSha !== row.candidateHeadSha) {
744
- throw new OpError("conflict", 409, "Kernel verification did not prove the exact gated candidate");
745
- }
746
- patch.receiptTargetSha = targetHeadSha;
747
- patch.receiptVerifiedAt = now;
748
- patch.receiptVerificationGeneration = row.commandGeneration + 1;
749
- patch.receiptKind = "kernel";
750
- state = "cleanup";
751
- } else state = status === "unknown" ? "reconciliation_required" : "retry_wait";
752
- } else if (input.type === "landing.observe-target") {
753
- state = status === "unknown" ? "reconciliation_required" : row.state;
754
- } else if (input.type === "landing.cleanup") {
755
- state = status === "removed" || status === "already-clean" ? "receipt_ready" : "reconciliation_required";
756
- }
757
- const changedState = state !== row.state;
758
- patch.state = state;
759
- patch.stateVersion = row.stateVersion + 1;
760
- patch.stateSince = changedState ? now : row.stateSince;
761
- const updated = await entity.run.update(row.id, patch);
762
- const lane = row.laneKey ? await entity.lane.get(row.laneKey) : undefined;
763
- if (lane && lane.activeCommandId === input.commandId) await entity.lane.update(lane.id, {
764
- activeCommandId: undefined, activeExecutionGeneration: undefined,
765
- lastTerminalProcessProof: input.terminalProcessProof, updatedAt: now,
766
- });
767
- if (changedState) await ctx.events.emit("landing.run_transitioned", { runId: row.id, state, stateVersion: updated.stateVersion });
768
- if (!["landed", "superseded", "canceled", "failed"].includes(state)) {
769
- await ctx.events.emit("landing.controller_activated", { runId: row.id, correlationId: row.id });
770
- }
771
- return updated;
772
- },
773
- },
774
- cancelLanding: {
775
- kind: "mutation", idempotent: false, grant: LANDING_ADMIN_GRANT, summary: "Cancel a fenced inert landing run.", expose: { http: false, mcp: false, cli: false },
776
- emits: ["landing.run_transitioned"],
777
- input: z.object({ runId: z.string(), expectedGeneration: z.number().int(), expectedStateVersion: z.number().int() }), output: runSchema,
778
- handler: (ctx, input) => transitionRun(ctx, input, "cancel"),
779
- },
780
- failLanding: {
781
- kind: "mutation", idempotent: false, grant: LANDING_ADMIN_GRANT, summary: "Fail a fenced landing run with a structured blocker.", expose: { http: false, mcp: false, cli: false },
782
- emits: ["landing.run_transitioned"],
783
- input: z.object({ runId: z.string(), expectedGeneration: z.number().int(), expectedStateVersion: z.number().int(), blockerCode: z.string(), blockerDetail: z.string().optional() }), output: runSchema,
784
- handler: (ctx, input) => transitionRun(ctx, input, "fail"),
785
- },
786
- supersedeLanding: {
787
- kind: "mutation", idempotent: false, grant: LANDING_ADMIN_GRANT, summary: "Supersede a fenced inert landing run.", expose: { http: false, mcp: false, cli: false },
788
- emits: ["landing.run_transitioned"],
789
- input: z.object({ runId: z.string(), expectedGeneration: z.number().int(), expectedStateVersion: z.number().int() }), output: runSchema,
790
- handler: (ctx, input) => transitionRun(ctx, input, "supersede"),
791
- },
792
- retryLanding: {
793
- kind: "mutation", idempotent: false, grant: LANDING_ADMIN_GRANT, summary: "Reopen a fenced failed run into inert controller wait.", expose: { http: false, mcp: false, cli: false },
794
- emits: ["landing.run_transitioned", "landing.controller_activated"],
795
- input: z.object({ runId: z.string(), expectedGeneration: z.number().int(), expectedStateVersion: z.number().int() }), output: runSchema,
796
- handler: (ctx, input) => transitionRun(ctx, input, "retry"),
797
- },
798
- reconcileLanding: {
799
- kind: "mutation", idempotent: false, grant: LANDING_ADMIN_GRANT, summary: "Re-arm a fenced reconciliation blocker for canonical controller recovery.", expose: { http: false, mcp: false, cli: false },
800
- emits: ["landing.run_transitioned", "landing.controller_activated"],
801
- input: z.object({ runId: z.string(), expectedGeneration: z.number().int(), expectedStateVersion: z.number().int() }), output: runSchema,
802
- handler: (ctx, input) => transitionRun(ctx, input, "reconcile"),
803
- },
804
- finalizeControl: {
805
- kind: "mutation", idempotent: true, grant: LANDING_ADMIN_GRANT,
806
- summary: "Finalize cancel/fail/supersede only after the exact active process is positively retired.",
807
- expose: { http: false, mcp: false, cli: false }, emits: ["landing.run_transitioned"],
808
- input: finalizeControlInputSchema, output: runSchema,
809
- handler: async (ctx, input) => {
810
- const entity = stores(ctx);
811
- const row = await entity.run.get(input.runId);
812
- if (!row) throw new OpError("not_found", 404, `landing run '${input.runId}' was not found`);
813
- if (row.generation !== input.expectedGeneration || row.stateVersion !== input.expectedStateVersion
814
- || row.commandId !== input.commandId
815
- || (row.state !== "cancel_pending" && row.state !== "supersede_pending")) {
816
- throw new OpError("conflict", 409, "landing control finalization fence mismatch");
817
- }
818
- const terminal: LandingState = row.state === "supersede_pending" ? "superseded"
819
- : row.blockerCode ? "failed" : "canceled";
820
- const now = ctx.clock.now();
821
- const lane = row.laneKey ? await entity.lane.get(row.laneKey) : undefined;
822
- if (lane?.activeCommandId && lane.activeCommandId !== input.commandId) {
823
- throw new OpError("conflict", 409, "landing control active-command fence changed");
824
- }
825
- const updated = await entity.run.update(row.id, { state: terminal,
826
- stateVersion: row.stateVersion + 1, stateSince: now, commandId: undefined,
827
- terminalProcessProof: input.terminalProcessProof, durableExitKind: "explicit_action", updatedAt: now });
828
- if (lane && lane.currentRunId === row.id) await entity.lane.update(lane.id, { currentRunId: undefined,
829
- retiringRunId: undefined, laneToken: undefined, leaseExpiresAt: undefined,
830
- activeCommandId: undefined, activeExecutionGeneration: undefined,
831
- lastTerminalProcessProof: input.terminalProcessProof, updatedAt: now });
832
- await stageWorkflowControl(ctx, terminal === "failed" ? "fail" : "cancel", row.workflowInstanceId,
833
- terminal === "failed" ? { code: row.blockerCode ?? "landing_failed", detail: row.blockerDetail ?? "" } : undefined);
834
- await ctx.events.emit("landing.run_transitioned", { runId: row.id, state: terminal,
835
- stateVersion: updated.stateVersion });
836
- return updated;
837
- },
838
- },
839
- acceptTargetObservation: {
840
- kind: "mutation", idempotent: true, grant: LANDING_ADMIN_GRANT,
841
- summary: "Persist one fenced three-valued dry target observation and its shadow-only prediction.",
842
- expose: { http: false, mcp: false, cli: false },
843
- emits: ["landing.run_transitioned", "landing.controller_activated", "landing.target_observed"],
844
- input: observationInputSchema,
845
- output: z.object({ run: runSchema, observation: targetObservationSchema.optional() }),
846
- handler: async (ctx, input) => {
847
- const entity = stores(ctx);
848
- const observationId = `${input.runId}:observation:${input.observationGeneration}`;
849
- const replay = await entity.targetObservation.get(observationId);
850
- if (replay) {
851
- const replayRun = await entity.run.get(input.runId);
852
- if (!replayRun) throw new OpError("not_found", 404, `landing run '${input.runId}' was not found`);
853
- return { run: replayRun, observation: replay };
854
- }
855
- const row = await entity.run.get(input.runId);
856
- if (!row) throw new OpError("not_found", 404, `landing run '${input.runId}' was not found`);
857
- if (row.generation !== input.expectedGeneration || row.stateVersion !== input.expectedStateVersion) {
858
- throw new OpError("conflict", 409, `landing run '${input.runId}' fence mismatch`);
859
- }
860
- if (input.observationGeneration <= (row.observationGeneration ?? 0)) {
861
- throw new OpError("conflict", 409, `landing run '${input.runId}' observation is stale`);
862
- }
863
- if (input.status === "positive" && input.sourceContained !== true && input.candidateContained !== true) {
864
- throw new OpError("invalid_input", 400, "positive target observation requires positive remote containment evidence");
865
- }
866
- const now = ctx.clock.now();
867
- if (row.lastObservationDigest === input.factDigest) {
868
- const observationRepeatCount = (row.observationRepeatCount ?? 0) + 1;
869
- const repeated = await entity.run.update(row.id, { observationGeneration: input.observationGeneration,
870
- lastObservedAt: now, nextObservationAt: now + repeatedObservationDelayMs(observationRepeatCount),
871
- observationRepeatCount, updatedAt: now });
872
- return { run: repeated };
873
- }
874
- const observation = await entity.targetObservation.create({ id: observationId, runId: row.id,
875
- generation: row.generation, observationGeneration: input.observationGeneration,
876
- sourceHeadSha: input.sourceHeadSha, candidateHeadSha: input.candidateHeadSha,
877
- targetRef: input.targetRef, targetHeadSha: input.targetHeadSha,
878
- sourceContained: input.sourceContained, candidateContained: input.candidateContained,
879
- containmentMethod: input.containmentMethod, status: input.status, observedAt: now });
880
- const terminal = ["landed", "superseded", "canceled", "failed"].includes(row.state);
881
- const targetState = terminal ? row.state : input.status === "positive" ? "external_retire_pending"
882
- : input.status === "unknown" ? "reconciliation_required" : row.state;
883
- const updated = await entity.run.update(row.id, {
884
- state: targetState,
885
- stateVersion: row.stateVersion + (targetState === row.state ? 0 : 1),
886
- stateSince: targetState === row.state ? row.stateSince : now,
887
- observationGeneration: input.observationGeneration,
888
- lastObservedAt: now,
889
- nextObservationAt: now + LANDING_OBSERVATION_BASE_MS,
890
- lastObservationDigest: input.factDigest,
891
- observationRepeatCount: 0,
892
- predictedOutcome: input.status === "positive" ? "external_retire" : input.status === "unknown" ? "reconciliation_required" : "none",
893
- durableExitKind: terminal ? row.durableExitKind : LANDING_DURABLE_EXITS[targetState as keyof typeof LANDING_DURABLE_EXITS],
894
- lastReconciledAt: now,
895
- updatedAt: now,
896
- });
897
- if (input.status === "unknown") {
898
- const noticeId = `${row.id}:observation-unknown:${input.factDigest}`;
899
- const payload = JSON.stringify({ targetRef: input.targetRef,
900
- containmentMethod: input.containmentMethod ?? "unavailable", action: "reconcile_remote_evidence" });
901
- if (!await entity.notice.get(noticeId)) await entity.notice.create({ id: noticeId, runId: row.id,
902
- stateVersion: updated.stateVersion, audience: "landing-operator", kind: "observation-unknown",
903
- status: "pending", payload, createdAt: now });
904
- if (!await entity.effect.get(noticeId)) await entity.effect.create({ id: noticeId, runId: row.id,
905
- stateVersion: updated.stateVersion, kind: "observation-unknown", status: "pending", payload,
906
- deliveryKey: noticeId, attempts: 0, createdAt: now });
907
- }
908
- if (targetState !== row.state) await ctx.events.emit("landing.run_transitioned", { runId: row.id, state: targetState, stateVersion: updated.stateVersion });
909
- await ctx.events.emit("landing.target_observed", { runId: row.id, status: input.status, observationGeneration: input.observationGeneration });
910
- if (targetState !== row.state || (input.status === "negative" && row.state === "publish_ready")) {
911
- await ctx.events.emit("landing.controller_activated", { runId: row.id, correlationId: row.id });
912
- }
913
- return { run: updated, observation };
914
- },
915
- },
916
- reconcileTerminal: {
917
- kind: "mutation", idempotent: true, grant: LANDING_ADMIN_GRANT,
918
- summary: "Converge generic workflow and landing-domain terminal state without guessing.",
919
- expose: { http: false, mcp: false, cli: false }, emits: ["landing.run_transitioned", "landing.controller_activated"],
920
- input: z.object({ runId: z.string(), expectedGeneration: z.number().int(), expectedStateVersion: z.number().int(), workflowStatus: z.enum(["pending", "running", "waiting", "sleeping", "paused", "done", "failed", "canceled"]) }),
921
- output: runSchema,
922
- handler: async (ctx, input) => {
923
- const row = await stores(ctx).run.get(input.runId);
924
- if (!row) throw new OpError("not_found", 404, `landing run '${input.runId}' was not found`);
925
- if (row.generation !== input.expectedGeneration || row.stateVersion !== input.expectedStateVersion) throw new OpError("conflict", 409, `landing run '${input.runId}' fence mismatch`);
926
- const domainTerminal = ["landed", "superseded", "canceled", "failed"].includes(row.state);
927
- const workflowTerminal = ["done", "failed", "canceled"].includes(input.workflowStatus);
928
- if (domainTerminal && !workflowTerminal) {
929
- if (row.state === "canceled" || row.state === "superseded") await stageWorkflowControl(ctx, "cancel", row.workflowInstanceId);
930
- else if (row.state === "failed") await stageWorkflowControl(ctx, "fail", row.workflowInstanceId, { code: "landing_domain_failed" });
931
- else await ctx.events.emit("landing.controller_activated", { runId: row.id, correlationId: row.id });
932
- return row;
933
- }
934
- if (!domainTerminal && workflowTerminal) {
935
- const now = ctx.clock.now();
936
- const state: LandingState = input.workflowStatus === "canceled" ? "canceled"
937
- : input.workflowStatus === "failed" ? "failed" : "reconciliation_required";
938
- if (row.state === state) return row;
939
- const updated = await stores(ctx).run.update(row.id, { state, stateVersion: row.stateVersion + 1,
940
- stateSince: now, durableExitKind: state === "reconciliation_required" ? "explicit_action" : row.durableExitKind,
941
- blockerCode: "workflow_domain_disagreement", blockerDetail: `workflow=${input.workflowStatus}; domain=${row.state}`,
942
- blockedAt: now, updatedAt: now });
943
- await ctx.events.emit("landing.run_transitioned", { runId: row.id, state, stateVersion: updated.stateVersion });
944
- return updated;
945
- }
946
- return row;
947
- },
948
- },
949
- },
950
- });
951
-
952
- export type LandingRun = z.output<typeof runSchema>;
953
- export type LandingWorkspaceHead = z.output<typeof workspaceHeadSchema>;