chainlesschain 0.166.6 → 0.166.9

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 (214) hide show
  1. package/README.md +21 -0
  2. package/package.json +5 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-CVnFjTfG.js → AIOps-DJvpifGx.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-BxkwuuQC.js → ActionButton-D2UhuF_A.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-C2670q6z.js → Analytics-C9ho9obu.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CU_rylW6.js → AppLayout-BvRCpFK3.js} +3 -3
  8. package/src/assets/web-panel/assets/{Artifacts-DGJmYqnj.js → Artifacts-jxrySvpk.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-BZOQbINJ.js → Audit-ovEw0ZYl.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-DghB4QwF.js → BackgroundAgents-D7H5z6zS.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-l0mwPbta.js → Backup-BuXob8za.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-Bv3WSZOh.js → BaseInput-CtnszuQ2.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-s88nBdvt.js → Chat-B_mYE8fJ.js} +6 -6
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-Z8CBuB1a.js → ChatBubbleRenderer-DaiA6CsX.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-B5icasEs.js → Checkbox-ierdtcJD.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-CsUFqNud.js → Codegen-3VKPJD_B.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-DlBtClaW.js → Col-1EdMyf5P.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-xRn1IdLB.js → Community-D6H7UA8K.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-naIr8a_o.js → Compact-BBAZvYIm.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-Dp9esfFE.js → Compliance-CgsTrAoj.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DQlMYJB-.js → Cowork-tnZgLoTE.js} +4 -4
  22. package/src/assets/web-panel/assets/{Cron-Cd0oNLzJ.js → Cron-CPpZw2Nh.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-BtDIx3XX.js → Crosschain-BymBcp5z.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-BOlpzEwg.js → DID-DKPxS_K1.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-DV15Axk1.js → Dashboard-DvJhEmH9.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-COXrewN1.js → Dropdown-b8iQyQIZ.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-EjIt0Cm7.js → EmailListRenderer--0-A-zKb.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DJlhwb-R.js → FamilyGuardDashboard-C12iDN06.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-BMiFl82k.js → Federation-BRhaE2Z8.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-BUcDQyxn.js → FormItemContext-C4dnq7yf.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-C25uFxvK.js → GenericCardRenderer-CGAdbR5n.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-D20uaEUU.js → Git-4eA2fYzL.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-CQxYJjCX.js → Governance-BJuY9Y-9.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-m-sczpS5.js → Inference-DZ5Dy4up.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-CBsIlU0S.js → KnowledgeGraph-BMTjRs4A.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-D0-5bqeu.js → Logs-BKEeQEfJ.js} +2 -2
  37. package/src/assets/web-panel/assets/MarkdownRenderer-B05DgASC.js +1 -0
  38. package/src/assets/web-panel/assets/{Marketplace-CwDGFu2Q.js → Marketplace-Dhho4fEg.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-7FxJ3omT.js → McpTools-BW6xxSlU.js} +2 -2
  40. package/src/assets/web-panel/assets/{Memory-DOVyb59U.js → Memory-mPqlpSkq.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-DZXl-Hje.js → MobileBridge-Cb9N_DQI.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-DlZ8tRXt.js → MobileProjects-BNs8tah_.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-D7Pjx1s_.js → Mtc-CaxBt8tn.js} +5 -5
  44. package/src/assets/web-panel/assets/{MtcAudit-BND7lX8p.js → MtcAudit-DRPQbp3t.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-BknS7B-p.js → Multisig-CRm-d8bK.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-7C2-KPHt.js → NLProgramming-BCCk3LPs.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-DNWdTndN.js → Notes-Cv8SN8gN.js} +4 -4
  48. package/src/assets/web-panel/assets/{NotificationSettings-DCm4KcJZ.js → NotificationSettings-CggIvTh5.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-DyVwwjJx.js → OrderTableRenderer-BW3_bV12.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-Dl2O_qJ9.js → Organization-BuRE-VCO.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-BOFCvk9Q.js → Overflow-DBrm1gju.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-DgdVowY1.js → P2P-CWVnDYgS.js} +2 -2
  53. package/src/assets/web-panel/assets/PdhVaultBrowser-8RKUIpte.js +7 -0
  54. package/src/assets/web-panel/assets/{Permissions-BHNEUUwa.js → Permissions-tDUAxogW.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-CaZbrOdz.js → PersonalDataHub-Dv2-CFis.js} +4 -4
  56. package/src/assets/web-panel/assets/{Pipeline-DJDnoXmE.js → Pipeline-DM6kvFni.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-CBQr2eX8.js → Privacy-DzK-yL0d.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-Yghqa06z.js → ProjectInit-B5UrNpkj.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-HBAtJi8U.js → ProjectSettings-CxJD43oW.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-Z2Orm8Ma.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-uJTz77GI.js → Providers-CYl4jRYK.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-k3M5ouTp.js → QrScannerModal-DeH2T0fH.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-CMji45PH.js → QuickAsk-Bw9k_kTk.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-DyoZYPls.js → Recommend-Bl_jGwoi.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-d6u01Kq0.js +5 -0
  66. package/src/assets/web-panel/assets/{Reputation-D4hIs4vV.js → Reputation-Ds_Dfr-k.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-mzE-ElTg.js → Row-BpQUQTNj.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-cg7J3hI6.js → RssFeed-BatpZBGt.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-6M0TkGSZ.js → Search-D7hOObzi.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-B1GQ67qh.js → Security-dXQ4FKNt.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-D_RwU_5S.js → Services-D8wInGPa.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-TZjk-dsW.js → Skeleton-DXQCoYpM.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-CG6FcaCV.js → Skills-D3oLRIVn.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-BI190QDv.js → Sla-BmP4-LnJ.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-BWmxF2Wu.js → SpeechSettings-DLj-xzro.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-CwPiGw6L.js → SyncSettings-8KKaaRMr.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-BIXgCeSk.js → Tasks-GyK42I7C.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-CQFgGCoK.js → Templates-n1y_8UBG.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-DAelEdkL.js → Tenant-B39nBph8.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-BbqWK6qh.js → Terminal-DCQoOKXe.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-cmG3zvY5.js → TimelineRenderer-BkhbDfXk.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-C8444NKU.js → Tokens-BeHIIU8D.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-C1Rpnz0I.js → Trigger-BHvWXVOb.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-u4koPiFD.js → Trust-y2WaOSto.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-oPWKn80Z.js → UkeySign-DBFeJBzt.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-D0CUUY_5.js → VideoEditing-BEOWitC-.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-fu9pIAbt.js → Wallet-BITedTe6.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-Bc5ciuOx.js → WebAuthn-DMwlJVin.js} +2 -2
  89. package/src/assets/web-panel/assets/{WorkflowEditor-BCin4_4N.js → WorkflowEditor-CKfGFEu1.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-B2jbM-XN.js → chat-DR6gnBGF.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-CUVoUQ_2.js → colors-C_Hw5NCn.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-DgOGpkRH.js → compact-item-B8IPwsEu.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-DIxU6eKI.js → createContext-D-u4JVnJ.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-Cq6lSd_q.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-DoKRKMav.js → hasIn-CZubdS4D.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-ByjrulIZ.js → index-11ASqx-A.js} +1 -1
  97. package/src/assets/web-panel/assets/index-BQ3213fz.js +1 -0
  98. package/src/assets/web-panel/assets/{index-BwYofQJg.js → index-BXS6wYJZ.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CLdpHe6w.js → index-Bi21Etg4.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-2flsPFIX.js → index-Bizm87cX.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BXb-Smmm.js → index-Bv2132PO.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-B5ySiGYy.js → index-BzoFanEQ.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CWbL1-N0.js → index-C25rOz7O.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Cj-us-D_.js → index-C2VmtNaQ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C9TsO9T9.js → index-Cbm8F0M2.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-eTWPaeBI.js → index-CfniPP5p.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-D2yo6ast.js → index-Cg2bCeol.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CPXdBnGi.js → index-CiXkhOkY.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BCLeNqIL.js → index-Cl5RpreD.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BtSPhEyC.js → index-CqmLw8cs.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-eYxUASSG.js → index-Ctje8Vl3.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-i5MB455u.js → index-D1w96gg9.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CpvyOt5Z.js → index-D47jt8ET.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CeWSw-z5.js → index-DJ6pG79m.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B3F_p8NA.js → index-DTosx2Wu.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-B9mSYovv.js → index-DY6MHo9D.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DJMHnbmu.js → index-DcFjRT-K.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-D-ufbAJB.js → index-DfnzrUxU.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BFUprhNd.js → index-DjRrqooR.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Co4lDEk-.js → index-DmRcgiYT.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DGTZdZOE.js → index-DpjHpRoq.js} +3 -3
  122. package/src/assets/web-panel/assets/{index-DHugFl3v.js → index-Dt-w3xQ7.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-B-f6l4Vm.js → index-DuOU5apg.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DtyAuGOm.js → index-LvjAKDSl.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-be8SonCh.js → index-_nL79NT8.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BRMEO2Tl.js → index-auyNYjLo.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C88m9lsi.js → index-cVqbas5C.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CqlwIjJx.js → index-ffe7bvqP.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DpTFx8Xx.js → index-idZKyADQ.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Cskz--fd.js → index-k1Xa5iw1.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CF80xxPG.js → index-mYZJxGH6.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Ds8r3Fzb.js → index-rYBHSDcJ.js} +1 -1
  133. package/src/assets/web-panel/assets/index-vJagfET2.js +1 -0
  134. package/src/assets/web-panel/assets/{index-C846lPGD.js → index-xoNssgz_.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-B4chl7iR.js → initDefaultProps-lcU_dT2r.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-DruN9h96.js → motion-Dc7UNXZ1.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-Cl0UtCgU.js → move-Cll8ZXaL.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-FX2ttw70.js → mtc-parser-CAb_yLIM.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-BDCneY46.js → omit-DurYMyTe.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-DycsEg8b.js → pickAttrs-BITQyOlw.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-Bc-dBuMv.js → placementArrow-Dg1rH91i.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve--Wwaf8lu.js → responsiveObserve-D_EWIeac.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-vDmHQqHH.js → slide-lK4d3zsh.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-9c52yv3G.js → statusUtils-Bflq-Git.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-BT4BiO6_.js → styleChecker-Ccp0I-wt.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-C7XxWPcP.js → useFlexGapSupport-CYZbByYA.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-BHoMqh4i.js → useFs-B_x0nxFD.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-BOLxvsAG.js → usePersonalDataHub-DW55rL85.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-CA7poeeP.js → vnode-Ca6MH3W8.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-B_sAn-co.js → zoom-NdHelOog.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +1 -1
  153. package/src/commands/graph.js +215 -5
  154. package/src/commands/orchestrate.js +61 -9
  155. package/src/commands/team-distributed.js +342 -2
  156. package/src/commands/team.js +169 -7
  157. package/src/data/changelog.json +46 -0
  158. package/src/gateways/ws/message-dispatcher.js +4 -0
  159. package/src/gateways/ws/session-protocol.js +89 -11
  160. package/src/gateways/ws/ws-agent-handler.js +65 -9
  161. package/src/gateways/ws/ws-approval-gate.js +181 -16
  162. package/src/gateways/ws/ws-server.js +16 -6
  163. package/src/generated/cc-agent-protocol.schema.json +154 -3
  164. package/src/lib/agent-team/task-lease.js +487 -3
  165. package/src/lib/agent-team/team-agent-stream.js +16 -0
  166. package/src/lib/agent-team/team-distributed-graph-bridge.js +257 -0
  167. package/src/lib/agent-team/team-distributed-graph-writer.js +523 -0
  168. package/src/lib/agent-team/team-distributed-queue.js +251 -0
  169. package/src/lib/agent-team/team-graph-runtime-adapter.js +1918 -0
  170. package/src/lib/agent-team/team-runner.js +285 -3
  171. package/src/lib/app-server/graph-runtime.js +1078 -0
  172. package/src/lib/app-server/rollout-store.js +32 -0
  173. package/src/lib/app-server/server.js +200 -0
  174. package/src/lib/approval-grant-ledger.js +69 -0
  175. package/src/lib/autonomous-agent.js +11 -0
  176. package/src/lib/codex-app-server-adapter.js +11 -20
  177. package/src/lib/cowork-graph-authority-adapter.js +455 -0
  178. package/src/lib/dynamic-workflow-facade.js +83 -16
  179. package/src/lib/graph-kernel/adapters.js +93 -20
  180. package/src/lib/graph-kernel/authority.js +324 -0
  181. package/src/lib/graph-kernel/compiler.js +67 -21
  182. package/src/lib/graph-kernel/cutover-authority-resolver.js +245 -0
  183. package/src/lib/graph-kernel/cutover-ledger.js +692 -0
  184. package/src/lib/graph-kernel/event-store.js +15 -1
  185. package/src/lib/graph-kernel/graph-runtime-surfaces.json +1427 -0
  186. package/src/lib/graph-kernel/index.js +4 -0
  187. package/src/lib/graph-kernel/migration-saga.js +309 -0
  188. package/src/lib/graph-kernel/retirement-evidence.js +567 -0
  189. package/src/lib/graph-kernel/runtime-surface-manifest.js +481 -0
  190. package/src/lib/graph-kernel/runtime.js +737 -38
  191. package/src/lib/graph-kernel/store-cutover-evidence.js +354 -0
  192. package/src/lib/graph-kernel/trace-reducer.js +6 -0
  193. package/src/lib/graph-kernel/trigger-adapter.js +60 -3
  194. package/src/lib/interaction-adapter.js +61 -9
  195. package/src/lib/legacy-runtime-authority.js +160 -0
  196. package/src/lib/orchestrator.js +28 -0
  197. package/src/lib/policy-decision-event.js +149 -0
  198. package/src/lib/process-execution-broker/platform-sandbox.js +15 -1
  199. package/src/lib/record-replay/index.js +1 -0
  200. package/src/lib/record-replay/playwright-ui-driver.js +327 -0
  201. package/src/lib/scheduler-kernel/graph-authority-adapter.js +512 -0
  202. package/src/lib/scheduler-kernel/runtime.js +75 -2
  203. package/src/lib/webhook-security.js +484 -36
  204. package/src/runtime/coding-agent-events.cjs +13 -11
  205. package/src/runtime/headless-runner.js +17 -1
  206. package/src/runtime/headless-stream.js +66 -3
  207. package/src/runtime/session-slash-commands.js +57 -6
  208. package/src/assets/web-panel/assets/MarkdownRenderer-BwH414Ms.js +0 -1
  209. package/src/assets/web-panel/assets/PdhVaultBrowser-BiaZfQIM.js +0 -7
  210. package/src/assets/web-panel/assets/Projects-DiCGnMjz.js +0 -1
  211. package/src/assets/web-panel/assets/RemoteSession-s1gR38IO.js +0 -5
  212. package/src/assets/web-panel/assets/devWarning-DiSlgCC4.js +0 -1
  213. package/src/assets/web-panel/assets/index-9WIQEHvv.js +0 -1
  214. package/src/assets/web-panel/assets/index-B7mDobAl.js +0 -1
@@ -4,9 +4,15 @@ import {
4
4
  compileGraphDefinition,
5
5
  executionAttemptId,
6
6
  graphDigest,
7
+ validateGraphDefinitionMigrationEvidence,
7
8
  writeScopesOverlap,
8
9
  } from "./compiler.js";
9
10
  import { GraphEventStore } from "./event-store.js";
11
+ import {
12
+ GRAPH_PROJECTION_VERSION,
13
+ assertGraphAuthorityWriter,
14
+ createGraphAuthorityBinding,
15
+ } from "./authority.js";
10
16
 
11
17
  export const GRAPH_RUN_PHASES = Object.freeze(["open", "sealed"]);
12
18
  export const GRAPH_RUN_STATUSES = Object.freeze([
@@ -351,6 +357,7 @@ function stateSnapshot(run) {
351
357
  version: 1,
352
358
  id: run.id,
353
359
  definition: run.compiled.definition,
360
+ definitionMigration: clone(run.definitionMigration),
354
361
  subgraphDefinitions: run.compiled.subgraphDefinitions,
355
362
  phase: run.phase,
356
363
  status: run.status,
@@ -358,6 +365,7 @@ function stateSnapshot(run) {
358
365
  revisionDigest: run.revisionDigest,
359
366
  occurrenceRef: run.occurrenceRef,
360
367
  authorityDigest: run.authorityDigest,
368
+ authority: clone(run.authority),
361
369
  budget: run.budget,
362
370
  budgetUsed: run.budgetUsed,
363
371
  budgetReserved: run.budgetReserved,
@@ -398,12 +406,24 @@ function restoreState(run, snapshot, compiled = null) {
398
406
  subgraphs: persistedSubgraphRegistry(snapshot.subgraphDefinitions),
399
407
  allowUnresolvedSubgraphs: snapshot.subgraphDefinitions == null,
400
408
  });
409
+ run.definitionMigration = snapshot.definitionMigration
410
+ ? clone(
411
+ validateGraphDefinitionMigrationEvidence(snapshot.definitionMigration),
412
+ )
413
+ : clone(run.compiled.definitionMigration || null);
401
414
  run.phase = snapshot.phase;
402
415
  run.status = snapshot.status;
403
416
  run.graphRevision = snapshot.graphRevision;
404
417
  run.revisionDigest = snapshot.revisionDigest;
405
418
  run.occurrenceRef = clone(snapshot.occurrenceRef);
406
419
  run.authorityDigest = snapshot.authorityDigest;
420
+ if (!snapshot.authority) {
421
+ throw kernelError(
422
+ "CC_GRAPH_AUTHORITY_MIGRATION_REQUIRED",
423
+ "legacy GraphRun snapshot has no writer authority; import it at a verified safe point",
424
+ );
425
+ }
426
+ run.authority = createGraphAuthorityBinding(snapshot.authority);
407
427
  run.budget = clone(snapshot.budget);
408
428
  run.budgetUsed = clone(snapshot.budgetUsed);
409
429
  run.budgetReserved = clone(snapshot.budgetReserved || emptyBudgetUsage());
@@ -462,17 +482,49 @@ function attemptProjection(attempt) {
462
482
  return Object.freeze(clone(attempt));
463
483
  }
464
484
 
485
+ function humanTaskProjection(task) {
486
+ const projected = clone(task);
487
+ if (
488
+ projected.claimExpiresAt == null &&
489
+ Number.isFinite(projected.claimExpiresAtMs)
490
+ ) {
491
+ projected.claimExpiresAt = nowIso(() => projected.claimExpiresAtMs);
492
+ }
493
+ delete projected.claimExpiresAtMs;
494
+ delete projected.expiresAtMs;
495
+ return Object.freeze(projected);
496
+ }
497
+
465
498
  function runProjection(run) {
466
499
  return Object.freeze({
467
500
  id: run.id,
468
501
  definitionId: run.compiled.definitionId,
469
502
  revision: run.compiled.revision,
470
503
  revisionDigest: run.revisionDigest,
504
+ definitionMigration: run.definitionMigration
505
+ ? Object.freeze({
506
+ schema: run.definitionMigration.schema,
507
+ fromVersion: run.definitionMigration.fromVersion,
508
+ toVersion: run.definitionMigration.toVersion,
509
+ revisionDigest: run.definitionMigration.revisionDigest,
510
+ rollbackDigest: run.definitionMigration.rollbackDigest,
511
+ backupAvailable: true,
512
+ })
513
+ : null,
471
514
  occurrenceRef: clone(run.occurrenceRef),
472
515
  phase: run.phase,
473
516
  status: run.status,
474
517
  graphRevision: run.graphRevision,
475
518
  authorityDigest: run.authorityDigest,
519
+ originSurface: run.authority.originSurface,
520
+ authoritySource: run.authority.authoritySource,
521
+ authorityMode: run.authority.authorityMode,
522
+ authorityGeneration: run.authority.authorityGeneration,
523
+ writerId: run.authority.writerId,
524
+ writerLeaseId: run.authority.writerLeaseId,
525
+ writerLeaseExpiresAt: run.authority.writerLeaseExpiresAt,
526
+ eventHead: run.authority.eventHead,
527
+ projectionVersion: run.authority.projectionVersion,
476
528
  budget: clone(run.budget),
477
529
  budgetUsed: clone(run.budgetUsed),
478
530
  budgetReserved: clone(run.budgetReserved),
@@ -527,11 +579,49 @@ function runProjection(run) {
527
579
  });
528
580
  }
529
581
 
530
- function buildRun(compiled, options, now) {
582
+ function buildRun(compiled, options, now, writerIdentity, writerLeaseTtlMs) {
531
583
  const createdAt = nowIso(now);
584
+ const id = safeIdentifier(options.runId || randomUUID(), "runId");
585
+ const authority = createGraphAuthorityBinding(
586
+ options.authority || {
587
+ logicalRunId: id,
588
+ originSurface: options.originSurface || "cli_team",
589
+ authorityMode:
590
+ writerIdentity.authoritySource === "graph_kernel_shadow"
591
+ ? "shadow"
592
+ : "canonical",
593
+ authoritySource: writerIdentity.authoritySource,
594
+ authorityGeneration:
595
+ options.authorityGeneration || writerIdentity.authorityGeneration,
596
+ writerId: writerIdentity.writerId,
597
+ writerLeaseId: writerIdentity.writerLeaseId,
598
+ writerLeaseExpiresAt: new Date(
599
+ now() + Math.max(1, Number(writerLeaseTtlMs)),
600
+ ).toISOString(),
601
+ eventHead: null,
602
+ projectionVersion: GRAPH_PROJECTION_VERSION,
603
+ },
604
+ );
605
+ if (authority.logicalRunId !== id) {
606
+ throw kernelError(
607
+ "CC_GRAPH_AUTHORITY_RUN_MISMATCH",
608
+ "Graph authority must be bound to the exact logical run id",
609
+ );
610
+ }
611
+ if (authority.authorityMode === "legacy") {
612
+ throw kernelError(
613
+ "CC_GRAPH_NON_CANONICAL_WRITER",
614
+ "legacy authority cannot mutate Graph Kernel state",
615
+ );
616
+ }
617
+ assertGraphAuthorityWriter(authority, writerIdentity, {
618
+ now: now(),
619
+ requireCanonical: false,
620
+ });
532
621
  const run = {
533
- id: safeIdentifier(options.runId || randomUUID(), "runId"),
622
+ id,
534
623
  compiled,
624
+ definitionMigration: clone(compiled.definitionMigration || null),
535
625
  phase: "open",
536
626
  status: "running",
537
627
  graphRevision: compiled.revision,
@@ -547,6 +637,7 @@ function buildRun(compiled, options, now) {
547
637
  },
548
638
  "cc.graph.authority/v1",
549
639
  ),
640
+ authority,
550
641
  budget: clone(options.budget || compiled.definition.budget || {}),
551
642
  budgetUsed: emptyBudgetUsage(),
552
643
  budgetReserved: emptyBudgetUsage(),
@@ -885,6 +976,7 @@ function sweepExpirations(run, now) {
885
976
  handoff.expiresAtMs <= now
886
977
  ) {
887
978
  handoff.status = "expired";
979
+ handoff.expiredAt = nowIso(() => now);
888
980
  handoff.updatedAt = nowIso(() => now);
889
981
  changed = true;
890
982
  }
@@ -902,6 +994,7 @@ function sweepExpirations(run, now) {
902
994
  task.status = "open";
903
995
  task.claimActorId = null;
904
996
  task.claimLeaseId = null;
997
+ task.claimExpiresAt = null;
905
998
  task.claimExpiresAtMs = null;
906
999
  task.updatedAt = nowIso(() => now);
907
1000
  changed = true;
@@ -1109,6 +1202,13 @@ function classifyQuiescence(run, now) {
1109
1202
  ) {
1110
1203
  return "running";
1111
1204
  }
1205
+ if (
1206
+ [...run.humanTasks.values()].some((task) =>
1207
+ ["open", "claimed"].includes(task.status),
1208
+ )
1209
+ ) {
1210
+ return "waiting_human";
1211
+ }
1112
1212
  if (
1113
1213
  [...run.attempts.values()].some((attempt) =>
1114
1214
  ACTIVE_ATTEMPT.has(attempt.status),
@@ -1130,13 +1230,6 @@ function classifyQuiescence(run, now) {
1130
1230
  ) {
1131
1231
  return "reconciliation_required";
1132
1232
  }
1133
- if (
1134
- [...run.humanTasks.values()].some((task) =>
1135
- ["open", "claimed"].includes(task.status),
1136
- )
1137
- ) {
1138
- return "waiting_human";
1139
- }
1140
1233
  if (
1141
1234
  [...run.messages.values()].some((message) =>
1142
1235
  ["admitted", "delivered", "read"].includes(message.status),
@@ -1187,6 +1280,11 @@ export class GraphKernel {
1187
1280
  agingWindowMs = 1000,
1188
1281
  maxPendingMessagesPerAgent = 100,
1189
1282
  maxLivelockRepeats = 8,
1283
+ writerId = `graph-kernel:${process.pid}:${randomUUID()}`,
1284
+ writerLeaseId = `graph-kernel-lease:${randomUUID()}`,
1285
+ authoritySource = "graph_kernel",
1286
+ authorityGeneration = 1,
1287
+ writerLeaseTtlMs = 24 * 60 * 60 * 1000,
1190
1288
  } = {}) {
1191
1289
  this.eventStore = eventStore;
1192
1290
  this.now = now;
@@ -1197,6 +1295,13 @@ export class GraphKernel {
1197
1295
  Number(maxPendingMessagesPerAgent) || 100,
1198
1296
  );
1199
1297
  this.maxLivelockRepeats = Math.max(2, Number(maxLivelockRepeats) || 8);
1298
+ this.writerIdentity = Object.freeze({
1299
+ authoritySource,
1300
+ authorityGeneration: Math.max(1, Number(authorityGeneration) || 1),
1301
+ writerId: safeIdentifier(writerId, "writerId"),
1302
+ writerLeaseId: safeIdentifier(writerLeaseId, "writerLeaseId"),
1303
+ });
1304
+ this.writerLeaseTtlMs = Math.max(1, Number(writerLeaseTtlMs) || 1);
1200
1305
  this.runs = new Map();
1201
1306
  this.occurrences = new Map();
1202
1307
  }
@@ -1233,6 +1338,19 @@ export class GraphKernel {
1233
1338
  `GraphRun is terminal: ${run.status}`,
1234
1339
  );
1235
1340
  }
1341
+ const expectedHeadHash = run.lastEvent?.hash || run.authority.eventHead;
1342
+ const expectedRevision = run.lastEvent?.seq;
1343
+ if (run.authority.authorityMode === "legacy") {
1344
+ throw kernelError(
1345
+ "CC_GRAPH_NON_CANONICAL_WRITER",
1346
+ "legacy authority cannot mutate Graph Kernel state",
1347
+ );
1348
+ }
1349
+ assertGraphAuthorityWriter(run.authority, this.writerIdentity, {
1350
+ now: this.now(),
1351
+ expectedEventHead: expectedHeadHash,
1352
+ requireCanonical: false,
1353
+ });
1236
1354
  const before = stateSnapshot(run);
1237
1355
  try {
1238
1356
  const result = mutator();
@@ -1245,9 +1363,16 @@ export class GraphKernel {
1245
1363
  {
1246
1364
  idempotencyKey: options.idempotencyKey || null,
1247
1365
  traceId: options.traceId || null,
1366
+ authority: run.authority,
1367
+ expectedRevision,
1368
+ expectedHeadHash,
1248
1369
  },
1249
1370
  );
1250
1371
  run.lastEvent = event;
1372
+ run.authority = createGraphAuthorityBinding({
1373
+ ...run.authority,
1374
+ eventHead: event.hash,
1375
+ });
1251
1376
  return result;
1252
1377
  } catch (error) {
1253
1378
  restoreState(run, before);
@@ -1271,16 +1396,32 @@ export class GraphKernel {
1271
1396
  }
1272
1397
  return runProjection(existing);
1273
1398
  }
1274
- const run = buildRun(compiled, options, this.now);
1399
+ const run = buildRun(
1400
+ compiled,
1401
+ options,
1402
+ this.now,
1403
+ this.writerIdentity,
1404
+ this.writerLeaseTtlMs,
1405
+ );
1275
1406
  if (this.runs.has(run.id)) {
1276
1407
  throw kernelError(
1277
1408
  "CC_GRAPH_RUN_CONFLICT",
1278
1409
  `GraphRun id already exists: ${run.id}`,
1279
1410
  );
1280
1411
  }
1281
- this.eventStore.start(run.id, {
1412
+ const initialHead = this.eventStore.start(run.id, {
1282
1413
  definitionId: compiled.definitionId,
1283
1414
  revisionDigest: compiled.revisionDigest,
1415
+ originSurface: run.authority.originSurface,
1416
+ authorityMode: run.authority.authorityMode,
1417
+ authorityGeneration: run.authority.authorityGeneration,
1418
+ writerId: run.authority.writerId,
1419
+ projectionVersion: run.authority.projectionVersion,
1420
+ });
1421
+ run.lastEvent = initialHead;
1422
+ run.authority = createGraphAuthorityBinding({
1423
+ ...run.authority,
1424
+ eventHead: initialHead.hash,
1284
1425
  });
1285
1426
  this.runs.set(run.id, run);
1286
1427
  try {
@@ -1306,7 +1447,7 @@ export class GraphKernel {
1306
1447
  return runProjection(run);
1307
1448
  }
1308
1449
 
1309
- recoverRun(runId) {
1450
+ recoverRun(runId, { authority = null } = {}) {
1310
1451
  const id = safeIdentifier(runId, "runId");
1311
1452
  const events = this.eventStore.read(id);
1312
1453
  const latest = [...events]
@@ -1322,7 +1463,67 @@ export class GraphKernel {
1322
1463
  const run = { id, lastEvent: latest };
1323
1464
  restoreState(run, snapshot);
1324
1465
  run.lastEvent = latest;
1466
+ run.authority = createGraphAuthorityBinding({
1467
+ ...run.authority,
1468
+ eventHead: latest.hash,
1469
+ });
1325
1470
  this.runs.set(id, run);
1471
+ if (authority) {
1472
+ const nextAuthority = createGraphAuthorityBinding({
1473
+ ...authority,
1474
+ logicalRunId: id,
1475
+ eventHead: latest.hash,
1476
+ });
1477
+ const previousAuthority = run.authority;
1478
+ if (
1479
+ nextAuthority.authorityGeneration <=
1480
+ previousAuthority.authorityGeneration
1481
+ ) {
1482
+ this.runs.delete(id);
1483
+ throw kernelError(
1484
+ "CC_GRAPH_STALE_GENERATION",
1485
+ "recovery authority must use a higher writer generation",
1486
+ );
1487
+ }
1488
+ assertGraphAuthorityWriter(nextAuthority, this.writerIdentity, {
1489
+ now: this.now(),
1490
+ expectedEventHead: latest.hash,
1491
+ requireCanonical: false,
1492
+ });
1493
+ run.authority = nextAuthority;
1494
+ try {
1495
+ this._transaction(
1496
+ run,
1497
+ "run.authority_transferred",
1498
+ {
1499
+ previousAuthoritySource: previousAuthority.authoritySource,
1500
+ previousAuthorityGeneration: previousAuthority.authorityGeneration,
1501
+ previousWriterId: previousAuthority.writerId,
1502
+ authorityGeneration: nextAuthority.authorityGeneration,
1503
+ writerId: nextAuthority.writerId,
1504
+ },
1505
+ () => {},
1506
+ {
1507
+ allowTerminal: true,
1508
+ idempotencyKey: `authority-transfer:${nextAuthority.authorityGeneration}`,
1509
+ },
1510
+ );
1511
+ } catch (error) {
1512
+ this.runs.delete(id);
1513
+ throw error;
1514
+ }
1515
+ } else {
1516
+ try {
1517
+ assertGraphAuthorityWriter(run.authority, this.writerIdentity, {
1518
+ now: this.now(),
1519
+ expectedEventHead: latest.hash,
1520
+ requireCanonical: false,
1521
+ });
1522
+ } catch (error) {
1523
+ this.runs.delete(id);
1524
+ throw error;
1525
+ }
1526
+ }
1326
1527
  if (run.occurrenceRef) {
1327
1528
  const key = `${run.occurrenceRef.jobRevision || ""}\0${run.occurrenceRef.occurrenceId || run.occurrenceRef.idempotencyKey || ""}`;
1328
1529
  this.occurrences.set(key, id);
@@ -1367,6 +1568,65 @@ export class GraphKernel {
1367
1568
  return this.eventStore.read(safeIdentifier(runId, "runId"), options);
1368
1569
  }
1369
1570
 
1571
+ collaborationState(runId) {
1572
+ const run = this._run(runId);
1573
+ return Object.freeze({
1574
+ messages: Object.freeze(
1575
+ [...run.messages.values()]
1576
+ .sort(
1577
+ (left, right) =>
1578
+ left.createdAt.localeCompare(right.createdAt) ||
1579
+ left.id.localeCompare(right.id),
1580
+ )
1581
+ .map((message) => Object.freeze(clone(message))),
1582
+ ),
1583
+ messageConsumers: Object.freeze(
1584
+ [...run.messageConsumers.values()]
1585
+ .sort(
1586
+ (left, right) =>
1587
+ left.processedAt.localeCompare(right.processedAt) ||
1588
+ left.messageId.localeCompare(right.messageId),
1589
+ )
1590
+ .map((receipt) => Object.freeze(clone(receipt))),
1591
+ ),
1592
+ handoffs: Object.freeze(
1593
+ [...run.handoffs.values()]
1594
+ .sort(
1595
+ (left, right) =>
1596
+ left.createdAt.localeCompare(right.createdAt) ||
1597
+ left.id.localeCompare(right.id),
1598
+ )
1599
+ .map((handoff) => Object.freeze(clone(handoff))),
1600
+ ),
1601
+ });
1602
+ }
1603
+
1604
+ effectState(runId) {
1605
+ const run = this._run(runId);
1606
+ return Object.freeze(
1607
+ [...run.effects.values()]
1608
+ .sort(
1609
+ (left, right) =>
1610
+ left.createdAt.localeCompare(right.createdAt) ||
1611
+ left.id.localeCompare(right.id),
1612
+ )
1613
+ .map((effect) => Object.freeze(clone(effect))),
1614
+ );
1615
+ }
1616
+
1617
+ humanTasks(runId) {
1618
+ const run = this._run(runId);
1619
+ return Object.freeze(
1620
+ [...run.humanTasks.values()]
1621
+ .sort(
1622
+ (left, right) =>
1623
+ left.createdAt.localeCompare(right.createdAt) ||
1624
+ left.id.localeCompare(right.id),
1625
+ )
1626
+ .map(humanTaskProjection),
1627
+ );
1628
+ }
1629
+
1370
1630
  registerAgent(runId, { agentId, capacity = 1, resident = true } = {}) {
1371
1631
  const run = this._run(runId);
1372
1632
  const id = safeIdentifier(agentId, "agentId");
@@ -1471,6 +1731,7 @@ export class GraphKernel {
1471
1731
  edges = [],
1472
1732
  loops = [],
1473
1733
  subgraphCalls = [],
1734
+ metadataPatch = {},
1474
1735
  } = {},
1475
1736
  ) {
1476
1737
  const run = this._run(runId);
@@ -1483,6 +1744,7 @@ export class GraphKernel {
1483
1744
  edges,
1484
1745
  loops,
1485
1746
  subgraphCalls,
1747
+ metadataPatch,
1486
1748
  };
1487
1749
  const requestDigest = graphDigest(request, "cc.graph.append-request/v1");
1488
1750
  const replay = run.requestCache.get(key);
@@ -1538,6 +1800,10 @@ export class GraphKernel {
1538
1800
  ...clone(run.compiled.definition.subgraphCalls),
1539
1801
  ...clone(subgraphCalls),
1540
1802
  ],
1803
+ metadata: {
1804
+ ...clone(run.compiled.definition.metadata || {}),
1805
+ ...clone(metadataPatch || {}),
1806
+ },
1541
1807
  };
1542
1808
  const compiled = compileGraphDefinition(definition, {
1543
1809
  subgraphs: persistedSubgraphRegistry(
@@ -1942,6 +2208,12 @@ export class GraphKernel {
1942
2208
  } else {
1943
2209
  this.startRun(childCompiled, {
1944
2210
  runId: id,
2211
+ originSurface: parent.authority.originSurface,
2212
+ authority: {
2213
+ ...parent.authority,
2214
+ logicalRunId: id,
2215
+ eventHead: null,
2216
+ },
1945
2217
  parentRunRef: {
1946
2218
  runId: parent.id,
1947
2219
  nodeId: safeNodeId,
@@ -2257,6 +2529,8 @@ export class GraphKernel {
2257
2529
  : null,
2258
2530
  leaseId: safeIdentifier(leaseId, "leaseId"),
2259
2531
  fence,
2532
+ authorityGeneration: run.authority.authorityGeneration,
2533
+ writerId: run.authority.writerId,
2260
2534
  status: "active",
2261
2535
  participationStatus: "active",
2262
2536
  grant: clone(grant),
@@ -2266,6 +2540,7 @@ export class GraphKernel {
2266
2540
  expiresAtMs: now + finitePositive(ttlMs, 60_000),
2267
2541
  terminalEvidence: null,
2268
2542
  usage: null,
2543
+ error: null,
2269
2544
  };
2270
2545
  run.attempts.set(id, attempt);
2271
2546
  state.attemptIds.push(id);
@@ -2291,9 +2566,261 @@ export class GraphKernel {
2291
2566
  });
2292
2567
  }
2293
2568
 
2569
+ /**
2570
+ * Recover a dispatch that was durably assigned by an older writer but never
2571
+ * crossed the effect boundary. Once an effect record exists its outcome can
2572
+ * no longer be inferred from the absence of a terminal attempt, so recovery
2573
+ * must go through reconciliation instead of silently replaying the task.
2574
+ */
2575
+ reclaimAttempt(
2576
+ runId,
2577
+ attemptId,
2578
+ { reason = "writer generation takeover" } = {},
2579
+ ) {
2580
+ const run = this._run(runId);
2581
+ const id = safeIdentifier(attemptId, "attemptId");
2582
+ return this._transaction(
2583
+ run,
2584
+ "assignment.reclaimed",
2585
+ { attemptId: id, reason },
2586
+ () => {
2587
+ const attempt = run.attempts.get(id);
2588
+ if (!attempt) {
2589
+ throw kernelError(
2590
+ "CC_GRAPH_ATTEMPT_NOT_FOUND",
2591
+ `assignment attempt not found: ${id}`,
2592
+ );
2593
+ }
2594
+ if (
2595
+ attempt.status === "expired" &&
2596
+ attempt.participationStatus === "lost"
2597
+ ) {
2598
+ return attemptProjection(attempt);
2599
+ }
2600
+ if (!ACTIVE_ATTEMPT.has(attempt.status)) {
2601
+ throw kernelError(
2602
+ "CC_GRAPH_ASSIGNMENT_RECLAIM_TERMINAL",
2603
+ "only an active assignment can be reclaimed",
2604
+ );
2605
+ }
2606
+ if (
2607
+ Number(attempt.authorityGeneration || 0) >=
2608
+ run.authority.authorityGeneration
2609
+ ) {
2610
+ throw kernelError(
2611
+ "CC_GRAPH_ASSIGNMENT_RECLAIM_STALE_WRITER",
2612
+ "an assignment can only be reclaimed by a higher authority generation",
2613
+ );
2614
+ }
2615
+ const effects = [...run.effects.values()].filter(
2616
+ (effect) => effect.attemptId === attempt.id,
2617
+ );
2618
+ if (effects.length > 0) {
2619
+ throw kernelError(
2620
+ "CC_GRAPH_ASSIGNMENT_RECLAIM_UNSAFE",
2621
+ "an assignment with an effect record requires audited reconciliation",
2622
+ { effectIds: effects.map((effect) => effect.id).sort() },
2623
+ );
2624
+ }
2625
+ const state = run.nodeStates.get(attempt.nodeId);
2626
+ if (
2627
+ !state ||
2628
+ state.acceptedAttemptId ||
2629
+ !["running", "pending"].includes(state.status)
2630
+ ) {
2631
+ throw kernelError(
2632
+ "CC_GRAPH_ASSIGNMENT_RECLAIM_UNSAFE",
2633
+ "assignment state is not safe to return to the ready frontier",
2634
+ );
2635
+ }
2636
+ const now = this.now();
2637
+ attempt.status = "expired";
2638
+ attempt.participationStatus = "lost";
2639
+ attempt.error = String(reason).slice(0, 4096);
2640
+ attempt.updatedAt = nowIso(() => now);
2641
+ state.status = "pending";
2642
+ state.updatedAt = nowIso(() => now);
2643
+ const agent = run.agents.get(attempt.agentId);
2644
+ if (agent) {
2645
+ agent.status = "idle";
2646
+ agent.updatedAt = nowIso(() => now);
2647
+ }
2648
+ propagate(run, () => now);
2649
+ if (refreshLoopStates(run, () => now)) propagate(run, () => now);
2650
+ run.status = classifyQuiescence(run, now);
2651
+ run.completedAt = TERMINAL_RUN.has(run.status)
2652
+ ? nowIso(() => now)
2653
+ : null;
2654
+ return attemptProjection(attempt);
2655
+ },
2656
+ {
2657
+ idempotencyKey: `assignment-reclaim:${id}:${run.authority.authorityGeneration}`,
2658
+ },
2659
+ );
2660
+ }
2661
+
2662
+ /**
2663
+ * Rotate a safely recoverable assignment into the current writer generation.
2664
+ * A persisted attempt is never reused after takeover: callers receive a new
2665
+ * lease/fence, while terminal effect receipts retain an explicit custody
2666
+ * chain. An in-flight/unknown effect remains reconciliation-only.
2667
+ */
2668
+ resumeAttempt(
2669
+ runId,
2670
+ attemptId,
2671
+ {
2672
+ resumedAttemptId = this.createId(),
2673
+ leaseId = this.createId(),
2674
+ ttlMs = 60_000,
2675
+ reason = "writer generation takeover",
2676
+ } = {},
2677
+ ) {
2678
+ const run = this._run(runId);
2679
+ const sourceId = safeIdentifier(attemptId, "attemptId");
2680
+ const successorId = safeIdentifier(resumedAttemptId, "attemptId");
2681
+ return this._transaction(
2682
+ run,
2683
+ "assignment.resumed",
2684
+ { attemptId: sourceId, resumedAttemptId: successorId, reason },
2685
+ () => {
2686
+ const existing = [...run.attempts.values()].find(
2687
+ (candidate) =>
2688
+ candidate.resumedFromAttemptId === sourceId &&
2689
+ candidate.authorityGeneration ===
2690
+ run.authority.authorityGeneration &&
2691
+ candidate.writerId === run.authority.writerId,
2692
+ );
2693
+ if (existing) return attemptProjection(existing);
2694
+ const source = run.attempts.get(sourceId);
2695
+ if (!source) {
2696
+ throw kernelError(
2697
+ "CC_GRAPH_ATTEMPT_NOT_FOUND",
2698
+ `assignment attempt not found: ${sourceId}`,
2699
+ );
2700
+ }
2701
+ const auditedRecovery =
2702
+ source.status === "expired" &&
2703
+ source.participationStatus === "reconciled";
2704
+ if (!ACTIVE_ATTEMPT.has(source.status) && !auditedRecovery) {
2705
+ throw kernelError(
2706
+ "CC_GRAPH_ASSIGNMENT_RESUME_TERMINAL",
2707
+ "only an active or audited-reconciled assignment can be resumed",
2708
+ );
2709
+ }
2710
+ if (
2711
+ Number(source.authorityGeneration || 0) >=
2712
+ run.authority.authorityGeneration
2713
+ ) {
2714
+ throw kernelError(
2715
+ "CC_GRAPH_ASSIGNMENT_RESUME_STALE_WRITER",
2716
+ "an assignment can only be resumed by a higher authority generation",
2717
+ );
2718
+ }
2719
+ const effects = [...run.effects.values()].filter(
2720
+ (effect) => effect.attemptId === source.id,
2721
+ );
2722
+ const unsafe = effects.filter((effect) =>
2723
+ ["started", "unknown"].includes(effect.status),
2724
+ );
2725
+ if (unsafe.length > 0) {
2726
+ throw kernelError(
2727
+ "CC_GRAPH_ASSIGNMENT_RESUME_UNSAFE",
2728
+ "an assignment with an unresolved effect requires audited reconciliation",
2729
+ { effectIds: unsafe.map((effect) => effect.id).sort() },
2730
+ );
2731
+ }
2732
+ const state = run.nodeStates.get(source.nodeId);
2733
+ if (
2734
+ !state ||
2735
+ state.acceptedAttemptId ||
2736
+ !["running", "pending"].includes(state.status)
2737
+ ) {
2738
+ throw kernelError(
2739
+ "CC_GRAPH_ASSIGNMENT_RESUME_UNSAFE",
2740
+ "assignment state is not safe to rotate into the new generation",
2741
+ );
2742
+ }
2743
+ if (run.attempts.has(successorId)) {
2744
+ throw kernelError(
2745
+ "CC_GRAPH_ATTEMPT_ID_CONFLICT",
2746
+ `assignment attempt already exists: ${successorId}`,
2747
+ );
2748
+ }
2749
+ const now = this.now();
2750
+ source.status = "expired";
2751
+ source.participationStatus = "superseded";
2752
+ source.error = String(reason).slice(0, 4096);
2753
+ source.updatedAt = nowIso(() => now);
2754
+ const fence = (run.fenceCounters.get(source.nodeId) || 0) + 1;
2755
+ run.fenceCounters.set(source.nodeId, fence);
2756
+ const successor = {
2757
+ ...clone(source),
2758
+ id: successorId,
2759
+ attempt:
2760
+ [...run.attempts.values()].filter(
2761
+ (candidate) =>
2762
+ candidate.nodeId === source.nodeId &&
2763
+ JSON.stringify(candidate.iterationPath || []) ===
2764
+ JSON.stringify(source.iterationPath || []),
2765
+ ).length + 1,
2766
+ leaseId: safeIdentifier(leaseId, "leaseId"),
2767
+ fence,
2768
+ authorityGeneration: run.authority.authorityGeneration,
2769
+ writerId: run.authority.writerId,
2770
+ status: "active",
2771
+ participationStatus: "active",
2772
+ resumedFromAttemptId: source.id,
2773
+ resumeReason: String(reason).slice(0, 4096),
2774
+ createdAt: nowIso(() => now),
2775
+ updatedAt: nowIso(() => now),
2776
+ expiresAt: nowIso(() => now + finitePositive(ttlMs, 60_000)),
2777
+ expiresAtMs: now + finitePositive(ttlMs, 60_000),
2778
+ terminalEvidence: null,
2779
+ usage: null,
2780
+ error: null,
2781
+ };
2782
+ for (const effect of effects) {
2783
+ effect.custodyHistory ||= [];
2784
+ effect.custodyHistory.push({
2785
+ fromAttemptId: source.id,
2786
+ fromLeaseId: source.leaseId,
2787
+ fromFence: source.fence,
2788
+ toAttemptId: successor.id,
2789
+ toLeaseId: successor.leaseId,
2790
+ toFence: successor.fence,
2791
+ reason: "authority_generation_takeover",
2792
+ transferredAt: nowIso(() => now),
2793
+ });
2794
+ effect.attemptId = successor.id;
2795
+ effect.leaseId = successor.leaseId;
2796
+ effect.fence = successor.fence;
2797
+ effect.updatedAt = nowIso(() => now);
2798
+ }
2799
+ run.attempts.set(successor.id, successor);
2800
+ state.attemptIds.push(successor.id);
2801
+ state.status = "running";
2802
+ state.updatedAt = nowIso(() => now);
2803
+ return attemptProjection(successor);
2804
+ },
2805
+ {
2806
+ idempotencyKey: `assignment-resume:${sourceId}:${run.authority.authorityGeneration}`,
2807
+ },
2808
+ );
2809
+ }
2810
+
2294
2811
  _requireAttemptLease(run, attemptId, leaseId, fence, statuses = ["active"]) {
2295
2812
  const attempt = run.attempts.get(safeIdentifier(attemptId, "attemptId"));
2296
2813
  const now = this.now();
2814
+ if (
2815
+ attempt &&
2816
+ (attempt.authorityGeneration !== run.authority.authorityGeneration ||
2817
+ attempt.writerId !== run.authority.writerId)
2818
+ ) {
2819
+ throw kernelError(
2820
+ "CC_GRAPH_STALE_ATTEMPT_AUTHORITY",
2821
+ "attempt belongs to a stale authority generation or writer",
2822
+ );
2823
+ }
2297
2824
  if (
2298
2825
  !attempt ||
2299
2826
  attempt.leaseId !== safeIdentifier(leaseId, "leaseId") ||
@@ -2323,17 +2850,25 @@ export class GraphKernel {
2323
2850
  ) {
2324
2851
  const run = this._run(runId);
2325
2852
  const key = String(idempotencyKey || "").trim();
2326
- const existing = [...run.effects.values()].find(
2853
+ const matching = [...run.effects.values()].filter(
2327
2854
  (effect) => effect.idempotencyKey === key,
2328
2855
  );
2329
- if (existing) {
2330
- if (existing.operationDigest !== operationDigest) {
2856
+ if (matching.length > 0) {
2857
+ if (
2858
+ matching.some((effect) => effect.operationDigest !== operationDigest)
2859
+ ) {
2331
2860
  throw kernelError(
2332
2861
  "CC_GRAPH_EFFECT_IDEMPOTENCY_CONFLICT",
2333
2862
  "effect idempotency key was reused for a different operation",
2334
2863
  );
2335
2864
  }
2336
- return Object.freeze(clone(existing));
2865
+ // A proved/audited failure means the operation did not commit, so a
2866
+ // later fenced attempt must receive a fresh effect boundary. Started,
2867
+ // unknown, and committed effects continue to deduplicate fail-closed.
2868
+ const authoritative = [...matching]
2869
+ .reverse()
2870
+ .find((effect) => effect.status !== "failed");
2871
+ if (authoritative) return Object.freeze(clone(authoritative));
2337
2872
  }
2338
2873
  const id = safeIdentifier(effectId, "effectId");
2339
2874
  return this._transaction(
@@ -3003,6 +3538,7 @@ export class GraphKernel {
3003
3538
  dataPolicy,
3004
3539
  causationId = null,
3005
3540
  correlationId = null,
3541
+ systemSource = null,
3006
3542
  ttlMs = 24 * 60 * 60 * 1000,
3007
3543
  } = {},
3008
3544
  ) {
@@ -3010,7 +3546,15 @@ export class GraphKernel {
3010
3546
  const id = safeIdentifier(messageId, "messageId");
3011
3547
  const recipient = safeIdentifier(toAgentId, "toAgentId");
3012
3548
  const contentDigest = graphDigest(
3013
- { recipient, mode, payload, causationId, correlationId },
3549
+ {
3550
+ recipient,
3551
+ mode,
3552
+ payload,
3553
+ causationId,
3554
+ correlationId,
3555
+ fromAttemptId: fromAttemptId || null,
3556
+ systemSource: systemSource || null,
3557
+ },
3014
3558
  "cc.graph.message/v1",
3015
3559
  );
3016
3560
  const existing = run.messages.get(id);
@@ -3028,12 +3572,13 @@ export class GraphKernel {
3028
3572
  "message.admitted",
3029
3573
  { messageId: id, toAgentId: recipient, mode },
3030
3574
  () => {
3031
- const attempt = this._requireAttemptLease(
3032
- run,
3033
- fromAttemptId,
3034
- leaseId,
3035
- fence,
3036
- );
3575
+ const source =
3576
+ systemSource == null
3577
+ ? null
3578
+ : safeIdentifier(systemSource, "systemSource");
3579
+ const attempt = source
3580
+ ? null
3581
+ : this._requireAttemptLease(run, fromAttemptId, leaseId, fence);
3037
3582
  const policy = validateDataPolicy(dataPolicy);
3038
3583
  const sink = `agent:${recipient}`;
3039
3584
  if (!sinkAllowed(policy, sink)) {
@@ -3067,9 +3612,12 @@ export class GraphKernel {
3067
3612
  const message = {
3068
3613
  id,
3069
3614
  runId: run.id,
3070
- fromAttemptId: attempt.id,
3071
- fromLeaseId: attempt.leaseId,
3072
- fromFence: attempt.fence,
3615
+ fromAttemptId: attempt?.id || null,
3616
+ fromLeaseId: attempt?.leaseId || null,
3617
+ fromFence: attempt?.fence ?? null,
3618
+ systemSource: source,
3619
+ sourceAuthorityGeneration: run.authority.authorityGeneration,
3620
+ sourceWriterId: run.authority.writerId,
3073
3621
  toAgentId: recipient,
3074
3622
  causationId,
3075
3623
  correlationId,
@@ -3165,6 +3713,16 @@ export class GraphKernel {
3165
3713
  if (run.messageConsumers.has(dedupKey)) {
3166
3714
  return Object.freeze(clone(run.messageConsumers.get(dedupKey)));
3167
3715
  }
3716
+ const priorConsumer = [...run.messageConsumers.values()].find(
3717
+ (receipt) => receipt.messageId === id && receipt.agentId === recipient,
3718
+ );
3719
+ if (priorConsumer) {
3720
+ throw kernelError(
3721
+ "CC_GRAPH_MESSAGE_CONSUMER_CONFLICT",
3722
+ "message was already processed by another consumer key",
3723
+ { consumerKey: priorConsumer.consumerKey },
3724
+ );
3725
+ }
3168
3726
  return this._transaction(
3169
3727
  run,
3170
3728
  "message.processed",
@@ -3244,13 +3802,33 @@ export class GraphKernel {
3244
3802
  ) {
3245
3803
  const run = this._run(runId);
3246
3804
  const id = safeIdentifier(handoffId, "handoffId");
3805
+ const requestDigest = graphDigest(
3806
+ {
3807
+ fromAttemptId,
3808
+ leaseId,
3809
+ fence,
3810
+ toAgentId,
3811
+ artifactIds,
3812
+ preconditions,
3813
+ ttlMs,
3814
+ },
3815
+ "cc.graph.handoff-request/v1",
3816
+ );
3247
3817
  return this._transaction(
3248
3818
  run,
3249
3819
  "handoff.offered",
3250
3820
  { handoffId: id, fromAttemptId, toAgentId },
3251
3821
  () => {
3252
3822
  const existing = run.handoffs.get(id);
3253
- if (existing) return Object.freeze(clone(existing));
3823
+ if (existing) {
3824
+ if (existing.requestDigest !== requestDigest) {
3825
+ throw kernelError(
3826
+ "CC_GRAPH_HANDOFF_ID_CONFLICT",
3827
+ "handoffId was reused with different custody content",
3828
+ );
3829
+ }
3830
+ return Object.freeze(clone(existing));
3831
+ }
3254
3832
  const attempt = this._requireAttemptLease(
3255
3833
  run,
3256
3834
  fromAttemptId,
@@ -3278,6 +3856,7 @@ export class GraphKernel {
3278
3856
  authorityDigest: run.authorityDigest,
3279
3857
  artifactIds: [...artifactIds],
3280
3858
  preconditions: clone(preconditions),
3859
+ requestDigest,
3281
3860
  status: "offered",
3282
3861
  createdAt: nowIso(() => now),
3283
3862
  updatedAt: nowIso(() => now),
@@ -3301,6 +3880,9 @@ export class GraphKernel {
3301
3880
  { handoffId: id, agentId: recipient },
3302
3881
  () => {
3303
3882
  const handoff = run.handoffs.get(id);
3883
+ if (handoff?.status === "accepted" && handoff.toAgentId === recipient) {
3884
+ return Object.freeze(clone(handoff));
3885
+ }
3304
3886
  if (
3305
3887
  !handoff ||
3306
3888
  handoff.status !== "offered" ||
@@ -3313,7 +3895,8 @@ export class GraphKernel {
3313
3895
  );
3314
3896
  }
3315
3897
  handoff.status = "accepted";
3316
- handoff.updatedAt = nowIso(this.now);
3898
+ handoff.acceptedAt = nowIso(this.now);
3899
+ handoff.updatedAt = handoff.acceptedAt;
3317
3900
  return Object.freeze(clone(handoff));
3318
3901
  },
3319
3902
  );
@@ -3329,6 +3912,9 @@ export class GraphKernel {
3329
3912
  { handoffId: id, reason },
3330
3913
  () => {
3331
3914
  const handoff = run.handoffs.get(id);
3915
+ if (handoff?.status === "rejected" && handoff.toAgentId === recipient) {
3916
+ return Object.freeze(clone(handoff));
3917
+ }
3332
3918
  if (
3333
3919
  !handoff ||
3334
3920
  handoff.status !== "offered" ||
@@ -3341,7 +3927,8 @@ export class GraphKernel {
3341
3927
  }
3342
3928
  handoff.status = "rejected";
3343
3929
  handoff.reason = String(reason).slice(0, 1024);
3344
- handoff.updatedAt = nowIso(this.now);
3930
+ handoff.rejectedAt = nowIso(this.now);
3931
+ handoff.updatedAt = handoff.rejectedAt;
3345
3932
  return Object.freeze(clone(handoff));
3346
3933
  },
3347
3934
  );
@@ -3364,6 +3951,21 @@ export class GraphKernel {
3364
3951
  { handoffId: id },
3365
3952
  () => {
3366
3953
  const handoff = run.handoffs.get(id);
3954
+ if (handoff?.status === "committed") {
3955
+ const assignmentAttempt = run.attempts.get(
3956
+ handoff.committedAttemptId,
3957
+ );
3958
+ if (!assignmentAttempt) {
3959
+ throw kernelError(
3960
+ "CC_GRAPH_HANDOFF_COMMIT_CORRUPT",
3961
+ "committed handoff is missing its assignment attempt",
3962
+ );
3963
+ }
3964
+ return Object.freeze({
3965
+ handoff: Object.freeze(clone(handoff)),
3966
+ assignmentAttempt: attemptProjection(assignmentAttempt),
3967
+ });
3968
+ }
3367
3969
  if (!handoff || handoff.status !== "accepted") {
3368
3970
  throw kernelError(
3369
3971
  "CC_GRAPH_HANDOFF_NOT_COMMITTABLE",
@@ -3407,8 +4009,12 @@ export class GraphKernel {
3407
4009
  attempt: attemptNumber,
3408
4010
  agentId: handoff.toAgentId,
3409
4011
  role: "executor",
4012
+ compensationForNodeId: sender.compensationForNodeId || null,
4013
+ effectIdempotencyKey: sender.effectIdempotencyKey || null,
3410
4014
  leaseId: safeIdentifier(leaseId, "leaseId"),
3411
4015
  fence,
4016
+ authorityGeneration: run.authority.authorityGeneration,
4017
+ writerId: run.authority.writerId,
3412
4018
  status: "active",
3413
4019
  participationStatus: "active",
3414
4020
  grant: clone(sender.grant),
@@ -3419,11 +4025,37 @@ export class GraphKernel {
3419
4025
  terminalEvidence: null,
3420
4026
  usage: null,
3421
4027
  };
4028
+ for (const effect of run.effects.values()) {
4029
+ if (effect.attemptId !== sender.id) continue;
4030
+ if (effect.status !== "started") {
4031
+ throw kernelError(
4032
+ "CC_GRAPH_HANDOFF_EFFECT_TERMINAL",
4033
+ "custody cannot transfer after the source effect became terminal",
4034
+ { effectId: effect.id, effectStatus: effect.status },
4035
+ );
4036
+ }
4037
+ effect.custodyHistory ||= [];
4038
+ effect.custodyHistory.push({
4039
+ fromAttemptId: sender.id,
4040
+ fromLeaseId: sender.leaseId,
4041
+ fromFence: sender.fence,
4042
+ toAttemptId: newAttempt.id,
4043
+ toLeaseId: newAttempt.leaseId,
4044
+ toFence: newAttempt.fence,
4045
+ handoffId: handoff.id,
4046
+ transferredAt: nowIso(() => now),
4047
+ });
4048
+ effect.attemptId = newAttempt.id;
4049
+ effect.leaseId = newAttempt.leaseId;
4050
+ effect.fence = newAttempt.fence;
4051
+ effect.updatedAt = nowIso(() => now);
4052
+ }
3422
4053
  run.attempts.set(newAttempt.id, newAttempt);
3423
4054
  run.nodeStates.get(sender.nodeId).attemptIds.push(newAttempt.id);
3424
4055
  handoff.status = "committed";
3425
4056
  handoff.committedAttemptId = newAttempt.id;
3426
- handoff.updatedAt = nowIso(() => now);
4057
+ handoff.committedAt = nowIso(() => now);
4058
+ handoff.updatedAt = handoff.committedAt;
3427
4059
  return Object.freeze({
3428
4060
  handoff: Object.freeze(clone(handoff)),
3429
4061
  assignmentAttempt: attemptProjection(newAttempt),
@@ -3432,11 +4064,60 @@ export class GraphKernel {
3432
4064
  );
3433
4065
  }
3434
4066
 
4067
+ expireHandoffForRecovery(
4068
+ runId,
4069
+ handoffId,
4070
+ { reason = "source writer generation was superseded" } = {},
4071
+ ) {
4072
+ const run = this._run(runId);
4073
+ const id = safeIdentifier(handoffId, "handoffId");
4074
+ return this._transaction(
4075
+ run,
4076
+ "handoff.expired",
4077
+ { handoffId: id, reason },
4078
+ () => {
4079
+ const handoff = run.handoffs.get(id);
4080
+ if (handoff?.status === "expired") {
4081
+ return Object.freeze(clone(handoff));
4082
+ }
4083
+ if (!handoff || !["offered", "accepted"].includes(handoff.status)) {
4084
+ throw kernelError(
4085
+ "CC_GRAPH_HANDOFF_NOT_RECOVERY_EXPIRABLE",
4086
+ "only an open handoff can expire during writer recovery",
4087
+ );
4088
+ }
4089
+ const source = run.attempts.get(handoff.fromAttemptId);
4090
+ if (
4091
+ !source ||
4092
+ Number(source.authorityGeneration || 0) >=
4093
+ run.authority.authorityGeneration
4094
+ ) {
4095
+ throw kernelError(
4096
+ "CC_GRAPH_HANDOFF_RECOVERY_AUTHORITY_INVALID",
4097
+ "handoff recovery requires a source from an older authority generation",
4098
+ );
4099
+ }
4100
+ const now = this.now();
4101
+ handoff.status = "expired";
4102
+ handoff.reason = String(reason).slice(0, 1024);
4103
+ handoff.expiredAt = nowIso(() => now);
4104
+ handoff.updatedAt = nowIso(() => now);
4105
+ return Object.freeze(clone(handoff));
4106
+ },
4107
+ {
4108
+ idempotencyKey: `handoff-recovery-expire:${id}:${run.authority.authorityGeneration}`,
4109
+ },
4110
+ );
4111
+ }
4112
+
3435
4113
  revokeHandoff(runId, handoffId, fromAttemptId, leaseId, fence) {
3436
4114
  const run = this._run(runId);
3437
4115
  const id = safeIdentifier(handoffId, "handoffId");
3438
4116
  return this._transaction(run, "handoff.revoked", { handoffId: id }, () => {
3439
4117
  const handoff = run.handoffs.get(id);
4118
+ if (handoff?.status === "revoked") {
4119
+ return Object.freeze(clone(handoff));
4120
+ }
3440
4121
  if (!handoff || !["offered", "accepted"].includes(handoff.status)) {
3441
4122
  throw kernelError(
3442
4123
  "CC_GRAPH_HANDOFF_NOT_REVOKABLE",
@@ -3451,7 +4132,8 @@ export class GraphKernel {
3451
4132
  );
3452
4133
  }
3453
4134
  handoff.status = "revoked";
3454
- handoff.updatedAt = nowIso(this.now);
4135
+ handoff.revokedAt = nowIso(this.now);
4136
+ handoff.updatedAt = handoff.revokedAt;
3455
4137
  return Object.freeze(clone(handoff));
3456
4138
  });
3457
4139
  }
@@ -3509,11 +4191,12 @@ export class GraphKernel {
3509
4191
  authorityDigest: run.authorityDigest,
3510
4192
  status: "open",
3511
4193
  nonce: safeIdentifier(nonce, "nonce"),
3512
- quorum: Math.max(1, Number(quorum) || 1),
4194
+ quorum: Math.min(64, Math.max(1, Math.trunc(Number(quorum)) || 1)),
3513
4195
  separationOfDuties: separationOfDuties === true,
3514
4196
  decisions: [],
3515
4197
  claimActorId: null,
3516
4198
  claimLeaseId: null,
4199
+ claimExpiresAt: null,
3517
4200
  claimExpiresAtMs: null,
3518
4201
  createdAt: nowIso(() => now),
3519
4202
  updatedAt: nowIso(() => now),
@@ -3523,7 +4206,7 @@ export class GraphKernel {
3523
4206
  };
3524
4207
  run.humanTasks.set(id, task);
3525
4208
  run.status = "waiting_human";
3526
- return Object.freeze(clone(task));
4209
+ return humanTaskProjection(task);
3527
4210
  },
3528
4211
  { idempotencyKey: `human-task:${id}` },
3529
4212
  );
@@ -3569,8 +4252,9 @@ export class GraphKernel {
3569
4252
  task.claimActorId = actor;
3570
4253
  task.claimLeaseId = safeIdentifier(claimLeaseId, "claimLeaseId");
3571
4254
  task.claimExpiresAtMs = now + finitePositive(ttlMs, 300_000);
4255
+ task.claimExpiresAt = nowIso(() => task.claimExpiresAtMs);
3572
4256
  task.updatedAt = nowIso(() => now);
3573
- return Object.freeze(clone(task));
4257
+ return humanTaskProjection(task);
3574
4258
  },
3575
4259
  );
3576
4260
  }
@@ -3675,11 +4359,15 @@ export class GraphKernel {
3675
4359
  task.status = "open";
3676
4360
  task.claimActorId = null;
3677
4361
  task.claimLeaseId = null;
4362
+ task.claimExpiresAt = null;
3678
4363
  task.claimExpiresAtMs = null;
3679
4364
  }
3680
4365
  task.updatedAt = nowIso(() => now);
3681
4366
  run.status = classifyQuiescence(run, now);
3682
- return Object.freeze(clone(task));
4367
+ if (TERMINAL_RUN.has(run.status)) {
4368
+ run.completedAt = nowIso(() => now);
4369
+ }
4370
+ return humanTaskProjection(task);
3683
4371
  },
3684
4372
  );
3685
4373
  }
@@ -3699,14 +4387,25 @@ export class GraphKernel {
3699
4387
  "human task is already terminal",
3700
4388
  );
3701
4389
  }
4390
+ const now = this.now();
3702
4391
  task.status = "cancelled";
3703
4392
  task.reason = String(reason).slice(0, 1024);
3704
- task.updatedAt = nowIso(this.now);
4393
+ task.updatedAt = nowIso(() => now);
4394
+ const attempt = run.attempts.get(task.attemptId);
4395
+ if (attempt && attempt.status === "waiting_human") {
4396
+ attempt.status = "cancelled";
4397
+ attempt.participationStatus = "human_cancelled";
4398
+ attempt.updatedAt = nowIso(() => now);
4399
+ }
3705
4400
  const state = run.nodeStates.get(task.nodeId);
3706
4401
  state.status = "cancelled";
3707
4402
  state.blockedRoot = task.id;
3708
- state.updatedAt = nowIso(this.now);
3709
- return Object.freeze(clone(task));
4403
+ state.updatedAt = nowIso(() => now);
4404
+ run.status = classifyQuiescence(run, now);
4405
+ if (TERMINAL_RUN.has(run.status)) {
4406
+ run.completedAt = nowIso(() => now);
4407
+ }
4408
+ return humanTaskProjection(task);
3710
4409
  },
3711
4410
  );
3712
4411
  }