chainlesschain 0.166.6 → 0.166.7

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 (189) hide show
  1. package/package.json +3 -1
  2. package/src/assets/web-panel/assets/{AIOps-CVnFjTfG.js → AIOps-BlrmQxa2.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BxkwuuQC.js → ActionButton-zyRyLITg.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-C2670q6z.js → Analytics-DwGz7OGN.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-CU_rylW6.js → AppLayout-Dc-nj7Zd.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-DGJmYqnj.js → Artifacts-7Hu3O73p.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-BZOQbINJ.js → Audit-Bnoe3-fR.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DghB4QwF.js → BackgroundAgents-BqYFrDKZ.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-l0mwPbta.js → Backup-B3hwW1nz.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-Bv3WSZOh.js → BaseInput-XFVxLA4w.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-s88nBdvt.js → Chat-CWRDNw7e.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-Z8CBuB1a.js → ChatBubbleRenderer-Bf30VM8G.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-B5icasEs.js → Checkbox-kFeeBgac.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-CsUFqNud.js → Codegen-CCxp8xoI.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DlBtClaW.js → Col-BLKZalle.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-xRn1IdLB.js → Community-DD0eWCez.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-naIr8a_o.js → Compact-CHqHWdy0.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-Dp9esfFE.js → Compliance-Bw54uPiK.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-DQlMYJB-.js → Cowork-DZ13zKNJ.js} +4 -4
  20. package/src/assets/web-panel/assets/{Cron-Cd0oNLzJ.js → Cron-Dlj5Jevg.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BtDIx3XX.js → Crosschain-Nvz0aR0j.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BOlpzEwg.js → DID-Ct89eUyY.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-DV15Axk1.js → Dashboard-BHUH5IAe.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-COXrewN1.js → Dropdown-BhZG-PO7.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-EjIt0Cm7.js → EmailListRenderer-ae4RVwwJ.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DJlhwb-R.js → FamilyGuardDashboard-AOcxLfLr.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-BMiFl82k.js → Federation-BK2PJQOF.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-BUcDQyxn.js → FormItemContext-C2n3xO2_.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-C25uFxvK.js → GenericCardRenderer-DcyUVFZq.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-D20uaEUU.js → Git-tBae9Lt8.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CQxYJjCX.js → Governance-DmNR3AP4.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-m-sczpS5.js → Inference-CoseEigi.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-CBsIlU0S.js → KnowledgeGraph-Bdjd1sxr.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-D0-5bqeu.js → Logs-CtlkKIcn.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BwH414Ms.js → MarkdownRenderer-CZsTBEQE.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-CwDGFu2Q.js → Marketplace-BTYb7nEx.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-7FxJ3omT.js → McpTools-D0iUdW3P.js} +2 -2
  38. package/src/assets/web-panel/assets/{Memory-DOVyb59U.js → Memory--gwI7w4Y.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-DZXl-Hje.js → MobileBridge-JmFBi24T.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-DlZ8tRXt.js → MobileProjects-BK01cQRx.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-D7Pjx1s_.js → Mtc-u7avzyoO.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-BND7lX8p.js → MtcAudit-gT3z5fo_.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-BknS7B-p.js → Multisig-BIqZTz5n.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-7C2-KPHt.js → NLProgramming-DdhVfXYq.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-DNWdTndN.js → Notes-CBlyUmY3.js} +4 -4
  46. package/src/assets/web-panel/assets/{NotificationSettings-DCm4KcJZ.js → NotificationSettings-DSyI26N3.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DyVwwjJx.js → OrderTableRenderer-B7IEuNz4.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-Dl2O_qJ9.js → Organization-ozhi4kGS.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-BOFCvk9Q.js → Overflow-CBy57RVM.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-DgdVowY1.js → P2P-CA-ZRv77.js} +2 -2
  51. package/src/assets/web-panel/assets/PdhVaultBrowser-DOHc9zm8.js +7 -0
  52. package/src/assets/web-panel/assets/{Permissions-BHNEUUwa.js → Permissions-DiP632GP.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-CaZbrOdz.js → PersonalDataHub-AnU8O-SS.js} +5 -5
  54. package/src/assets/web-panel/assets/{Pipeline-DJDnoXmE.js → Pipeline-DDibOUaP.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-CBQr2eX8.js → Privacy-D421t36T.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-Yghqa06z.js → ProjectInit-DR879ZPJ.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-HBAtJi8U.js → ProjectSettings-Ded8o6aY.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-DiCGnMjz.js → Projects-BHPxiucB.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-uJTz77GI.js → Providers-Boivuu-E.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-k3M5ouTp.js → QrScannerModal-xKD_ntKp.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CMji45PH.js → QuickAsk-D3y_A6FV.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-DyoZYPls.js → Recommend-BjKM-YC2.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-s1gR38IO.js → RemoteSession-CgqKMXG4.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-D4hIs4vV.js → Reputation-Ci2GjH5p.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-mzE-ElTg.js → Row-B0-T2BSh.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-cg7J3hI6.js → RssFeed-Bet-ur-r.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-6M0TkGSZ.js → Search-DWtRAS2R.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-B1GQ67qh.js → Security-BDfu2Qn6.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-D_RwU_5S.js → Services-CvJmvSMM.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-TZjk-dsW.js → Skeleton-CEbrKpdC.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CG6FcaCV.js → Skills-CjVeiETf.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-BI190QDv.js → Sla-Dibypxum.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-BWmxF2Wu.js → SpeechSettings-XflYXp7a.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CwPiGw6L.js → SyncSettings-ro1Ivx1E.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-BIXgCeSk.js → Tasks-CHq1qFbo.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-CQFgGCoK.js → Templates-CRnUZxtZ.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-DAelEdkL.js → Tenant-jUX72JCA.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-BbqWK6qh.js → Terminal-BBhAs7pt.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-cmG3zvY5.js → TimelineRenderer-CD5M3RGp.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-C8444NKU.js → Tokens-sah3v0gu.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-C1Rpnz0I.js → Trigger-BNT3Uz3s.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-u4koPiFD.js → Trust-BytMGPMo.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-oPWKn80Z.js → UkeySign-BkYVMRQs.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-D0CUUY_5.js → VideoEditing-psvb5Q7Y.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-fu9pIAbt.js → Wallet-PFQTW0MH.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Bc5ciuOx.js → WebAuthn-DgO-OwRT.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-BCin4_4N.js → WorkflowEditor-DL2l9VNl.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-B2jbM-XN.js → chat-C84akEzN.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CUVoUQ_2.js → colors-DzTWuQKP.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-DgOGpkRH.js → compact-item-CR9LJQFv.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-DIxU6eKI.js → createContext-CTTov5Kn.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-BOhvmlpc.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DoKRKMav.js → hasIn-DALj523o.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-i5MB455u.js → index-3-XJoYgX.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CLdpHe6w.js → index-7ux_XohH.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-B3F_p8NA.js → index-AXnERsyV.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-D2yo6ast.js → index-B2qpjU-D.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CWbL1-N0.js → index-B6QOJbSn.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DtyAuGOm.js → index-BDyt57zL.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DpTFx8Xx.js → index-BHRXKiU1.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CF80xxPG.js → index-BKNIzw0U.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BRMEO2Tl.js → index-BNaQt49r.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-ByjrulIZ.js → index-Be6Os5S8.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BCLeNqIL.js → index-C5eeMl_2.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DJMHnbmu.js → index-CNmTDXmf.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-2flsPFIX.js → index-CXTEpfGe.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BwYofQJg.js → index-CXaCRXN4.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-B5ySiGYy.js → index-CZhhFQi9.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CeWSw-z5.js → index-Cef0n4k_.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C846lPGD.js → index-CjkPDSX4.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-C9TsO9T9.js → index-CmELknPe.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CqlwIjJx.js → index-CnxjXIsK.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Co4lDEk-.js → index-CqUgXYIQ.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Cj-us-D_.js → index-CunGDV0g.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BFUprhNd.js → index-D0MqUnKO.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DGTZdZOE.js → index-D9ScRFxz.js} +3 -3
  117. package/src/assets/web-panel/assets/{index-C88m9lsi.js → index-DBIPnoTj.js} +1 -1
  118. package/src/assets/web-panel/assets/index-DDyMJexx.js +1 -0
  119. package/src/assets/web-panel/assets/{index-BXb-Smmm.js → index-DH6OG-ci.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-eTWPaeBI.js → index-DZER6Lj3.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Cskz--fd.js → index-D_Bg0LdK.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-D-ufbAJB.js → index-Dah8M4l2.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DHugFl3v.js → index-DjE_ew7U.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B9mSYovv.js → index-DtW6Zr_i.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Ds8r3Fzb.js → index-DxdzSljS.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CPXdBnGi.js → index-QW0EXx14.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-B-f6l4Vm.js → index-W9axN_Ce.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CpvyOt5Z.js → index-_8vetPbU.js} +1 -1
  129. package/src/assets/web-panel/assets/index-btiojFYn.js +1 -0
  130. package/src/assets/web-panel/assets/{index-eYxUASSG.js → index-eJcFleIV.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-be8SonCh.js → index-hzzTITR9.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BtSPhEyC.js → index-s4CFyn-n.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-B4chl7iR.js → initDefaultProps-JuW8mFj7.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-DruN9h96.js → motion-Cwb0HBve.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-Cl0UtCgU.js → move-DHokzMgu.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-FX2ttw70.js → mtc-parser-DX31y6QS.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BDCneY46.js → omit-BCIUYDA6.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-DycsEg8b.js → pickAttrs-DMCj4EoZ.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-Bc-dBuMv.js → placementArrow-Dy5K-u1s.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve--Wwaf8lu.js → responsiveObserve-DkGeZu54.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-vDmHQqHH.js → slide-Dc5dyJdg.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-9c52yv3G.js → statusUtils-DIUBjOaC.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BT4BiO6_.js → styleChecker-DmyEOxm5.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-C7XxWPcP.js → useFlexGapSupport-FRhakk1e.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-BHoMqh4i.js → useFs-clXyV1WQ.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BOLxvsAG.js → usePersonalDataHub-UF6DG41z.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-CA7poeeP.js → vnode-CoCt95G7.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-B_sAn-co.js → zoom-yVWt-ENm.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/graph.js +215 -5
  151. package/src/commands/team-distributed.js +342 -2
  152. package/src/commands/team.js +169 -7
  153. package/src/data/changelog.json +13 -0
  154. package/src/generated/cc-agent-protocol.schema.json +6 -1
  155. package/src/lib/agent-team/task-lease.js +315 -0
  156. package/src/lib/agent-team/team-agent-stream.js +16 -0
  157. package/src/lib/agent-team/team-distributed-graph-bridge.js +257 -0
  158. package/src/lib/agent-team/team-distributed-graph-writer.js +523 -0
  159. package/src/lib/agent-team/team-distributed-queue.js +251 -0
  160. package/src/lib/agent-team/team-graph-runtime-adapter.js +1918 -0
  161. package/src/lib/agent-team/team-runner.js +225 -2
  162. package/src/lib/app-server/graph-runtime.js +754 -0
  163. package/src/lib/app-server/rollout-store.js +32 -0
  164. package/src/lib/app-server/server.js +140 -0
  165. package/src/lib/autonomous-agent.js +11 -0
  166. package/src/lib/cowork-graph-authority-adapter.js +455 -0
  167. package/src/lib/dynamic-workflow-facade.js +83 -16
  168. package/src/lib/graph-kernel/adapters.js +93 -20
  169. package/src/lib/graph-kernel/authority.js +324 -0
  170. package/src/lib/graph-kernel/compiler.js +1 -8
  171. package/src/lib/graph-kernel/cutover-authority-resolver.js +219 -0
  172. package/src/lib/graph-kernel/cutover-ledger.js +650 -0
  173. package/src/lib/graph-kernel/event-store.js +15 -1
  174. package/src/lib/graph-kernel/graph-runtime-surfaces.json +1427 -0
  175. package/src/lib/graph-kernel/index.js +4 -0
  176. package/src/lib/graph-kernel/migration-saga.js +309 -0
  177. package/src/lib/graph-kernel/runtime-surface-manifest.js +481 -0
  178. package/src/lib/graph-kernel/runtime.js +661 -24
  179. package/src/lib/graph-kernel/store-cutover-evidence.js +354 -0
  180. package/src/lib/graph-kernel/trace-reducer.js +6 -0
  181. package/src/lib/graph-kernel/trigger-adapter.js +60 -3
  182. package/src/lib/legacy-runtime-authority.js +160 -0
  183. package/src/lib/orchestrator.js +28 -0
  184. package/src/lib/scheduler-kernel/graph-authority-adapter.js +512 -0
  185. package/src/lib/scheduler-kernel/runtime.js +75 -2
  186. package/src/assets/web-panel/assets/PdhVaultBrowser-BiaZfQIM.js +0 -7
  187. package/src/assets/web-panel/assets/devWarning-DiSlgCC4.js +0 -1
  188. package/src/assets/web-panel/assets/index-9WIQEHvv.js +0 -1
  189. package/src/assets/web-panel/assets/index-B7mDobAl.js +0 -1
@@ -7,6 +7,11 @@ import {
7
7
  writeScopesOverlap,
8
8
  } from "./compiler.js";
9
9
  import { GraphEventStore } from "./event-store.js";
10
+ import {
11
+ GRAPH_PROJECTION_VERSION,
12
+ assertGraphAuthorityWriter,
13
+ createGraphAuthorityBinding,
14
+ } from "./authority.js";
10
15
 
11
16
  export const GRAPH_RUN_PHASES = Object.freeze(["open", "sealed"]);
12
17
  export const GRAPH_RUN_STATUSES = Object.freeze([
@@ -358,6 +363,7 @@ function stateSnapshot(run) {
358
363
  revisionDigest: run.revisionDigest,
359
364
  occurrenceRef: run.occurrenceRef,
360
365
  authorityDigest: run.authorityDigest,
366
+ authority: clone(run.authority),
361
367
  budget: run.budget,
362
368
  budgetUsed: run.budgetUsed,
363
369
  budgetReserved: run.budgetReserved,
@@ -404,6 +410,13 @@ function restoreState(run, snapshot, compiled = null) {
404
410
  run.revisionDigest = snapshot.revisionDigest;
405
411
  run.occurrenceRef = clone(snapshot.occurrenceRef);
406
412
  run.authorityDigest = snapshot.authorityDigest;
413
+ if (!snapshot.authority) {
414
+ throw kernelError(
415
+ "CC_GRAPH_AUTHORITY_MIGRATION_REQUIRED",
416
+ "legacy GraphRun snapshot has no writer authority; import it at a verified safe point",
417
+ );
418
+ }
419
+ run.authority = createGraphAuthorityBinding(snapshot.authority);
407
420
  run.budget = clone(snapshot.budget);
408
421
  run.budgetUsed = clone(snapshot.budgetUsed);
409
422
  run.budgetReserved = clone(snapshot.budgetReserved || emptyBudgetUsage());
@@ -473,6 +486,15 @@ function runProjection(run) {
473
486
  status: run.status,
474
487
  graphRevision: run.graphRevision,
475
488
  authorityDigest: run.authorityDigest,
489
+ originSurface: run.authority.originSurface,
490
+ authoritySource: run.authority.authoritySource,
491
+ authorityMode: run.authority.authorityMode,
492
+ authorityGeneration: run.authority.authorityGeneration,
493
+ writerId: run.authority.writerId,
494
+ writerLeaseId: run.authority.writerLeaseId,
495
+ writerLeaseExpiresAt: run.authority.writerLeaseExpiresAt,
496
+ eventHead: run.authority.eventHead,
497
+ projectionVersion: run.authority.projectionVersion,
476
498
  budget: clone(run.budget),
477
499
  budgetUsed: clone(run.budgetUsed),
478
500
  budgetReserved: clone(run.budgetReserved),
@@ -527,10 +549,47 @@ function runProjection(run) {
527
549
  });
528
550
  }
529
551
 
530
- function buildRun(compiled, options, now) {
552
+ function buildRun(compiled, options, now, writerIdentity, writerLeaseTtlMs) {
531
553
  const createdAt = nowIso(now);
554
+ const id = safeIdentifier(options.runId || randomUUID(), "runId");
555
+ const authority = createGraphAuthorityBinding(
556
+ options.authority || {
557
+ logicalRunId: id,
558
+ originSurface: options.originSurface || "cli_team",
559
+ authorityMode:
560
+ writerIdentity.authoritySource === "graph_kernel_shadow"
561
+ ? "shadow"
562
+ : "canonical",
563
+ authoritySource: writerIdentity.authoritySource,
564
+ authorityGeneration:
565
+ options.authorityGeneration || writerIdentity.authorityGeneration,
566
+ writerId: writerIdentity.writerId,
567
+ writerLeaseId: writerIdentity.writerLeaseId,
568
+ writerLeaseExpiresAt: new Date(
569
+ now() + Math.max(1, Number(writerLeaseTtlMs)),
570
+ ).toISOString(),
571
+ eventHead: null,
572
+ projectionVersion: GRAPH_PROJECTION_VERSION,
573
+ },
574
+ );
575
+ if (authority.logicalRunId !== id) {
576
+ throw kernelError(
577
+ "CC_GRAPH_AUTHORITY_RUN_MISMATCH",
578
+ "Graph authority must be bound to the exact logical run id",
579
+ );
580
+ }
581
+ if (authority.authorityMode === "legacy") {
582
+ throw kernelError(
583
+ "CC_GRAPH_NON_CANONICAL_WRITER",
584
+ "legacy authority cannot mutate Graph Kernel state",
585
+ );
586
+ }
587
+ assertGraphAuthorityWriter(authority, writerIdentity, {
588
+ now: now(),
589
+ requireCanonical: false,
590
+ });
532
591
  const run = {
533
- id: safeIdentifier(options.runId || randomUUID(), "runId"),
592
+ id,
534
593
  compiled,
535
594
  phase: "open",
536
595
  status: "running",
@@ -547,6 +606,7 @@ function buildRun(compiled, options, now) {
547
606
  },
548
607
  "cc.graph.authority/v1",
549
608
  ),
609
+ authority,
550
610
  budget: clone(options.budget || compiled.definition.budget || {}),
551
611
  budgetUsed: emptyBudgetUsage(),
552
612
  budgetReserved: emptyBudgetUsage(),
@@ -885,6 +945,7 @@ function sweepExpirations(run, now) {
885
945
  handoff.expiresAtMs <= now
886
946
  ) {
887
947
  handoff.status = "expired";
948
+ handoff.expiredAt = nowIso(() => now);
888
949
  handoff.updatedAt = nowIso(() => now);
889
950
  changed = true;
890
951
  }
@@ -1187,6 +1248,11 @@ export class GraphKernel {
1187
1248
  agingWindowMs = 1000,
1188
1249
  maxPendingMessagesPerAgent = 100,
1189
1250
  maxLivelockRepeats = 8,
1251
+ writerId = `graph-kernel:${process.pid}:${randomUUID()}`,
1252
+ writerLeaseId = `graph-kernel-lease:${randomUUID()}`,
1253
+ authoritySource = "graph_kernel",
1254
+ authorityGeneration = 1,
1255
+ writerLeaseTtlMs = 24 * 60 * 60 * 1000,
1190
1256
  } = {}) {
1191
1257
  this.eventStore = eventStore;
1192
1258
  this.now = now;
@@ -1197,6 +1263,13 @@ export class GraphKernel {
1197
1263
  Number(maxPendingMessagesPerAgent) || 100,
1198
1264
  );
1199
1265
  this.maxLivelockRepeats = Math.max(2, Number(maxLivelockRepeats) || 8);
1266
+ this.writerIdentity = Object.freeze({
1267
+ authoritySource,
1268
+ authorityGeneration: Math.max(1, Number(authorityGeneration) || 1),
1269
+ writerId: safeIdentifier(writerId, "writerId"),
1270
+ writerLeaseId: safeIdentifier(writerLeaseId, "writerLeaseId"),
1271
+ });
1272
+ this.writerLeaseTtlMs = Math.max(1, Number(writerLeaseTtlMs) || 1);
1200
1273
  this.runs = new Map();
1201
1274
  this.occurrences = new Map();
1202
1275
  }
@@ -1233,6 +1306,19 @@ export class GraphKernel {
1233
1306
  `GraphRun is terminal: ${run.status}`,
1234
1307
  );
1235
1308
  }
1309
+ const expectedHeadHash = run.lastEvent?.hash || run.authority.eventHead;
1310
+ const expectedRevision = run.lastEvent?.seq;
1311
+ if (run.authority.authorityMode === "legacy") {
1312
+ throw kernelError(
1313
+ "CC_GRAPH_NON_CANONICAL_WRITER",
1314
+ "legacy authority cannot mutate Graph Kernel state",
1315
+ );
1316
+ }
1317
+ assertGraphAuthorityWriter(run.authority, this.writerIdentity, {
1318
+ now: this.now(),
1319
+ expectedEventHead: expectedHeadHash,
1320
+ requireCanonical: false,
1321
+ });
1236
1322
  const before = stateSnapshot(run);
1237
1323
  try {
1238
1324
  const result = mutator();
@@ -1245,9 +1331,16 @@ export class GraphKernel {
1245
1331
  {
1246
1332
  idempotencyKey: options.idempotencyKey || null,
1247
1333
  traceId: options.traceId || null,
1334
+ authority: run.authority,
1335
+ expectedRevision,
1336
+ expectedHeadHash,
1248
1337
  },
1249
1338
  );
1250
1339
  run.lastEvent = event;
1340
+ run.authority = createGraphAuthorityBinding({
1341
+ ...run.authority,
1342
+ eventHead: event.hash,
1343
+ });
1251
1344
  return result;
1252
1345
  } catch (error) {
1253
1346
  restoreState(run, before);
@@ -1271,16 +1364,32 @@ export class GraphKernel {
1271
1364
  }
1272
1365
  return runProjection(existing);
1273
1366
  }
1274
- const run = buildRun(compiled, options, this.now);
1367
+ const run = buildRun(
1368
+ compiled,
1369
+ options,
1370
+ this.now,
1371
+ this.writerIdentity,
1372
+ this.writerLeaseTtlMs,
1373
+ );
1275
1374
  if (this.runs.has(run.id)) {
1276
1375
  throw kernelError(
1277
1376
  "CC_GRAPH_RUN_CONFLICT",
1278
1377
  `GraphRun id already exists: ${run.id}`,
1279
1378
  );
1280
1379
  }
1281
- this.eventStore.start(run.id, {
1380
+ const initialHead = this.eventStore.start(run.id, {
1282
1381
  definitionId: compiled.definitionId,
1283
1382
  revisionDigest: compiled.revisionDigest,
1383
+ originSurface: run.authority.originSurface,
1384
+ authorityMode: run.authority.authorityMode,
1385
+ authorityGeneration: run.authority.authorityGeneration,
1386
+ writerId: run.authority.writerId,
1387
+ projectionVersion: run.authority.projectionVersion,
1388
+ });
1389
+ run.lastEvent = initialHead;
1390
+ run.authority = createGraphAuthorityBinding({
1391
+ ...run.authority,
1392
+ eventHead: initialHead.hash,
1284
1393
  });
1285
1394
  this.runs.set(run.id, run);
1286
1395
  try {
@@ -1306,7 +1415,7 @@ export class GraphKernel {
1306
1415
  return runProjection(run);
1307
1416
  }
1308
1417
 
1309
- recoverRun(runId) {
1418
+ recoverRun(runId, { authority = null } = {}) {
1310
1419
  const id = safeIdentifier(runId, "runId");
1311
1420
  const events = this.eventStore.read(id);
1312
1421
  const latest = [...events]
@@ -1322,7 +1431,67 @@ export class GraphKernel {
1322
1431
  const run = { id, lastEvent: latest };
1323
1432
  restoreState(run, snapshot);
1324
1433
  run.lastEvent = latest;
1434
+ run.authority = createGraphAuthorityBinding({
1435
+ ...run.authority,
1436
+ eventHead: latest.hash,
1437
+ });
1325
1438
  this.runs.set(id, run);
1439
+ if (authority) {
1440
+ const nextAuthority = createGraphAuthorityBinding({
1441
+ ...authority,
1442
+ logicalRunId: id,
1443
+ eventHead: latest.hash,
1444
+ });
1445
+ const previousAuthority = run.authority;
1446
+ if (
1447
+ nextAuthority.authorityGeneration <=
1448
+ previousAuthority.authorityGeneration
1449
+ ) {
1450
+ this.runs.delete(id);
1451
+ throw kernelError(
1452
+ "CC_GRAPH_STALE_GENERATION",
1453
+ "recovery authority must use a higher writer generation",
1454
+ );
1455
+ }
1456
+ assertGraphAuthorityWriter(nextAuthority, this.writerIdentity, {
1457
+ now: this.now(),
1458
+ expectedEventHead: latest.hash,
1459
+ requireCanonical: false,
1460
+ });
1461
+ run.authority = nextAuthority;
1462
+ try {
1463
+ this._transaction(
1464
+ run,
1465
+ "run.authority_transferred",
1466
+ {
1467
+ previousAuthoritySource: previousAuthority.authoritySource,
1468
+ previousAuthorityGeneration: previousAuthority.authorityGeneration,
1469
+ previousWriterId: previousAuthority.writerId,
1470
+ authorityGeneration: nextAuthority.authorityGeneration,
1471
+ writerId: nextAuthority.writerId,
1472
+ },
1473
+ () => {},
1474
+ {
1475
+ allowTerminal: true,
1476
+ idempotencyKey: `authority-transfer:${nextAuthority.authorityGeneration}`,
1477
+ },
1478
+ );
1479
+ } catch (error) {
1480
+ this.runs.delete(id);
1481
+ throw error;
1482
+ }
1483
+ } else {
1484
+ try {
1485
+ assertGraphAuthorityWriter(run.authority, this.writerIdentity, {
1486
+ now: this.now(),
1487
+ expectedEventHead: latest.hash,
1488
+ requireCanonical: false,
1489
+ });
1490
+ } catch (error) {
1491
+ this.runs.delete(id);
1492
+ throw error;
1493
+ }
1494
+ }
1326
1495
  if (run.occurrenceRef) {
1327
1496
  const key = `${run.occurrenceRef.jobRevision || ""}\0${run.occurrenceRef.occurrenceId || run.occurrenceRef.idempotencyKey || ""}`;
1328
1497
  this.occurrences.set(key, id);
@@ -1367,6 +1536,52 @@ export class GraphKernel {
1367
1536
  return this.eventStore.read(safeIdentifier(runId, "runId"), options);
1368
1537
  }
1369
1538
 
1539
+ collaborationState(runId) {
1540
+ const run = this._run(runId);
1541
+ return Object.freeze({
1542
+ messages: Object.freeze(
1543
+ [...run.messages.values()]
1544
+ .sort(
1545
+ (left, right) =>
1546
+ left.createdAt.localeCompare(right.createdAt) ||
1547
+ left.id.localeCompare(right.id),
1548
+ )
1549
+ .map((message) => Object.freeze(clone(message))),
1550
+ ),
1551
+ messageConsumers: Object.freeze(
1552
+ [...run.messageConsumers.values()]
1553
+ .sort(
1554
+ (left, right) =>
1555
+ left.processedAt.localeCompare(right.processedAt) ||
1556
+ left.messageId.localeCompare(right.messageId),
1557
+ )
1558
+ .map((receipt) => Object.freeze(clone(receipt))),
1559
+ ),
1560
+ handoffs: Object.freeze(
1561
+ [...run.handoffs.values()]
1562
+ .sort(
1563
+ (left, right) =>
1564
+ left.createdAt.localeCompare(right.createdAt) ||
1565
+ left.id.localeCompare(right.id),
1566
+ )
1567
+ .map((handoff) => Object.freeze(clone(handoff))),
1568
+ ),
1569
+ });
1570
+ }
1571
+
1572
+ effectState(runId) {
1573
+ const run = this._run(runId);
1574
+ return Object.freeze(
1575
+ [...run.effects.values()]
1576
+ .sort(
1577
+ (left, right) =>
1578
+ left.createdAt.localeCompare(right.createdAt) ||
1579
+ left.id.localeCompare(right.id),
1580
+ )
1581
+ .map((effect) => Object.freeze(clone(effect))),
1582
+ );
1583
+ }
1584
+
1370
1585
  registerAgent(runId, { agentId, capacity = 1, resident = true } = {}) {
1371
1586
  const run = this._run(runId);
1372
1587
  const id = safeIdentifier(agentId, "agentId");
@@ -1471,6 +1686,7 @@ export class GraphKernel {
1471
1686
  edges = [],
1472
1687
  loops = [],
1473
1688
  subgraphCalls = [],
1689
+ metadataPatch = {},
1474
1690
  } = {},
1475
1691
  ) {
1476
1692
  const run = this._run(runId);
@@ -1483,6 +1699,7 @@ export class GraphKernel {
1483
1699
  edges,
1484
1700
  loops,
1485
1701
  subgraphCalls,
1702
+ metadataPatch,
1486
1703
  };
1487
1704
  const requestDigest = graphDigest(request, "cc.graph.append-request/v1");
1488
1705
  const replay = run.requestCache.get(key);
@@ -1538,6 +1755,10 @@ export class GraphKernel {
1538
1755
  ...clone(run.compiled.definition.subgraphCalls),
1539
1756
  ...clone(subgraphCalls),
1540
1757
  ],
1758
+ metadata: {
1759
+ ...clone(run.compiled.definition.metadata || {}),
1760
+ ...clone(metadataPatch || {}),
1761
+ },
1541
1762
  };
1542
1763
  const compiled = compileGraphDefinition(definition, {
1543
1764
  subgraphs: persistedSubgraphRegistry(
@@ -1942,6 +2163,12 @@ export class GraphKernel {
1942
2163
  } else {
1943
2164
  this.startRun(childCompiled, {
1944
2165
  runId: id,
2166
+ originSurface: parent.authority.originSurface,
2167
+ authority: {
2168
+ ...parent.authority,
2169
+ logicalRunId: id,
2170
+ eventHead: null,
2171
+ },
1945
2172
  parentRunRef: {
1946
2173
  runId: parent.id,
1947
2174
  nodeId: safeNodeId,
@@ -2257,6 +2484,8 @@ export class GraphKernel {
2257
2484
  : null,
2258
2485
  leaseId: safeIdentifier(leaseId, "leaseId"),
2259
2486
  fence,
2487
+ authorityGeneration: run.authority.authorityGeneration,
2488
+ writerId: run.authority.writerId,
2260
2489
  status: "active",
2261
2490
  participationStatus: "active",
2262
2491
  grant: clone(grant),
@@ -2266,6 +2495,7 @@ export class GraphKernel {
2266
2495
  expiresAtMs: now + finitePositive(ttlMs, 60_000),
2267
2496
  terminalEvidence: null,
2268
2497
  usage: null,
2498
+ error: null,
2269
2499
  };
2270
2500
  run.attempts.set(id, attempt);
2271
2501
  state.attemptIds.push(id);
@@ -2291,9 +2521,261 @@ export class GraphKernel {
2291
2521
  });
2292
2522
  }
2293
2523
 
2524
+ /**
2525
+ * Recover a dispatch that was durably assigned by an older writer but never
2526
+ * crossed the effect boundary. Once an effect record exists its outcome can
2527
+ * no longer be inferred from the absence of a terminal attempt, so recovery
2528
+ * must go through reconciliation instead of silently replaying the task.
2529
+ */
2530
+ reclaimAttempt(
2531
+ runId,
2532
+ attemptId,
2533
+ { reason = "writer generation takeover" } = {},
2534
+ ) {
2535
+ const run = this._run(runId);
2536
+ const id = safeIdentifier(attemptId, "attemptId");
2537
+ return this._transaction(
2538
+ run,
2539
+ "assignment.reclaimed",
2540
+ { attemptId: id, reason },
2541
+ () => {
2542
+ const attempt = run.attempts.get(id);
2543
+ if (!attempt) {
2544
+ throw kernelError(
2545
+ "CC_GRAPH_ATTEMPT_NOT_FOUND",
2546
+ `assignment attempt not found: ${id}`,
2547
+ );
2548
+ }
2549
+ if (
2550
+ attempt.status === "expired" &&
2551
+ attempt.participationStatus === "lost"
2552
+ ) {
2553
+ return attemptProjection(attempt);
2554
+ }
2555
+ if (!ACTIVE_ATTEMPT.has(attempt.status)) {
2556
+ throw kernelError(
2557
+ "CC_GRAPH_ASSIGNMENT_RECLAIM_TERMINAL",
2558
+ "only an active assignment can be reclaimed",
2559
+ );
2560
+ }
2561
+ if (
2562
+ Number(attempt.authorityGeneration || 0) >=
2563
+ run.authority.authorityGeneration
2564
+ ) {
2565
+ throw kernelError(
2566
+ "CC_GRAPH_ASSIGNMENT_RECLAIM_STALE_WRITER",
2567
+ "an assignment can only be reclaimed by a higher authority generation",
2568
+ );
2569
+ }
2570
+ const effects = [...run.effects.values()].filter(
2571
+ (effect) => effect.attemptId === attempt.id,
2572
+ );
2573
+ if (effects.length > 0) {
2574
+ throw kernelError(
2575
+ "CC_GRAPH_ASSIGNMENT_RECLAIM_UNSAFE",
2576
+ "an assignment with an effect record requires audited reconciliation",
2577
+ { effectIds: effects.map((effect) => effect.id).sort() },
2578
+ );
2579
+ }
2580
+ const state = run.nodeStates.get(attempt.nodeId);
2581
+ if (
2582
+ !state ||
2583
+ state.acceptedAttemptId ||
2584
+ !["running", "pending"].includes(state.status)
2585
+ ) {
2586
+ throw kernelError(
2587
+ "CC_GRAPH_ASSIGNMENT_RECLAIM_UNSAFE",
2588
+ "assignment state is not safe to return to the ready frontier",
2589
+ );
2590
+ }
2591
+ const now = this.now();
2592
+ attempt.status = "expired";
2593
+ attempt.participationStatus = "lost";
2594
+ attempt.error = String(reason).slice(0, 4096);
2595
+ attempt.updatedAt = nowIso(() => now);
2596
+ state.status = "pending";
2597
+ state.updatedAt = nowIso(() => now);
2598
+ const agent = run.agents.get(attempt.agentId);
2599
+ if (agent) {
2600
+ agent.status = "idle";
2601
+ agent.updatedAt = nowIso(() => now);
2602
+ }
2603
+ propagate(run, () => now);
2604
+ if (refreshLoopStates(run, () => now)) propagate(run, () => now);
2605
+ run.status = classifyQuiescence(run, now);
2606
+ run.completedAt = TERMINAL_RUN.has(run.status)
2607
+ ? nowIso(() => now)
2608
+ : null;
2609
+ return attemptProjection(attempt);
2610
+ },
2611
+ {
2612
+ idempotencyKey: `assignment-reclaim:${id}:${run.authority.authorityGeneration}`,
2613
+ },
2614
+ );
2615
+ }
2616
+
2617
+ /**
2618
+ * Rotate a safely recoverable assignment into the current writer generation.
2619
+ * A persisted attempt is never reused after takeover: callers receive a new
2620
+ * lease/fence, while terminal effect receipts retain an explicit custody
2621
+ * chain. An in-flight/unknown effect remains reconciliation-only.
2622
+ */
2623
+ resumeAttempt(
2624
+ runId,
2625
+ attemptId,
2626
+ {
2627
+ resumedAttemptId = this.createId(),
2628
+ leaseId = this.createId(),
2629
+ ttlMs = 60_000,
2630
+ reason = "writer generation takeover",
2631
+ } = {},
2632
+ ) {
2633
+ const run = this._run(runId);
2634
+ const sourceId = safeIdentifier(attemptId, "attemptId");
2635
+ const successorId = safeIdentifier(resumedAttemptId, "attemptId");
2636
+ return this._transaction(
2637
+ run,
2638
+ "assignment.resumed",
2639
+ { attemptId: sourceId, resumedAttemptId: successorId, reason },
2640
+ () => {
2641
+ const existing = [...run.attempts.values()].find(
2642
+ (candidate) =>
2643
+ candidate.resumedFromAttemptId === sourceId &&
2644
+ candidate.authorityGeneration ===
2645
+ run.authority.authorityGeneration &&
2646
+ candidate.writerId === run.authority.writerId,
2647
+ );
2648
+ if (existing) return attemptProjection(existing);
2649
+ const source = run.attempts.get(sourceId);
2650
+ if (!source) {
2651
+ throw kernelError(
2652
+ "CC_GRAPH_ATTEMPT_NOT_FOUND",
2653
+ `assignment attempt not found: ${sourceId}`,
2654
+ );
2655
+ }
2656
+ const auditedRecovery =
2657
+ source.status === "expired" &&
2658
+ source.participationStatus === "reconciled";
2659
+ if (!ACTIVE_ATTEMPT.has(source.status) && !auditedRecovery) {
2660
+ throw kernelError(
2661
+ "CC_GRAPH_ASSIGNMENT_RESUME_TERMINAL",
2662
+ "only an active or audited-reconciled assignment can be resumed",
2663
+ );
2664
+ }
2665
+ if (
2666
+ Number(source.authorityGeneration || 0) >=
2667
+ run.authority.authorityGeneration
2668
+ ) {
2669
+ throw kernelError(
2670
+ "CC_GRAPH_ASSIGNMENT_RESUME_STALE_WRITER",
2671
+ "an assignment can only be resumed by a higher authority generation",
2672
+ );
2673
+ }
2674
+ const effects = [...run.effects.values()].filter(
2675
+ (effect) => effect.attemptId === source.id,
2676
+ );
2677
+ const unsafe = effects.filter((effect) =>
2678
+ ["started", "unknown"].includes(effect.status),
2679
+ );
2680
+ if (unsafe.length > 0) {
2681
+ throw kernelError(
2682
+ "CC_GRAPH_ASSIGNMENT_RESUME_UNSAFE",
2683
+ "an assignment with an unresolved effect requires audited reconciliation",
2684
+ { effectIds: unsafe.map((effect) => effect.id).sort() },
2685
+ );
2686
+ }
2687
+ const state = run.nodeStates.get(source.nodeId);
2688
+ if (
2689
+ !state ||
2690
+ state.acceptedAttemptId ||
2691
+ !["running", "pending"].includes(state.status)
2692
+ ) {
2693
+ throw kernelError(
2694
+ "CC_GRAPH_ASSIGNMENT_RESUME_UNSAFE",
2695
+ "assignment state is not safe to rotate into the new generation",
2696
+ );
2697
+ }
2698
+ if (run.attempts.has(successorId)) {
2699
+ throw kernelError(
2700
+ "CC_GRAPH_ATTEMPT_ID_CONFLICT",
2701
+ `assignment attempt already exists: ${successorId}`,
2702
+ );
2703
+ }
2704
+ const now = this.now();
2705
+ source.status = "expired";
2706
+ source.participationStatus = "superseded";
2707
+ source.error = String(reason).slice(0, 4096);
2708
+ source.updatedAt = nowIso(() => now);
2709
+ const fence = (run.fenceCounters.get(source.nodeId) || 0) + 1;
2710
+ run.fenceCounters.set(source.nodeId, fence);
2711
+ const successor = {
2712
+ ...clone(source),
2713
+ id: successorId,
2714
+ attempt:
2715
+ [...run.attempts.values()].filter(
2716
+ (candidate) =>
2717
+ candidate.nodeId === source.nodeId &&
2718
+ JSON.stringify(candidate.iterationPath || []) ===
2719
+ JSON.stringify(source.iterationPath || []),
2720
+ ).length + 1,
2721
+ leaseId: safeIdentifier(leaseId, "leaseId"),
2722
+ fence,
2723
+ authorityGeneration: run.authority.authorityGeneration,
2724
+ writerId: run.authority.writerId,
2725
+ status: "active",
2726
+ participationStatus: "active",
2727
+ resumedFromAttemptId: source.id,
2728
+ resumeReason: String(reason).slice(0, 4096),
2729
+ createdAt: nowIso(() => now),
2730
+ updatedAt: nowIso(() => now),
2731
+ expiresAt: nowIso(() => now + finitePositive(ttlMs, 60_000)),
2732
+ expiresAtMs: now + finitePositive(ttlMs, 60_000),
2733
+ terminalEvidence: null,
2734
+ usage: null,
2735
+ error: null,
2736
+ };
2737
+ for (const effect of effects) {
2738
+ effect.custodyHistory ||= [];
2739
+ effect.custodyHistory.push({
2740
+ fromAttemptId: source.id,
2741
+ fromLeaseId: source.leaseId,
2742
+ fromFence: source.fence,
2743
+ toAttemptId: successor.id,
2744
+ toLeaseId: successor.leaseId,
2745
+ toFence: successor.fence,
2746
+ reason: "authority_generation_takeover",
2747
+ transferredAt: nowIso(() => now),
2748
+ });
2749
+ effect.attemptId = successor.id;
2750
+ effect.leaseId = successor.leaseId;
2751
+ effect.fence = successor.fence;
2752
+ effect.updatedAt = nowIso(() => now);
2753
+ }
2754
+ run.attempts.set(successor.id, successor);
2755
+ state.attemptIds.push(successor.id);
2756
+ state.status = "running";
2757
+ state.updatedAt = nowIso(() => now);
2758
+ return attemptProjection(successor);
2759
+ },
2760
+ {
2761
+ idempotencyKey: `assignment-resume:${sourceId}:${run.authority.authorityGeneration}`,
2762
+ },
2763
+ );
2764
+ }
2765
+
2294
2766
  _requireAttemptLease(run, attemptId, leaseId, fence, statuses = ["active"]) {
2295
2767
  const attempt = run.attempts.get(safeIdentifier(attemptId, "attemptId"));
2296
2768
  const now = this.now();
2769
+ if (
2770
+ attempt &&
2771
+ (attempt.authorityGeneration !== run.authority.authorityGeneration ||
2772
+ attempt.writerId !== run.authority.writerId)
2773
+ ) {
2774
+ throw kernelError(
2775
+ "CC_GRAPH_STALE_ATTEMPT_AUTHORITY",
2776
+ "attempt belongs to a stale authority generation or writer",
2777
+ );
2778
+ }
2297
2779
  if (
2298
2780
  !attempt ||
2299
2781
  attempt.leaseId !== safeIdentifier(leaseId, "leaseId") ||
@@ -2323,17 +2805,25 @@ export class GraphKernel {
2323
2805
  ) {
2324
2806
  const run = this._run(runId);
2325
2807
  const key = String(idempotencyKey || "").trim();
2326
- const existing = [...run.effects.values()].find(
2808
+ const matching = [...run.effects.values()].filter(
2327
2809
  (effect) => effect.idempotencyKey === key,
2328
2810
  );
2329
- if (existing) {
2330
- if (existing.operationDigest !== operationDigest) {
2811
+ if (matching.length > 0) {
2812
+ if (
2813
+ matching.some((effect) => effect.operationDigest !== operationDigest)
2814
+ ) {
2331
2815
  throw kernelError(
2332
2816
  "CC_GRAPH_EFFECT_IDEMPOTENCY_CONFLICT",
2333
2817
  "effect idempotency key was reused for a different operation",
2334
2818
  );
2335
2819
  }
2336
- return Object.freeze(clone(existing));
2820
+ // A proved/audited failure means the operation did not commit, so a
2821
+ // later fenced attempt must receive a fresh effect boundary. Started,
2822
+ // unknown, and committed effects continue to deduplicate fail-closed.
2823
+ const authoritative = [...matching]
2824
+ .reverse()
2825
+ .find((effect) => effect.status !== "failed");
2826
+ if (authoritative) return Object.freeze(clone(authoritative));
2337
2827
  }
2338
2828
  const id = safeIdentifier(effectId, "effectId");
2339
2829
  return this._transaction(
@@ -3003,6 +3493,7 @@ export class GraphKernel {
3003
3493
  dataPolicy,
3004
3494
  causationId = null,
3005
3495
  correlationId = null,
3496
+ systemSource = null,
3006
3497
  ttlMs = 24 * 60 * 60 * 1000,
3007
3498
  } = {},
3008
3499
  ) {
@@ -3010,7 +3501,15 @@ export class GraphKernel {
3010
3501
  const id = safeIdentifier(messageId, "messageId");
3011
3502
  const recipient = safeIdentifier(toAgentId, "toAgentId");
3012
3503
  const contentDigest = graphDigest(
3013
- { recipient, mode, payload, causationId, correlationId },
3504
+ {
3505
+ recipient,
3506
+ mode,
3507
+ payload,
3508
+ causationId,
3509
+ correlationId,
3510
+ fromAttemptId: fromAttemptId || null,
3511
+ systemSource: systemSource || null,
3512
+ },
3014
3513
  "cc.graph.message/v1",
3015
3514
  );
3016
3515
  const existing = run.messages.get(id);
@@ -3028,12 +3527,13 @@ export class GraphKernel {
3028
3527
  "message.admitted",
3029
3528
  { messageId: id, toAgentId: recipient, mode },
3030
3529
  () => {
3031
- const attempt = this._requireAttemptLease(
3032
- run,
3033
- fromAttemptId,
3034
- leaseId,
3035
- fence,
3036
- );
3530
+ const source =
3531
+ systemSource == null
3532
+ ? null
3533
+ : safeIdentifier(systemSource, "systemSource");
3534
+ const attempt = source
3535
+ ? null
3536
+ : this._requireAttemptLease(run, fromAttemptId, leaseId, fence);
3037
3537
  const policy = validateDataPolicy(dataPolicy);
3038
3538
  const sink = `agent:${recipient}`;
3039
3539
  if (!sinkAllowed(policy, sink)) {
@@ -3067,9 +3567,12 @@ export class GraphKernel {
3067
3567
  const message = {
3068
3568
  id,
3069
3569
  runId: run.id,
3070
- fromAttemptId: attempt.id,
3071
- fromLeaseId: attempt.leaseId,
3072
- fromFence: attempt.fence,
3570
+ fromAttemptId: attempt?.id || null,
3571
+ fromLeaseId: attempt?.leaseId || null,
3572
+ fromFence: attempt?.fence ?? null,
3573
+ systemSource: source,
3574
+ sourceAuthorityGeneration: run.authority.authorityGeneration,
3575
+ sourceWriterId: run.authority.writerId,
3073
3576
  toAgentId: recipient,
3074
3577
  causationId,
3075
3578
  correlationId,
@@ -3165,6 +3668,16 @@ export class GraphKernel {
3165
3668
  if (run.messageConsumers.has(dedupKey)) {
3166
3669
  return Object.freeze(clone(run.messageConsumers.get(dedupKey)));
3167
3670
  }
3671
+ const priorConsumer = [...run.messageConsumers.values()].find(
3672
+ (receipt) => receipt.messageId === id && receipt.agentId === recipient,
3673
+ );
3674
+ if (priorConsumer) {
3675
+ throw kernelError(
3676
+ "CC_GRAPH_MESSAGE_CONSUMER_CONFLICT",
3677
+ "message was already processed by another consumer key",
3678
+ { consumerKey: priorConsumer.consumerKey },
3679
+ );
3680
+ }
3168
3681
  return this._transaction(
3169
3682
  run,
3170
3683
  "message.processed",
@@ -3244,13 +3757,33 @@ export class GraphKernel {
3244
3757
  ) {
3245
3758
  const run = this._run(runId);
3246
3759
  const id = safeIdentifier(handoffId, "handoffId");
3760
+ const requestDigest = graphDigest(
3761
+ {
3762
+ fromAttemptId,
3763
+ leaseId,
3764
+ fence,
3765
+ toAgentId,
3766
+ artifactIds,
3767
+ preconditions,
3768
+ ttlMs,
3769
+ },
3770
+ "cc.graph.handoff-request/v1",
3771
+ );
3247
3772
  return this._transaction(
3248
3773
  run,
3249
3774
  "handoff.offered",
3250
3775
  { handoffId: id, fromAttemptId, toAgentId },
3251
3776
  () => {
3252
3777
  const existing = run.handoffs.get(id);
3253
- if (existing) return Object.freeze(clone(existing));
3778
+ if (existing) {
3779
+ if (existing.requestDigest !== requestDigest) {
3780
+ throw kernelError(
3781
+ "CC_GRAPH_HANDOFF_ID_CONFLICT",
3782
+ "handoffId was reused with different custody content",
3783
+ );
3784
+ }
3785
+ return Object.freeze(clone(existing));
3786
+ }
3254
3787
  const attempt = this._requireAttemptLease(
3255
3788
  run,
3256
3789
  fromAttemptId,
@@ -3278,6 +3811,7 @@ export class GraphKernel {
3278
3811
  authorityDigest: run.authorityDigest,
3279
3812
  artifactIds: [...artifactIds],
3280
3813
  preconditions: clone(preconditions),
3814
+ requestDigest,
3281
3815
  status: "offered",
3282
3816
  createdAt: nowIso(() => now),
3283
3817
  updatedAt: nowIso(() => now),
@@ -3301,6 +3835,9 @@ export class GraphKernel {
3301
3835
  { handoffId: id, agentId: recipient },
3302
3836
  () => {
3303
3837
  const handoff = run.handoffs.get(id);
3838
+ if (handoff?.status === "accepted" && handoff.toAgentId === recipient) {
3839
+ return Object.freeze(clone(handoff));
3840
+ }
3304
3841
  if (
3305
3842
  !handoff ||
3306
3843
  handoff.status !== "offered" ||
@@ -3313,7 +3850,8 @@ export class GraphKernel {
3313
3850
  );
3314
3851
  }
3315
3852
  handoff.status = "accepted";
3316
- handoff.updatedAt = nowIso(this.now);
3853
+ handoff.acceptedAt = nowIso(this.now);
3854
+ handoff.updatedAt = handoff.acceptedAt;
3317
3855
  return Object.freeze(clone(handoff));
3318
3856
  },
3319
3857
  );
@@ -3329,6 +3867,9 @@ export class GraphKernel {
3329
3867
  { handoffId: id, reason },
3330
3868
  () => {
3331
3869
  const handoff = run.handoffs.get(id);
3870
+ if (handoff?.status === "rejected" && handoff.toAgentId === recipient) {
3871
+ return Object.freeze(clone(handoff));
3872
+ }
3332
3873
  if (
3333
3874
  !handoff ||
3334
3875
  handoff.status !== "offered" ||
@@ -3341,7 +3882,8 @@ export class GraphKernel {
3341
3882
  }
3342
3883
  handoff.status = "rejected";
3343
3884
  handoff.reason = String(reason).slice(0, 1024);
3344
- handoff.updatedAt = nowIso(this.now);
3885
+ handoff.rejectedAt = nowIso(this.now);
3886
+ handoff.updatedAt = handoff.rejectedAt;
3345
3887
  return Object.freeze(clone(handoff));
3346
3888
  },
3347
3889
  );
@@ -3364,6 +3906,21 @@ export class GraphKernel {
3364
3906
  { handoffId: id },
3365
3907
  () => {
3366
3908
  const handoff = run.handoffs.get(id);
3909
+ if (handoff?.status === "committed") {
3910
+ const assignmentAttempt = run.attempts.get(
3911
+ handoff.committedAttemptId,
3912
+ );
3913
+ if (!assignmentAttempt) {
3914
+ throw kernelError(
3915
+ "CC_GRAPH_HANDOFF_COMMIT_CORRUPT",
3916
+ "committed handoff is missing its assignment attempt",
3917
+ );
3918
+ }
3919
+ return Object.freeze({
3920
+ handoff: Object.freeze(clone(handoff)),
3921
+ assignmentAttempt: attemptProjection(assignmentAttempt),
3922
+ });
3923
+ }
3367
3924
  if (!handoff || handoff.status !== "accepted") {
3368
3925
  throw kernelError(
3369
3926
  "CC_GRAPH_HANDOFF_NOT_COMMITTABLE",
@@ -3407,8 +3964,12 @@ export class GraphKernel {
3407
3964
  attempt: attemptNumber,
3408
3965
  agentId: handoff.toAgentId,
3409
3966
  role: "executor",
3967
+ compensationForNodeId: sender.compensationForNodeId || null,
3968
+ effectIdempotencyKey: sender.effectIdempotencyKey || null,
3410
3969
  leaseId: safeIdentifier(leaseId, "leaseId"),
3411
3970
  fence,
3971
+ authorityGeneration: run.authority.authorityGeneration,
3972
+ writerId: run.authority.writerId,
3412
3973
  status: "active",
3413
3974
  participationStatus: "active",
3414
3975
  grant: clone(sender.grant),
@@ -3419,11 +3980,37 @@ export class GraphKernel {
3419
3980
  terminalEvidence: null,
3420
3981
  usage: null,
3421
3982
  };
3983
+ for (const effect of run.effects.values()) {
3984
+ if (effect.attemptId !== sender.id) continue;
3985
+ if (effect.status !== "started") {
3986
+ throw kernelError(
3987
+ "CC_GRAPH_HANDOFF_EFFECT_TERMINAL",
3988
+ "custody cannot transfer after the source effect became terminal",
3989
+ { effectId: effect.id, effectStatus: effect.status },
3990
+ );
3991
+ }
3992
+ effect.custodyHistory ||= [];
3993
+ effect.custodyHistory.push({
3994
+ fromAttemptId: sender.id,
3995
+ fromLeaseId: sender.leaseId,
3996
+ fromFence: sender.fence,
3997
+ toAttemptId: newAttempt.id,
3998
+ toLeaseId: newAttempt.leaseId,
3999
+ toFence: newAttempt.fence,
4000
+ handoffId: handoff.id,
4001
+ transferredAt: nowIso(() => now),
4002
+ });
4003
+ effect.attemptId = newAttempt.id;
4004
+ effect.leaseId = newAttempt.leaseId;
4005
+ effect.fence = newAttempt.fence;
4006
+ effect.updatedAt = nowIso(() => now);
4007
+ }
3422
4008
  run.attempts.set(newAttempt.id, newAttempt);
3423
4009
  run.nodeStates.get(sender.nodeId).attemptIds.push(newAttempt.id);
3424
4010
  handoff.status = "committed";
3425
4011
  handoff.committedAttemptId = newAttempt.id;
3426
- handoff.updatedAt = nowIso(() => now);
4012
+ handoff.committedAt = nowIso(() => now);
4013
+ handoff.updatedAt = handoff.committedAt;
3427
4014
  return Object.freeze({
3428
4015
  handoff: Object.freeze(clone(handoff)),
3429
4016
  assignmentAttempt: attemptProjection(newAttempt),
@@ -3432,11 +4019,60 @@ export class GraphKernel {
3432
4019
  );
3433
4020
  }
3434
4021
 
4022
+ expireHandoffForRecovery(
4023
+ runId,
4024
+ handoffId,
4025
+ { reason = "source writer generation was superseded" } = {},
4026
+ ) {
4027
+ const run = this._run(runId);
4028
+ const id = safeIdentifier(handoffId, "handoffId");
4029
+ return this._transaction(
4030
+ run,
4031
+ "handoff.expired",
4032
+ { handoffId: id, reason },
4033
+ () => {
4034
+ const handoff = run.handoffs.get(id);
4035
+ if (handoff?.status === "expired") {
4036
+ return Object.freeze(clone(handoff));
4037
+ }
4038
+ if (!handoff || !["offered", "accepted"].includes(handoff.status)) {
4039
+ throw kernelError(
4040
+ "CC_GRAPH_HANDOFF_NOT_RECOVERY_EXPIRABLE",
4041
+ "only an open handoff can expire during writer recovery",
4042
+ );
4043
+ }
4044
+ const source = run.attempts.get(handoff.fromAttemptId);
4045
+ if (
4046
+ !source ||
4047
+ Number(source.authorityGeneration || 0) >=
4048
+ run.authority.authorityGeneration
4049
+ ) {
4050
+ throw kernelError(
4051
+ "CC_GRAPH_HANDOFF_RECOVERY_AUTHORITY_INVALID",
4052
+ "handoff recovery requires a source from an older authority generation",
4053
+ );
4054
+ }
4055
+ const now = this.now();
4056
+ handoff.status = "expired";
4057
+ handoff.reason = String(reason).slice(0, 1024);
4058
+ handoff.expiredAt = nowIso(() => now);
4059
+ handoff.updatedAt = nowIso(() => now);
4060
+ return Object.freeze(clone(handoff));
4061
+ },
4062
+ {
4063
+ idempotencyKey: `handoff-recovery-expire:${id}:${run.authority.authorityGeneration}`,
4064
+ },
4065
+ );
4066
+ }
4067
+
3435
4068
  revokeHandoff(runId, handoffId, fromAttemptId, leaseId, fence) {
3436
4069
  const run = this._run(runId);
3437
4070
  const id = safeIdentifier(handoffId, "handoffId");
3438
4071
  return this._transaction(run, "handoff.revoked", { handoffId: id }, () => {
3439
4072
  const handoff = run.handoffs.get(id);
4073
+ if (handoff?.status === "revoked") {
4074
+ return Object.freeze(clone(handoff));
4075
+ }
3440
4076
  if (!handoff || !["offered", "accepted"].includes(handoff.status)) {
3441
4077
  throw kernelError(
3442
4078
  "CC_GRAPH_HANDOFF_NOT_REVOKABLE",
@@ -3451,7 +4087,8 @@ export class GraphKernel {
3451
4087
  );
3452
4088
  }
3453
4089
  handoff.status = "revoked";
3454
- handoff.updatedAt = nowIso(this.now);
4090
+ handoff.revokedAt = nowIso(this.now);
4091
+ handoff.updatedAt = handoff.revokedAt;
3455
4092
  return Object.freeze(clone(handoff));
3456
4093
  });
3457
4094
  }