chainlesschain 0.166.0 → 0.166.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-D5_pwQ7M.js → AIOps-BnZ24WRk.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DgoYO32D.js → ActionButton-ivXY7MGb.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BIpoZ4QH.js → Analytics-BQS4OD-k.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BJ3XrCEe.js → AppLayout-wOBuOKTO.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-DUFs_izZ.js → Artifacts-BYuC4nw-.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-BZdPwwfL.js → Audit-BIeVfBt6.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DK9Fn-8B.js → BackgroundAgents-hIuyhmJ7.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Bd6bOhV_.js → Backup-B6ohdgPQ.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CRnWlXrd.js → BaseInput-BzgYCw6h.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-D1G_yEl6.js → Chat-CwahvZU1.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DDAlEtm-.js → ChatBubbleRenderer-B-1IZ_k8.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-CP35SH7R.js → Checkbox-26EDP2x0.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-DugeL4pj.js → Codegen-DYLw1Zvd.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DPuJsvKg.js → Col-DH_zAvD-.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CZqNjNNp.js → Community-DURrTXJu.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-eeyIPEAb.js → Compact-CQyBqN0p.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-MGzTw87k.js → Compliance-a0DfgKHD.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-D3goVIA5.js → Cowork-C6fY_5Ke.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-9HA8hLLG.js → Cron-DOwmpbTe.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DCSOX3l6.js → Crosschain-DEPOIEUs.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-ji3lN5Bc.js → DID-DaKc-CEV.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-DlZvF1E5.js → Dashboard-C-0KLXq5.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-2FyZcow9.js → Dropdown-CnsDukzr.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-qH2bSpSY.js → EmailListRenderer-Bi_ACD4N.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bfx47wZj.js → FamilyGuardDashboard-BZASpnx3.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-DhGcZbIv.js → Federation-43oTtzyp.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-BMgi9_YQ.js → FormItemContext-oyVadnkZ.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-DugMac-4.js → GenericCardRenderer-Cwvfd6TZ.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-D943BpH-.js → Git-Cgi2wvU0.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DZ66a8Aa.js → Governance-l8TWDKZ0.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-B00Mp2tN.js → Inference-Dc8j6TaO.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-BdwXXO2Q.js → KnowledgeGraph-DgcO-Fwz.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-_MVXyYWc.js → Logs-DjiZpiwa.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-hOTeNyi7.js → MarkdownRenderer-B5MFCi0N.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-CxeHRsnO.js → Marketplace-BeDn1hFc.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-BOieTxZ4.js → McpTools-PGdVfWK6.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-BQD26CM3.js → Memory-e76lY3a1.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-CPJ8ZlvR.js → MobileBridge-DKlQqot_.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-D7qRVu0y.js → MobileProjects-C4g6WOEr.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BOyndGuk.js → Mtc-1j3NpRQq.js} +4 -4
  42. package/src/assets/web-panel/assets/{MtcAudit-CXQ_8ZoR.js → MtcAudit-DgwiVd1C.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-32Oh9KbH.js → Multisig-Dzg5lZZY.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CFAd00CX.js → NLProgramming-D7GYhst-.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-BBCY_qJA.js → Notes-Blcfvaic.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-uaQ0PdmW.js → NotificationSettings-Dc4Y6vfr.js} +1 -1
  47. package/src/assets/web-panel/assets/OrderTableRenderer-dgM87XIn.js +1 -0
  48. package/src/assets/web-panel/assets/{Organization-CmFn7Oeo.js → Organization-DBXq024L.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-R_avat3a.js → Overflow-CFPbdx1Y.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-BIhLFmkY.js → P2P-w7z61NR5.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-DTJjk-_i.js → PdhVaultBrowser-B7eGGZOa.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-CkzYv8Yb.js → Permissions-Df5Cl50D.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub--0QvN2Nz.js → PersonalDataHub-C-We7RF6.js} +5 -5
  54. package/src/assets/web-panel/assets/{Pipeline-Bfw5gQCH.js → Pipeline-B6fHPnKy.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-DicjKfax.js → Privacy-B85BdiIc.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-DUl_ykBp.js → ProjectInit-CcOyazSe.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-CPYDvN8a.js → ProjectSettings-BE53KfuU.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-DnZ9_fAr.js → Projects-DOvMOoe_.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-BX1jP31w.js → Providers-5l7sT9lq.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-COab5qUl.js → QrScannerModal-DE37RXy4.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CdKQ_JZ0.js → QuickAsk-BGmGCo7j.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BPFHg8pK.js → Recommend-BFdx8Nrm.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-CqrfTJvO.js → RemoteSession-C1w-j_hB.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DM-l8Pb3.js → Reputation-BOvn-10y.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-CKGBTeCw.js → Row-db-a1YO9.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-Wb0YHIkc.js → RssFeed-DeS43ZGM.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DiBjxKv-.js → Search-BaQYNfeb.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-BAVEmbWP.js → Security-yt1ESHDg.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-A_a3TIRn.js → Services-BcNqPUsS.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-CloHJ-I7.js → Skeleton-X3ceUQWf.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CkvV5hKa.js → Skills-DmYeax6C.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-BYozxxsV.js → Sla-C_fdfbuJ.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-CWianiZF.js → SpeechSettings-CDqohXXQ.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-BR4G75do.js → SyncSettings-B25maYEY.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-D6gyUVrM.js → Tasks-3ILwauYw.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-DbfYIRzT.js → Templates-DUH0iuc4.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-BHutvukT.js → Tenant-DrvhHf9T.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-CYEiy2wJ.js → Terminal-C2lJVYHA.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-D9s1AxfE.js → TimelineRenderer-C1Hf_jYn.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-ij9vxnoy.js → Tokens-DniS1NpS.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-Dq_DCVeQ.js → Trigger-4jrcb8oA.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-Bs6wdfoH.js → Trust-DiBte55w.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-CLazyr5t.js → UkeySign-CZRtc4VB.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-viWotIHA.js → VideoEditing-BtYGJDmg.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-DlZ-A78d.js → Wallet-D1nrTm7H.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DSmOND3Y.js → WebAuthn-DI91acpx.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-BempLOJi.js → WorkflowEditor-BtQjI50d.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CyJzf4JI.js → chat-I4A-f4FC.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-C62q7WrL.js → colors-QZkDkFVt.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-7dYM1KW3.js → compact-item--FtCdzpr.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-CYTy7z2Q.js → createContext-CaIKIKxP.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-D7qVrkrO.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DHoclUqm.js → hasIn-DLYUII6-.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CW86F2Zw.js → index-0DYBQ-6e.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-C2efftJb.js → index-21I79XSf.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-4gWv-Ubg.js → index-3rVtgqa2.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-QXtrdrub.js → index-B65QxJaG.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BMxvWauP.js → index-BGWSmE3G.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Dyq9tK5u.js → index-BLGJmgmx.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DYKwGrAn.js → index-BcgtvJbC.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BMRmxFU_.js → index-BebTT7iR.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-XrsAHHQN.js → index-BkJgWW_L.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-2C8Xx2UO.js → index-Bs0qPPFb.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CnEZ4ZHj.js → index-BvdwNnHC.js} +3 -3
  105. package/src/assets/web-panel/assets/{index-DkczsbiP.js → index-BxPoPofq.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DvEtHpLc.js → index-C3PnIEOH.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DK84lAS0.js → index-CBtII-nl.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DKdv8cju.js → index-CL4c5D6G.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CjeViaU-.js → index-CQ7OBHka.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DhJjifZe.js → index-CXaeZCRP.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BghdJ87W.js → index-CgMm85BY.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-lHfNxiB5.js → index-Ch0IFpGy.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DJzZaPlb.js → index-CisYjy88.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DghnbMlY.js → index-D2xETCkj.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C5YWYlW9.js → index-D4CXFU-w.js} +1 -1
  116. package/src/assets/web-panel/assets/index-D9Y_ZwZn.js +1 -0
  117. package/src/assets/web-panel/assets/{index-LK1MZoBj.js → index-DA_TPdzX.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DOFAwpUK.js → index-DAuf8r5o.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DA-_807C.js → index-DGLHSCHM.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-9uR7rc9a.js → index-DJrd_CR3.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CVTnruDt.js → index-DK2r6Bv7.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-ZXYp76IB.js → index-DVitI3UQ.js} +1 -1
  123. package/src/assets/web-panel/assets/index-D_cPes4G.js +1 -0
  124. package/src/assets/web-panel/assets/{index-Bzz0aJ86.js → index-Deg2S9ZJ.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DHgSRimK.js → index-Du2wUavy.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-94ktimZG.js → index-OQPIS_am.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-3pI7AfmD.js → index-Ooo_5jHo.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DNLD3nZY.js → index-Ygi2rYL1.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CZRBvN6i.js → index-eTqL91aV.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-D6BC4mmz.js → index-hfiZ-i2b.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DAYanuSk.js → index-vfiDxdN2.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-NuNCstRO.js → index-yayaayEA.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-CwCl5sFQ.js → initDefaultProps-DDkTvw8k.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BFJFy70N.js → motion-fkr96tN5.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-ByrtVGq8.js → move-CGBV0SV2.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-BFiH1mT3.js → mtc-parser-DAsCliFt.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-bdvgpivj.js → omit-BM8PMmKN.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BlfuuE3s.js → pickAttrs-C6SdWoTK.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-t2SC3DMy.js → placementArrow-BG0EVu-d.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-CoEXv2Li.js → responsiveObserve-L10IvfYC.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BfXCf0Fu.js → slide-TddIkbrn.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-DNsdyOuS.js → statusUtils-FlveF2Yn.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-9aHukmoG.js → styleChecker-BrXRyebg.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-dTkPxzHN.js → useFlexGapSupport-B7_oiDXZ.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DINwfItP.js → useFs-CQYeRYWb.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-Bj_RriBV.js → usePersonalDataHub-DspIkhVi.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-BzRAZrTc.js → vnode-BCwmwMAW.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-CamPjkeK.js → zoom-B0cEGluV.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/team.js +74 -2
  151. package/src/data/changelog.json +26 -0
  152. package/src/generated/cc-agent-protocol.schema.json +49 -0
  153. package/src/lib/agent-team/team-mailbox.js +508 -2
  154. package/src/lib/agent-team/team-message-bridge.js +644 -0
  155. package/src/lib/agent-team/team-message-tools.js +198 -0
  156. package/src/lib/agent-team/team-runner.js +57 -3
  157. package/src/lib/graph-kernel/compiler.js +548 -16
  158. package/src/lib/graph-kernel/runtime.js +1158 -30
  159. package/src/lib/graph-kernel/trace-reducer.js +101 -7
  160. package/src/lib/subagent-inheritance.js +5 -3
  161. package/src/runtime/agent-core.js +38 -0
  162. package/src/runtime/headless-runner.js +36 -3
  163. package/src/assets/web-panel/assets/OrderTableRenderer-kpBxJ-SG.js +0 -1
  164. package/src/assets/web-panel/assets/devWarning-BdTg_AMF.js +0 -1
  165. package/src/assets/web-panel/assets/index-B6WvFV4N.js +0 -1
  166. package/src/assets/web-panel/assets/index-BKgfptzA.js +0 -1
@@ -10,7 +10,10 @@ const graphProtocolSchema = JSON.parse(
10
10
  );
11
11
 
12
12
  export const GRAPH_DEFINITION_VERSION = 1;
13
- export const GRAPH_DEFINITION_MIN_VERSION = 1;
13
+ export const GRAPH_DEFINITION_MIN_VERSION = Math.max(
14
+ 0,
15
+ GRAPH_DEFINITION_VERSION - 1,
16
+ );
14
17
  export const GRAPH_COMPILE_ERROR = "CC_GRAPH_COMPILE_FAILED";
15
18
 
16
19
  const compiledGraphs = new WeakSet();
@@ -272,7 +275,14 @@ export function writeScopesOverlap(left, right) {
272
275
  return a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
273
276
  }
274
277
 
275
- function loopMultipliers(definition, diagnostics, nodeIds) {
278
+ function loopMultipliers(
279
+ definition,
280
+ diagnostics,
281
+ nodeIds,
282
+ nodes,
283
+ dependencies,
284
+ compensationByNode,
285
+ ) {
276
286
  const multiplier = new Map([...nodeIds].map((id) => [id, 1]));
277
287
  const membership = new Map();
278
288
  for (const [index, loop] of definition.loops.entries()) {
@@ -300,6 +310,27 @@ function loopMultipliers(definition, diagnostics, nodeIds) {
300
310
  owners.push(loop.id);
301
311
  membership.set(nodeId, owners);
302
312
  multiplier.set(nodeId, multiplier.get(nodeId) * loop.maxIterations);
313
+ const node = nodes.get(nodeId);
314
+ if (
315
+ node &&
316
+ !["none", "read"].includes(node.effectClass || "none") &&
317
+ !compensationByNode.has(nodeId)
318
+ ) {
319
+ diagnostic(
320
+ diagnostics,
321
+ "GRAPH_LOOP_EFFECT_COMPENSATION_REQUIRED",
322
+ `#/loops/${index}/nodeIds`,
323
+ `effectful loop node requires an isolated compensation handler: ${nodeId}`,
324
+ );
325
+ }
326
+ if (node?.kind === "subgraph") {
327
+ diagnostic(
328
+ diagnostics,
329
+ "GRAPH_LOOP_SUBGRAPH_UNSUPPORTED",
330
+ `#/loops/${index}/nodeIds`,
331
+ `subgraph calls cannot be nested inside a loop region: ${nodeId}`,
332
+ );
333
+ }
303
334
  }
304
335
  if (
305
336
  !loop.nodeIds.includes(loop.entryNodeId) ||
@@ -312,6 +343,67 @@ function loopMultipliers(definition, diagnostics, nodeIds) {
312
343
  "loop entry and exit must be members of the region",
313
344
  );
314
345
  }
346
+ const regionNodeIds = new Set(loop.nodeIds);
347
+ const regionOutgoing = new Map(
348
+ loop.nodeIds.map((nodeId) => [nodeId, new Set()]),
349
+ );
350
+ for (const nodeId of loop.nodeIds) {
351
+ for (const dependency of dependencies.get(nodeId) || []) {
352
+ if (regionNodeIds.has(dependency)) {
353
+ regionOutgoing.get(dependency).add(nodeId);
354
+ } else if (nodeId !== loop.entryNodeId) {
355
+ diagnostic(
356
+ diagnostics,
357
+ "GRAPH_LOOP_ENTRY_LEAK",
358
+ `#/loops/${index}/entryNodeId`,
359
+ `external dependencies may only enter a loop through its entry node: ${nodeId} depends on ${dependency}`,
360
+ );
361
+ }
362
+ }
363
+ }
364
+ const canReach = (source, target) => {
365
+ const pending = [source];
366
+ const seen = new Set();
367
+ while (pending.length) {
368
+ const current = pending.pop();
369
+ if (current === target) return true;
370
+ if (seen.has(current)) continue;
371
+ seen.add(current);
372
+ pending.push(...(regionOutgoing.get(current) || []));
373
+ }
374
+ return false;
375
+ };
376
+ for (const nodeId of loop.nodeIds) {
377
+ if (!canReach(loop.entryNodeId, nodeId)) {
378
+ diagnostic(
379
+ diagnostics,
380
+ "GRAPH_LOOP_NODE_UNREACHABLE_FROM_ENTRY",
381
+ `#/loops/${index}/nodeIds`,
382
+ `loop node is unreachable from its entry: ${nodeId}`,
383
+ );
384
+ }
385
+ if (!canReach(nodeId, loop.exitNodeId)) {
386
+ diagnostic(
387
+ diagnostics,
388
+ "GRAPH_LOOP_NODE_CANNOT_REACH_EXIT",
389
+ `#/loops/${index}/nodeIds`,
390
+ `loop node cannot reach its exit: ${nodeId}`,
391
+ );
392
+ }
393
+ }
394
+ for (const node of definition.nodes) {
395
+ if (regionNodeIds.has(node.id)) continue;
396
+ for (const dependency of dependencies.get(node.id) || []) {
397
+ if (regionNodeIds.has(dependency) && dependency !== loop.exitNodeId) {
398
+ diagnostic(
399
+ diagnostics,
400
+ "GRAPH_LOOP_EXIT_LEAK",
401
+ `#/loops/${index}/exitNodeId`,
402
+ `nodes outside a loop may only depend on its exit node: ${node.id} depends on ${dependency}`,
403
+ );
404
+ }
405
+ }
406
+ }
315
407
  }
316
408
  for (const [nodeId, owners] of membership) {
317
409
  if (owners.length > 1) {
@@ -327,12 +419,19 @@ function loopMultipliers(definition, diagnostics, nodeIds) {
327
419
  return multiplier;
328
420
  }
329
421
 
330
- function budgetUpperBound(definition, multiplier, diagnostics) {
422
+ function budgetUpperBound(
423
+ definition,
424
+ multiplier,
425
+ diagnostics,
426
+ subgraphCalls = new Map(),
427
+ ) {
331
428
  const total = Object.fromEntries(BUDGET_FIELDS.map((field) => [field, 0]));
332
429
  for (const node of definition.nodes) {
430
+ const subgraphBudget = subgraphCalls.get(node.id)?.budget;
333
431
  for (const field of BUDGET_FIELDS) {
334
432
  total[field] +=
335
- Number(node.budget?.[field] || 0) * multiplier.get(node.id);
433
+ Number(subgraphBudget?.[field] ?? node.budget?.[field] ?? 0) *
434
+ multiplier.get(node.id);
336
435
  }
337
436
  }
338
437
  for (const field of BUDGET_FIELDS) {
@@ -350,9 +449,172 @@ function budgetUpperBound(definition, multiplier, diagnostics) {
350
449
  return total;
351
450
  }
352
451
 
452
+ function subgraphTarget(registry, definitionId) {
453
+ return registry instanceof Map
454
+ ? registry.get(definitionId)
455
+ : registry[definitionId];
456
+ }
457
+
458
+ function requiredPortNames(ports) {
459
+ return (ports || [])
460
+ .filter((port) => port.required !== false)
461
+ .map((port) => port.name);
462
+ }
463
+
464
+ function validateSubgraphPortBindings(
465
+ call,
466
+ callNode,
467
+ targetDefinition,
468
+ callIndex,
469
+ diagnostics,
470
+ ) {
471
+ const parentInputs = new Map(
472
+ (callNode?.inputs || []).map((port) => [port.name, port]),
473
+ );
474
+ const parentOutputs = new Map(
475
+ (callNode?.outputs || []).map((port) => [port.name, port]),
476
+ );
477
+ const childNodes = new Map(
478
+ (targetDefinition?.nodes || []).map((node) => [node.id, node]),
479
+ );
480
+ const mappedInputs = new Set();
481
+ const mappedChildInputs = new Set();
482
+ const mappedOutputs = new Set();
483
+
484
+ const validateBindings = (direction) => {
485
+ const bindings = call[`${direction}Bindings`] || [];
486
+ for (const [bindingIndex, binding] of bindings.entries()) {
487
+ const path = `#/subgraphCalls/${callIndex}/${direction}Bindings/${bindingIndex}`;
488
+ const parentPorts = direction === "input" ? parentInputs : parentOutputs;
489
+ const parentPort = parentPorts.get(binding.parentPort);
490
+ const childNode = childNodes.get(binding.childNodeId);
491
+ const childPorts = new Map(
492
+ (childNode?.[direction === "input" ? "inputs" : "outputs"] || []).map(
493
+ (port) => [port.name, port],
494
+ ),
495
+ );
496
+ const childPort = childPorts.get(binding.childPort);
497
+ const parentSet = direction === "input" ? mappedInputs : mappedOutputs;
498
+ if (parentSet.has(binding.parentPort)) {
499
+ diagnostic(
500
+ diagnostics,
501
+ "GRAPH_SUBGRAPH_DUPLICATE_PARENT_PORT",
502
+ `${path}/parentPort`,
503
+ `subgraph ${direction} port is mapped more than once: ${binding.parentPort}`,
504
+ );
505
+ }
506
+ parentSet.add(binding.parentPort);
507
+ if (!parentPort) {
508
+ diagnostic(
509
+ diagnostics,
510
+ "GRAPH_SUBGRAPH_UNKNOWN_PARENT_PORT",
511
+ `${path}/parentPort`,
512
+ `unknown parent ${direction} port: ${binding.parentPort}`,
513
+ );
514
+ }
515
+ if (!childNode) {
516
+ diagnostic(
517
+ diagnostics,
518
+ "GRAPH_SUBGRAPH_UNKNOWN_CHILD_NODE",
519
+ `${path}/childNodeId`,
520
+ `unknown child node: ${binding.childNodeId}`,
521
+ );
522
+ } else if (!childPort) {
523
+ diagnostic(
524
+ diagnostics,
525
+ "GRAPH_SUBGRAPH_UNKNOWN_CHILD_PORT",
526
+ `${path}/childPort`,
527
+ `unknown child ${direction} port: ${binding.childNodeId}.${binding.childPort}`,
528
+ );
529
+ }
530
+ if (direction === "input") {
531
+ const childKey = `${binding.childNodeId}\0${binding.childPort}`;
532
+ if (mappedChildInputs.has(childKey)) {
533
+ diagnostic(
534
+ diagnostics,
535
+ "GRAPH_SUBGRAPH_DUPLICATE_CHILD_INPUT",
536
+ path,
537
+ `child input is mapped more than once: ${binding.childNodeId}.${binding.childPort}`,
538
+ );
539
+ }
540
+ mappedChildInputs.add(childKey);
541
+ }
542
+ const producer = direction === "input" ? parentPort : childPort;
543
+ const consumer = direction === "input" ? childPort : parentPort;
544
+ if (
545
+ producer &&
546
+ consumer &&
547
+ !isPortSchemaAssignable(producer.schema, consumer.schema)
548
+ ) {
549
+ diagnostic(
550
+ diagnostics,
551
+ "GRAPH_SUBGRAPH_PORT_TYPE_MISMATCH",
552
+ path,
553
+ direction === "input"
554
+ ? `${callNode.id}.${binding.parentPort} is not assignable to ${binding.childNodeId}.${binding.childPort}`
555
+ : `${binding.childNodeId}.${binding.childPort} is not assignable to ${callNode.id}.${binding.parentPort}`,
556
+ );
557
+ }
558
+ }
559
+ };
560
+
561
+ validateBindings("input");
562
+ validateBindings("output");
563
+
564
+ for (const portName of requiredPortNames(callNode?.inputs)) {
565
+ if (!mappedInputs.has(portName)) {
566
+ diagnostic(
567
+ diagnostics,
568
+ "GRAPH_SUBGRAPH_REQUIRED_INPUT_UNMAPPED",
569
+ `#/subgraphCalls/${callIndex}/inputBindings`,
570
+ `required parent input is not mapped into the child graph: ${portName}`,
571
+ );
572
+ }
573
+ }
574
+ for (const portName of requiredPortNames(callNode?.outputs)) {
575
+ if (!mappedOutputs.has(portName)) {
576
+ diagnostic(
577
+ diagnostics,
578
+ "GRAPH_SUBGRAPH_REQUIRED_OUTPUT_UNMAPPED",
579
+ `#/subgraphCalls/${callIndex}/outputBindings`,
580
+ `required parent output is not mapped from the child graph: ${portName}`,
581
+ );
582
+ }
583
+ }
584
+
585
+ const internallyBoundInputs = new Set(
586
+ (targetDefinition?.edges || [])
587
+ .filter((edge) => edge.kind === "data")
588
+ .map((edge) => `${edge.to}\0${edge.toPort}`),
589
+ );
590
+ for (const childNode of childNodes.values()) {
591
+ for (const portName of requiredPortNames(childNode.inputs)) {
592
+ const key = `${childNode.id}\0${portName}`;
593
+ if (
594
+ !mappedChildInputs.has(key) &&
595
+ !internallyBoundInputs.has(key) &&
596
+ !Object.hasOwn(childNode.inputBindings || {}, portName)
597
+ ) {
598
+ diagnostic(
599
+ diagnostics,
600
+ "GRAPH_SUBGRAPH_REQUIRED_CHILD_INPUT_UNBOUND",
601
+ `#/subgraphCalls/${callIndex}/inputBindings`,
602
+ `required child input has no internal or parent binding: ${childNode.id}.${portName}`,
603
+ );
604
+ }
605
+ }
606
+ }
607
+ }
608
+
353
609
  function validateSubgraphs(definition, nodeIds, options, diagnostics) {
354
610
  const registry = options.subgraphs || new Map();
355
611
  const calls = new Map();
612
+ const registryDefinitions = new Map();
613
+ const registryEntries =
614
+ registry instanceof Map ? registry.entries() : Object.entries(registry);
615
+ for (const [id, value] of registryEntries) {
616
+ registryDefinitions.set(id, stableValue(value?.definition || value));
617
+ }
356
618
  for (const [index, call] of definition.subgraphCalls.entries()) {
357
619
  if (!nodeIds.has(call.nodeId)) {
358
620
  diagnostic(
@@ -362,10 +624,24 @@ function validateSubgraphs(definition, nodeIds, options, diagnostics) {
362
624
  `subgraph call node does not exist: ${call.nodeId}`,
363
625
  );
364
626
  }
365
- const target =
366
- registry instanceof Map
367
- ? registry.get(call.definitionId)
368
- : registry[call.definitionId];
627
+ const callNode = definition.nodes.find((node) => node.id === call.nodeId);
628
+ if (callNode && callNode.kind !== "subgraph") {
629
+ diagnostic(
630
+ diagnostics,
631
+ "GRAPH_SUBGRAPH_NODE_KIND_INVALID",
632
+ `#/subgraphCalls/${index}/nodeId`,
633
+ `subgraph call must target a node with kind=subgraph: ${call.nodeId}`,
634
+ );
635
+ }
636
+ const target = subgraphTarget(registry, call.definitionId);
637
+ if (!target && options.allowUnresolvedSubgraphs !== true) {
638
+ diagnostic(
639
+ diagnostics,
640
+ "GRAPH_SUBGRAPH_DEFINITION_REQUIRED",
641
+ `#/subgraphCalls/${index}/definitionId`,
642
+ `subgraph definition must be available during compilation: ${call.definitionId}`,
643
+ );
644
+ }
369
645
  if (target && target.revisionDigest !== call.revisionDigest) {
370
646
  diagnostic(
371
647
  diagnostics,
@@ -374,7 +650,44 @@ function validateSubgraphs(definition, nodeIds, options, diagnostics) {
374
650
  `subgraph digest does not match pinned revision: ${call.definitionId}`,
375
651
  );
376
652
  }
377
- calls.set(call.nodeId, call);
653
+ const targetDefinition = target?.definition || target;
654
+ if (targetDefinition) {
655
+ validateSubgraphPortBindings(
656
+ call,
657
+ callNode,
658
+ targetDefinition,
659
+ index,
660
+ diagnostics,
661
+ );
662
+ }
663
+ const targetBudget = target?.budgetUpperBound || targetDefinition?.budget;
664
+ const budget = stableValue(
665
+ call.budget || callNode?.budget || targetDefinition?.budget || {},
666
+ );
667
+ for (const field of BUDGET_FIELDS) {
668
+ const required = Number(targetBudget?.[field] || 0);
669
+ const cap = budget[field];
670
+ if (required > 0 && (cap == null || Number(cap) < required)) {
671
+ diagnostic(
672
+ diagnostics,
673
+ "GRAPH_SUBGRAPH_BUDGET_SLICE_TOO_SMALL",
674
+ `#/subgraphCalls/${index}/budget/${field}`,
675
+ `subgraph ${field} budget slice ${cap ?? "unbounded"} is below the child upper bound ${required}`,
676
+ { field, required, cap: cap ?? null },
677
+ );
678
+ }
679
+ }
680
+ calls.set(call.nodeId, { ...call, budget });
681
+ }
682
+ for (const [index, node] of definition.nodes.entries()) {
683
+ if (node.kind === "subgraph" && !calls.has(node.id)) {
684
+ diagnostic(
685
+ diagnostics,
686
+ "GRAPH_SUBGRAPH_CALL_REQUIRED",
687
+ `#/nodes/${index}`,
688
+ `subgraph node requires a pinned SubgraphCall: ${node.id}`,
689
+ );
690
+ }
378
691
  }
379
692
 
380
693
  const definitions = new Map();
@@ -411,15 +724,51 @@ function validateSubgraphs(definition, nodeIds, options, diagnostics) {
411
724
  visited.add(id);
412
725
  };
413
726
  visit(definition.id);
414
- return calls;
727
+ return { calls, definitions: registryDefinitions };
728
+ }
729
+
730
+ function upcastGraphDefinitionV0(input) {
731
+ const definition = stableValue(input);
732
+ return {
733
+ schemaVersion: 1,
734
+ id: definition.id,
735
+ revision: definition.revision ?? 1,
736
+ triggers: definition.triggers || [],
737
+ regions: definition.regions || [],
738
+ nodes: (definition.nodes || []).map((node) => ({
739
+ ...node,
740
+ kind: node.kind || "task",
741
+ dependsOn: node.dependsOn || [],
742
+ inputs: node.inputs || [],
743
+ outputs: node.outputs || [],
744
+ effectClass: node.effectClass || "none",
745
+ })),
746
+ edges: (definition.edges || []).map((edge, index) => ({
747
+ ...edge,
748
+ id: edge.id || `edge-${index + 1}`,
749
+ kind: edge.kind || "control",
750
+ when: edge.when || "success",
751
+ })),
752
+ loops: definition.loops || [],
753
+ subgraphCalls: definition.subgraphCalls || [],
754
+ budget: definition.budget || {},
755
+ allowedCapabilities: definition.allowedCapabilities || [],
756
+ metadata: definition.metadata || {},
757
+ };
415
758
  }
416
759
 
760
+ const BUILTIN_GRAPH_UPCASTERS = Object.freeze({
761
+ 0: upcastGraphDefinitionV0,
762
+ });
763
+
417
764
  function migrateDefinition(input, options) {
418
765
  const sourceVersion = Number(input?.schemaVersion);
419
766
  if (sourceVersion === GRAPH_DEFINITION_VERSION) {
420
767
  return { definition: stableValue(input), migratedFrom: null };
421
768
  }
422
- const upcaster = options.upcasters?.[sourceVersion];
769
+ const upcaster =
770
+ options.upcasters?.[sourceVersion] ||
771
+ BUILTIN_GRAPH_UPCASTERS[sourceVersion];
423
772
  if (
424
773
  sourceVersion === GRAPH_DEFINITION_VERSION - 1 &&
425
774
  typeof upcaster === "function"
@@ -518,6 +867,118 @@ export function compileGraphDefinition(input, options = {}) {
518
867
  }
519
868
  const nodes = new Map(definition.nodes.map((node) => [node.id, node]));
520
869
  const nodeIds = new Set(nodes.keys());
870
+ const compensationTargets = new Map();
871
+ const declareCompensation = (sourceId, targetId) => {
872
+ if (!sourceId || !targetId) return;
873
+ const targets = compensationTargets.get(sourceId) || new Set();
874
+ targets.add(targetId);
875
+ compensationTargets.set(sourceId, targets);
876
+ };
877
+ for (const node of definition.nodes) {
878
+ if (node.compensationNodeId) {
879
+ declareCompensation(node.id, node.compensationNodeId);
880
+ }
881
+ }
882
+ for (const edge of definition.edges) {
883
+ if (edge.kind === "compensation") {
884
+ declareCompensation(edge.from, edge.to);
885
+ }
886
+ }
887
+ const compensationByNode = new Map();
888
+ const compensationOwners = new Map();
889
+ for (const [sourceId, targets] of compensationTargets) {
890
+ if (targets.size !== 1) {
891
+ diagnostic(
892
+ diagnostics,
893
+ "GRAPH_COMPENSATION_TARGET_CONFLICT",
894
+ "#/nodes",
895
+ `effect node declares multiple compensation targets: ${sourceId}`,
896
+ { sourceId, targetIds: [...targets].sort() },
897
+ );
898
+ continue;
899
+ }
900
+ const [targetId] = targets;
901
+ const source = nodes.get(sourceId);
902
+ const target = nodes.get(targetId);
903
+ if (!target) {
904
+ diagnostic(
905
+ diagnostics,
906
+ "GRAPH_UNKNOWN_COMPENSATION_TARGET",
907
+ "#/nodes",
908
+ `unknown compensation target ${targetId} for ${sourceId}`,
909
+ { sourceId, targetId },
910
+ );
911
+ }
912
+ if (!source || !target) continue;
913
+ if (sourceId === targetId) {
914
+ diagnostic(
915
+ diagnostics,
916
+ "GRAPH_COMPENSATION_SELF_REFERENCE",
917
+ "#/nodes",
918
+ `node cannot compensate itself: ${sourceId}`,
919
+ );
920
+ }
921
+ if (!["workspace_write", "external"].includes(source.effectClass)) {
922
+ diagnostic(
923
+ diagnostics,
924
+ "GRAPH_COMPENSATION_SOURCE_NOT_EFFECTFUL",
925
+ "#/nodes",
926
+ `compensation source must be effectful: ${sourceId}`,
927
+ );
928
+ }
929
+ if (!["workspace_write", "external"].includes(target.effectClass)) {
930
+ diagnostic(
931
+ diagnostics,
932
+ "GRAPH_COMPENSATION_TARGET_NOT_EFFECTFUL",
933
+ "#/nodes",
934
+ `compensation target must be effectful: ${targetId}`,
935
+ );
936
+ }
937
+ const priorOwner = compensationOwners.get(targetId);
938
+ if (priorOwner && priorOwner !== sourceId) {
939
+ diagnostic(
940
+ diagnostics,
941
+ "GRAPH_COMPENSATION_TARGET_REUSED",
942
+ "#/nodes",
943
+ `compensation target ${targetId} is already owned by ${priorOwner}`,
944
+ );
945
+ } else {
946
+ compensationOwners.set(targetId, sourceId);
947
+ compensationByNode.set(sourceId, targetId);
948
+ }
949
+ }
950
+ const compensationNodeIds = new Set(compensationOwners.keys());
951
+ for (const targetId of compensationNodeIds) {
952
+ const target = nodes.get(targetId);
953
+ const usedAsForwardDependency = definition.nodes.some((node) =>
954
+ node.dependsOn.includes(targetId),
955
+ );
956
+ const usedByForwardEdge = definition.edges.some(
957
+ (edge) =>
958
+ edge.kind !== "compensation" &&
959
+ (edge.from === targetId || edge.to === targetId),
960
+ );
961
+ if (
962
+ target.dependsOn.length > 0 ||
963
+ usedAsForwardDependency ||
964
+ usedByForwardEdge
965
+ ) {
966
+ diagnostic(
967
+ diagnostics,
968
+ "GRAPH_COMPENSATION_TARGET_IN_FORWARD_GRAPH",
969
+ "#/nodes",
970
+ `compensation target must be isolated from the forward dependency graph: ${targetId}`,
971
+ );
972
+ }
973
+ if (compensationTargets.has(targetId)) {
974
+ diagnostic(
975
+ diagnostics,
976
+ "GRAPH_COMPENSATION_RECURSIVE",
977
+ "#/nodes",
978
+ `compensation target cannot declare another compensation target: ${targetId}`,
979
+ );
980
+ }
981
+ }
521
982
  for (const [index, trigger] of (definition.triggers || []).entries()) {
522
983
  if (!nodeIds.has(trigger.targetNodeId)) {
523
984
  diagnostic(
@@ -761,6 +1222,7 @@ export function compileGraphDefinition(input, options = {}) {
761
1222
 
762
1223
  for (let leftIndex = 0; leftIndex < definition.nodes.length; leftIndex += 1) {
763
1224
  const left = definition.nodes[leftIndex];
1225
+ if (compensationNodeIds.has(left.id)) continue;
764
1226
  if (!["workspace_write", "external"].includes(left.effectClass)) continue;
765
1227
  for (
766
1228
  let rightIndex = leftIndex + 1;
@@ -768,6 +1230,7 @@ export function compileGraphDefinition(input, options = {}) {
768
1230
  rightIndex += 1
769
1231
  ) {
770
1232
  const right = definition.nodes[rightIndex];
1233
+ if (compensationNodeIds.has(right.id)) continue;
771
1234
  if (!["workspace_write", "external"].includes(right.effectClass))
772
1235
  continue;
773
1236
  if (
@@ -803,14 +1266,26 @@ export function compileGraphDefinition(input, options = {}) {
803
1266
  }
804
1267
  }
805
1268
 
806
- const multiplier = loopMultipliers(definition, diagnostics, nodeIds);
807
- const budget = budgetUpperBound(definition, multiplier, diagnostics);
808
- const subgraphCalls = validateSubgraphs(
1269
+ const multiplier = loopMultipliers(
1270
+ definition,
1271
+ diagnostics,
1272
+ nodeIds,
1273
+ nodes,
1274
+ dependencies,
1275
+ compensationByNode,
1276
+ );
1277
+ const subgraphs = validateSubgraphs(
809
1278
  definition,
810
1279
  nodeIds,
811
1280
  options,
812
1281
  diagnostics,
813
1282
  );
1283
+ const budget = budgetUpperBound(
1284
+ definition,
1285
+ multiplier,
1286
+ diagnostics,
1287
+ subgraphs.calls,
1288
+ );
814
1289
  if (diagnostics.length) throw new GraphCompileError(diagnostics);
815
1290
 
816
1291
  const revisionDigest = graphDigest(
@@ -829,6 +1304,11 @@ export function compileGraphDefinition(input, options = {}) {
829
1304
  Object.fromEntries([...nodes].map(([id, value]) => [id, value])),
830
1305
  ),
831
1306
  topologicalOrder: Object.freeze(order),
1307
+ forwardTopologicalOrder: Object.freeze(
1308
+ order.filter((nodeId) => !compensationNodeIds.has(nodeId)),
1309
+ ),
1310
+ compensationByNode: deepFreeze(Object.fromEntries(compensationByNode)),
1311
+ compensationNodeIds: Object.freeze([...compensationNodeIds].sort()),
832
1312
  dependencies: deepFreeze(
833
1313
  Object.fromEntries(
834
1314
  [...dependencies].map(([id, values]) => [id, [...values].sort()]),
@@ -853,8 +1333,24 @@ export function compileGraphDefinition(input, options = {}) {
853
1333
  ),
854
1334
  ),
855
1335
  loopMultipliers: deepFreeze(Object.fromEntries(multiplier)),
1336
+ loops: deepFreeze(
1337
+ Object.fromEntries(definition.loops.map((loop) => [loop.id, loop])),
1338
+ ),
1339
+ loopByNode: deepFreeze(
1340
+ Object.fromEntries(
1341
+ definition.loops.flatMap((loop) =>
1342
+ loop.nodeIds.map((nodeId) => [nodeId, loop.id]),
1343
+ ),
1344
+ ),
1345
+ ),
1346
+ loopByExitNode: deepFreeze(
1347
+ Object.fromEntries(
1348
+ definition.loops.map((loop) => [loop.exitNodeId, loop.id]),
1349
+ ),
1350
+ ),
856
1351
  budgetUpperBound: deepFreeze(budget),
857
- subgraphCalls: deepFreeze(Object.fromEntries(subgraphCalls)),
1352
+ subgraphCalls: deepFreeze(Object.fromEntries(subgraphs.calls)),
1353
+ subgraphDefinitions: deepFreeze(Object.fromEntries(subgraphs.definitions)),
858
1354
  triggers: deepFreeze([...(definition.triggers || [])]),
859
1355
  regions: deepFreeze([...(definition.regions || [])]),
860
1356
  };
@@ -882,6 +1378,8 @@ export function assertCompiledGraph(value) {
882
1378
  export function migrateGraphDefinition(input, options = {}) {
883
1379
  const { definition, migratedFrom } = migrateDefinition(input, options);
884
1380
  const compiled = compileGraphDefinition(definition, options);
1381
+ const backupDefinition =
1382
+ migratedFrom == null ? null : deepFreeze(stableValue(input));
885
1383
  return Object.freeze({
886
1384
  dryRun: options.dryRun !== false,
887
1385
  fromVersion: migratedFrom ?? definition.schemaVersion,
@@ -889,9 +1387,39 @@ export function migrateGraphDefinition(input, options = {}) {
889
1387
  definition: compiled.definition,
890
1388
  revisionDigest: compiled.revisionDigest,
891
1389
  backupRequired: migratedFrom != null,
1390
+ backupDefinition,
1391
+ rollbackDigest:
1392
+ backupDefinition == null
1393
+ ? null
1394
+ : graphDigest(
1395
+ backupDefinition,
1396
+ `cc.graph.definition-backup/${migratedFrom}`,
1397
+ ),
892
1398
  });
893
1399
  }
894
1400
 
1401
+ export function restoreGraphDefinitionBackup(
1402
+ backupDefinition,
1403
+ expectedRollbackDigest,
1404
+ ) {
1405
+ const backup = stableValue(backupDefinition);
1406
+ const version = Number(backup.schemaVersion);
1407
+ if (version !== GRAPH_DEFINITION_VERSION - 1) {
1408
+ const error = new TypeError(
1409
+ `backup must use GraphDefinition version ${GRAPH_DEFINITION_VERSION - 1}`,
1410
+ );
1411
+ error.code = "CC_GRAPH_MIGRATION_BACKUP_INVALID";
1412
+ throw error;
1413
+ }
1414
+ const digest = graphDigest(backup, `cc.graph.definition-backup/${version}`);
1415
+ if (digest !== expectedRollbackDigest) {
1416
+ const error = new TypeError("graph migration backup digest does not match");
1417
+ error.code = "CC_GRAPH_MIGRATION_BACKUP_TAMPERED";
1418
+ throw error;
1419
+ }
1420
+ return deepFreeze(backup);
1421
+ }
1422
+
895
1423
  export function executionAttemptId(nodeId, iterationPath = [], attempt = 1) {
896
1424
  if (!Number.isSafeInteger(attempt) || attempt < 1) {
897
1425
  throw new TypeError("attempt must be a positive integer");
@@ -907,5 +1435,9 @@ export function executionAttemptId(nodeId, iterationPath = [], attempt = 1) {
907
1435
  return number;
908
1436
  })
909
1437
  : [];
910
- return `${nodeId}@${path.length ? path.join(".") : "root"}#${attempt}`;
1438
+ const digest = graphDigest(
1439
+ { nodeId: String(nodeId), iterationPath: path, attempt },
1440
+ "cc.graph.execution-attempt/v1",
1441
+ );
1442
+ return `attempt-${digest.slice(7, 47)}`;
911
1443
  }