@rudderhq/server 0.7.1 → 0.7.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 (265) hide show
  1. package/dist/agent-runtimes/codex-models.d.ts +11 -0
  2. package/dist/agent-runtimes/codex-models.d.ts.map +1 -1
  3. package/dist/agent-runtimes/codex-models.js +155 -2
  4. package/dist/agent-runtimes/codex-models.js.map +1 -1
  5. package/dist/agent-runtimes/cursor-models.d.ts.map +1 -1
  6. package/dist/agent-runtimes/cursor-models.js +31 -9
  7. package/dist/agent-runtimes/cursor-models.js.map +1 -1
  8. package/dist/agent-runtimes/index.d.ts +1 -1
  9. package/dist/agent-runtimes/index.d.ts.map +1 -1
  10. package/dist/agent-runtimes/index.js +1 -1
  11. package/dist/agent-runtimes/index.js.map +1 -1
  12. package/dist/agent-runtimes/registry.d.ts +11 -4
  13. package/dist/agent-runtimes/registry.d.ts.map +1 -1
  14. package/dist/agent-runtimes/registry.js +54 -3
  15. package/dist/agent-runtimes/registry.js.map +1 -1
  16. package/dist/bundled-plugins/plugin-linear/dist/worker.js +287 -18
  17. package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +3 -3
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +86 -30
  20. package/dist/index.js.map +1 -1
  21. package/dist/redaction.d.ts +1 -0
  22. package/dist/redaction.d.ts.map +1 -1
  23. package/dist/redaction.js +13 -0
  24. package/dist/redaction.js.map +1 -1
  25. package/dist/routes/access.helpers.d.ts +1 -1
  26. package/dist/routes/agents.d.ts.map +1 -1
  27. package/dist/routes/agents.js +20 -6
  28. package/dist/routes/agents.js.map +1 -1
  29. package/dist/routes/agents.management-routes.d.ts.map +1 -1
  30. package/dist/routes/agents.management-routes.js +13 -10
  31. package/dist/routes/agents.management-routes.js.map +1 -1
  32. package/dist/routes/approvals.d.ts.map +1 -1
  33. package/dist/routes/approvals.js +77 -1
  34. package/dist/routes/approvals.js.map +1 -1
  35. package/dist/routes/chats.d.ts.map +1 -1
  36. package/dist/routes/chats.js +1 -0
  37. package/dist/routes/chats.js.map +1 -1
  38. package/dist/routes/chats.stream-routes.d.ts.map +1 -1
  39. package/dist/routes/chats.stream-routes.js +4 -0
  40. package/dist/routes/chats.stream-routes.js.map +1 -1
  41. package/dist/routes/goals.d.ts.map +1 -1
  42. package/dist/routes/goals.js +349 -23
  43. package/dist/routes/goals.js.map +1 -1
  44. package/dist/routes/health.d.ts.map +1 -1
  45. package/dist/routes/health.js +1 -0
  46. package/dist/routes/health.js.map +1 -1
  47. package/dist/routes/managed-mcp-connections.d.ts.map +1 -1
  48. package/dist/routes/managed-mcp-connections.js +46 -7
  49. package/dist/routes/managed-mcp-connections.js.map +1 -1
  50. package/dist/routes/product-analytics-collector-report.d.ts +29 -0
  51. package/dist/routes/product-analytics-collector-report.d.ts.map +1 -1
  52. package/dist/routes/product-analytics-collector-report.js +57 -1
  53. package/dist/routes/product-analytics-collector-report.js.map +1 -1
  54. package/dist/routes/product-analytics-collector-report.test.d.ts +2 -0
  55. package/dist/routes/product-analytics-collector-report.test.d.ts.map +1 -0
  56. package/dist/routes/product-analytics-collector-report.test.js +50 -0
  57. package/dist/routes/product-analytics-collector-report.test.js.map +1 -0
  58. package/dist/services/automation-chat-output.d.ts +1 -1
  59. package/dist/services/automation-chat-output.d.ts.map +1 -1
  60. package/dist/services/automation-chat-output.js +24 -14
  61. package/dist/services/automation-chat-output.js.map +1 -1
  62. package/dist/services/chat-annotation-copy-lineage.d.ts +1 -1
  63. package/dist/services/chat-assistant.d.ts.map +1 -1
  64. package/dist/services/chat-assistant.helpers.d.ts +3 -3
  65. package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
  66. package/dist/services/chat-assistant.helpers.js.map +1 -1
  67. package/dist/services/chat-assistant.js +34 -11
  68. package/dist/services/chat-assistant.js.map +1 -1
  69. package/dist/services/chat-assistant.runtime-overrides.d.ts +2 -1
  70. package/dist/services/chat-assistant.runtime-overrides.d.ts.map +1 -1
  71. package/dist/services/chat-assistant.runtime-overrides.js +47 -18
  72. package/dist/services/chat-assistant.runtime-overrides.js.map +1 -1
  73. package/dist/services/chat-assistant.runtime-overrides.test.d.ts +2 -0
  74. package/dist/services/chat-assistant.runtime-overrides.test.d.ts.map +1 -0
  75. package/dist/services/chat-assistant.runtime-overrides.test.js +37 -0
  76. package/dist/services/chat-assistant.runtime-overrides.test.js.map +1 -0
  77. package/dist/services/chat-generation-protocol.d.ts.map +1 -1
  78. package/dist/services/chat-generation-protocol.js +1 -12
  79. package/dist/services/chat-generation-protocol.js.map +1 -1
  80. package/dist/services/chat-generation-protocol.test.js +14 -23
  81. package/dist/services/chat-generation-protocol.test.js.map +1 -1
  82. package/dist/services/chat-generation-provenance.d.ts.map +1 -1
  83. package/dist/services/chat-generation-provenance.js +4 -2
  84. package/dist/services/chat-generation-provenance.js.map +1 -1
  85. package/dist/services/chat-queued-message-materialization.d.ts.map +1 -1
  86. package/dist/services/chat-queued-message-materialization.js +12 -2
  87. package/dist/services/chat-queued-message-materialization.js.map +1 -1
  88. package/dist/services/chat-transcript-persistence.d.ts +49 -0
  89. package/dist/services/chat-transcript-persistence.d.ts.map +1 -0
  90. package/dist/services/chat-transcript-persistence.js +238 -0
  91. package/dist/services/chat-transcript-persistence.js.map +1 -0
  92. package/dist/services/chat-transcript-persistence.test.d.ts +2 -0
  93. package/dist/services/chat-transcript-persistence.test.d.ts.map +1 -0
  94. package/dist/services/chat-transcript-persistence.test.js +23 -0
  95. package/dist/services/chat-transcript-persistence.test.js.map +1 -0
  96. package/dist/services/chat-work-manifest.d.ts.map +1 -1
  97. package/dist/services/chat-work-manifest.js +36 -10
  98. package/dist/services/chat-work-manifest.js.map +1 -1
  99. package/dist/services/chats.annotation-persistence.d.ts.map +1 -1
  100. package/dist/services/chats.annotation-persistence.js +25 -6
  101. package/dist/services/chats.annotation-persistence.js.map +1 -1
  102. package/dist/services/chats.create.d.ts.map +1 -1
  103. package/dist/services/chats.create.js +49 -4
  104. package/dist/services/chats.create.js.map +1 -1
  105. package/dist/services/chats.d.ts +8 -8
  106. package/dist/services/chats.d.ts.map +1 -1
  107. package/dist/services/chats.inline-visual-persistence.d.ts +2 -1
  108. package/dist/services/chats.inline-visual-persistence.d.ts.map +1 -1
  109. package/dist/services/chats.inline-visual-persistence.js +19 -2
  110. package/dist/services/chats.inline-visual-persistence.js.map +1 -1
  111. package/dist/services/chats.js +177 -95
  112. package/dist/services/chats.js.map +1 -1
  113. package/dist/services/finance.d.ts +1 -1
  114. package/dist/services/goals.d.ts +2041 -126
  115. package/dist/services/goals.d.ts.map +1 -1
  116. package/dist/services/goals.js +1533 -176
  117. package/dist/services/goals.js.map +1 -1
  118. package/dist/services/instance-settings.d.ts.map +1 -1
  119. package/dist/services/instance-settings.js +2 -0
  120. package/dist/services/instance-settings.js.map +1 -1
  121. package/dist/services/issues.d.ts.map +1 -1
  122. package/dist/services/issues.js +35 -16
  123. package/dist/services/issues.js.map +1 -1
  124. package/dist/services/mcp/managed-bindings.d.ts.map +1 -1
  125. package/dist/services/mcp/managed-bindings.js +11 -4
  126. package/dist/services/mcp/managed-bindings.js.map +1 -1
  127. package/dist/services/mcp/managed-connections.d.ts +19 -1
  128. package/dist/services/mcp/managed-connections.d.ts.map +1 -1
  129. package/dist/services/mcp/managed-connections.js +211 -37
  130. package/dist/services/mcp/managed-connections.js.map +1 -1
  131. package/dist/services/mcp/managed-runtime.js +1 -1
  132. package/dist/services/mcp/managed-runtime.js.map +1 -1
  133. package/dist/services/mcp/oauth.d.ts.map +1 -1
  134. package/dist/services/mcp/oauth.js +3 -0
  135. package/dist/services/mcp/oauth.js.map +1 -1
  136. package/dist/services/mcp/provider-registry.d.ts +24 -1
  137. package/dist/services/mcp/provider-registry.d.ts.map +1 -1
  138. package/dist/services/mcp/provider-registry.js +26 -0
  139. package/dist/services/mcp/provider-registry.js.map +1 -1
  140. package/dist/services/mcp/provider-registry.test.js +26 -0
  141. package/dist/services/mcp/provider-registry.test.js.map +1 -1
  142. package/dist/services/messenger-saved-views.d.ts.map +1 -1
  143. package/dist/services/messenger-saved-views.js +1 -0
  144. package/dist/services/messenger-saved-views.js.map +1 -1
  145. package/dist/services/messenger.d.ts.map +1 -1
  146. package/dist/services/messenger.js +16 -2
  147. package/dist/services/messenger.js.map +1 -1
  148. package/dist/services/plugin-host-services.d.ts.map +1 -1
  149. package/dist/services/plugin-host-services.js +5 -7
  150. package/dist/services/plugin-host-services.js.map +1 -1
  151. package/dist/services/product-analytics.d.ts +20 -4
  152. package/dist/services/product-analytics.d.ts.map +1 -1
  153. package/dist/services/product-analytics.js +47 -0
  154. package/dist/services/product-analytics.js.map +1 -1
  155. package/dist/services/product-analytics.test.js +63 -1
  156. package/dist/services/product-analytics.test.js.map +1 -1
  157. package/dist/services/run-events.d.ts +1 -1
  158. package/dist/services/runtime-kernel/heartbeat.d.ts +4 -0
  159. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  160. package/dist/services/runtime-kernel/heartbeat.execute.d.ts.map +1 -1
  161. package/dist/services/runtime-kernel/heartbeat.execute.js +51 -3
  162. package/dist/services/runtime-kernel/heartbeat.execute.js.map +1 -1
  163. package/dist/services/runtime-kernel/heartbeat.js +187 -9
  164. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  165. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts +3 -1
  166. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts.map +1 -1
  167. package/dist/services/runtime-kernel/heartbeat.terminal.js +2 -1
  168. package/dist/services/runtime-kernel/heartbeat.terminal.js.map +1 -1
  169. package/dist/services/side-chats.d.ts.map +1 -1
  170. package/dist/services/side-chats.js +12 -0
  171. package/dist/services/side-chats.js.map +1 -1
  172. package/dist/services/workspace-runtime.services.d.ts +1 -1
  173. package/package.json +16 -14
  174. package/resources/bundled-skills/browser/SKILL.md +11 -4
  175. package/resources/bundled-skills/browser/agents/openai.yaml +2 -2
  176. package/resources/bundled-skills/browser/evals/trigger-evals.json +22 -0
  177. package/skills/browser/SKILL.md +11 -4
  178. package/skills/browser/agents/openai.yaml +2 -2
  179. package/skills/browser/evals/trigger-evals.json +22 -0
  180. package/ui-dist/assets/{LibraryLiveSurface-CZED7oKj.js → LibraryLiveSurface-cHCJmmsl.js} +1 -1
  181. package/ui-dist/assets/{abnfDiagram-VRR7QNED-BoPyIoAq.js → abnfDiagram-VRR7QNED-BORJvkew.js} +1 -1
  182. package/ui-dist/assets/{arc-DTYs4RCH.js → arc-DPUzhx87.js} +1 -1
  183. package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-BGfp2S_3.js → architectureDiagram-ZJ3FMSHR-Bz_hY8LM.js} +1 -1
  184. package/ui-dist/assets/{blockDiagram-677ZJIJ3-aW0EGIUf.js → blockDiagram-677ZJIJ3-Bq1X3YkP.js} +1 -1
  185. package/ui-dist/assets/{c4Diagram-LMCZKHZV--1itKgA2.js → c4Diagram-LMCZKHZV-BawEM9Pc.js} +1 -1
  186. package/ui-dist/assets/channel-C08AyYJr.js +1 -0
  187. package/ui-dist/assets/{chunk-2Q5K7J3B-ZIqIozPQ.js → chunk-2Q5K7J3B-D0D5ZOaA.js} +1 -1
  188. package/ui-dist/assets/{chunk-32BRIVSS-DiwBEZVQ.js → chunk-32BRIVSS-T90P7lug.js} +1 -1
  189. package/ui-dist/assets/{chunk-5VM5RSS4-CLOrTmL8.js → chunk-5VM5RSS4-jcAERTgh.js} +1 -1
  190. package/ui-dist/assets/{chunk-EX3LRPZG-D6xZzF47.js → chunk-EX3LRPZG-t9-JXwnR.js} +1 -1
  191. package/ui-dist/assets/{chunk-JWPE2WC7-B9tYvTZl.js → chunk-JWPE2WC7-hqgD1saC.js} +1 -1
  192. package/ui-dist/assets/{chunk-MOJQB5TN-D_qo-SzM.js → chunk-MOJQB5TN-BsSUoYzd.js} +1 -1
  193. package/ui-dist/assets/{chunk-RYQCIY6F-ChPHGc2P.js → chunk-RYQCIY6F-YcdDh2Zn.js} +1 -1
  194. package/ui-dist/assets/{chunk-V7JOEXUC-Dk6GbZYp.js → chunk-V7JOEXUC-ChFyH9Su.js} +1 -1
  195. package/ui-dist/assets/{chunk-VR4S4FIN-B6AREC1T.js → chunk-VR4S4FIN-BjVK8IyX.js} +1 -1
  196. package/ui-dist/assets/{chunk-XXDRQBXY-CUyO-JUr.js → chunk-XXDRQBXY-D-TvO9vT.js} +1 -1
  197. package/ui-dist/assets/classDiagram-OUVF2IWQ-BIXq6fec.js +1 -0
  198. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-BIXq6fec.js +1 -0
  199. package/ui-dist/assets/{cose-bilkent-JH36ORCC-BCxIInLZ.js → cose-bilkent-JH36ORCC-DBwhASJG.js} +1 -1
  200. package/ui-dist/assets/{cynefin-VYW2F7L2-DqsnpnHA.js → cynefin-VYW2F7L2-SlCRmwl2.js} +1 -1
  201. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-BD7QQNLW.js → cynefinDiagram-TSTJHNR4-DImV6N_p.js} +1 -1
  202. package/ui-dist/assets/{dagre-VKFMJZFB-DaE160f7.js → dagre-VKFMJZFB-FH4Wv8Zn.js} +1 -1
  203. package/ui-dist/assets/{diagram-FQU43EPY-DZ0c59y8.js → diagram-FQU43EPY-CrZvQjId.js} +1 -1
  204. package/ui-dist/assets/{diagram-G47NLZAW-C_413g5E.js → diagram-G47NLZAW-B7IGl_T3.js} +1 -1
  205. package/ui-dist/assets/{diagram-NH7WQ7WH-D_o9pXB-.js → diagram-NH7WQ7WH-CB4i7H1a.js} +1 -1
  206. package/ui-dist/assets/{diagram-OA4YK3LP-BI2xMqiW.js → diagram-OA4YK3LP-DNyWPYY9.js} +1 -1
  207. package/ui-dist/assets/{diagram-WEI45ONY-hhM8R_14.js → diagram-WEI45ONY-BAhxbhpi.js} +1 -1
  208. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-DfFgEtmG.js → ebnfDiagram-CCIWWBDH-ClXtQ9qt.js} +1 -1
  209. package/ui-dist/assets/{erDiagram-Q63AITRT-CCw4ysHK.js → erDiagram-Q63AITRT-DGXiMYfn.js} +1 -1
  210. package/ui-dist/assets/{flowDiagram-23GEKE2U-CzC1SMmo.js → flowDiagram-23GEKE2U-CdZ-02zc.js} +1 -1
  211. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-CbPJIrIw.js → ganttDiagram-NO4QXBWP-TxhzNpta.js} +1 -1
  212. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-jCxJ66_C.js → gitGraphDiagram-IHSO6WYX-D6Q_w6wR.js} +1 -1
  213. package/ui-dist/assets/{graph-BfNZ9Jzj.js → graph-CxNmbpl9.js} +1 -1
  214. package/ui-dist/assets/{index-D00XdcOq.js → index-AkhgKOxC.js} +1 -1
  215. package/ui-dist/assets/{index-DiMDROQY.js → index-BA9tMBhO.js} +1 -1
  216. package/ui-dist/assets/{index-DmjAbzHq.js → index-BQ87dGIq.js} +1 -1
  217. package/ui-dist/assets/index-BmXb0yC1.css +1 -0
  218. package/ui-dist/assets/{index-B8q4cbOz.js → index-BoTz5S-X.js} +1 -1
  219. package/ui-dist/assets/{index-D_K-9VDs.js → index-BujwWE3j.js} +1 -1
  220. package/ui-dist/assets/{index-Chop-Dpa.js → index-Bxz75ICF.js} +1 -1
  221. package/ui-dist/assets/{index-BKn4zRjf.js → index-C12QC1Ex.js} +1 -1
  222. package/ui-dist/assets/{index-BjxRqzZK.js → index-CW21qWFm.js} +1 -1
  223. package/ui-dist/assets/{index-ByUFNj0O.js → index-CXnqg1Oq.js} +1 -1
  224. package/ui-dist/assets/{index-B3h9Vdxx.js → index-C_nwI4Yj.js} +1 -1
  225. package/ui-dist/assets/{index-Ck-ZNQeD.js → index-Cx6oIZPm.js} +572 -561
  226. package/ui-dist/assets/{index-BE1vSI6a.js → index-DU2LI7f9.js} +1 -1
  227. package/ui-dist/assets/{index-SkHIC7Ma.js → index-Do78GymY.js} +1 -1
  228. package/ui-dist/assets/{index-Bwo-C8W5.js → index-Rau2cGm0.js} +1 -1
  229. package/ui-dist/assets/{index-BULll2e0.js → index-kbWUQ1um.js} +1 -1
  230. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-BkDimGw3.js → infoDiagram-FWYZ7A6U-CnJd4-Jq.js} +1 -1
  231. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-BAyUfn6x.js → ishikawaDiagram-FXEZZL3T-Bq-qc3pD.js} +1 -1
  232. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-DlbpTZHG.js → journeyDiagram-5HDEW3XC-NSmpUGUA.js} +1 -1
  233. package/ui-dist/assets/{kanban-definition-HUTT4EX6-Cd67S14K.js → kanban-definition-HUTT4EX6-D7pgWtfA.js} +1 -1
  234. package/ui-dist/assets/{layout-Dc4YEiCC.js → layout-BRuo6LWe.js} +1 -1
  235. package/ui-dist/assets/{linear-BhRsbD_5.js → linear-CkaWI9Nd.js} +1 -1
  236. package/ui-dist/assets/{map-CEWybj6z.js → map-CgA6tTpZ.js} +1 -1
  237. package/ui-dist/assets/{mermaid.core-CqqAFYNg.js → mermaid.core-DrXOt-P6.js} +4 -4
  238. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-CCb-XmEi.js → mindmap-definition-LN4V7U3C-D9ImeWBe.js} +1 -1
  239. package/ui-dist/assets/{pegDiagram-2B236MQR-CqUV14VZ.js → pegDiagram-2B236MQR-Cq-zZ_zz.js} +1 -1
  240. package/ui-dist/assets/{pieDiagram-ENE6RG2P-Dn_M_GRM.js → pieDiagram-ENE6RG2P-FgNgy726.js} +1 -1
  241. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-rAEhJDr2.js → quadrantDiagram-ABIIQ3AL-BxQbmGmi.js} +1 -1
  242. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-iE2o_jkH.js → railroadDiagram-RFXS5EU6-CrpbkPH2.js} +1 -1
  243. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-BFyKBR1h.js → requirementDiagram-TGXJPOKE-DQ-65ZW6.js} +1 -1
  244. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-DRa_2YB6.js → sankeyDiagram-HTMAVEWB-Bom0lMZm.js} +1 -1
  245. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-BG8VIjD7.js → sequenceDiagram-DBY2YBRQ-C1MSDLDj.js} +1 -1
  246. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-CS1xlbzG.js → sizeCapture-X5ZJPWSS-Bn1vxebD.js} +1 -1
  247. package/ui-dist/assets/{stateDiagram-2N3HPSRC-BSl2RUJ1.js → stateDiagram-2N3HPSRC-GRRQpmzI.js} +1 -1
  248. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-KvSJhiLM.js +1 -0
  249. package/ui-dist/assets/{swimlanes-5IMT3BWC-BT8Hrm9J.js → swimlanes-5IMT3BWC-BcOOCZ4q.js} +2 -2
  250. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Cr92NnJ4.js +8 -0
  251. package/ui-dist/assets/{timeline-definition-FHXFAJF6-CfYIqEuQ.js → timeline-definition-FHXFAJF6-gi6amswh.js} +1 -1
  252. package/ui-dist/assets/{vennDiagram-L72KCM5P-C8zBH7o9.js → vennDiagram-L72KCM5P-B9uqeyQt.js} +1 -1
  253. package/ui-dist/assets/{wardleyDiagram-EHGQE667-D5HyFfI9.js → wardleyDiagram-EHGQE667-BnqWAMiE.js} +1 -1
  254. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-CGItJmLx.js → xychartDiagram-FW5EYKEG-BJXKltIN.js} +1 -1
  255. package/ui-dist/index.html +2 -2
  256. package/dist/services/issue-goal-fallback.d.ts +0 -15
  257. package/dist/services/issue-goal-fallback.d.ts.map +0 -1
  258. package/dist/services/issue-goal-fallback.js +0 -27
  259. package/dist/services/issue-goal-fallback.js.map +0 -1
  260. package/ui-dist/assets/channel-DLNwhPnC.js +0 -1
  261. package/ui-dist/assets/classDiagram-OUVF2IWQ-JsIc-rlc.js +0 -1
  262. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-JsIc-rlc.js +0 -1
  263. package/ui-dist/assets/index-CxPYXL3t.css +0 -1
  264. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DUVNSzI8.js +0 -1
  265. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DfLFj7sh.js +0 -8
@@ -1,30 +1,141 @@
1
- import { agents, automations, costEvents, financeEvents, goals, issues, projectGoals, projects } from "@rudderhq/db";
2
- import { and, asc, eq, isNull, sql } from "drizzle-orm";
3
- import { conflict, unprocessable } from "../errors.js";
1
+ import { agentWakeupRequests, agents, approvals, automations, calendarEvents, costEvents, financeEvents, goalActivities, goalChangeProposals, goalFeedbackEntries, goalOwnerAssignments, goalPlans, goalResultProposals, goalStartRequests, goals, heartbeatRuns, issues, projectGoals, projects, } from "@rudderhq/db";
2
+ import { activateGoalSchema } from "@rudderhq/shared";
3
+ import { and, asc, desc, eq, inArray, isNull, or, sql } from "drizzle-orm";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { conflict, forbidden, notFound, unprocessable } from "../errors.js";
4
6
  const DEPENDENCY_PREVIEW_LIMIT = 5;
5
- export async function getDefaultCompanyGoal(db, orgId) {
6
- const activeRootGoal = await db
7
- .select()
8
- .from(goals)
9
- .where(and(eq(goals.orgId, orgId), eq(goals.level, "organization"), eq(goals.status, "active"), isNull(goals.parentId)))
10
- .orderBy(asc(goals.createdAt))
11
- .then((rows) => rows[0] ?? null);
12
- if (activeRootGoal)
13
- return activeRootGoal;
14
- const anyRootGoal = await db
15
- .select()
16
- .from(goals)
17
- .where(and(eq(goals.orgId, orgId), eq(goals.level, "organization"), isNull(goals.parentId)))
18
- .orderBy(asc(goals.createdAt))
19
- .then((rows) => rows[0] ?? null);
20
- if (anyRootGoal)
21
- return anyRootGoal;
22
- return db
23
- .select()
24
- .from(goals)
25
- .where(and(eq(goals.orgId, orgId), eq(goals.level, "organization")))
26
- .orderBy(asc(goals.createdAt))
27
- .then((rows) => rows[0] ?? null);
7
+ const TERMINAL_RUN_STATUSES = ["succeeded", "completed", "failed", "cancelled", "timed_out"];
8
+ const NON_INVOKABLE_AGENT_STATUSES = new Set(["terminated", "pending_approval"]);
9
+ const GOAL_OUTCOME_PATTERNS = [
10
+ /\b(ship|publish|launch|deliver|creat(?:e|ed|ion)|build|complet(?:e|ed|ion)|finish|deploy|release|migrate|implement|fix|resolve|remove|establish|achieve|reach|increase|reduce|decrease|grow|choose|select|decide|approve|pass|verify|maintain|keep|sustain|recommend|submit)\b/i,
11
+ /(?:发布|上线|交付|创建|构建|完成|部署|发版|迁移|实现|修复|解决|删除|建立|达到|提升|增加|降低|减少|增长|选择|决定|批准|通过|验证|保持|维持|产出|提交)/,
12
+ /(?:\d+(?:\.\d+)?\s*(?:%|percent|x|倍|个|项|天|周|月|年))|(?:[$€£¥¥]\s*\d)/i,
13
+ ];
14
+ const GOAL_MODE_PATTERNS = {
15
+ decide: /\b(choose|select|decide|recommend|approve)\b|(?:选择|决定|推荐|批准)/i,
16
+ maintain: /\b(maintain|keep|sustain|preserve|prevent)\b|(?:保持|维持|防止|确保持续)/i,
17
+ maximize: /\b(increase|reduce|decrease|grow|improve by|maximi[sz]e|minimi[sz]e)\b|(?:提升|增加|降低|减少|增长|最大化|最小化)/i,
18
+ };
19
+ const BROAD_CAPABILITY_PATTERNS = [
20
+ /\b(general[- ]purpose|end[- ]to[- ]end)\b/i,
21
+ /\bplan\w*\b[\s\S]*\bexecut\w*\b|\bexecut\w*\b[\s\S]*\bplan\w*\b/i,
22
+ /(?:通用|端到端|规划[\s\S]*执行|执行[\s\S]*规划)/,
23
+ ];
24
+ const CAPABILITY_STOP_WORDS = new Set([
25
+ "about", "after", "agent", "and", "are", "available", "before", "bounded", "can", "context",
26
+ "external", "first", "for", "from", "goal", "have", "into", "must", "not", "produce", "result",
27
+ "should", "that", "the", "their", "this", "through", "user", "will", "with", "work",
28
+ ]);
29
+ function hasConcreteGoalOutcome(input) {
30
+ const combined = `${input.title.trim()} ${input.context?.trim() ?? ""}`;
31
+ return input.title.trim().length >= 8 && GOAL_OUTCOME_PATTERNS.some((pattern) => pattern.test(combined));
32
+ }
33
+ function goalMode(input) {
34
+ const title = input.title.trim();
35
+ if (GOAL_MODE_PATTERNS.decide.test(title))
36
+ return "decide";
37
+ if (GOAL_MODE_PATTERNS.maintain.test(title))
38
+ return "maintain";
39
+ if (GOAL_MODE_PATTERNS.maximize.test(title))
40
+ return "maximize";
41
+ if (GOAL_OUTCOME_PATTERNS.some((pattern) => pattern.test(title)))
42
+ return "target";
43
+ const context = input.context?.trim() ?? "";
44
+ if (GOAL_MODE_PATTERNS.decide.test(context))
45
+ return "decide";
46
+ if (GOAL_MODE_PATTERNS.maintain.test(context))
47
+ return "maintain";
48
+ if (GOAL_MODE_PATTERNS.maximize.test(context))
49
+ return "maximize";
50
+ return "target";
51
+ }
52
+ function meaningfulTokens(value) {
53
+ return new Set((value.toLowerCase().match(/[a-z0-9]+|[\p{Script=Han}]{2,}/gu) ?? [])
54
+ .map((token) => token.replace(/(?:ing|ed|es|s)$/i, ""))
55
+ .filter((token) => token.length >= 3 && !CAPABILITY_STOP_WORDS.has(token)));
56
+ }
57
+ function ownerCanAdvanceGoal(owner, input) {
58
+ const capabilities = owner.capabilities?.trim() ?? "";
59
+ if (!capabilities)
60
+ return false;
61
+ const descriptor = [owner.role, owner.title, capabilities].filter(Boolean).join(" ");
62
+ if (BROAD_CAPABILITY_PATTERNS.some((pattern) => pattern.test(descriptor)))
63
+ return true;
64
+ const goalTokens = meaningfulTokens(`${input.title} ${input.context ?? ""}`);
65
+ const capabilityTokens = meaningfulTokens(descriptor);
66
+ let overlappingTokenCount = 0;
67
+ for (const token of goalTokens) {
68
+ if (capabilityTokens.has(token))
69
+ overlappingTokenCount += 1;
70
+ }
71
+ if (overlappingTokenCount >= 2)
72
+ return true;
73
+ const domainPairs = [
74
+ [/\b(api|app|build|code|deploy|engineer|implement|release|software|system|test|ui|workspace)\b|(?:代码|工程|开发|部署|测试|系统|界面)/i, /\b(engineer|software|code|develop|build|deploy|test|technical|product)\b|(?:工程|开发|代码|部署|测试|技术|产品)/i],
75
+ [/\b(analy[sz]e|research|study|report|investigate)\b|(?:分析|研究|调研|报告)/i, /\b(analyst|analysis|research|data|strategy)\b|(?:分析|研究|数据|策略)/i],
76
+ [/\b(article|content|copy|marketing|publish|write)\b|(?:文章|内容|文案|营销|发布|写作)/i, /\b(content|copy|marketing|publish|writer|writing)\b|(?:内容|文案|营销|发布|写作)/i],
77
+ [/\b(design|ux|ui|prototype)\b|(?:设计|原型|界面)/i, /\b(design|designer|ux|ui|product)\b|(?:设计|产品|界面)/i],
78
+ [/\b(budget|finance|invoice|pricing|revenue)\b|(?:预算|财务|发票|定价|收入)/i, /\b(finance|financial|invoice|pricing|revenue|accounting)\b|(?:财务|发票|定价|收入|会计)/i],
79
+ ];
80
+ const goalText = `${input.title} ${input.context ?? ""}`;
81
+ return domainPairs.some(([goalPattern, capabilityPattern]) => goalPattern.test(goalText) && capabilityPattern.test(descriptor));
82
+ }
83
+ function canonicalizeJson(value) {
84
+ if (value instanceof Date)
85
+ return value.toISOString();
86
+ if (Array.isArray(value))
87
+ return value.map(canonicalizeJson);
88
+ if (!value || typeof value !== "object")
89
+ return value;
90
+ return Object.fromEntries(Object.entries(value)
91
+ .filter(([, entry]) => entry !== undefined)
92
+ .sort(([left], [right]) => left.localeCompare(right))
93
+ .map(([key, entry]) => [key, canonicalizeJson(entry)]));
94
+ }
95
+ export function stableGoalHash(value) {
96
+ return createHash("sha256").update(JSON.stringify(canonicalizeJson(value))).digest("hex");
97
+ }
98
+ function stringEvidenceRefs(value) {
99
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
100
+ }
101
+ function resultSummary(run) {
102
+ const candidates = [run.resultSummaryJson, run.resultJson];
103
+ for (const candidate of candidates) {
104
+ for (const key of ["summary", "result", "message", "userMessage", "body", "error"]) {
105
+ const value = candidate?.[key];
106
+ if (typeof value === "string" && value.trim())
107
+ return value.trim();
108
+ }
109
+ }
110
+ return run.error?.trim() || `Agent run is ${run.status}.`;
111
+ }
112
+ function sameHashPayload(left, right) {
113
+ return stableGoalHash(left) === stableGoalHash(right);
114
+ }
115
+ function toIsoString(value) {
116
+ if (!value)
117
+ return null;
118
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
119
+ }
120
+ function goalContractSnapshot(goal) {
121
+ return {
122
+ contractRevision: goal.contractRevision,
123
+ outcomeStatement: goal.outcomeStatement ?? "",
124
+ objectiveMode: goal.objectiveMode,
125
+ criteria: goal.criteria,
126
+ autonomyEnvelope: goal.autonomyEnvelope,
127
+ humanAuthorities: goal.humanAuthorities,
128
+ evaluationPolicy: goal.evaluationPolicy,
129
+ actionDeadline: toIsoString(goal.actionDeadline),
130
+ evaluationDeadline: toIsoString(goal.evaluationDeadline),
131
+ };
132
+ }
133
+ function normalizeContractPatch(patch) {
134
+ return {
135
+ ...patch,
136
+ ...(patch.actionDeadline !== undefined ? { actionDeadline: toIsoString(patch.actionDeadline) } : {}),
137
+ ...(patch.evaluationDeadline !== undefined ? { evaluationDeadline: toIsoString(patch.evaluationDeadline) } : {}),
138
+ };
28
139
  }
29
140
  function countRows(rows) {
30
141
  return Number(rows[0]?.count ?? 0);
@@ -32,205 +143,1451 @@ function countRows(rows) {
32
143
  function previewRows(rows, map) {
33
144
  return rows.slice(0, DEPENDENCY_PREVIEW_LIMIT).map(map);
34
145
  }
35
- async function assertOwnerBelongsToOrg(db, orgId, ownerAgentId) {
36
- if (!ownerAgentId)
37
- return;
146
+ function assertCanonicalActiveGoal(goal) {
147
+ if (goal.lifecycle !== "active")
148
+ throw conflict("Only active Goals can receive this command");
149
+ if (!goal.ownerAgentId || !goal.outcomeStatement?.trim() || !Array.isArray(goal.criteria) || goal.criteria.length === 0
150
+ || goal.planRevision < 1 || !goal.continuationKind || !goal.continuationSummary?.trim()) {
151
+ throw conflict("Goal requires Contract activation before this command");
152
+ }
153
+ }
154
+ function assertGoalOwner(goal, actorAgentId) {
155
+ if (actorAgentId && actorAgentId !== goal.ownerAgentId) {
156
+ throw forbidden("Only the Goal Owner can perform this Agent command");
157
+ }
158
+ }
159
+ async function requireInvokableOwner(db, orgId, ownerAgentId) {
38
160
  const owner = await db
39
- .select({ id: agents.id })
161
+ .select({
162
+ id: agents.id,
163
+ orgId: agents.orgId,
164
+ name: agents.name,
165
+ status: agents.status,
166
+ role: agents.role,
167
+ title: agents.title,
168
+ capabilities: agents.capabilities,
169
+ })
40
170
  .from(agents)
41
171
  .where(and(eq(agents.id, ownerAgentId), eq(agents.orgId, orgId)))
42
172
  .then((rows) => rows[0] ?? null);
43
- if (!owner) {
173
+ if (!owner)
44
174
  throw unprocessable("Goal owner must belong to the same organization");
175
+ if (NON_INVOKABLE_AGENT_STATUSES.has(owner.status)) {
176
+ throw unprocessable("Goal owner must be invokable", { status: owner.status });
45
177
  }
178
+ return owner;
46
179
  }
47
- async function assertParentBelongsToOrgAndDoesNotCycle(db, orgId, goalId, parentId) {
48
- if (!parentId)
49
- return;
50
- if (goalId && parentId === goalId) {
51
- throw unprocessable("Goal cannot be its own parent");
52
- }
53
- const allGoals = await db
54
- .select({ id: goals.id, orgId: goals.orgId, parentId: goals.parentId })
55
- .from(goals)
56
- .where(eq(goals.orgId, orgId));
57
- const parent = allGoals.find((goal) => goal.id === parentId);
58
- if (!parent) {
59
- throw unprocessable("Goal parent must belong to the same organization");
60
- }
61
- if (!goalId)
62
- return;
63
- const byId = new Map(allGoals.map((goal) => [goal.id, goal]));
64
- let cursor = parentId;
65
- const seen = new Set();
66
- while (cursor) {
67
- if (cursor === goalId) {
68
- throw unprocessable("Goal parent cannot create a cycle");
69
- }
70
- if (seen.has(cursor))
71
- break;
72
- seen.add(cursor);
73
- cursor = byId.get(cursor)?.parentId ?? null;
180
+ export function compileGoalStartPreview(input, owner) {
181
+ const title = input.title.trim();
182
+ if (!hasConcreteGoalOutcome(input)) {
183
+ return {
184
+ valid: false,
185
+ packetHash: null,
186
+ packet: null,
187
+ review: null,
188
+ alignmentQuestion: "What observable result or decision should this Goal produce, and how will we know it worked?",
189
+ };
74
190
  }
75
- }
76
- async function assertGoalReferences(db, orgId, goalId, data) {
77
- if (data.ownerAgentId !== undefined) {
78
- await assertOwnerBelongsToOrg(db, orgId, data.ownerAgentId);
191
+ if (!owner || owner.id !== input.ownerAgentId || NON_INVOKABLE_AGENT_STATUSES.has(owner.status)) {
192
+ return {
193
+ valid: false,
194
+ packetHash: null,
195
+ packet: null,
196
+ review: null,
197
+ alignmentQuestion: "Which available same-organization Agent should own and advance this Goal?",
198
+ };
79
199
  }
80
- if (data.parentId !== undefined) {
81
- await assertParentBelongsToOrgAndDoesNotCycle(db, orgId, goalId, data.parentId);
200
+ if (!ownerCanAdvanceGoal(owner, input)) {
201
+ return {
202
+ valid: false,
203
+ packetHash: null,
204
+ packet: null,
205
+ review: null,
206
+ alignmentQuestion: "The assigned Agent does not appear to cover this Goal. Which better-matched Agent should own it?",
207
+ };
82
208
  }
209
+ const mode = goalMode(input);
210
+ const context = input.context?.trim() || null;
211
+ const success = context
212
+ ? `Evidence demonstrates that ${title} while satisfying: ${context}`
213
+ : mode === "decide"
214
+ ? `A documented decision resolves: ${title}`
215
+ : mode === "maximize"
216
+ ? `Measured evidence demonstrates progress toward: ${title}`
217
+ : mode === "maintain"
218
+ ? `Evidence confirms the condition remained true: ${title}`
219
+ : `An inspectable artifact demonstrates that ${title}`;
220
+ const firstAction = mode === "decide"
221
+ ? "Gather the decision inputs and return with a clear recommendation and tradeoffs."
222
+ : mode === "maximize"
223
+ ? "Establish the current baseline and take the first measurable action."
224
+ : mode === "maintain"
225
+ ? "Confirm the current baseline and monitor the first material risk."
226
+ : "Inspect the available context and produce the first bounded, reviewable artifact.";
227
+ const evaluator = mode === "decide" ? "human" : mode === "maximize" ? "metric" : mode === "maintain" ? "policy" : "artifact";
228
+ const boundary = "The Owner may perform bounded, reversible work; consequential or irreversible actions require human approval.";
229
+ const activation = activateGoalSchema.parse({
230
+ confirmed: true,
231
+ ownerAgentId: owner.id,
232
+ outcomeStatement: title,
233
+ objectiveMode: mode,
234
+ criteria: [{ id: "primary-outcome", label: success, evaluator }],
235
+ autonomyEnvelope: {
236
+ allowed: ["bounded_reversible_work"],
237
+ requiresHumanApproval: ["external_or_irreversible_action", "authority_expansion"],
238
+ },
239
+ humanAuthorities: {
240
+ acceptance: "board_human",
241
+ consequentialChanges: "board_human",
242
+ },
243
+ evaluationPolicy: {
244
+ terminalEvidenceRequired: true,
245
+ humanAcceptanceRequired: true,
246
+ },
247
+ actionDeadline: null,
248
+ evaluationDeadline: input.targetTime,
249
+ initialPlan: { summary: firstAction },
250
+ initialContinuation: {
251
+ kind: "commitment",
252
+ summary: "Advance the first bounded action and report evidence or a named blocker.",
253
+ },
254
+ });
255
+ const packet = {
256
+ version: 1,
257
+ title,
258
+ description: context,
259
+ ownerAgentId: owner.id,
260
+ activation,
261
+ };
262
+ return {
263
+ valid: true,
264
+ packetHash: stableGoalHash(packet),
265
+ packet,
266
+ review: {
267
+ outcome: title,
268
+ success,
269
+ boundary,
270
+ firstAction,
271
+ ownerAgentId: owner.id,
272
+ targetTime: input.targetTime,
273
+ },
274
+ alignmentQuestion: null,
275
+ };
83
276
  }
84
277
  export async function getGoalDependencies(db, goal) {
85
- const [childGoalRows, projectJoinRows, legacyProjectRows, issueRows, automationRows, costEventRows, financeEventRows, rootGoalRows,] = await Promise.all([
86
- db
87
- .select({ id: goals.id, title: goals.title, status: goals.status })
88
- .from(goals)
89
- .where(and(eq(goals.orgId, goal.orgId), eq(goals.parentId, goal.id)))
90
- .orderBy(asc(goals.createdAt)),
91
- db
92
- .select({ id: projects.id, name: projects.name, status: projects.status })
93
- .from(projectGoals)
278
+ const [childGoalRows, projectJoinRows, legacyProjectRows, issueRows, automationRows, calendarEventRows, costEventRows, financeEventRows] = await Promise.all([
279
+ db.select({ id: goals.id, title: goals.title, status: goals.status }).from(goals)
280
+ .where(and(eq(goals.orgId, goal.orgId), eq(goals.parentId, goal.id))).orderBy(asc(goals.createdAt)),
281
+ db.select({ id: projects.id, name: projects.name, status: projects.status }).from(projectGoals)
94
282
  .innerJoin(projects, eq(projectGoals.projectId, projects.id))
95
- .where(and(eq(projectGoals.orgId, goal.orgId), eq(projectGoals.goalId, goal.id)))
96
- .orderBy(asc(projects.createdAt)),
97
- db
98
- .select({ id: projects.id, name: projects.name, status: projects.status })
99
- .from(projects)
100
- .where(and(eq(projects.orgId, goal.orgId), eq(projects.goalId, goal.id)))
101
- .orderBy(asc(projects.createdAt)),
102
- db
103
- .select({ id: issues.id, identifier: issues.identifier, title: issues.title, status: issues.status })
104
- .from(issues)
105
- .where(and(eq(issues.orgId, goal.orgId), eq(issues.goalId, goal.id)))
106
- .orderBy(asc(issues.createdAt)),
107
- db
108
- .select({ id: automations.id, title: automations.title, status: automations.status })
109
- .from(automations)
110
- .where(and(eq(automations.orgId, goal.orgId), eq(automations.goalId, goal.id)))
111
- .orderBy(asc(automations.createdAt)),
112
- db
113
- .select({ count: sql `count(*)::int` })
114
- .from(costEvents)
283
+ .where(and(eq(projectGoals.orgId, goal.orgId), eq(projectGoals.goalId, goal.id))).orderBy(asc(projects.createdAt)),
284
+ db.select({ id: projects.id, name: projects.name, status: projects.status }).from(projects)
285
+ .where(and(eq(projects.orgId, goal.orgId), eq(projects.goalId, goal.id))).orderBy(asc(projects.createdAt)),
286
+ db.select({ id: issues.id, identifier: issues.identifier, title: issues.title, status: issues.status }).from(issues)
287
+ .where(and(eq(issues.orgId, goal.orgId), eq(issues.goalId, goal.id))).orderBy(asc(issues.createdAt)),
288
+ db.select({ id: automations.id, title: automations.title, status: automations.status }).from(automations)
289
+ .where(and(eq(automations.orgId, goal.orgId), eq(automations.goalId, goal.id))).orderBy(asc(automations.createdAt)),
290
+ db.select({ id: calendarEvents.id, title: calendarEvents.title, status: calendarEvents.eventStatus }).from(calendarEvents)
291
+ .where(and(eq(calendarEvents.orgId, goal.orgId), eq(calendarEvents.goalId, goal.id), isNull(calendarEvents.deletedAt)))
292
+ .orderBy(asc(calendarEvents.createdAt)),
293
+ db.select({ count: sql `count(*)::int` }).from(costEvents)
115
294
  .where(and(eq(costEvents.orgId, goal.orgId), eq(costEvents.goalId, goal.id))),
116
- db
117
- .select({ count: sql `count(*)::int` })
118
- .from(financeEvents)
295
+ db.select({ count: sql `count(*)::int` }).from(financeEvents)
119
296
  .where(and(eq(financeEvents.orgId, goal.orgId), eq(financeEvents.goalId, goal.id))),
120
- goal.level === "organization" && goal.parentId === null
121
- ? db
122
- .select({ count: sql `count(*)::int` })
123
- .from(goals)
124
- .where(and(eq(goals.orgId, goal.orgId), eq(goals.level, "organization"), isNull(goals.parentId)))
125
- : Promise.resolve([{ count: 0 }]),
126
297
  ]);
127
298
  const linkedProjectsById = new Map();
128
- for (const project of [...projectJoinRows, ...legacyProjectRows]) {
299
+ for (const project of [...projectJoinRows, ...legacyProjectRows])
129
300
  linkedProjectsById.set(project.id, project);
130
- }
131
301
  const linkedProjects = [...linkedProjectsById.values()];
132
302
  const counts = {
133
303
  childGoals: childGoalRows.length,
134
304
  linkedProjects: linkedProjects.length,
135
305
  linkedIssues: issueRows.length,
136
306
  automations: automationRows.length,
307
+ calendarEvents: calendarEventRows.length,
137
308
  costEvents: countRows(costEventRows),
138
309
  financeEvents: countRows(financeEventRows),
139
310
  };
140
- const isLastRootOrganizationGoal = goal.level === "organization" &&
141
- goal.parentId === null &&
142
- countRows(rootGoalRows) <= 1;
143
311
  const blockers = [
144
- ...(isLastRootOrganizationGoal ? ["last_root_organization_goal"] : []),
145
312
  ...(counts.childGoals > 0 ? ["child_goals"] : []),
146
313
  ...(counts.linkedProjects > 0 ? ["linked_projects"] : []),
147
314
  ...(counts.linkedIssues > 0 ? ["linked_issues"] : []),
148
315
  ...(counts.automations > 0 ? ["automations"] : []),
316
+ ...(counts.calendarEvents > 0 ? ["calendar_events"] : []),
149
317
  ...(counts.costEvents > 0 ? ["cost_events"] : []),
150
318
  ...(counts.financeEvents > 0 ? ["finance_events"] : []),
151
319
  ];
152
320
  return {
153
321
  goalId: goal.id,
154
322
  orgId: goal.orgId,
155
- canDelete: blockers.length === 0,
156
- blockers,
157
- isLastRootOrganizationGoal,
323
+ canDelete: blockers.length === 0 && goal.lifecycle === "draft",
324
+ blockers: goal.lifecycle === "draft" ? blockers : [...blockers, "goal_not_draft"],
325
+ isLastRootOrganizationGoal: false,
158
326
  counts,
159
327
  previews: {
160
- childGoals: previewRows(childGoalRows, (row) => ({
161
- id: row.id,
162
- title: row.title,
163
- subtitle: row.status,
164
- })),
165
- linkedProjects: previewRows(linkedProjects, (row) => ({
166
- id: row.id,
167
- title: row.name,
168
- subtitle: row.status,
169
- })),
170
- linkedIssues: previewRows(issueRows, (row) => ({
171
- id: row.id,
172
- title: row.title,
173
- subtitle: row.identifier ?? row.status,
174
- })),
175
- automations: previewRows(automationRows, (row) => ({
176
- id: row.id,
177
- title: row.title,
178
- subtitle: row.status,
179
- })),
328
+ childGoals: previewRows(childGoalRows, (row) => ({ id: row.id, title: row.title, subtitle: row.status })),
329
+ linkedProjects: previewRows(linkedProjects, (row) => ({ id: row.id, title: row.name, subtitle: row.status })),
330
+ linkedIssues: previewRows(issueRows, (row) => ({ id: row.id, title: row.title, subtitle: row.identifier ?? row.status })),
331
+ automations: previewRows(automationRows, (row) => ({ id: row.id, title: row.title, subtitle: row.status })),
332
+ calendarEvents: previewRows(calendarEventRows, (row) => ({ id: row.id, title: row.title, subtitle: row.status })),
180
333
  },
181
334
  };
182
335
  }
336
+ async function requireGoal(db, id) {
337
+ const goal = await db.select().from(goals).where(eq(goals.id, id)).then((rows) => rows[0] ?? null);
338
+ if (!goal)
339
+ throw notFound("Goal not found");
340
+ return goal;
341
+ }
342
+ function positiveOutcome(mode, outcome) {
343
+ return (mode === "target" && outcome === "achieved")
344
+ || (mode === "maximize" && outcome === "completed_with_result")
345
+ || (mode === "maintain" && outcome === "maintained")
346
+ || (mode === "decide" && outcome === "decided");
347
+ }
348
+ export function reduceGoalEvaluation(goal, input) {
349
+ const statuses = new Map(input.criteria.map((criterion) => [criterion.id, criterion.status]));
350
+ const evidenceRefs = input.evidenceRefs;
351
+ const hasReference = (reference) => evidenceRefs.some((evidence) => evidence === reference);
352
+ const criterionStatuses = goal.criteria.map((criterion) => {
353
+ const requiredEvidence = criterion.evidenceRequirements ?? [];
354
+ const missingEvidence = requiredEvidence.filter((reference) => !hasReference(reference));
355
+ const evaluatorEvidenceSatisfied = criterion.evaluator === "metric"
356
+ ? input.resultValue !== undefined
357
+ : criterion.evaluator === "human"
358
+ ? Boolean(input.decision?.trim() || input.resultPayload.humanApproval === true)
359
+ : evidenceRefs.length > 0;
360
+ const evidenceSatisfied = missingEvidence.length === 0 && evaluatorEvidenceSatisfied;
361
+ const submittedStatus = statuses.get(criterion.id) ?? "unknown";
362
+ return {
363
+ id: criterion.id,
364
+ evaluator: criterion.evaluator ?? null,
365
+ status: !evidenceSatisfied ? "unknown" : submittedStatus,
366
+ evidenceSatisfied,
367
+ missingEvidence,
368
+ };
369
+ });
370
+ const has = (status) => criterionStatuses.some((criterion) => criterion.status === status);
371
+ const allMet = criterionStatuses.length > 0 && criterionStatuses.every((criterion) => criterion.status === "met");
372
+ const hasFailure = has("unmet") || has("breached");
373
+ let outcome;
374
+ switch (goal.objectiveMode) {
375
+ case "target":
376
+ outcome = hasFailure ? "not_achieved" : allMet ? "achieved" : "inconclusive";
377
+ break;
378
+ case "maximize":
379
+ outcome = !hasFailure && allMet && input.resultValue !== undefined ? "completed_with_result" : "inconclusive";
380
+ break;
381
+ case "maintain":
382
+ outcome = has("breached") ? "breached" : allMet ? "maintained" : "inconclusive";
383
+ break;
384
+ case "decide":
385
+ outcome = !hasFailure && allMet && input.decision?.trim() ? "decided" : "inconclusive";
386
+ break;
387
+ default:
388
+ outcome = "inconclusive";
389
+ }
390
+ return {
391
+ mode: goal.objectiveMode,
392
+ outcome,
393
+ criteria: criterionStatuses,
394
+ evidenceRefs: input.evidenceRefs,
395
+ resultValue: input.resultValue,
396
+ decision: input.decision ?? null,
397
+ evaluatedAt: new Date().toISOString(),
398
+ };
399
+ }
400
+ function evaluationCandidate(input) {
401
+ return {
402
+ evidenceRefs: input.evidenceRefs,
403
+ criteria: input.criteria,
404
+ ...(input.resultValue !== undefined ? { resultValue: input.resultValue } : {}),
405
+ ...(input.decision !== undefined ? { decision: input.decision } : {}),
406
+ resultPayload: input.resultPayload,
407
+ };
408
+ }
409
+ function isTerminalEvaluation(outcome) {
410
+ return outcome !== "inconclusive";
411
+ }
412
+ function inconclusiveResultSummary(preflight, riskSummary) {
413
+ const gaps = preflight.criteria.flatMap((criterion) => {
414
+ if (criterion.missingEvidence.length > 0) {
415
+ return criterion.missingEvidence.map((requirement) => `${criterion.id}: missing ${requirement}`);
416
+ }
417
+ return criterion.status === "unknown" ? [`${criterion.id}: unknown`] : [];
418
+ });
419
+ const details = [
420
+ preflight.resultValue !== undefined ? `Result value: ${String(preflight.resultValue)}.` : null,
421
+ preflight.decision ? `Decision: ${preflight.decision}.` : null,
422
+ gaps.length > 0 ? `Gaps: ${gaps.join("; ")}.` : null,
423
+ ].filter((value) => Boolean(value));
424
+ return [`Result attempt remains inconclusive: ${riskSummary}`, ...details].join(" ");
425
+ }
426
+ function proposalCreationPayload(input, normalizedPatch) {
427
+ return {
428
+ expectedContractRevision: input.expectedContractRevision,
429
+ afterContract: normalizedPatch,
430
+ rationale: input.rationale,
431
+ evidenceRefs: input.evidenceRefs,
432
+ approvalId: input.approvalId ?? null,
433
+ };
434
+ }
435
+ function assertSameChangeProposalPayload(existing, input, normalizedPatch) {
436
+ if (input.approvalId && existing.approvalId !== input.approvalId) {
437
+ throw conflict("Goal change proposal idempotency key was reused with a different approval");
438
+ }
439
+ const stored = {
440
+ expectedContractRevision: existing.expectedContractRevision,
441
+ afterContract: existing.afterContract,
442
+ rationale: existing.rationale,
443
+ evidenceRefs: existing.evidenceRefs,
444
+ approvalId: input.approvalId ?? null,
445
+ };
446
+ if (!sameHashPayload(stored, proposalCreationPayload(input, normalizedPatch))) {
447
+ throw conflict("Goal change proposal idempotency key was reused with a different payload");
448
+ }
449
+ }
450
+ function assertSameResultProposalPayload(existing, input, candidateHash) {
451
+ if (existing.contractRevision !== input.contractRevision
452
+ || existing.candidateHash !== candidateHash
453
+ || existing.riskSummary !== input.riskSummary) {
454
+ throw conflict("Goal result proposal idempotency key was reused with a different payload");
455
+ }
456
+ }
183
457
  export function goalService(db) {
458
+ function facetFor(goal, pendingChange, readyResult) {
459
+ if (goal.lifecycle === "closed")
460
+ return "closed";
461
+ if (goal.lifecycle === "draft")
462
+ return "needs_attention";
463
+ if (readyResult)
464
+ return "ready_for_acceptance";
465
+ if (pendingChange)
466
+ return "needs_attention";
467
+ if (goal.continuationKind === "wait")
468
+ return "waiting_external";
469
+ return "agent_advancing";
470
+ }
471
+ async function latestExternalCheckpoint(goal) {
472
+ const [issueFacts, linkedProjectFacts] = await Promise.all([
473
+ db.select({
474
+ id: issues.id,
475
+ identifier: issues.identifier,
476
+ title: issues.title,
477
+ status: issues.status,
478
+ updatedAt: issues.updatedAt,
479
+ checkoutRunId: issues.checkoutRunId,
480
+ executionRunId: issues.executionRunId,
481
+ }).from(issues).where(and(eq(issues.orgId, goal.orgId), eq(issues.goalId, goal.id))),
482
+ db.select({
483
+ id: projects.id,
484
+ name: projects.name,
485
+ status: projects.status,
486
+ updatedAt: projects.updatedAt,
487
+ }).from(projects).leftJoin(projectGoals, and(eq(projectGoals.projectId, projects.id), eq(projectGoals.orgId, goal.orgId))).where(and(eq(projects.orgId, goal.orgId), or(eq(projects.goalId, goal.id), eq(projectGoals.goalId, goal.id)))),
488
+ ]);
489
+ const runIds = [...new Set(issueFacts.flatMap((issue) => [issue.checkoutRunId, issue.executionRunId]).filter((id) => Boolean(id)))];
490
+ const runFacts = runIds.length > 0
491
+ ? await db.select({
492
+ id: heartbeatRuns.id,
493
+ status: heartbeatRuns.status,
494
+ updatedAt: heartbeatRuns.updatedAt,
495
+ resultJson: heartbeatRuns.resultJson,
496
+ resultSummaryJson: heartbeatRuns.resultSummaryJson,
497
+ error: heartbeatRuns.error,
498
+ }).from(heartbeatRuns).where(and(eq(heartbeatRuns.orgId, goal.orgId), inArray(heartbeatRuns.id, runIds)))
499
+ : [];
500
+ const facts = [
501
+ ...issueFacts.map((issue) => ({
502
+ id: `checkpoint:issue:${issue.id}`,
503
+ kind: "checkpoint",
504
+ summary: `Issue ${issue.identifier ?? issue.title} is ${issue.status}.`,
505
+ occurredAt: issue.updatedAt,
506
+ sourceId: issue.id,
507
+ sourceRunId: issue.executionRunId ?? issue.checkoutRunId,
508
+ evidenceRefs: [`issue://${issue.id}`],
509
+ })),
510
+ ...linkedProjectFacts.map((project) => ({
511
+ id: `checkpoint:project:${project.id}`,
512
+ kind: "checkpoint",
513
+ summary: `Project ${project.name} is ${project.status}.`,
514
+ occurredAt: project.updatedAt,
515
+ sourceId: project.id,
516
+ sourceRunId: null,
517
+ evidenceRefs: [`project://${project.id}`],
518
+ })),
519
+ ...runFacts.map((run) => ({
520
+ id: `checkpoint:run:${run.id}`,
521
+ kind: "checkpoint",
522
+ summary: `Agent run ${run.id.slice(0, 8)} is ${run.status}: ${resultSummary(run)}`,
523
+ occurredAt: run.updatedAt,
524
+ sourceId: run.id,
525
+ sourceRunId: run.id,
526
+ evidenceRefs: [`run://${run.id}`],
527
+ })),
528
+ ];
529
+ return facts.sort((left, right) => right.occurredAt.getTime() - left.occurredAt.getTime())[0] ?? null;
530
+ }
531
+ async function recordFeedback(database, goal, input, actorUserId) {
532
+ assertCanonicalActiveGoal(goal);
533
+ const contentHash = stableGoalHash({
534
+ actorType: "user",
535
+ actorId: actorUserId,
536
+ body: input.body,
537
+ attachments: input.attachments,
538
+ feedbackKind: input.feedbackKind,
539
+ });
540
+ const existing = await database.select().from(goalFeedbackEntries).where(and(eq(goalFeedbackEntries.goalId, goal.id), eq(goalFeedbackEntries.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
541
+ if (existing) {
542
+ if (existing.contentHash !== contentHash) {
543
+ throw conflict("Goal feedback idempotency key was reused with a different payload");
544
+ }
545
+ return existing;
546
+ }
547
+ const [inserted] = await database.insert(goalFeedbackEntries).values({
548
+ orgId: goal.orgId,
549
+ goalId: goal.id,
550
+ actorType: "user",
551
+ actorId: actorUserId,
552
+ body: input.body,
553
+ attachments: input.attachments,
554
+ contentHash,
555
+ feedbackKind: input.feedbackKind,
556
+ idempotencyKey: input.idempotencyKey,
557
+ }).onConflictDoNothing().returning();
558
+ const feedback = inserted ?? await database.select().from(goalFeedbackEntries).where(and(eq(goalFeedbackEntries.goalId, goal.id), eq(goalFeedbackEntries.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
559
+ if (!feedback)
560
+ throw conflict("Goal feedback could not be recorded");
561
+ if (feedback.contentHash !== contentHash) {
562
+ throw conflict("Goal feedback idempotency key was reused with a different payload");
563
+ }
564
+ if (feedback.routedWakeupRequestId)
565
+ return feedback;
566
+ const wakeupKey = `goal-feedback:${feedback.id}`;
567
+ const [insertedWakeup] = await database.insert(agentWakeupRequests).values({
568
+ orgId: goal.orgId,
569
+ agentId: goal.ownerAgentId,
570
+ source: "goal_feedback",
571
+ triggerDetail: "goal_feedback",
572
+ reason: "Goal feedback requires Owner review",
573
+ payload: { goalId: goal.id, feedbackId: feedback.id },
574
+ status: "queued",
575
+ requestedByActorType: "user",
576
+ requestedByActorId: actorUserId,
577
+ idempotencyKey: wakeupKey,
578
+ }).onConflictDoNothing().returning();
579
+ const wakeup = insertedWakeup ?? await database.select().from(agentWakeupRequests).where(and(eq(agentWakeupRequests.orgId, goal.orgId), eq(agentWakeupRequests.agentId, goal.ownerAgentId), eq(agentWakeupRequests.idempotencyKey, wakeupKey))).then((rows) => rows[0] ?? null);
580
+ if (!wakeup)
581
+ throw conflict("Goal feedback Owner wakeup could not be queued");
582
+ return database.update(goalFeedbackEntries).set({
583
+ routedWakeupRequestId: wakeup.id,
584
+ updatedAt: new Date(),
585
+ }).where(and(eq(goalFeedbackEntries.id, feedback.id), eq(goalFeedbackEntries.orgId, goal.orgId))).returning().then((rows) => rows[0] ?? feedback);
586
+ }
587
+ async function evaluateInTransaction(database, current, input, actorAgentId) {
588
+ assertCanonicalActiveGoal(current);
589
+ assertGoalOwner(current, actorAgentId);
590
+ const evaluation = reduceGoalEvaluation(current, input);
591
+ const terminal = isTerminalEvaluation(evaluation.outcome);
592
+ let acceptedProposal = null;
593
+ if (terminal) {
594
+ if (!input.resultProposalId) {
595
+ throw conflict("Terminal Goal evaluation requires an accepted Result Proposal");
596
+ }
597
+ acceptedProposal = await database.select().from(goalResultProposals).where(and(eq(goalResultProposals.id, input.resultProposalId), eq(goalResultProposals.goalId, current.id), eq(goalResultProposals.orgId, current.orgId))).then((rows) => rows[0] ?? null);
598
+ const candidate = evaluationCandidate(input);
599
+ if (!acceptedProposal
600
+ || acceptedProposal.status !== "accepted"
601
+ || acceptedProposal.acceptedByActorType !== "user"
602
+ || !acceptedProposal.acceptedByActorId
603
+ || !acceptedProposal.acceptedAt
604
+ || acceptedProposal.consumedAt
605
+ || acceptedProposal.contractRevision !== current.contractRevision
606
+ || acceptedProposal.candidateHash !== stableGoalHash(candidate)
607
+ || !sameHashPayload(acceptedProposal.candidate, candidate)) {
608
+ throw conflict("Accepted Result Proposal does not match the current Goal and evaluation candidate");
609
+ }
610
+ const consumed = await database.update(goalResultProposals).set({
611
+ consumedAt: new Date(),
612
+ updatedAt: new Date(),
613
+ }).where(and(eq(goalResultProposals.id, acceptedProposal.id), isNull(goalResultProposals.consumedAt))).returning().then((rows) => rows[0] ?? null);
614
+ if (!consumed)
615
+ throw conflict("Result Proposal was already consumed");
616
+ }
617
+ const lifecycle = terminal ? "closed" : "active";
618
+ const status = terminal
619
+ ? positiveOutcome(current.objectiveMode, evaluation.outcome) ? "achieved" : "cancelled"
620
+ : "active";
621
+ const [goal] = await database.update(goals).set({
622
+ lifecycle,
623
+ status,
624
+ ...(terminal ? { focus: false, closeReason: "evaluated" } : {}),
625
+ evaluationResult: evaluation,
626
+ resultPayload: input.resultPayload,
627
+ updatedAt: new Date(),
628
+ }).where(and(eq(goals.id, current.id), eq(goals.orgId, current.orgId), eq(goals.lifecycle, "active"), eq(goals.contractRevision, current.contractRevision))).returning();
629
+ if (!goal)
630
+ throw conflict("Goal changed before evaluation; reload and retry");
631
+ await database.insert(goalActivities).values({
632
+ orgId: current.orgId,
633
+ goalId: current.id,
634
+ contractRevision: current.contractRevision,
635
+ submittedByAgentId: actorAgentId,
636
+ agentOwnerRefAtTime: current.ownerAgentId,
637
+ activityKind: "evidence",
638
+ summary: `Goal evaluated as ${evaluation.outcome}`,
639
+ evidenceRefs: input.evidenceRefs,
640
+ idempotencyKey: acceptedProposal ? `goal-result-evaluation:${acceptedProposal.id}` : null,
641
+ }).onConflictDoNothing();
642
+ return goal;
643
+ }
644
+ async function workspaceFor(id) {
645
+ const goal = await requireGoal(db, id);
646
+ const [ownerAssignment, plan, activities, feedback, changes, results] = await Promise.all([
647
+ db.select().from(goalOwnerAssignments).where(and(eq(goalOwnerAssignments.goalId, id), isNull(goalOwnerAssignments.endsAt))).then((rows) => rows[0] ?? null),
648
+ goal.planRevision > 0
649
+ ? db.select().from(goalPlans).where(and(eq(goalPlans.goalId, id), eq(goalPlans.revision, goal.planRevision))).then((rows) => rows[0] ?? null)
650
+ : Promise.resolve(null),
651
+ db.select().from(goalActivities).where(eq(goalActivities.goalId, id))
652
+ .orderBy(desc(goalActivities.occurredAt), desc(goalActivities.createdAt)).limit(100),
653
+ db.select().from(goalFeedbackEntries).where(eq(goalFeedbackEntries.goalId, id))
654
+ .orderBy(desc(goalFeedbackEntries.createdAt)).limit(100),
655
+ db.select().from(goalChangeProposals).where(eq(goalChangeProposals.goalId, id))
656
+ .orderBy(desc(goalChangeProposals.createdAt)).limit(100),
657
+ db.select().from(goalResultProposals).where(eq(goalResultProposals.goalId, id))
658
+ .orderBy(desc(goalResultProposals.createdAt)).limit(100),
659
+ ]);
660
+ const externalCheckpoint = await latestExternalCheckpoint(goal);
661
+ const evidenceActivity = activities.find((activity) => stringEvidenceRefs(activity.evidenceRefs).length > 0) ?? null;
662
+ const progressSource = [
663
+ evidenceActivity
664
+ ? {
665
+ summary: evidenceActivity.summary,
666
+ sourceActivityId: evidenceActivity.id,
667
+ evidenceRefs: stringEvidenceRefs(evidenceActivity.evidenceRefs),
668
+ occurredAt: evidenceActivity.occurredAt,
669
+ }
670
+ : null,
671
+ externalCheckpoint
672
+ ? {
673
+ summary: externalCheckpoint.summary,
674
+ sourceActivityId: null,
675
+ evidenceRefs: externalCheckpoint.evidenceRefs,
676
+ occurredAt: externalCheckpoint.occurredAt,
677
+ }
678
+ : null,
679
+ ].filter((value) => Boolean(value))
680
+ .sort((left, right) => right.occurredAt.getTime() - left.occurredAt.getTime())[0] ?? null;
681
+ const pendingChange = changes.find((proposal) => proposal.status === "pending") ?? null;
682
+ const readyResult = results.find((proposal) => proposal.status === "ready") ?? null;
683
+ const facet = facetFor(goal, pendingChange, readyResult);
684
+ const attention = readyResult
685
+ ? {
686
+ kind: "result_proposal",
687
+ reason: `Review the proposed Goal result: ${readyResult.preflight.outcome}`,
688
+ sourceId: readyResult.id,
689
+ }
690
+ : pendingChange
691
+ ? {
692
+ kind: "change_proposal",
693
+ reason: pendingChange.rationale,
694
+ sourceId: pendingChange.id,
695
+ }
696
+ : goal.lifecycle === "draft" && goal.alignmentQuestion
697
+ ? { kind: "alignment_question", reason: goal.alignmentQuestion, sourceId: goal.id }
698
+ : null;
699
+ const timeline = [
700
+ ...activities.map((activity) => ({
701
+ id: activity.id,
702
+ kind: "activity",
703
+ summary: activity.summary,
704
+ occurredAt: activity.occurredAt,
705
+ sourceId: activity.id,
706
+ sourceRunId: activity.runRef,
707
+ evidenceRefs: stringEvidenceRefs(activity.evidenceRefs),
708
+ })),
709
+ ...feedback.map((entry) => ({
710
+ id: entry.id,
711
+ kind: "feedback",
712
+ summary: entry.body,
713
+ occurredAt: entry.createdAt,
714
+ sourceId: entry.id,
715
+ actorType: entry.actorType,
716
+ actorId: entry.actorId,
717
+ attachments: entry.attachments,
718
+ feedbackKind: entry.feedbackKind,
719
+ })),
720
+ ...changes.map((proposal) => ({
721
+ id: proposal.id,
722
+ kind: "change_proposal",
723
+ summary: proposal.rationale,
724
+ occurredAt: proposal.createdAt,
725
+ sourceId: proposal.id,
726
+ approvalId: proposal.approvalId,
727
+ status: proposal.status,
728
+ evidenceRefs: proposal.evidenceRefs,
729
+ })),
730
+ ...results.map((proposal) => ({
731
+ id: proposal.id,
732
+ kind: "result_proposal",
733
+ summary: `Result proposed as ${proposal.preflight.outcome}: ${proposal.riskSummary}`,
734
+ occurredAt: proposal.createdAt,
735
+ sourceId: proposal.id,
736
+ status: proposal.status,
737
+ evidenceRefs: proposal.candidate.evidenceRefs,
738
+ })),
739
+ ...(externalCheckpoint ? [externalCheckpoint] : []),
740
+ ].sort((left, right) => right.occurredAt.getTime() - left.occurredAt.getTime());
741
+ return {
742
+ goal: { ...goal, ownerAssignment, plan, activities },
743
+ facet,
744
+ currentGoal: { summary: goal.outcomeStatement ?? goal.title, revision: goal.contractRevision },
745
+ currentProgress: progressSource
746
+ ? {
747
+ summary: progressSource.summary,
748
+ sourceActivityId: progressSource.sourceActivityId,
749
+ evidenceRefs: progressSource.evidenceRefs,
750
+ }
751
+ : {
752
+ summary: "No evidence-backed progress has been recorded yet.",
753
+ sourceActivityId: null,
754
+ evidenceRefs: [],
755
+ },
756
+ agentAction: plan ? { summary: plan.summary, sourcePlanId: plan.id, sourceIds: [plan.id] } : null,
757
+ nextStep: goal.continuationKind && goal.continuationSummary
758
+ ? {
759
+ kind: goal.continuationKind,
760
+ summary: goal.continuationSummary,
761
+ wakeCondition: goal.wakeCondition,
762
+ }
763
+ : null,
764
+ attention,
765
+ timeline,
766
+ changeProposals: changes,
767
+ resultProposals: results,
768
+ };
769
+ }
184
770
  return {
185
- list: (orgId) => db.select().from(goals).where(eq(goals.orgId, orgId)),
186
- getById: (id) => db
187
- .select()
188
- .from(goals)
189
- .where(eq(goals.id, id))
190
- .then((rows) => rows[0] ?? null),
191
- getDefaultCompanyGoal: (orgId) => getDefaultCompanyGoal(db, orgId),
771
+ list: (orgId) => db.select().from(goals).where(eq(goals.orgId, orgId)).orderBy(asc(goals.createdAt)),
772
+ getById: (id) => db.select().from(goals).where(eq(goals.id, id)).then((rows) => rows[0] ?? null),
773
+ getChangeProposalById: (id) => db.select().from(goalChangeProposals)
774
+ .where(eq(goalChangeProposals.id, id)).then((rows) => rows[0] ?? null),
775
+ getResultProposalById: (id) => db.select().from(goalResultProposals)
776
+ .where(eq(goalResultProposals.id, id)).then((rows) => rows[0] ?? null),
192
777
  dependencies: (goal) => getGoalDependencies(db, goal),
193
- create: async (orgId, data) => {
194
- await assertGoalReferences(db, orgId, null, data);
195
- return db
196
- .insert(goals)
197
- .values({ ...data, orgId })
198
- .returning()
199
- .then((rows) => rows[0]);
200
- },
201
- update: async (id, data) => {
202
- const existing = await db
203
- .select()
204
- .from(goals)
205
- .where(eq(goals.id, id))
206
- .then((rows) => rows[0] ?? null);
207
- if (!existing)
208
- return null;
209
- await assertGoalReferences(db, existing.orgId, id, data);
210
- return db
211
- .update(goals)
212
- .set({ ...data, updatedAt: new Date() })
213
- .where(eq(goals.id, id))
214
- .returning()
215
- .then((rows) => rows[0] ?? null);
778
+ previewStart: async (orgId, input) => {
779
+ if (!hasConcreteGoalOutcome(input))
780
+ return compileGoalStartPreview(input, null);
781
+ const owner = input.ownerAgentId
782
+ ? await db.select({
783
+ id: agents.id,
784
+ orgId: agents.orgId,
785
+ name: agents.name,
786
+ status: agents.status,
787
+ role: agents.role,
788
+ title: agents.title,
789
+ capabilities: agents.capabilities,
790
+ })
791
+ .from(agents).where(and(eq(agents.id, input.ownerAgentId), eq(agents.orgId, orgId)))
792
+ .then((rows) => rows[0] ?? null)
793
+ : null;
794
+ return compileGoalStartPreview(input, owner);
795
+ },
796
+ start: async (orgId, input) => {
797
+ const computedHash = stableGoalHash(input.packet);
798
+ if (computedHash !== input.packetHash) {
799
+ throw conflict("Goal Start packet hash does not match the submitted packet");
800
+ }
801
+ const replay = await db.select().from(goalStartRequests).where(and(eq(goalStartRequests.orgId, orgId), eq(goalStartRequests.requestKey, input.requestKey))).then((rows) => rows[0] ?? null);
802
+ if (replay) {
803
+ if (replay.packetHash !== input.packetHash) {
804
+ throw conflict("Goal Start request key was reused with a different packet hash");
805
+ }
806
+ if (!sameHashPayload(replay.packet, input.packet)) {
807
+ throw conflict("Goal Start request packet does not match the original request");
808
+ }
809
+ if (replay.status !== "completed" || !replay.goalId) {
810
+ throw conflict("Goal Start request has not completed");
811
+ }
812
+ const goal = await db.select().from(goals).where(and(eq(goals.id, replay.goalId), eq(goals.orgId, orgId))).then((rows) => rows[0] ?? null);
813
+ if (!goal)
814
+ throw conflict("Completed Goal Start request has no Goal");
815
+ return { goal, replayed: true };
816
+ }
817
+ return db.transaction(async (tx) => {
818
+ const database = tx;
819
+ const owner = await requireInvokableOwner(database, orgId, input.packet.ownerAgentId);
820
+ if (!ownerCanAdvanceGoal(owner, {
821
+ title: input.packet.title,
822
+ context: input.packet.description,
823
+ ownerAgentId: input.packet.ownerAgentId,
824
+ targetTime: input.packet.activation.evaluationDeadline ?? null,
825
+ })) {
826
+ throw unprocessable("Goal owner capabilities no longer match the Goal");
827
+ }
828
+ const [insertedRequest] = await database.insert(goalStartRequests).values({
829
+ orgId,
830
+ requestKey: input.requestKey,
831
+ packetHash: input.packetHash,
832
+ packet: input.packet,
833
+ draftGoalId: input.draftGoalId ?? null,
834
+ status: "pending",
835
+ }).onConflictDoNothing().returning();
836
+ if (!insertedRequest) {
837
+ const existing = await database.select().from(goalStartRequests).where(and(eq(goalStartRequests.orgId, orgId), eq(goalStartRequests.requestKey, input.requestKey))).then((rows) => rows[0] ?? null);
838
+ if (!existing || existing.packetHash !== input.packetHash || !sameHashPayload(existing.packet, input.packet)) {
839
+ throw conflict("Goal Start request key was reused with a different packet hash");
840
+ }
841
+ if (existing.status !== "completed" || !existing.goalId) {
842
+ throw conflict("Goal Start request has not completed");
843
+ }
844
+ const existingGoal = await database.select().from(goals).where(and(eq(goals.id, existing.goalId), eq(goals.orgId, orgId))).then((rows) => rows[0] ?? null);
845
+ if (!existingGoal)
846
+ throw conflict("Completed Goal Start request has no Goal");
847
+ return { goal: existingGoal, replayed: true };
848
+ }
849
+ let draft;
850
+ if (input.draftGoalId) {
851
+ draft = await database.select().from(goals).where(and(eq(goals.id, input.draftGoalId), eq(goals.orgId, orgId), eq(goals.lifecycle, "draft"))).then((rows) => rows[0] ?? null);
852
+ if (!draft)
853
+ throw conflict("Reusable Goal Draft was not found in this organization");
854
+ const updatedDraft = await database.update(goals).set({
855
+ title: input.packet.title,
856
+ description: input.packet.description,
857
+ alignmentQuestion: null,
858
+ updatedAt: new Date(),
859
+ }).where(and(eq(goals.id, input.draftGoalId), eq(goals.orgId, orgId), eq(goals.lifecycle, "draft"))).returning().then((rows) => rows[0] ?? null);
860
+ if (!updatedDraft)
861
+ throw conflict("Goal Draft changed before start; reload and retry");
862
+ draft = updatedDraft;
863
+ }
864
+ else {
865
+ draft = await database.insert(goals).values({
866
+ orgId,
867
+ title: input.packet.title,
868
+ description: input.packet.description,
869
+ alignmentQuestion: null,
870
+ level: "task",
871
+ status: "planned",
872
+ lifecycle: "draft",
873
+ parentId: null,
874
+ ownerAgentId: null,
875
+ }).returning().then((rows) => rows[0] ?? null);
876
+ }
877
+ if (!draft)
878
+ throw conflict("Goal Draft could not be created");
879
+ const activation = input.packet.activation;
880
+ const [goal] = await database.update(goals).set({
881
+ outcomeStatement: activation.outcomeStatement,
882
+ objectiveMode: activation.objectiveMode,
883
+ lifecycle: "active",
884
+ status: "active",
885
+ contractRevision: 1,
886
+ criteria: activation.criteria,
887
+ autonomyEnvelope: activation.autonomyEnvelope,
888
+ humanAuthorities: activation.humanAuthorities,
889
+ evaluationPolicy: activation.evaluationPolicy,
890
+ actionDeadline: activation.actionDeadline ?? null,
891
+ evaluationDeadline: activation.evaluationDeadline ?? null,
892
+ ownerAgentId: input.packet.ownerAgentId,
893
+ planRevision: 1,
894
+ continuationKind: activation.initialContinuation.kind,
895
+ continuationSummary: activation.initialContinuation.summary,
896
+ wakeCondition: activation.initialContinuation.wakeCondition ?? null,
897
+ alignmentQuestion: null,
898
+ updatedAt: new Date(),
899
+ }).where(and(eq(goals.id, draft.id), eq(goals.orgId, orgId), eq(goals.lifecycle, "draft"))).returning();
900
+ if (!goal)
901
+ throw conflict("Goal changed before start activation; reload and retry");
902
+ await database.insert(goalOwnerAssignments).values({
903
+ orgId,
904
+ goalId: goal.id,
905
+ agentId: input.packet.ownerAgentId,
906
+ assignedByAuthorityRef: `goal_start:${insertedRequest.id}`,
907
+ assignmentRevision: 1,
908
+ });
909
+ await database.insert(goalPlans).values({
910
+ orgId,
911
+ goalId: goal.id,
912
+ revision: 1,
913
+ ...activation.initialPlan,
914
+ createdByAgentId: null,
915
+ });
916
+ await database.insert(goalActivities).values({
917
+ orgId,
918
+ goalId: goal.id,
919
+ contractRevision: 1,
920
+ submittedByAgentId: null,
921
+ agentOwnerRefAtTime: input.packet.ownerAgentId,
922
+ activityKind: "progress",
923
+ summary: `Goal activated with initial ${activation.initialContinuation.kind}: ${activation.initialContinuation.summary}`,
924
+ evidenceRefs: [],
925
+ idempotencyKey: `goal-start:${insertedRequest.id}`,
926
+ });
927
+ const completedAt = new Date();
928
+ const completedRequest = await database.update(goalStartRequests).set({
929
+ goalId: goal.id,
930
+ status: "completed",
931
+ completedAt,
932
+ updatedAt: completedAt,
933
+ }).where(and(eq(goalStartRequests.id, insertedRequest.id), eq(goalStartRequests.status, "pending"))).returning().then((rows) => rows[0] ?? null);
934
+ if (!completedRequest)
935
+ throw conflict("Goal Start request could not be completed");
936
+ return { goal, replayed: false };
937
+ });
938
+ },
939
+ workspace: workspaceFor,
940
+ workspaceCards: async (orgId) => {
941
+ const organizationGoals = await db.select().from(goals).where(eq(goals.orgId, orgId))
942
+ .orderBy(asc(goals.createdAt));
943
+ return Promise.all(organizationGoals.map(async (goal) => {
944
+ const [progress, externalCheckpoint, pendingChange, readyResult, owner] = await Promise.all([
945
+ db.select().from(goalActivities).where(and(eq(goalActivities.goalId, goal.id), sql `jsonb_array_length(${goalActivities.evidenceRefs}) > 0`)).orderBy(desc(goalActivities.occurredAt), desc(goalActivities.createdAt)).limit(1)
946
+ .then((rows) => rows[0] ?? null),
947
+ latestExternalCheckpoint(goal),
948
+ db.select().from(goalChangeProposals).where(and(eq(goalChangeProposals.goalId, goal.id), eq(goalChangeProposals.status, "pending"))).orderBy(asc(goalChangeProposals.createdAt)).limit(1).then((rows) => rows[0] ?? null),
949
+ db.select().from(goalResultProposals).where(and(eq(goalResultProposals.goalId, goal.id), eq(goalResultProposals.status, "ready"))).orderBy(asc(goalResultProposals.createdAt)).limit(1).then((rows) => rows[0] ?? null),
950
+ goal.ownerAgentId
951
+ ? db.select({ name: agents.name }).from(agents).where(and(eq(agents.id, goal.ownerAgentId), eq(agents.orgId, orgId))).then((rows) => rows[0] ?? null)
952
+ : Promise.resolve(null),
953
+ ]);
954
+ const currentProgress = [
955
+ progress ? { summary: progress.summary, occurredAt: progress.occurredAt } : null,
956
+ externalCheckpoint ? { summary: externalCheckpoint.summary, occurredAt: externalCheckpoint.occurredAt } : null,
957
+ ].filter((value) => Boolean(value))
958
+ .sort((left, right) => right.occurredAt.getTime() - left.occurredAt.getTime())[0]?.summary
959
+ ?? "No evidence-backed progress has been recorded yet.";
960
+ const attentionReason = readyResult
961
+ ? `Review the proposed Goal result: ${readyResult.preflight.outcome}`
962
+ : pendingChange?.rationale ?? (goal.lifecycle === "draft" ? goal.alignmentQuestion : null);
963
+ return {
964
+ id: goal.id,
965
+ orgId: goal.orgId,
966
+ title: goal.title,
967
+ lifecycle: goal.lifecycle,
968
+ status: goal.status,
969
+ facet: facetFor(goal, pendingChange, readyResult),
970
+ ownerAgentId: goal.ownerAgentId,
971
+ ownerName: owner?.name ?? null,
972
+ currentProgress,
973
+ progressSummary: currentProgress,
974
+ nextAction: goal.continuationSummary,
975
+ nextStepSummary: goal.continuationSummary ?? "No next step has been recorded.",
976
+ targetTime: goal.evaluationDeadline ?? goal.actionDeadline,
977
+ attentionReason,
978
+ focus: goal.focus,
979
+ updatedAt: goal.updatedAt,
980
+ };
981
+ }));
982
+ },
983
+ detail: async (id) => {
984
+ const goal = await requireGoal(db, id);
985
+ const [ownerAssignment, plan, activities] = await Promise.all([
986
+ db.select().from(goalOwnerAssignments).where(and(eq(goalOwnerAssignments.goalId, id), isNull(goalOwnerAssignments.endsAt)))
987
+ .then((rows) => rows[0] ?? null),
988
+ goal.planRevision > 0
989
+ ? db.select().from(goalPlans).where(and(eq(goalPlans.goalId, id), eq(goalPlans.revision, goal.planRevision))).then((rows) => rows[0] ?? null)
990
+ : Promise.resolve(null),
991
+ db.select().from(goalActivities).where(eq(goalActivities.goalId, id)).orderBy(desc(goalActivities.occurredAt), desc(goalActivities.createdAt)).limit(100),
992
+ ]);
993
+ return { ...goal, ownerAssignment, plan, activities };
994
+ },
995
+ create: async (orgId, data) => db.insert(goals).values({
996
+ orgId,
997
+ title: data.title,
998
+ description: data.description ?? null,
999
+ alignmentQuestion: data.alignmentQuestion ?? null,
1000
+ level: "task",
1001
+ status: "planned",
1002
+ lifecycle: "draft",
1003
+ parentId: null,
1004
+ ownerAgentId: null,
1005
+ }).returning().then((rows) => rows[0]),
1006
+ update: async (id, data, actorAgentId = null) => {
1007
+ const current = await requireGoal(db, id);
1008
+ assertGoalOwner(current, actorAgentId);
1009
+ const { title, description, alignmentQuestion } = data;
1010
+ return db.update(goals).set({ title, description, alignmentQuestion, updatedAt: new Date() })
1011
+ .where(eq(goals.id, id)).returning().then((rows) => rows[0] ?? null);
1012
+ },
1013
+ activate: async (id, input, actorAgentId = null) => {
1014
+ const current = await requireGoal(db, id);
1015
+ if (current.lifecycle !== "draft")
1016
+ throw conflict("Only draft Goals can be activated");
1017
+ if (actorAgentId && actorAgentId !== input.ownerAgentId) {
1018
+ throw forbidden("An Agent can only activate a Goal for itself as Owner");
1019
+ }
1020
+ await requireInvokableOwner(db, current.orgId, input.ownerAgentId);
1021
+ const result = await db.transaction(async (tx) => {
1022
+ const [goal] = await tx.update(goals).set({
1023
+ outcomeStatement: input.outcomeStatement,
1024
+ objectiveMode: input.objectiveMode,
1025
+ lifecycle: "active",
1026
+ status: "active",
1027
+ contractRevision: 1,
1028
+ criteria: input.criteria,
1029
+ autonomyEnvelope: input.autonomyEnvelope,
1030
+ humanAuthorities: input.humanAuthorities,
1031
+ evaluationPolicy: input.evaluationPolicy,
1032
+ actionDeadline: input.actionDeadline ?? null,
1033
+ evaluationDeadline: input.evaluationDeadline ?? null,
1034
+ ownerAgentId: input.ownerAgentId,
1035
+ planRevision: 1,
1036
+ continuationKind: input.initialContinuation.kind,
1037
+ continuationSummary: input.initialContinuation.summary,
1038
+ wakeCondition: input.initialContinuation.wakeCondition ?? null,
1039
+ alignmentQuestion: null,
1040
+ updatedAt: new Date(),
1041
+ }).where(and(eq(goals.id, id), eq(goals.lifecycle, "draft"))).returning();
1042
+ if (!goal)
1043
+ throw conflict("Goal changed before activation; reload and retry");
1044
+ await tx.insert(goalOwnerAssignments).values({
1045
+ orgId: current.orgId,
1046
+ goalId: id,
1047
+ agentId: input.ownerAgentId,
1048
+ assignedByAuthorityRef: "activation",
1049
+ assignmentRevision: 1,
1050
+ });
1051
+ await tx.insert(goalPlans).values({
1052
+ orgId: current.orgId,
1053
+ goalId: id,
1054
+ revision: 1,
1055
+ ...input.initialPlan,
1056
+ createdByAgentId: actorAgentId,
1057
+ });
1058
+ await tx.insert(goalActivities).values({
1059
+ orgId: current.orgId,
1060
+ goalId: id,
1061
+ contractRevision: 1,
1062
+ submittedByAgentId: actorAgentId,
1063
+ agentOwnerRefAtTime: input.ownerAgentId,
1064
+ activityKind: "progress",
1065
+ summary: `Goal activated with initial ${input.initialContinuation.kind}: ${input.initialContinuation.summary}`,
1066
+ evidenceRefs: [],
1067
+ });
1068
+ return goal;
1069
+ });
1070
+ return result;
1071
+ },
1072
+ updatePlan: async (id, input, actorAgentId = null) => {
1073
+ const current = await requireGoal(db, id);
1074
+ assertCanonicalActiveGoal(current);
1075
+ assertGoalOwner(current, actorAgentId);
1076
+ const revision = current.planRevision + 1;
1077
+ return db.transaction(async (tx) => {
1078
+ const [plan] = await tx.insert(goalPlans).values({
1079
+ orgId: current.orgId,
1080
+ goalId: id,
1081
+ revision,
1082
+ ...input,
1083
+ createdByAgentId: actorAgentId,
1084
+ }).returning();
1085
+ await tx.update(goals).set({ planRevision: revision, updatedAt: new Date() }).where(eq(goals.id, id));
1086
+ return plan;
1087
+ });
1088
+ },
1089
+ listActivities: (id) => db.select().from(goalActivities).where(eq(goalActivities.goalId, id))
1090
+ .orderBy(desc(goalActivities.occurredAt), desc(goalActivities.createdAt)).limit(100),
1091
+ createActivity: async (id, input, actorAgentId = null) => {
1092
+ const current = await requireGoal(db, id);
1093
+ assertCanonicalActiveGoal(current);
1094
+ assertGoalOwner(current, actorAgentId);
1095
+ if (input.activityKind === "closeout" && !input.runRef) {
1096
+ throw unprocessable("A closeout Activity requires a Run reference");
1097
+ }
1098
+ if (input.runRef) {
1099
+ const run = await db.select({ id: heartbeatRuns.id, orgId: heartbeatRuns.orgId, status: heartbeatRuns.status })
1100
+ .from(heartbeatRuns).where(eq(heartbeatRuns.id, input.runRef)).then((rows) => rows[0] ?? null);
1101
+ if (!run || run.orgId !== current.orgId)
1102
+ throw unprocessable("Goal Activity run must belong to the same organization");
1103
+ if (input.activityKind === "closeout" && !TERMINAL_RUN_STATUSES.includes(run.status)) {
1104
+ throw conflict("A closeout Activity requires a terminal Run");
1105
+ }
1106
+ }
1107
+ const [activity] = await db.insert(goalActivities).values({
1108
+ orgId: current.orgId,
1109
+ goalId: id,
1110
+ contractRevision: current.contractRevision,
1111
+ submittedByAgentId: actorAgentId,
1112
+ agentOwnerRefAtTime: current.ownerAgentId,
1113
+ activityKind: input.activityKind ?? "progress",
1114
+ commitmentRef: input.commitmentRef ?? null,
1115
+ runRef: input.runRef ?? null,
1116
+ summary: input.summary,
1117
+ evidenceRefs: input.evidenceRefs,
1118
+ idempotencyKey: input.idempotencyKey ?? null,
1119
+ occurredAt: input.occurredAt ?? new Date(),
1120
+ }).onConflictDoNothing().returning();
1121
+ if (activity)
1122
+ return activity;
1123
+ if (input.idempotencyKey) {
1124
+ const existingIdempotent = await db.select().from(goalActivities).where(and(eq(goalActivities.goalId, id), eq(goalActivities.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
1125
+ if (existingIdempotent)
1126
+ return existingIdempotent;
1127
+ }
1128
+ if (input.activityKind === "closeout" && input.runRef) {
1129
+ const existingCloseout = await db.select().from(goalActivities).where(and(eq(goalActivities.goalId, id), eq(goalActivities.runRef, input.runRef), eq(goalActivities.activityKind, "closeout"))).then((rows) => rows[0] ?? null);
1130
+ if (existingCloseout)
1131
+ throw conflict("A closeout Activity already exists for this Run");
1132
+ }
1133
+ throw conflict("Goal Activity was already recorded");
1134
+ },
1135
+ feedback: async (id, input, actorUserId) => {
1136
+ return db.transaction(async (tx) => {
1137
+ const database = tx;
1138
+ const current = await requireGoal(database, id);
1139
+ return recordFeedback(database, current, input, actorUserId);
1140
+ });
1141
+ },
1142
+ createChangeProposal: async (id, input, actorAgentId) => {
1143
+ if (!actorAgentId)
1144
+ throw forbidden("Only the Goal Owner Agent can propose a consequential change");
1145
+ const normalizedPatch = normalizeContractPatch(input.afterContract);
1146
+ return db.transaction(async (tx) => {
1147
+ const database = tx;
1148
+ const current = await requireGoal(database, id);
1149
+ assertCanonicalActiveGoal(current);
1150
+ assertGoalOwner(current, actorAgentId);
1151
+ const existing = await database.select().from(goalChangeProposals).where(and(eq(goalChangeProposals.goalId, id), eq(goalChangeProposals.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
1152
+ if (existing) {
1153
+ assertSameChangeProposalPayload(existing, input, normalizedPatch);
1154
+ return existing;
1155
+ }
1156
+ if (current.contractRevision !== input.expectedContractRevision) {
1157
+ throw conflict("Goal Contract revision changed before proposal creation", {
1158
+ expectedContractRevision: input.expectedContractRevision,
1159
+ currentContractRevision: current.contractRevision,
1160
+ });
1161
+ }
1162
+ const beforeContract = goalContractSnapshot(current);
1163
+ const mergedContract = { ...beforeContract, ...normalizedPatch };
1164
+ activateGoalSchema.parse({
1165
+ confirmed: true,
1166
+ ownerAgentId: current.ownerAgentId,
1167
+ outcomeStatement: mergedContract.outcomeStatement,
1168
+ objectiveMode: mergedContract.objectiveMode,
1169
+ criteria: mergedContract.criteria,
1170
+ autonomyEnvelope: mergedContract.autonomyEnvelope,
1171
+ humanAuthorities: mergedContract.humanAuthorities,
1172
+ evaluationPolicy: mergedContract.evaluationPolicy,
1173
+ actionDeadline: mergedContract.actionDeadline,
1174
+ evaluationDeadline: mergedContract.evaluationDeadline,
1175
+ initialPlan: { summary: "Validate the proposed Contract revision" },
1176
+ initialContinuation: {
1177
+ kind: current.continuationKind,
1178
+ summary: current.continuationSummary,
1179
+ wakeCondition: current.wakeCondition,
1180
+ },
1181
+ });
1182
+ const proposalId = randomUUID();
1183
+ const approvalId = input.approvalId ?? randomUUID();
1184
+ if (input.approvalId) {
1185
+ const linkedApproval = await database.select().from(approvals).where(eq(approvals.id, approvalId))
1186
+ .then((rows) => rows[0] ?? null);
1187
+ if (!linkedApproval
1188
+ || linkedApproval.orgId !== current.orgId
1189
+ || linkedApproval.type !== "goal_change"
1190
+ || linkedApproval.status !== "pending") {
1191
+ throw unprocessable("Goal change approval must be pending and belong to the same organization");
1192
+ }
1193
+ await database.update(approvals).set({
1194
+ payload: {
1195
+ ...linkedApproval.payload,
1196
+ goalId: id,
1197
+ proposalId,
1198
+ expectedContractRevision: current.contractRevision,
1199
+ },
1200
+ updatedAt: new Date(),
1201
+ }).where(eq(approvals.id, approvalId));
1202
+ }
1203
+ else {
1204
+ await database.insert(approvals).values({
1205
+ id: approvalId,
1206
+ orgId: current.orgId,
1207
+ type: "goal_change",
1208
+ requestedByAgentId: actorAgentId,
1209
+ requestedByUserId: null,
1210
+ status: "pending",
1211
+ payload: {
1212
+ goalId: id,
1213
+ proposalId,
1214
+ expectedContractRevision: current.contractRevision,
1215
+ beforeContract,
1216
+ afterContract: normalizedPatch,
1217
+ rationale: input.rationale,
1218
+ evidenceRefs: input.evidenceRefs,
1219
+ },
1220
+ });
1221
+ }
1222
+ const [proposal] = await database.insert(goalChangeProposals).values({
1223
+ id: proposalId,
1224
+ orgId: current.orgId,
1225
+ goalId: id,
1226
+ expectedContractRevision: current.contractRevision,
1227
+ beforeContract,
1228
+ afterContract: normalizedPatch,
1229
+ rationale: input.rationale,
1230
+ evidenceRefs: input.evidenceRefs,
1231
+ approvalId,
1232
+ status: "pending",
1233
+ idempotencyKey: input.idempotencyKey,
1234
+ proposedByAgentId: actorAgentId,
1235
+ }).onConflictDoNothing().returning();
1236
+ if (proposal)
1237
+ return proposal;
1238
+ const raced = await database.select().from(goalChangeProposals).where(and(eq(goalChangeProposals.goalId, id), eq(goalChangeProposals.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
1239
+ if (!raced)
1240
+ throw conflict("Goal change proposal could not be recorded");
1241
+ assertSameChangeProposalPayload(raced, input, normalizedPatch);
1242
+ return raced;
1243
+ });
1244
+ },
1245
+ decideChangeProposal: async (proposalId, input, actorUserId) => {
1246
+ const result = await db.transaction(async (tx) => {
1247
+ const database = tx;
1248
+ const proposal = await database.select().from(goalChangeProposals)
1249
+ .where(eq(goalChangeProposals.id, proposalId)).then((rows) => rows[0] ?? null);
1250
+ if (!proposal)
1251
+ throw notFound("Goal change proposal not found");
1252
+ if (input.decision === "approve" && proposal.status === "applied")
1253
+ return { proposal, stale: false };
1254
+ if (input.decision === "reject" && proposal.status === "rejected")
1255
+ return { proposal, stale: false };
1256
+ if (proposal.status !== "pending") {
1257
+ throw conflict(`Goal change proposal is already ${proposal.status}`);
1258
+ }
1259
+ const current = await requireGoal(database, proposal.goalId);
1260
+ assertCanonicalActiveGoal(current);
1261
+ if (current.orgId !== proposal.orgId)
1262
+ throw unprocessable("Goal change proposal organization mismatch");
1263
+ const approval = await database.select().from(approvals).where(and(eq(approvals.id, proposal.approvalId), eq(approvals.orgId, proposal.orgId))).then((rows) => rows[0] ?? null);
1264
+ if (!approval || approval.type !== "goal_change") {
1265
+ throw unprocessable("Goal change proposal approval is invalid");
1266
+ }
1267
+ if (input.decision === "reject") {
1268
+ const now = new Date();
1269
+ await database.update(approvals).set({
1270
+ status: "rejected",
1271
+ decisionNote: input.note ?? null,
1272
+ decidedByUserId: actorUserId,
1273
+ decidedAt: now,
1274
+ updatedAt: now,
1275
+ }).where(and(eq(approvals.id, approval.id), eq(approvals.status, "pending")));
1276
+ const rejected = await database.update(goalChangeProposals).set({
1277
+ status: "rejected",
1278
+ updatedAt: now,
1279
+ }).where(and(eq(goalChangeProposals.id, proposal.id), eq(goalChangeProposals.status, "pending"))).returning().then((rows) => rows[0] ?? null);
1280
+ if (!rejected)
1281
+ throw conflict("Goal change proposal changed before rejection");
1282
+ await database.insert(goalActivities).values({
1283
+ orgId: current.orgId,
1284
+ goalId: current.id,
1285
+ contractRevision: current.contractRevision,
1286
+ submittedByAgentId: null,
1287
+ agentOwnerRefAtTime: current.ownerAgentId,
1288
+ activityKind: "decision_requested",
1289
+ summary: `Goal change proposal rejected${input.note ? `: ${input.note}` : ""}`,
1290
+ evidenceRefs: proposal.evidenceRefs,
1291
+ idempotencyKey: `goal-change:${proposal.id}:rejected`,
1292
+ }).onConflictDoNothing();
1293
+ return { proposal: rejected, stale: false };
1294
+ }
1295
+ if (current.contractRevision !== proposal.expectedContractRevision) {
1296
+ const now = new Date();
1297
+ await database.update(approvals).set({
1298
+ status: "approved",
1299
+ decisionNote: input.note ?? null,
1300
+ decidedByUserId: actorUserId,
1301
+ decidedAt: now,
1302
+ updatedAt: now,
1303
+ }).where(and(eq(approvals.id, approval.id), eq(approvals.status, "pending")));
1304
+ const superseded = await database.update(goalChangeProposals).set({
1305
+ status: "superseded",
1306
+ updatedAt: now,
1307
+ }).where(and(eq(goalChangeProposals.id, proposal.id), eq(goalChangeProposals.status, "pending"))).returning().then((rows) => rows[0] ?? proposal);
1308
+ return { proposal: superseded, stale: true, currentRevision: current.contractRevision };
1309
+ }
1310
+ const merged = { ...proposal.beforeContract, ...proposal.afterContract };
1311
+ const validated = activateGoalSchema.parse({
1312
+ confirmed: true,
1313
+ ownerAgentId: current.ownerAgentId,
1314
+ outcomeStatement: merged.outcomeStatement,
1315
+ objectiveMode: merged.objectiveMode,
1316
+ criteria: merged.criteria,
1317
+ autonomyEnvelope: merged.autonomyEnvelope,
1318
+ humanAuthorities: merged.humanAuthorities,
1319
+ evaluationPolicy: merged.evaluationPolicy,
1320
+ actionDeadline: merged.actionDeadline,
1321
+ evaluationDeadline: merged.evaluationDeadline,
1322
+ initialPlan: { summary: "Apply the approved Contract revision" },
1323
+ initialContinuation: {
1324
+ kind: current.continuationKind,
1325
+ summary: current.continuationSummary,
1326
+ wakeCondition: current.wakeCondition,
1327
+ },
1328
+ });
1329
+ const now = new Date();
1330
+ const approvalUpdate = await database.update(approvals).set({
1331
+ status: "approved",
1332
+ decisionNote: input.note ?? null,
1333
+ decidedByUserId: actorUserId,
1334
+ decidedAt: now,
1335
+ updatedAt: now,
1336
+ }).where(and(eq(approvals.id, approval.id), eq(approvals.status, "pending")))
1337
+ .returning().then((rows) => rows[0] ?? null);
1338
+ if (!approvalUpdate)
1339
+ throw conflict("Goal change approval changed before decision");
1340
+ const nextRevision = current.contractRevision + 1;
1341
+ const changedGoal = await database.update(goals).set({
1342
+ outcomeStatement: validated.outcomeStatement,
1343
+ objectiveMode: validated.objectiveMode,
1344
+ criteria: validated.criteria,
1345
+ autonomyEnvelope: validated.autonomyEnvelope,
1346
+ humanAuthorities: validated.humanAuthorities,
1347
+ evaluationPolicy: validated.evaluationPolicy,
1348
+ actionDeadline: validated.actionDeadline ?? null,
1349
+ evaluationDeadline: validated.evaluationDeadline ?? null,
1350
+ contractRevision: nextRevision,
1351
+ updatedAt: now,
1352
+ }).where(and(eq(goals.id, current.id), eq(goals.orgId, proposal.orgId), eq(goals.lifecycle, "active"), eq(goals.contractRevision, proposal.expectedContractRevision))).returning().then((rows) => rows[0] ?? null);
1353
+ if (!changedGoal)
1354
+ throw conflict("Goal Contract revision changed before proposal application");
1355
+ await database.insert(goalActivities).values({
1356
+ orgId: current.orgId,
1357
+ goalId: current.id,
1358
+ contractRevision: nextRevision,
1359
+ submittedByAgentId: proposal.proposedByAgentId,
1360
+ agentOwnerRefAtTime: current.ownerAgentId,
1361
+ activityKind: "decision_requested",
1362
+ summary: `Applied Goal Contract revision ${nextRevision}: ${proposal.rationale}`,
1363
+ evidenceRefs: proposal.evidenceRefs,
1364
+ idempotencyKey: `goal-change:${proposal.id}:applied`,
1365
+ }).onConflictDoNothing();
1366
+ const applied = await database.update(goalChangeProposals).set({
1367
+ status: "applied",
1368
+ appliedRevision: nextRevision,
1369
+ appliedAt: now,
1370
+ updatedAt: now,
1371
+ }).where(and(eq(goalChangeProposals.id, proposal.id), eq(goalChangeProposals.status, "pending"))).returning().then((rows) => rows[0] ?? null);
1372
+ if (!applied)
1373
+ throw conflict("Goal change proposal changed before application");
1374
+ return { proposal: applied, stale: false };
1375
+ });
1376
+ if (result.stale) {
1377
+ throw conflict("Goal Contract revision changed before proposal application", {
1378
+ expectedContractRevision: result.proposal.expectedContractRevision,
1379
+ currentContractRevision: result.currentRevision,
1380
+ });
1381
+ }
1382
+ return result.proposal;
1383
+ },
1384
+ createResultProposal: async (id, input, actorAgentId) => {
1385
+ if (!actorAgentId)
1386
+ throw forbidden("Only the Goal Owner Agent can submit a Result Proposal");
1387
+ const candidate = evaluationCandidate(input);
1388
+ const candidateHash = stableGoalHash(candidate);
1389
+ return db.transaction(async (tx) => {
1390
+ const database = tx;
1391
+ const current = await requireGoal(database, id);
1392
+ const existing = await database.select().from(goalResultProposals).where(and(eq(goalResultProposals.goalId, id), eq(goalResultProposals.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
1393
+ if (existing) {
1394
+ assertSameResultProposalPayload(existing, input, candidateHash);
1395
+ return existing;
1396
+ }
1397
+ assertCanonicalActiveGoal(current);
1398
+ assertGoalOwner(current, actorAgentId);
1399
+ if (input.contractRevision !== current.contractRevision) {
1400
+ throw conflict("Goal Contract revision changed before Result Proposal creation", {
1401
+ expectedContractRevision: input.contractRevision,
1402
+ currentContractRevision: current.contractRevision,
1403
+ });
1404
+ }
1405
+ const preflight = reduceGoalEvaluation(current, candidate);
1406
+ const status = isTerminalEvaluation(preflight.outcome) ? "ready" : "inconclusive";
1407
+ const [proposal] = await database.insert(goalResultProposals).values({
1408
+ orgId: current.orgId,
1409
+ goalId: current.id,
1410
+ contractRevision: current.contractRevision,
1411
+ candidate,
1412
+ candidateHash,
1413
+ preflight,
1414
+ riskSummary: input.riskSummary,
1415
+ status,
1416
+ idempotencyKey: input.idempotencyKey,
1417
+ proposedByAgentId: actorAgentId,
1418
+ }).onConflictDoNothing().returning();
1419
+ const persisted = proposal ?? await database.select().from(goalResultProposals).where(and(eq(goalResultProposals.goalId, id), eq(goalResultProposals.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
1420
+ if (!persisted)
1421
+ throw conflict("Goal Result Proposal could not be recorded");
1422
+ assertSameResultProposalPayload(persisted, input, candidateHash);
1423
+ if (status === "inconclusive") {
1424
+ await database.insert(goalActivities).values({
1425
+ orgId: current.orgId,
1426
+ goalId: current.id,
1427
+ contractRevision: current.contractRevision,
1428
+ submittedByAgentId: actorAgentId,
1429
+ agentOwnerRefAtTime: current.ownerAgentId,
1430
+ activityKind: "bottleneck",
1431
+ summary: inconclusiveResultSummary(preflight, input.riskSummary),
1432
+ evidenceRefs: candidate.evidenceRefs,
1433
+ idempotencyKey: `goal-result-inconclusive:${input.idempotencyKey}`,
1434
+ }).onConflictDoNothing();
1435
+ return persisted;
1436
+ }
1437
+ return persisted;
1438
+ });
1439
+ },
1440
+ acceptResultProposal: async (proposalId, input, actorUserId) => {
1441
+ return db.transaction(async (tx) => {
1442
+ const database = tx;
1443
+ const proposal = await database.select().from(goalResultProposals)
1444
+ .where(eq(goalResultProposals.id, proposalId)).then((rows) => rows[0] ?? null);
1445
+ if (!proposal)
1446
+ throw notFound("Goal Result Proposal not found");
1447
+ const current = await requireGoal(database, proposal.goalId);
1448
+ if (current.orgId !== proposal.orgId)
1449
+ throw unprocessable("Goal Result Proposal organization mismatch");
1450
+ if (proposal.consumedAt) {
1451
+ if (proposal.acceptanceIdempotencyKey !== input.idempotencyKey) {
1452
+ throw conflict("Goal Result Proposal acceptance was replayed with a different idempotency key");
1453
+ }
1454
+ return current;
1455
+ }
1456
+ if (proposal.status !== "ready") {
1457
+ throw conflict(`Goal Result Proposal is already ${proposal.status}`);
1458
+ }
1459
+ assertCanonicalActiveGoal(current);
1460
+ if (proposal.contractRevision !== current.contractRevision) {
1461
+ throw conflict("Goal Contract revision changed before Result Proposal acceptance", {
1462
+ expectedContractRevision: proposal.contractRevision,
1463
+ currentContractRevision: current.contractRevision,
1464
+ });
1465
+ }
1466
+ if (proposal.candidateHash !== stableGoalHash(proposal.candidate)) {
1467
+ throw conflict("Goal Result Proposal candidate no longer matches its immutable hash");
1468
+ }
1469
+ const freshPreflight = reduceGoalEvaluation(current, proposal.candidate);
1470
+ if (!isTerminalEvaluation(freshPreflight.outcome)
1471
+ || !sameHashPayload({ ...proposal.preflight, evaluatedAt: null }, { ...freshPreflight, evaluatedAt: null })) {
1472
+ throw conflict("Goal Result Proposal no longer matches canonical evaluation");
1473
+ }
1474
+ const now = new Date();
1475
+ const accepted = await database.update(goalResultProposals).set({
1476
+ status: "accepted",
1477
+ acceptedByActorType: "user",
1478
+ acceptedByActorId: actorUserId,
1479
+ acceptanceIdempotencyKey: input.idempotencyKey,
1480
+ acceptedAt: now,
1481
+ updatedAt: now,
1482
+ }).where(and(eq(goalResultProposals.id, proposal.id), eq(goalResultProposals.status, "ready"), isNull(goalResultProposals.consumedAt))).returning().then((rows) => rows[0] ?? null);
1483
+ if (!accepted) {
1484
+ const latestProposal = await database.select().from(goalResultProposals)
1485
+ .where(eq(goalResultProposals.id, proposal.id)).then((rows) => rows[0] ?? null);
1486
+ if (latestProposal?.consumedAt && latestProposal.acceptanceIdempotencyKey === input.idempotencyKey) {
1487
+ return requireGoal(database, proposal.goalId);
1488
+ }
1489
+ throw conflict("Goal Result Proposal changed before acceptance");
1490
+ }
1491
+ return evaluateInTransaction(database, current, {
1492
+ ...proposal.candidate,
1493
+ resultProposalId: proposal.id,
1494
+ }, null);
1495
+ });
1496
+ },
1497
+ rejectResultProposal: async (proposalId, input, actorUserId) => {
1498
+ return db.transaction(async (tx) => {
1499
+ const database = tx;
1500
+ const proposal = await database.select().from(goalResultProposals)
1501
+ .where(eq(goalResultProposals.id, proposalId)).then((rows) => rows[0] ?? null);
1502
+ if (!proposal)
1503
+ throw notFound("Goal Result Proposal not found");
1504
+ if (proposal.status === "rejected") {
1505
+ if (proposal.rejectionFeedback !== input.feedback) {
1506
+ throw conflict("Goal Result Proposal rejection was replayed with different feedback");
1507
+ }
1508
+ return proposal;
1509
+ }
1510
+ if (proposal.status !== "ready") {
1511
+ throw conflict(`Goal Result Proposal is already ${proposal.status}`);
1512
+ }
1513
+ const current = await requireGoal(database, proposal.goalId);
1514
+ assertCanonicalActiveGoal(current);
1515
+ if (current.orgId !== proposal.orgId)
1516
+ throw unprocessable("Goal Result Proposal organization mismatch");
1517
+ if (current.contractRevision !== proposal.contractRevision) {
1518
+ throw conflict("Goal Contract revision changed before Result Proposal rejection");
1519
+ }
1520
+ await recordFeedback(database, current, {
1521
+ body: input.feedback,
1522
+ attachments: [],
1523
+ feedbackKind: "ordinary",
1524
+ idempotencyKey: input.idempotencyKey,
1525
+ }, actorUserId);
1526
+ const now = new Date();
1527
+ const rejected = await database.update(goalResultProposals).set({
1528
+ status: "rejected",
1529
+ rejectedByActorType: "user",
1530
+ rejectedByActorId: actorUserId,
1531
+ rejectedAt: now,
1532
+ rejectionFeedback: input.feedback,
1533
+ updatedAt: now,
1534
+ }).where(and(eq(goalResultProposals.id, proposal.id), eq(goalResultProposals.status, "ready"))).returning().then((rows) => rows[0] ?? null);
1535
+ if (!rejected)
1536
+ throw conflict("Goal Result Proposal changed before rejection");
1537
+ return rejected;
1538
+ });
1539
+ },
1540
+ assignOwner: async (id, input, actorAgentId = null) => {
1541
+ const current = await requireGoal(db, id);
1542
+ assertCanonicalActiveGoal(current);
1543
+ assertGoalOwner(current, actorAgentId);
1544
+ await requireInvokableOwner(db, current.orgId, input.agentId);
1545
+ return db.transaction(async (tx) => {
1546
+ const previous = await tx.select().from(goalOwnerAssignments)
1547
+ .where(and(eq(goalOwnerAssignments.goalId, id), isNull(goalOwnerAssignments.endsAt)))
1548
+ .then((rows) => rows[0] ?? null);
1549
+ if (previous?.agentId === input.agentId)
1550
+ return previous;
1551
+ const now = new Date();
1552
+ if (previous)
1553
+ await tx.update(goalOwnerAssignments).set({ endsAt: now }).where(eq(goalOwnerAssignments.id, previous.id));
1554
+ const [assignment] = await tx.insert(goalOwnerAssignments).values({
1555
+ orgId: current.orgId,
1556
+ goalId: id,
1557
+ agentId: input.agentId,
1558
+ assignmentRevision: (previous?.assignmentRevision ?? 0) + 1,
1559
+ assignedByAuthorityRef: input.authorityRef ?? null,
1560
+ startsAt: now,
1561
+ }).returning();
1562
+ await tx.update(goals).set({ ownerAgentId: input.agentId, updatedAt: now }).where(eq(goals.id, id));
1563
+ return assignment;
1564
+ });
1565
+ },
1566
+ setFocus: async (id, focus, actorAgentId = null) => {
1567
+ const current = await requireGoal(db, id);
1568
+ if (focus)
1569
+ assertCanonicalActiveGoal(current);
1570
+ assertGoalOwner(current, actorAgentId);
1571
+ return db.transaction(async (tx) => {
1572
+ if (focus)
1573
+ await tx.update(goals).set({ focus: false }).where(eq(goals.orgId, current.orgId));
1574
+ const [goal] = await tx.update(goals).set({ focus, updatedAt: new Date() }).where(eq(goals.id, id)).returning();
1575
+ return goal;
1576
+ });
1577
+ },
1578
+ evaluate: async (id, input, actorAgentId = null) => {
1579
+ return db.transaction(async (tx) => {
1580
+ const database = tx;
1581
+ const current = await requireGoal(database, id);
1582
+ return evaluateInTransaction(database, current, input, actorAgentId);
1583
+ });
216
1584
  },
217
1585
  remove: async (id) => {
218
- const existing = await db
219
- .select()
220
- .from(goals)
221
- .where(eq(goals.id, id))
222
- .then((rows) => rows[0] ?? null);
223
- if (!existing)
224
- return null;
1586
+ const existing = await requireGoal(db, id);
225
1587
  const dependencies = await getGoalDependencies(db, existing);
226
- if (!dependencies.canDelete) {
227
- throw conflict("Goal cannot be deleted while it has dependencies", dependencies);
228
- }
229
- return db
230
- .delete(goals)
231
- .where(eq(goals.id, id))
232
- .returning()
233
- .then((rows) => rows[0] ?? null);
1588
+ if (!dependencies.canDelete)
1589
+ throw conflict("Only an unlinked draft Goal can be deleted", dependencies);
1590
+ return db.delete(goals).where(eq(goals.id, id)).returning().then((rows) => rows[0] ?? null);
234
1591
  },
235
1592
  };
236
1593
  }