chainlesschain 0.165.3 → 0.165.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +151 -29
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-BiHNUlbU.js → AIOps-CEeYTeDB.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-D-UFjDRQ.js → ActionButton-BeRUQ3V2.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-DazVsDgW.js → Analytics-DrV718XF.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-DXh9UfNJ.js → AppLayout-Cd2Frt7Q.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-CZ6tsceY.js → Artifacts-CUJMlORU.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-RyxnBr_h.js → Audit-CXJcfT6p.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-BWS51Q6X.js → BackgroundAgents-DOv6Z7H9.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-BRtPcUul.js → Backup-BwdDyBI0.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-CGd4vZYY.js → BaseInput-BmLn4kYH.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-h8ijVwCg.js → Chat-COg8pAW2.js} +4 -4
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-4gGkdGgA.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-NyF6dPl8.js → Checkbox-BCP4uoU8.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-Ci4OOxr5.js → Codegen-pRt6Y9JI.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-CwTkc9BM.js → Col-DLaprKhS.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-DW_tM82m.js → Community-DyATsz5f.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-DEvHRV4S.js → Compact-BXAaupZK.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-BbjBEEfD.js → Compliance-DpcJBELr.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-10IXBaBa.js → Cowork-DG4EFWyd.js} +2 -2
  22. package/src/assets/web-panel/assets/{Cron-BZkbpd5I.js → Cron-F6Oy5n2P.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-CFQ8L_6v.js → Crosschain-BOdgtalX.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-BDF6AkXe.js → DID-DT1dSkjH.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-CoOdWMi4.js → Dashboard-NBUIqWzC.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-DEsGmu8Y.js → Dropdown-CSpHeMYB.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-BS89SWlK.js → EmailListRenderer-CEG0xISr.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-C1FpCHiI.js → FamilyGuardDashboard-BTZ0BZna.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-BzvpVmLA.js → Federation-BuuCTn2V.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-BeC259PQ.js → FormItemContext-KAVceevk.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-azgG3bmV.js → GenericCardRenderer-DnHpcQvW.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-BmSSowc5.js → Git-C_B_0HwB.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-CTBCSyQZ.js → Governance-DPSZuzuu.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-DA5xyk4a.js → Inference-BV-06V7q.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-D1gPHNig.js → KnowledgeGraph-wpDuupn6.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-BPmLf1AR.js → Logs-Bseca1_I.js} +2 -2
  37. package/src/assets/web-panel/assets/{MarkdownRenderer-SVLOg49H.js → MarkdownRenderer-B4RLp2zs.js} +1 -1
  38. package/src/assets/web-panel/assets/{Marketplace-DtUFcR0E.js → Marketplace-d3kM4DDL.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-DS_kqBv-.js → McpTools-DQ_xZ23i.js} +3 -3
  40. package/src/assets/web-panel/assets/{Memory-Fvh7XL7V.js → Memory-CRIeYO4L.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-C7itAP45.js → MobileBridge-BsllIa9l.js} +1 -1
  42. package/src/assets/web-panel/assets/MobileProjects-BUuQy30X.js +1 -0
  43. package/src/assets/web-panel/assets/{Mtc-B57MiNLK.js → Mtc-BOFDIzrC.js} +6 -6
  44. package/src/assets/web-panel/assets/{MtcAudit-BXfCUcM7.js → MtcAudit-BQg-vyvG.js} +5 -5
  45. package/src/assets/web-panel/assets/{Multisig-yaJ-ZcF6.js → Multisig-Bo1jrPdN.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-CoaUCKrO.js → NLProgramming-Bmod8alw.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-BKLM_65R.js → Notes-CGGFL8w-.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-DoDtK9uZ.js → NotificationSettings-BsZvCJxC.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-BJ28TGr-.js → OrderTableRenderer-C1-Quo_h.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-MuGsdoIb.js → Organization-BjOeDYOi.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-BLUN9J4R.js → Overflow-DwVV3l5A.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-DWuoBUPd.js → P2P-Cu3DQT9W.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-CUi37YPj.js → PdhVaultBrowser-Cg0gR7dH.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-BYQg2pcr.js → Permissions-CsAXxRN5.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-CAxoV2xu.js → PersonalDataHub-C-nNmnYp.js} +6 -6
  56. package/src/assets/web-panel/assets/{Pipeline-B33wGvIO.js → Pipeline-BOnW7nNc.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-A8yXTORP.js → Privacy-ClLSyGKt.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-D49I2I1O.js → ProjectInit-DuLvjjq7.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-Ci73jJDw.js → ProjectSettings-DQYYbDD9.js} +2 -2
  60. package/src/assets/web-panel/assets/{Projects-CF-yD6Cn.js → Projects-Drw8MBGK.js} +1 -1
  61. package/src/assets/web-panel/assets/Providers-ckqHQe6K.js +1 -0
  62. package/src/assets/web-panel/assets/{QrScannerModal-Brza0ykJ.js → QrScannerModal-DW5BXk-G.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DFydhjxL.js → QuickAsk-DDLnhNTp.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-DLF8_NhA.js → Recommend-PJQ2C91U.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-dyuStW3x.js → RemoteSession-s6wmda34.js} +3 -3
  66. package/src/assets/web-panel/assets/{Reputation-5F3tA6yw.js → Reputation-DC2sb-I3.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-Ca2A57S5.js → Row-hPGk1eGj.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-B0bxBkq_.js → RssFeed-RIvfZql0.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-CFNvHalZ.js → Search-DFg1G1eR.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-DSoH3Hav.js → Security-D6H5p8Dp.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-BHpfEmBQ.js → Services-nxYiFV_w.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-Dsh-QNmn.js → Skeleton-BmgFNAdB.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-BhT1NkSU.js → Skills-CsaaD2av.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-DY9snPeJ.js → Sla-yM5y9yvK.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-CWaX0_Cx.js → SpeechSettings-DPy1XMYh.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-B690y_TC.js → SyncSettings-CFVhEyyr.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-2N-w3zWd.js → Tasks-DdO02Inn.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-BjwQOQYC.js → Templates-Eo2d8Llu.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-CUqHErxY.js → Tenant-ChESOXgy.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-C13rXvNy.js → Terminal-BJ7oDxTI.js} +2 -2
  81. package/src/assets/web-panel/assets/TimelineRenderer-hWX0fUvz.js +1 -0
  82. package/src/assets/web-panel/assets/{Tokens-BXHLGXYC.js → Tokens-DVBZQnd-.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-1e5i65o3.js → Trigger-D89cQfVq.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-FLGqNHs5.js → Trust-BIVSb4-V.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-BbaR_LJB.js → UkeySign-B58gWopl.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-NOp4RIov.js → VideoEditing-DhHI7bmG.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-B7DW7bG4.js → Wallet-C4FheWS_.js} +3 -3
  88. package/src/assets/web-panel/assets/{WebAuthn-BptSUWix.js → WebAuthn-DnPRq-Bx.js} +5 -5
  89. package/src/assets/web-panel/assets/{WorkflowEditor-CAJnDOXS.js → WorkflowEditor-BerRPjpJ.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-I8LgG1-Z.js → chat-BzZ52iKr.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-CWe7SOVD.js → colors-BI_E4BAn.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-Cx3u3CA3.js → compact-item-d50mr3yk.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-CgD5ArwY.js → createContext-C6FsQ1IH.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-CagEcIex.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-BfQWiXN8.js → hasIn-DLZEMgLP.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BuEQSorC.js → index-9H_M2jju.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BiI-xPRH.js → index-A5CDWP7y.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DAVy98u7.js → index-AHr7ryWf.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BUAR7RY-.js → index-B81mo1Et.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DlIyt1qF.js → index-BAE3hwev.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Dcdm_Z2c.js → index-BB9SqVSt.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CWZ3REg6.js → index-BRGx3w6T.js} +3 -3
  103. package/src/assets/web-panel/assets/{index-B6yChzt1.js → index-BTpIOTfR.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DeizoQVZ.js → index-BZjjdQ62.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-e8BM1yY1.js → index-BcSUl9fA.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CNYFQxAW.js → index-BeM4hfht.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DGMYTN5W.js → index-Bmr_vxXD.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BREnPKGo.js → index-BoNZlzhQ.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-B1cX4Rxu.js → index-C3k8kevG.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-3iKP9OTJ.js → index-CLoTQr0W.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-C1UjHdgZ.js → index-CO8DAF7U.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Cy_cCJlr.js → index-CRN5TRTQ.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-C0nDKhWv.js → index-CUaEHfQ0.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DslTuqro.js → index-CVzuawCX.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CAUnl4Jv.js → index-CdKt9fOd.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CMu16vPC.js → index-CgkoH3_g.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C-rIKccl.js → index-CjuKZBqJ.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Bx7BNIRH.js → index-Co7s9LsB.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Doc0Ftpd.js → index-D-aBumtY.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Cd79NQAx.js → index-DE0V6cp-.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-V-kIHzAV.js → index-DY4j5zkF.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Bi24S7Dw.js → index-Dec0Kf_Z.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DCJEY2T0.js → index-DoFtCVZ7.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C0ztiqtB.js → index-DqkbHHg8.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CcX4FSPf.js → index-DsscwxPE.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CdRkCwm4.js → index-GXE0D8Nx.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DmgtY58H.js → index-IPkwSAW_.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CQDKYbnv.js → index-KNGgO4Wm.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-87gMxqhN.js → index-Pvf30m8Q.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DmrdUwzU.js → index-Q5Tn735s.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DFb-XIKy.js → index-TWG-_D1M.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DsQcURkA.js → index-Y2-q7QLm.js} +1 -1
  133. package/src/assets/web-panel/assets/index-pzT9PpOU.js +1 -0
  134. package/src/assets/web-panel/assets/index-z1kFadaX.js +1 -0
  135. package/src/assets/web-panel/assets/{initDefaultProps-DiBszVdG.js → initDefaultProps-D-4Hzgej.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-BGIFFozv.js → motion-D4Wl4xU7.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-D5W7mp7B.js → move-Cd5eQvL0.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-JFIlxJ7F.js → mtc-parser-B4ilslCW.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-CQ3o6LIU.js → omit-Ckarx-tf.js} +1 -1
  140. package/src/assets/web-panel/assets/parsers-YNLm7sQ0.js +4 -0
  141. package/src/assets/web-panel/assets/{pickAttrs-R8MIlXo3.js → pickAttrs-QJ0bpCmc.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-sW7ylSky.js → placementArrow-CKHm-tq8.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-Cnl39Vbz.js → responsiveObserve-CV6a_w_Y.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-sn8tFd14.js → slide-mHpeQFgk.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-IKKY_QxZ.js → statusUtils-DIaQNQWA.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-BNpV4Nqs.js → styleChecker-PJpzavV8.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-BWyT_eoU.js → useFlexGapSupport-BH7zTw8S.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-D09p6_U9.js → useFs-BDNG3JDd.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-CzW3TkJZ.js → usePersonalDataHub-CY2VWdUg.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-3s3WoAKB.js → vnode-D0kw9jhz.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-Cjy_673s.js → zoom-B5g7TN7r.js} +1 -1
  152. package/src/assets/web-panel/index.html +1 -1
  153. package/src/command-help-index.json +3 -3
  154. package/src/commands/agent.js +105 -35
  155. package/src/commands/artifacts.js +598 -33
  156. package/src/commands/cowork.js +200 -5
  157. package/src/commands/session-budget.js +155 -0
  158. package/src/commands/session-location.js +968 -1
  159. package/src/commands/session.js +23 -2
  160. package/src/data/changelog.json +28 -2
  161. package/src/gateways/ws/artifact-protocol.js +77 -13
  162. package/src/gateways/ws/message-dispatcher.js +57 -0
  163. package/src/gateways/ws/session-protocol.js +391 -24
  164. package/src/gateways/ws/ws-agent-handler.js +191 -14
  165. package/src/gateways/ws/ws-server.js +14 -3
  166. package/src/gateways/ws/ws-session-gateway.js +101 -5
  167. package/src/harness/jsonl-session-store.js +1210 -0
  168. package/src/lib/agent-team/team-runner.js +43 -13
  169. package/src/lib/artifact-access-ledger.js +462 -0
  170. package/src/lib/artifact-cleanup-ledger.js +861 -0
  171. package/src/lib/artifact-deletion-ledger.js +678 -0
  172. package/src/lib/artifact-recovery.js +970 -0
  173. package/src/lib/artifact-store.js +256 -51
  174. package/src/lib/artifact-workbench-projection.js +171 -0
  175. package/src/lib/command-lifecycle-report.js +8 -4
  176. package/src/lib/cowork-adapter.js +9 -5
  177. package/src/lib/cowork-task-runner.js +20 -0
  178. package/src/lib/cowork-workflow.js +178 -20
  179. package/src/lib/direct-model-usage.js +62 -22
  180. package/src/lib/dynamic-workflow-runtime.js +3592 -112
  181. package/src/lib/execution-location-result-apply.js +339 -0
  182. package/src/lib/execution-location-result-artifact.js +271 -0
  183. package/src/lib/execution-location-result-review.js +253 -0
  184. package/src/lib/execution-location-result-store.js +331 -0
  185. package/src/lib/execution-location-result.js +617 -0
  186. package/src/lib/execution-location-target.js +240 -3
  187. package/src/lib/managed-tool-checkpoint.js +47 -4
  188. package/src/lib/mcp-stdio-executable-identity.js +16 -0
  189. package/src/lib/mcp-stdio-native-code-policy.js +74 -0
  190. package/src/lib/mcp-stdio-package-materialization.js +12 -3
  191. package/src/lib/process-execution-broker/index.js +112 -0
  192. package/src/lib/process-execution-broker/platform-sandbox.js +57 -3
  193. package/src/lib/process-execution-broker/workspace-transaction.js +29 -0
  194. package/src/lib/session-budget-production-root.js +376 -0
  195. package/src/lib/session-budget-runtime.js +109 -18
  196. package/src/lib/session-budget-usage.js +63 -0
  197. package/src/lib/session-host-snapshot.js +19 -2
  198. package/src/lib/session-projection.js +126 -10
  199. package/src/lib/session-resource-budget.js +766 -105
  200. package/src/lib/sub-agent-context.js +69 -22
  201. package/src/lib/web-ui-server.js +12 -1
  202. package/src/repl/agent-repl.js +237 -16
  203. package/src/runtime/agent-core.js +135 -0
  204. package/src/runtime/headless-runner.js +135 -46
  205. package/src/runtime/headless-stream.js +133 -25
  206. package/src/runtime/policies/agent-policy.js +10 -0
  207. package/src/assets/web-panel/assets/ChatBubbleRenderer-D7tcdDS9.js +0 -1
  208. package/src/assets/web-panel/assets/MobileProjects-DGbP36-3.js +0 -1
  209. package/src/assets/web-panel/assets/Providers-D-etEhg5.js +0 -1
  210. package/src/assets/web-panel/assets/TimelineRenderer-CpbWcya_.js +0 -1
  211. package/src/assets/web-panel/assets/devWarning-DLv_872g.js +0 -1
  212. package/src/assets/web-panel/assets/index-CdiGCibe.js +0 -1
  213. package/src/assets/web-panel/assets/index-D_0z-gyz.js +0 -1
  214. package/src/assets/web-panel/assets/parsers-UEvh_ShA.js +0 -4
@@ -6,8 +6,30 @@ import {
6
6
  COWORK_WORKFLOW_CONTROL_SIGNAL_CODE,
7
7
  executeWorkflow as executeCoworkWorkflow,
8
8
  } from "./cowork-workflow.js";
9
+ import {
10
+ ARTIFACT_KINDS,
11
+ ArtifactStore,
12
+ MAX_ARTIFACT_BYTES,
13
+ publicArtifactMetadata,
14
+ } from "./artifact-store.js";
9
15
  import { writeSecurityStore } from "./durable-security-store.js";
16
+ import {
17
+ CACHE_READ_MULTIPLIER,
18
+ CACHE_READ_MULTIPLIER_BY_PROVIDER,
19
+ CACHE_WRITE_MULTIPLIER,
20
+ FREE_PROVIDERS,
21
+ lookupRate,
22
+ PRICE_TABLE,
23
+ } from "./llm-pricing.js";
10
24
  import { containsSecret } from "./secret-scan.js";
25
+ import { managedToolCheckpointRequired } from "./managed-tool-checkpoint.js";
26
+ import {
27
+ digestWorkspaceEvidence,
28
+ WORKSPACE_TRANSACTION_COVERAGE,
29
+ WORKSPACE_TRANSACTION_STATE,
30
+ WORKSPACE_TRANSACTION_VERSION,
31
+ WorkspaceTransactionManager,
32
+ } from "./process-execution-broker/workspace-transaction.js";
11
33
  import {
12
34
  sameFileStatIdentity,
13
35
  samePathHandleFileIdentity,
@@ -20,6 +42,8 @@ export const DYNAMIC_WORKFLOW_RUNTIME_SCHEMA = "cc-dynamic-workflow-runtime/v1";
20
42
  export const DYNAMIC_WORKFLOW_RUNTIME_VERSION = 1;
21
43
  export const DYNAMIC_WORKFLOW_EFFECT_SCHEMA = "cc-dynamic-workflow-effect/v1";
22
44
  export const DYNAMIC_WORKFLOW_CALL_SCHEMA = "cc-dynamic-workflow-call/v1";
45
+ export const DYNAMIC_WORKFLOW_INPUT_REQUEST_SCHEMA =
46
+ "cc-dynamic-workflow-input-request/v1";
23
47
  export const DYNAMIC_WORKFLOW_OBSERVABILITY_SCHEMA =
24
48
  "cc-dynamic-workflow-observability/v1";
25
49
  export const DYNAMIC_WORKFLOW_RUNTIME_CONTROL_CODE =
@@ -29,6 +53,8 @@ const STATE_STATUSES = new Set([
29
53
  "ready",
30
54
  "running",
31
55
  "pause_requested",
56
+ "input_requested",
57
+ "needs_input",
32
58
  "paused",
33
59
  "blocked",
34
60
  "failed",
@@ -42,10 +68,20 @@ const MAX_EFFECTS = 64;
42
68
  const MAX_EFFECT_BATCH_SIZE = 64;
43
69
  const MAX_LINEAGE_EVENTS = 4096;
44
70
  const MAX_CALLS_PER_EFFECT = 128;
71
+ const MAX_INPUT_REQUESTS = 64;
72
+ const MAX_INPUT_OPTIONS = 32;
73
+ const MAX_INPUT_RESPONSE_BYTES = 64 * 1024;
74
+ const MAX_WORKBENCH_RUN_FILES = 1024;
45
75
  const MAX_PROJECTED_ARTIFACTS_PER_EFFECT = 256;
76
+ const MAX_PROVIDER_TOKEN_COUNT = 1_000_000_000;
77
+ const MAX_PROVIDER_USD_RATE_PER_MILLION = 1_000_000;
78
+ const MAX_PROVIDER_CACHE_RATE_MULTIPLIER = 100;
46
79
  const PROVIDER_CLIENT_REQUEST_ID_RE = /^ccwf_[a-f0-9]{64}$/u;
47
80
  const PROVIDER_RECEIPT_ID_RE = /^[A-Za-z0-9._:-]{1,256}$/u;
48
81
  const PROVIDER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
82
+ const PROVIDER_MODEL_RE = /^[\x21-\x7e]{1,256}$/u;
83
+ const ARTIFACT_ID_RE = /^art_[a-z0-9]+_[a-f0-9]{8}$/u;
84
+ const ARTIFACT_SHA256_RE = /^[a-f0-9]{64}$/u;
49
85
  const WORKFLOW_TOOL_CALL_ID_RE = /^[\x21-\x7e]{1,512}$/u;
50
86
  const WORKFLOW_TOOL_NAME_RE = /^[A-Za-z0-9._:-]{1,256}$/u;
51
87
  const WORKFLOW_CHILD_EFFECT_PROTOCOL = "cc-workflow-child-effect/v1";
@@ -53,6 +89,20 @@ const WORKFLOW_PROVIDER_ATTEMPT_PROTOCOL = "cc-provider-request-attempt/v1";
53
89
  const WORKFLOW_PROVIDER_CALL_ID_RE = /^[A-Za-z0-9._:-]{1,256}$/u;
54
90
  const WORKFLOW_PROVIDER_REQUEST_ID_RE = /^ccwf_[a-f0-9]{64}$/u;
55
91
  const DESCENDANT_OWNER_TOOL_NAMES = new Set(["run_skill", "spawn_sub_agent"]);
92
+ const ARTIFACT_METADATA_FIELDS = Object.freeze([
93
+ "id",
94
+ "title",
95
+ "kind",
96
+ "mime",
97
+ "size",
98
+ "sha256",
99
+ "file",
100
+ "sessionId",
101
+ "createdAt",
102
+ "expiresAt",
103
+ "immutable",
104
+ "recordDigest",
105
+ ]);
56
106
  const WORKFLOW_CALL_STATUSES = new Set([
57
107
  "started",
58
108
  "completed",
@@ -110,6 +160,709 @@ function nonNegativeSafeInteger(value) {
110
160
  return Number.isSafeInteger(value) && value >= 0 ? value : null;
111
161
  }
112
162
 
163
+ function providerUsageDigest(usage) {
164
+ return digest(
165
+ "chainlesschain.dynamic-workflow.provider-token-usage.v1\0",
166
+ usage,
167
+ );
168
+ }
169
+
170
+ function validProviderUsage(value) {
171
+ const fields =
172
+ value && typeof value === "object" ? Object.keys(value).sort() : [];
173
+ return (
174
+ fields.length === 6 &&
175
+ [
176
+ "cacheCreationInputTokens",
177
+ "cacheReadInputTokens",
178
+ "inputTokens",
179
+ "outputTokens",
180
+ "schema",
181
+ "totalTokens",
182
+ ].every((field, index) => fields[index] === field) &&
183
+ value?.schema === "cc-provider-token-usage/v1" &&
184
+ nonNegativeSafeInteger(value.inputTokens) !== null &&
185
+ nonNegativeSafeInteger(value.outputTokens) !== null &&
186
+ nonNegativeSafeInteger(value.cacheReadInputTokens) !== null &&
187
+ nonNegativeSafeInteger(value.cacheCreationInputTokens) !== null &&
188
+ nonNegativeSafeInteger(value.totalTokens) !== null &&
189
+ value.inputTokens <= MAX_PROVIDER_TOKEN_COUNT &&
190
+ value.outputTokens <= MAX_PROVIDER_TOKEN_COUNT &&
191
+ value.cacheReadInputTokens <= MAX_PROVIDER_TOKEN_COUNT &&
192
+ value.cacheCreationInputTokens <= MAX_PROVIDER_TOKEN_COUNT &&
193
+ value.totalTokens ===
194
+ value.inputTokens +
195
+ value.outputTokens +
196
+ value.cacheReadInputTokens +
197
+ value.cacheCreationInputTokens
198
+ );
199
+ }
200
+
201
+ function exactObjectFields(value, expected) {
202
+ const fields =
203
+ value && typeof value === "object" && !Array.isArray(value)
204
+ ? Object.keys(value).sort()
205
+ : [];
206
+ const sortedExpected = [...expected].sort();
207
+ return (
208
+ fields.length === sortedExpected.length &&
209
+ sortedExpected.every((field, index) => fields[index] === field)
210
+ );
211
+ }
212
+
213
+ function nonNegativeFinite(value, max = Number.MAX_VALUE) {
214
+ return Number.isFinite(value) && value >= 0 && value <= max;
215
+ }
216
+
217
+ function providerPricingDigest(pricing) {
218
+ return digest(
219
+ "chainlesschain.dynamic-workflow.provider-pricing-snapshot.v1\0",
220
+ pricing,
221
+ );
222
+ }
223
+
224
+ function providerCostEstimateDigest(cost) {
225
+ return digest(
226
+ "chainlesschain.dynamic-workflow.provider-cost-estimate.v1\0",
227
+ cost,
228
+ );
229
+ }
230
+
231
+ function providerPricingCatalogDigest() {
232
+ return digest(
233
+ "chainlesschain.dynamic-workflow.provider-pricing-catalog.v1\0",
234
+ {
235
+ freeProviders: FREE_PROVIDERS,
236
+ priceTable: PRICE_TABLE,
237
+ cacheReadMultiplier: CACHE_READ_MULTIPLIER,
238
+ cacheReadMultiplierByProvider: CACHE_READ_MULTIPLIER_BY_PROVIDER,
239
+ cacheCreationMultiplier: CACHE_WRITE_MULTIPLIER,
240
+ },
241
+ );
242
+ }
243
+
244
+ function providerPricingSnapshot(provider, model) {
245
+ const rate = lookupRate(provider, model);
246
+ if (!rate) return null;
247
+ const cacheReadMultiplier =
248
+ CACHE_READ_MULTIPLIER_BY_PROVIDER[provider] ?? CACHE_READ_MULTIPLIER;
249
+ return {
250
+ schema: "cc-provider-pricing-snapshot/v1",
251
+ currency: "USD",
252
+ authority: "builtin-public-list-estimate",
253
+ provider,
254
+ model,
255
+ pattern: rate.pattern,
256
+ inputUsdPerMillion: rate.in,
257
+ outputUsdPerMillion: rate.out,
258
+ cacheReadMultiplier,
259
+ cacheCreationMultiplier: CACHE_WRITE_MULTIPLIER,
260
+ free: rate.pattern === "free",
261
+ catalogDigest: providerPricingCatalogDigest(),
262
+ };
263
+ }
264
+
265
+ function validProviderPricing(value, provider, model) {
266
+ return (
267
+ exactObjectFields(value, [
268
+ "schema",
269
+ "currency",
270
+ "authority",
271
+ "provider",
272
+ "model",
273
+ "pattern",
274
+ "inputUsdPerMillion",
275
+ "outputUsdPerMillion",
276
+ "cacheReadMultiplier",
277
+ "cacheCreationMultiplier",
278
+ "free",
279
+ "catalogDigest",
280
+ ]) &&
281
+ value.schema === "cc-provider-pricing-snapshot/v1" &&
282
+ value.currency === "USD" &&
283
+ value.authority === "builtin-public-list-estimate" &&
284
+ value.provider === provider &&
285
+ value.model === model &&
286
+ typeof value.pattern === "string" &&
287
+ /^[A-Za-z0-9._:-]{1,128}$/u.test(value.pattern) &&
288
+ nonNegativeFinite(
289
+ value.inputUsdPerMillion,
290
+ MAX_PROVIDER_USD_RATE_PER_MILLION,
291
+ ) &&
292
+ nonNegativeFinite(
293
+ value.outputUsdPerMillion,
294
+ MAX_PROVIDER_USD_RATE_PER_MILLION,
295
+ ) &&
296
+ nonNegativeFinite(
297
+ value.cacheReadMultiplier,
298
+ MAX_PROVIDER_CACHE_RATE_MULTIPLIER,
299
+ ) &&
300
+ nonNegativeFinite(
301
+ value.cacheCreationMultiplier,
302
+ MAX_PROVIDER_CACHE_RATE_MULTIPLIER,
303
+ ) &&
304
+ typeof value.free === "boolean" &&
305
+ value.free ===
306
+ (value.inputUsdPerMillion === 0 && value.outputUsdPerMillion === 0) &&
307
+ SHA256_RE.test(value.catalogDigest || "")
308
+ );
309
+ }
310
+
311
+ function estimateProviderCost(pricing, usage) {
312
+ if (!pricing || !usage) return null;
313
+ const inputUsd = (usage.inputTokens / 1_000_000) * pricing.inputUsdPerMillion;
314
+ const outputUsd =
315
+ (usage.outputTokens / 1_000_000) * pricing.outputUsdPerMillion;
316
+ const cacheReadUsd =
317
+ (usage.cacheReadInputTokens / 1_000_000) *
318
+ pricing.inputUsdPerMillion *
319
+ pricing.cacheReadMultiplier;
320
+ const cacheCreationUsd =
321
+ (usage.cacheCreationInputTokens / 1_000_000) *
322
+ pricing.inputUsdPerMillion *
323
+ pricing.cacheCreationMultiplier;
324
+ return {
325
+ schema: "cc-provider-cost-estimate/v1",
326
+ currency: "USD",
327
+ authority: "durable-pricing-snapshot-estimate",
328
+ inputUsd,
329
+ outputUsd,
330
+ cacheReadUsd,
331
+ cacheCreationUsd,
332
+ totalUsd: inputUsd + outputUsd + cacheReadUsd + cacheCreationUsd,
333
+ pricingDigest: providerPricingDigest(pricing),
334
+ };
335
+ }
336
+
337
+ function validProviderCostEstimate(value, pricing, usage) {
338
+ if (
339
+ !exactObjectFields(value, [
340
+ "schema",
341
+ "currency",
342
+ "authority",
343
+ "inputUsd",
344
+ "outputUsd",
345
+ "cacheReadUsd",
346
+ "cacheCreationUsd",
347
+ "totalUsd",
348
+ "pricingDigest",
349
+ ]) ||
350
+ value.schema !== "cc-provider-cost-estimate/v1" ||
351
+ value.currency !== "USD" ||
352
+ value.authority !== "durable-pricing-snapshot-estimate" ||
353
+ ![
354
+ value.inputUsd,
355
+ value.outputUsd,
356
+ value.cacheReadUsd,
357
+ value.cacheCreationUsd,
358
+ value.totalUsd,
359
+ ].every((amount) => nonNegativeFinite(amount)) ||
360
+ value.pricingDigest !== providerPricingDigest(pricing)
361
+ ) {
362
+ return false;
363
+ }
364
+ const expected = estimateProviderCost(pricing, usage);
365
+ return (
366
+ canonicalJson(value, "providerCostEstimate") ===
367
+ canonicalJson(expected, "providerCostEstimate")
368
+ );
369
+ }
370
+
371
+ function strictDataObjectSnapshot(value, fields, field, maxBytes = 64 * 1024) {
372
+ let descriptors;
373
+ try {
374
+ if (
375
+ !value ||
376
+ typeof value !== "object" ||
377
+ Array.isArray(value) ||
378
+ utilTypes.isProxy(value)
379
+ ) {
380
+ throw new TypeError(`${field} must be a plain data object`);
381
+ }
382
+ descriptors = Object.getOwnPropertyDescriptors(value);
383
+ const keys = Reflect.ownKeys(descriptors);
384
+ if (
385
+ keys.length !== fields.length ||
386
+ keys.some((key) => typeof key !== "string") ||
387
+ fields.some(
388
+ (name) =>
389
+ !Object.hasOwn(descriptors, name) ||
390
+ !Object.hasOwn(descriptors[name], "value"),
391
+ )
392
+ ) {
393
+ throw new TypeError(`${field} has an invalid field set`);
394
+ }
395
+ } catch (cause) {
396
+ if (cause instanceof TypeError && cause.message.startsWith(field)) {
397
+ throw cause;
398
+ }
399
+ throw new TypeError(`${field} must be a plain data object`);
400
+ }
401
+ return snapshotJson(
402
+ Object.fromEntries(fields.map((name) => [name, descriptors[name].value])),
403
+ field,
404
+ maxBytes,
405
+ );
406
+ }
407
+
408
+ function validArtifactMetadata(value) {
409
+ if (!exactObjectFields(value, ARTIFACT_METADATA_FIELDS)) return false;
410
+ let timestampsValid = false;
411
+ try {
412
+ timestampsValid =
413
+ isoNow(value.createdAt) === value.createdAt &&
414
+ isoNow(value.expiresAt) === value.expiresAt &&
415
+ Date.parse(value.expiresAt) >= Date.parse(value.createdAt);
416
+ } catch {
417
+ timestampsValid = false;
418
+ }
419
+ return (
420
+ ARTIFACT_ID_RE.test(value.id || "") &&
421
+ typeof value.title === "string" &&
422
+ value.title.length >= 1 &&
423
+ value.title.length <= 1024 &&
424
+ !value.title.includes("\0") &&
425
+ ARTIFACT_KINDS.includes(value.kind) &&
426
+ typeof value.mime === "string" &&
427
+ /^[\x21-\x7e]{1,256}$/u.test(value.mime) &&
428
+ Number.isSafeInteger(value.size) &&
429
+ value.size >= 0 &&
430
+ value.size <= MAX_ARTIFACT_BYTES &&
431
+ ARTIFACT_SHA256_RE.test(value.sha256 || "") &&
432
+ typeof value.file === "string" &&
433
+ value.file.length >= 1 &&
434
+ value.file.length <= 512 &&
435
+ path.basename(value.file) === value.file &&
436
+ !/[\\/\0-\x1f\x7f]/u.test(value.file) &&
437
+ (value.sessionId === null ||
438
+ (typeof value.sessionId === "string" &&
439
+ value.sessionId.length >= 1 &&
440
+ value.sessionId.length <= 256)) &&
441
+ timestampsValid &&
442
+ typeof value.immutable === "boolean" &&
443
+ (value.recordDigest === null || SHA256_RE.test(value.recordDigest || ""))
444
+ );
445
+ }
446
+
447
+ function artifactMetadataDigest(metadata) {
448
+ return digest(
449
+ "chainlesschain.dynamic-workflow.artifact-store-metadata.v1\0",
450
+ metadata,
451
+ );
452
+ }
453
+
454
+ function artifactReadbackDigest(readback) {
455
+ return digest(
456
+ "chainlesschain.dynamic-workflow.artifact-store-readback.v1\0",
457
+ readback,
458
+ );
459
+ }
460
+
461
+ function validArtifactReadback(value) {
462
+ return (
463
+ exactObjectFields(value, [
464
+ "schema",
465
+ "authority",
466
+ "metadata",
467
+ "metadataDigest",
468
+ "contentDigest",
469
+ ]) &&
470
+ value.schema === "cc-dynamic-workflow-artifact-readback/v1" &&
471
+ value.authority === "artifact-store-index-and-bytes-at-settlement" &&
472
+ validArtifactMetadata(value.metadata) &&
473
+ value.metadataDigest === artifactMetadataDigest(value.metadata) &&
474
+ value.contentDigest === `sha256:${value.metadata.sha256}`
475
+ );
476
+ }
477
+
478
+ function artifactReadbackSettlement(event, artifactStore) {
479
+ const supplied = strictDataObjectSnapshot(
480
+ ownDataValue(event?.result, "published"),
481
+ ARTIFACT_METADATA_FIELDS,
482
+ "workflow published artifact metadata",
483
+ );
484
+ if (!validArtifactMetadata(supplied)) {
485
+ throw new TypeError("workflow published artifact metadata is malformed");
486
+ }
487
+ if (containsSecret(JSON.stringify(supplied))) {
488
+ throw new TypeError(
489
+ "workflow published artifact metadata contains secret-shaped data",
490
+ );
491
+ }
492
+ if (
493
+ !artifactStore ||
494
+ typeof artifactStore.get !== "function" ||
495
+ typeof artifactStore.verifyIntegrity !== "function"
496
+ ) {
497
+ throw new TypeError("workflow artifact store readback is unavailable");
498
+ }
499
+ let stored;
500
+ let integrity;
501
+ try {
502
+ stored = artifactStore.get(supplied.id);
503
+ integrity = stored ? artifactStore.verifyIntegrity(stored) : null;
504
+ } catch {
505
+ throw new TypeError("workflow artifact store readback failed");
506
+ }
507
+ if (!stored || !integrity?.ok) {
508
+ throw new TypeError(
509
+ "workflow artifact store bytes are unavailable or invalid",
510
+ );
511
+ }
512
+ const canonical = strictDataObjectSnapshot(
513
+ publicArtifactMetadata(stored),
514
+ ARTIFACT_METADATA_FIELDS,
515
+ "workflow artifact store metadata",
516
+ );
517
+ if (
518
+ !validArtifactMetadata(canonical) ||
519
+ canonicalJson(canonical, "workflowArtifactMetadata") !==
520
+ canonicalJson(supplied, "workflowArtifactMetadata") ||
521
+ integrity.expectedSha256 !== supplied.sha256 ||
522
+ integrity.actualSha256 !== supplied.sha256
523
+ ) {
524
+ throw new TypeError(
525
+ "workflow artifact store readback does not match settlement",
526
+ );
527
+ }
528
+ return {
529
+ schema: "cc-dynamic-workflow-artifact-readback/v1",
530
+ authority: "artifact-store-index-and-bytes-at-settlement",
531
+ metadata: canonical,
532
+ metadataDigest: artifactMetadataDigest(canonical),
533
+ contentDigest: `sha256:${integrity.actualSha256}`,
534
+ };
535
+ }
536
+
537
+ const CHECKPOINT_EVIDENCE_BASE_FIELDS = Object.freeze([
538
+ "version",
539
+ "transactionId",
540
+ "checkpointId",
541
+ "checkpointDigest",
542
+ "writeManifestDigest",
543
+ "fileCoverage",
544
+ "coverage",
545
+ "externalSideEffects",
546
+ "outcome",
547
+ "executions",
548
+ "exclusions",
549
+ "uncoveredPaths",
550
+ "evidenceDigest",
551
+ ]);
552
+
553
+ const CHECKPOINT_BINDING_FIELDS = Object.freeze([
554
+ "schema",
555
+ "authority",
556
+ "transactionId",
557
+ "checkpointId",
558
+ "checkpointDigest",
559
+ "preparedStateDigest",
560
+ "coverage",
561
+ "fileCoverage",
562
+ "externalSideEffects",
563
+ ]);
564
+
565
+ function checkpointBindingSnapshot(value) {
566
+ if (value === undefined || value === null) return null;
567
+ return strictDataObjectSnapshot(
568
+ value,
569
+ CHECKPOINT_BINDING_FIELDS,
570
+ "workflow managed checkpoint binding",
571
+ );
572
+ }
573
+
574
+ function validCheckpointBinding(value) {
575
+ return (
576
+ exactObjectFields(value, CHECKPOINT_BINDING_FIELDS) &&
577
+ value.schema === "cc-managed-tool-checkpoint-binding/v1" &&
578
+ value.authority === "process-broker-workspace-transaction-prepared" &&
579
+ /^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.transactionId || "") &&
580
+ /^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.checkpointId || "") &&
581
+ SHA256_RE.test(value.checkpointDigest || "") &&
582
+ SHA256_RE.test(value.preparedStateDigest || "") &&
583
+ Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(value.coverage) &&
584
+ Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(
585
+ value.fileCoverage,
586
+ ) &&
587
+ typeof value.externalSideEffects === "boolean"
588
+ );
589
+ }
590
+
591
+ function checkpointBindingDigest(binding) {
592
+ return digest(
593
+ "chainlesschain.dynamic-workflow.checkpoint-prepared-binding.v1\0",
594
+ binding,
595
+ );
596
+ }
597
+
598
+ function checkpointReadbackMatchesBinding(readback, binding) {
599
+ return (
600
+ binding === null ||
601
+ (validCheckpointBinding(binding) &&
602
+ readback.transactionId === binding.transactionId &&
603
+ readback.checkpointId === binding.checkpointId &&
604
+ readback.checkpointDigest === binding.checkpointDigest &&
605
+ readback.coverage === binding.coverage &&
606
+ readback.fileCoverage === binding.fileCoverage &&
607
+ readback.externalSideEffects === binding.externalSideEffects)
608
+ );
609
+ }
610
+
611
+ function checkpointEvidenceSnapshot(value) {
612
+ const outcome = ownDataValue(value, "outcome");
613
+ const fields =
614
+ outcome === "rolled_back"
615
+ ? [
616
+ ...CHECKPOINT_EVIDENCE_BASE_FIELDS,
617
+ "rollbackReason",
618
+ "verificationDigest",
619
+ ]
620
+ : CHECKPOINT_EVIDENCE_BASE_FIELDS;
621
+ return strictDataObjectSnapshot(
622
+ value,
623
+ fields,
624
+ "workflow managed checkpoint evidence",
625
+ 512 * 1024,
626
+ );
627
+ }
628
+
629
+ function boundedStringArray(value, maxItems = 4096, maxLength = 4096) {
630
+ return (
631
+ Array.isArray(value) &&
632
+ value.length <= maxItems &&
633
+ value.every(
634
+ (entry) =>
635
+ typeof entry === "string" &&
636
+ entry.length >= 1 &&
637
+ entry.length <= maxLength &&
638
+ !entry.includes("\0"),
639
+ )
640
+ );
641
+ }
642
+
643
+ function validCheckpointEvidence(value) {
644
+ if (
645
+ !value ||
646
+ value.version !== WORKSPACE_TRANSACTION_VERSION ||
647
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.transactionId || "") ||
648
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.checkpointId || "") ||
649
+ !SHA256_RE.test(value.checkpointDigest || "") ||
650
+ !SHA256_RE.test(value.writeManifestDigest || "") ||
651
+ !Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(
652
+ value.fileCoverage,
653
+ ) ||
654
+ !Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(value.coverage) ||
655
+ typeof value.externalSideEffects !== "boolean" ||
656
+ !["committed", "rolled_back"].includes(value.outcome) ||
657
+ !boundedStringArray(value.executions, 4096, 256) ||
658
+ !boundedStringArray(value.exclusions) ||
659
+ !boundedStringArray(value.uncoveredPaths) ||
660
+ !SHA256_RE.test(value.evidenceDigest || "") ||
661
+ (value.outcome === "rolled_back" &&
662
+ (typeof value.rollbackReason !== "string" ||
663
+ value.rollbackReason.length < 1 ||
664
+ value.rollbackReason.length > 1000 ||
665
+ !SHA256_RE.test(value.verificationDigest || "")))
666
+ ) {
667
+ return false;
668
+ }
669
+ const unsigned = { ...value };
670
+ delete unsigned.evidenceDigest;
671
+ return value.evidenceDigest === digestWorkspaceEvidence(unsigned);
672
+ }
673
+
674
+ function checkpointReadbackDigest(readback) {
675
+ return digest(
676
+ "chainlesschain.dynamic-workflow.checkpoint-store-readback.v1\0",
677
+ readback,
678
+ );
679
+ }
680
+
681
+ function validCheckpointReadback(value) {
682
+ return (
683
+ exactObjectFields(value, [
684
+ "schema",
685
+ "authority",
686
+ "transactionId",
687
+ "checkpointId",
688
+ "outcome",
689
+ "coverage",
690
+ "fileCoverage",
691
+ "externalSideEffects",
692
+ "checkpointDigest",
693
+ "writeManifestDigest",
694
+ "evidenceDigest",
695
+ "stateDigest",
696
+ ]) &&
697
+ value.schema === "cc-dynamic-workflow-checkpoint-readback/v1" &&
698
+ value.authority === "workspace-transaction-store-terminal-readback" &&
699
+ /^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.transactionId || "") &&
700
+ /^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.checkpointId || "") &&
701
+ ["committed", "rolled_back"].includes(value.outcome) &&
702
+ Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(value.coverage) &&
703
+ Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(
704
+ value.fileCoverage,
705
+ ) &&
706
+ typeof value.externalSideEffects === "boolean" &&
707
+ SHA256_RE.test(value.checkpointDigest || "") &&
708
+ SHA256_RE.test(value.writeManifestDigest || "") &&
709
+ SHA256_RE.test(value.evidenceDigest || "") &&
710
+ SHA256_RE.test(value.stateDigest || "")
711
+ );
712
+ }
713
+
714
+ function checkpointStoreReadback(evidence, checkpointStore, binding = null) {
715
+ if (
716
+ binding !== null &&
717
+ (!validCheckpointBinding(binding) ||
718
+ evidence.transactionId !== binding.transactionId ||
719
+ evidence.checkpointId !== binding.checkpointId ||
720
+ evidence.checkpointDigest !== binding.checkpointDigest ||
721
+ evidence.coverage !== binding.coverage ||
722
+ evidence.fileCoverage !== binding.fileCoverage ||
723
+ evidence.externalSideEffects !== binding.externalSideEffects)
724
+ ) {
725
+ throw new TypeError(
726
+ "workflow managed checkpoint settlement does not match its prepared binding",
727
+ );
728
+ }
729
+ if (!checkpointStore || typeof checkpointStore.inspect !== "function") {
730
+ throw new TypeError("workflow checkpoint store readback is unavailable");
731
+ }
732
+ let stored;
733
+ try {
734
+ stored = snapshotJson(
735
+ checkpointStore.inspect(evidence.transactionId),
736
+ "workflow checkpoint store state",
737
+ 2 * 1024 * 1024,
738
+ );
739
+ } catch {
740
+ throw new TypeError("workflow checkpoint store readback failed");
741
+ }
742
+ const unsignedState = { ...stored };
743
+ delete unsignedState.stateDigest;
744
+ const expectedState =
745
+ evidence.outcome === "committed"
746
+ ? WORKSPACE_TRANSACTION_STATE.COMMITTED
747
+ : WORKSPACE_TRANSACTION_STATE.ROLLED_BACK;
748
+ if (
749
+ stored.id !== evidence.transactionId ||
750
+ stored.checkpointId !== evidence.checkpointId ||
751
+ stored.state !== expectedState ||
752
+ !SHA256_RE.test(stored.stateDigest || "") ||
753
+ stored.stateDigest !== digestWorkspaceEvidence(unsignedState) ||
754
+ canonicalJson(stored.evidence, "workflowCheckpointEvidence") !==
755
+ canonicalJson(evidence, "workflowCheckpointEvidence")
756
+ ) {
757
+ throw new TypeError(
758
+ "workflow checkpoint store readback does not match settlement",
759
+ );
760
+ }
761
+ const readback = {
762
+ schema: "cc-dynamic-workflow-checkpoint-readback/v1",
763
+ authority: "workspace-transaction-store-terminal-readback",
764
+ transactionId: evidence.transactionId,
765
+ checkpointId: evidence.checkpointId,
766
+ outcome: evidence.outcome,
767
+ coverage: evidence.coverage,
768
+ fileCoverage: evidence.fileCoverage,
769
+ externalSideEffects: evidence.externalSideEffects,
770
+ checkpointDigest: evidence.checkpointDigest,
771
+ writeManifestDigest: evidence.writeManifestDigest,
772
+ evidenceDigest: evidence.evidenceDigest,
773
+ stateDigest: stored.stateDigest,
774
+ };
775
+ if (!checkpointReadbackMatchesBinding(readback, binding)) {
776
+ throw new TypeError(
777
+ "workflow checkpoint store readback does not match its prepared binding",
778
+ );
779
+ }
780
+ return readback;
781
+ }
782
+
783
+ function checkpointReadbackSettlement(event, checkpointStore, binding = null) {
784
+ const managed = ownDataValue(event?.result, "managedCheckpoint");
785
+ if (managed === undefined || managed === null) return null;
786
+ const skipped = ownDataValue(managed, "skipped");
787
+ const status = ownDataValue(managed, "status");
788
+ if (skipped === true) {
789
+ const observation = strictDataObjectSnapshot(
790
+ managed,
791
+ ["skipped", "toolName", "coverage", "fileCoverage", "reason"],
792
+ "workflow unavailable managed checkpoint",
793
+ );
794
+ if (
795
+ observation.toolName !== event.tool ||
796
+ observation.coverage !== WORKSPACE_TRANSACTION_COVERAGE.NONE ||
797
+ observation.fileCoverage !== WORKSPACE_TRANSACTION_COVERAGE.NONE ||
798
+ typeof observation.reason !== "string" ||
799
+ observation.reason.length < 1 ||
800
+ observation.reason.length > 256
801
+ ) {
802
+ throw new TypeError(
803
+ "workflow unavailable managed checkpoint is malformed",
804
+ );
805
+ }
806
+ return null;
807
+ }
808
+ if (["not_started", "recovery_required"].includes(status)) {
809
+ const observation = strictDataObjectSnapshot(
810
+ managed,
811
+ status === "not_started"
812
+ ? ["status", "coverage", "code"]
813
+ : [
814
+ "status",
815
+ "coverage",
816
+ "code",
817
+ "transactionId",
818
+ "checkpointId",
819
+ "settlement",
820
+ "originalToolError",
821
+ ],
822
+ "workflow unavailable managed checkpoint",
823
+ );
824
+ const hasToolError = Boolean(
825
+ event?.error || ownDataValue(event?.result, "error"),
826
+ );
827
+ if (
828
+ observation.coverage !== WORKSPACE_TRANSACTION_COVERAGE.NONE ||
829
+ !hasToolError
830
+ ) {
831
+ throw new TypeError(
832
+ "workflow unavailable managed checkpoint is malformed",
833
+ );
834
+ }
835
+ return null;
836
+ }
837
+ const settlement = strictDataObjectSnapshot(
838
+ managed,
839
+ [
840
+ "skipped",
841
+ "toolName",
842
+ "transactionId",
843
+ "checkpointId",
844
+ "evidence",
845
+ "coverage",
846
+ "fileCoverage",
847
+ ],
848
+ "workflow managed checkpoint settlement",
849
+ 1024 * 1024,
850
+ );
851
+ const evidence = checkpointEvidenceSnapshot(settlement.evidence);
852
+ if (
853
+ settlement.skipped !== false ||
854
+ settlement.toolName !== event.tool ||
855
+ settlement.transactionId !== evidence.transactionId ||
856
+ settlement.checkpointId !== evidence.checkpointId ||
857
+ settlement.coverage !== evidence.coverage ||
858
+ settlement.fileCoverage !== evidence.fileCoverage ||
859
+ !validCheckpointEvidence(evidence)
860
+ ) {
861
+ throw new TypeError("workflow managed checkpoint settlement is malformed");
862
+ }
863
+ return checkpointStoreReadback(evidence, checkpointStore, binding);
864
+ }
865
+
113
866
  function effectResultDigest(result) {
114
867
  return digest("chainlesschain.dynamic-workflow.effect-result.v1\0", result);
115
868
  }
@@ -248,7 +1001,14 @@ function expectedWorkflowProviderRequestId(effectId, source, sequence) {
248
1001
  .digest("hex")}`;
249
1002
  }
250
1003
 
251
- function verifyEffectCalls(effect, effectIndex, issues) {
1004
+ function verifyEffectCalls(
1005
+ effect,
1006
+ effectIndex,
1007
+ issues,
1008
+ startedLineageByCallId,
1009
+ receiptLineageByCallId,
1010
+ settlementLineageByCallId,
1011
+ ) {
252
1012
  const calls = effect?.calls === undefined ? [] : effect.calls;
253
1013
  if (!Array.isArray(calls) || calls.length > MAX_CALLS_PER_EFFECT) {
254
1014
  issues.push(`effect-${effectIndex}-calls-invalid`);
@@ -266,11 +1026,112 @@ function verifyEffectCalls(effect, effectIndex, issues) {
266
1026
  ? call?.source
267
1027
  : null
268
1028
  : call.requestSource;
1029
+ const startedLineage = startedLineageByCallId.get(call?.id) || [];
1030
+ const receiptLineage = receiptLineageByCallId.get(call?.id) || [];
1031
+ const settlementLineage = settlementLineageByCallId.get(call?.id) || [];
1032
+ const receiptLineageValid =
1033
+ receiptLineage.length === 1 &&
1034
+ receiptLineage[0]?.details?.effectId === effect.id &&
1035
+ receiptLineage[0]?.details?.ownerEffectId === ownerEffectId &&
1036
+ receiptLineage[0]?.details?.callRecordId === call?.id;
1037
+ const hasReceiptTimestamp = Object.prototype.hasOwnProperty.call(
1038
+ call || {},
1039
+ "providerReceiptRecordedAt",
1040
+ );
1041
+ const hasProviderUsage = Object.prototype.hasOwnProperty.call(
1042
+ call || {},
1043
+ "providerUsage",
1044
+ );
1045
+ const settlementUsageDigestPresent = settlementLineage.some((event) =>
1046
+ Object.prototype.hasOwnProperty.call(
1047
+ event?.details || {},
1048
+ "providerUsageDigest",
1049
+ ),
1050
+ );
1051
+ const providerCostFields = [
1052
+ "providerModel",
1053
+ "providerPricing",
1054
+ "providerCostEstimate",
1055
+ ];
1056
+ const providerCostFieldCount = providerCostFields.filter((field) =>
1057
+ Object.prototype.hasOwnProperty.call(call || {}, field),
1058
+ ).length;
1059
+ const hasProviderCostSchema =
1060
+ providerCostFieldCount === providerCostFields.length;
1061
+ const startedPricingFieldsPresent = startedLineage.some(
1062
+ (event) =>
1063
+ Object.prototype.hasOwnProperty.call(
1064
+ event?.details || {},
1065
+ "providerModel",
1066
+ ) ||
1067
+ Object.prototype.hasOwnProperty.call(
1068
+ event?.details || {},
1069
+ "providerPricingDigest",
1070
+ ),
1071
+ );
1072
+ const settlementCostDigestPresent = settlementLineage.some((event) =>
1073
+ Object.prototype.hasOwnProperty.call(
1074
+ event?.details || {},
1075
+ "providerCostEstimateDigest",
1076
+ ),
1077
+ );
1078
+ const hasArtifactReadback = Object.prototype.hasOwnProperty.call(
1079
+ call || {},
1080
+ "artifactReadback",
1081
+ );
1082
+ const startedArtifactSchemaPresent = startedLineage.some((event) =>
1083
+ Object.prototype.hasOwnProperty.call(
1084
+ event?.details || {},
1085
+ "artifactReadbackSchema",
1086
+ ),
1087
+ );
1088
+ const settlementArtifactDigestPresent = settlementLineage.some((event) =>
1089
+ Object.prototype.hasOwnProperty.call(
1090
+ event?.details || {},
1091
+ "artifactReadbackDigest",
1092
+ ),
1093
+ );
1094
+ const hasCheckpointReadback = Object.prototype.hasOwnProperty.call(
1095
+ call || {},
1096
+ "checkpointReadback",
1097
+ );
1098
+ const startedCheckpointSchemaPresent = startedLineage.some((event) =>
1099
+ Object.prototype.hasOwnProperty.call(
1100
+ event?.details || {},
1101
+ "checkpointReadbackSchema",
1102
+ ),
1103
+ );
1104
+ const settlementCheckpointDigestPresent = settlementLineage.some((event) =>
1105
+ Object.prototype.hasOwnProperty.call(
1106
+ event?.details || {},
1107
+ "checkpointReadbackDigest",
1108
+ ),
1109
+ );
1110
+ const hasCheckpointBinding = Object.prototype.hasOwnProperty.call(
1111
+ call || {},
1112
+ "checkpointBinding",
1113
+ );
1114
+ const startedCheckpointBindingDigestPresent = startedLineage.some((event) =>
1115
+ Object.prototype.hasOwnProperty.call(
1116
+ event?.details || {},
1117
+ "checkpointBindingDigest",
1118
+ ),
1119
+ );
1120
+ const checkpointBindingValid = hasCheckpointBinding
1121
+ ? startedLineage.length === 1 &&
1122
+ (call.checkpointBinding === null ||
1123
+ validCheckpointBinding(call.checkpointBinding)) &&
1124
+ startedLineage[0]?.details?.checkpointBindingDigest ===
1125
+ (call.checkpointBinding === null
1126
+ ? null
1127
+ : checkpointBindingDigest(call.checkpointBinding))
1128
+ : !startedCheckpointBindingDigestPresent;
269
1129
  const terminal = call?.status !== "started";
270
1130
  const identityKey = `${call?.kind || ""}\0${
271
1131
  call?.kind === "tool" ? call?.childEffectId : call?.callId
272
1132
  }`;
273
1133
  let timestampsValid = false;
1134
+ let receiptTimestampValid = false;
274
1135
  try {
275
1136
  timestampsValid =
276
1137
  isoNow(call?.startedAt) === call.startedAt &&
@@ -281,8 +1142,22 @@ function verifyEffectCalls(effect, effectIndex, issues) {
281
1142
  (effect.settledAt === null ||
282
1143
  Date.parse(call.settledAt) <= Date.parse(effect.settledAt))
283
1144
  : call?.settledAt === null);
1145
+ receiptTimestampValid = call?.providerReceiptPersisted
1146
+ ? receiptLineageValid
1147
+ ? hasReceiptTimestamp &&
1148
+ isoNow(call.providerReceiptRecordedAt) ===
1149
+ call.providerReceiptRecordedAt &&
1150
+ Date.parse(call.providerReceiptRecordedAt) >=
1151
+ Date.parse(call.startedAt) &&
1152
+ (!terminal ||
1153
+ Date.parse(call.providerReceiptRecordedAt) <=
1154
+ Date.parse(call.settledAt))
1155
+ : receiptLineage.length === 0 && !hasReceiptTimestamp && terminal
1156
+ : receiptLineage.length === 0 &&
1157
+ (call?.providerReceiptRecordedAt === null || !hasReceiptTimestamp);
284
1158
  } catch {
285
1159
  timestampsValid = false;
1160
+ receiptTimestampValid = false;
286
1161
  }
287
1162
  const commonValid =
288
1163
  call?.schema === DYNAMIC_WORKFLOW_CALL_SCHEMA &&
@@ -328,6 +1203,7 @@ function verifyEffectCalls(effect, effectIndex, issues) {
328
1203
  Boolean(
329
1204
  call.providerReceiptRequestId || call.providerReceiptResponseId,
330
1205
  ) &&
1206
+ receiptTimestampValid &&
331
1207
  (call.mcpLedgerId === null ||
332
1208
  PROVIDER_RECEIPT_ID_RE.test(call.mcpLedgerId || "")) &&
333
1209
  typeof call.mcpLedgerPrewritePersisted === "boolean" &&
@@ -355,8 +1231,63 @@ function verifyEffectCalls(effect, effectIndex, issues) {
355
1231
  call.sequence,
356
1232
  ) &&
357
1233
  call.identitySemantics === "trace-only" &&
358
- (call.providerReceiptPersisted === false ||
359
- ["completed", "outcome_unknown"].includes(call.status)) &&
1234
+ call.status !== "failed" &&
1235
+ (providerCostFieldCount === 0
1236
+ ? !startedPricingFieldsPresent && !settlementCostDigestPresent
1237
+ : hasProviderCostSchema &&
1238
+ (call.providerModel === null ||
1239
+ PROVIDER_MODEL_RE.test(call.providerModel || "")) &&
1240
+ (call.providerPricing === null ||
1241
+ validProviderPricing(
1242
+ call.providerPricing,
1243
+ call.name,
1244
+ call.providerModel,
1245
+ )) &&
1246
+ startedLineage.length === 1 &&
1247
+ startedLineage[0]?.details?.providerModel === call.providerModel &&
1248
+ startedLineage[0]?.details?.providerPricingDigest ===
1249
+ (call.providerPricing === null
1250
+ ? null
1251
+ : providerPricingDigest(call.providerPricing)) &&
1252
+ (call.status === "completed"
1253
+ ? (call.providerPricing === null
1254
+ ? call.providerCostEstimate === null
1255
+ : validProviderCostEstimate(
1256
+ call.providerCostEstimate,
1257
+ call.providerPricing,
1258
+ call.providerUsage,
1259
+ )) &&
1260
+ settlementLineage.length === 1 &&
1261
+ settlementLineage[0]?.details?.providerCostEstimateDigest ===
1262
+ (call.providerCostEstimate === null
1263
+ ? null
1264
+ : providerCostEstimateDigest(call.providerCostEstimate))
1265
+ : call.providerCostEstimate === null &&
1266
+ (call.status === "outcome_unknown"
1267
+ ? settlementLineage.length === 1 &&
1268
+ settlementLineage[0]?.details
1269
+ ?.providerCostEstimateDigest === null
1270
+ : !settlementCostDigestPresent))) &&
1271
+ (hasProviderUsage
1272
+ ? call.status === "completed"
1273
+ ? validProviderUsage(call.providerUsage) &&
1274
+ settlementLineage.length === 1 &&
1275
+ settlementLineage[0]?.details?.providerUsageDigest ===
1276
+ providerUsageDigest(call.providerUsage)
1277
+ : call.providerUsage === null &&
1278
+ (call.status === "outcome_unknown"
1279
+ ? settlementLineage.length === 1 &&
1280
+ settlementLineage[0]?.details?.providerUsageDigest === null
1281
+ : !settlementUsageDigestPresent)
1282
+ : !settlementUsageDigestPresent) &&
1283
+ !hasArtifactReadback &&
1284
+ !startedArtifactSchemaPresent &&
1285
+ !settlementArtifactDigestPresent &&
1286
+ !hasCheckpointReadback &&
1287
+ !startedCheckpointSchemaPresent &&
1288
+ !settlementCheckpointDigestPresent &&
1289
+ !hasCheckpointBinding &&
1290
+ !startedCheckpointBindingDigestPresent &&
360
1291
  call.mcpLedgerId === null &&
361
1292
  call.mcpLedgerPrewritePersisted === false &&
362
1293
  call.mcpLedgerSettlementPersisted === false);
@@ -379,14 +1310,62 @@ function verifyEffectCalls(effect, effectIndex, issues) {
379
1310
  call.providerReceiptPersisted === false &&
380
1311
  call.providerReceiptRequestId === null &&
381
1312
  call.providerReceiptResponseId === null &&
382
- call.identitySemantics === "runtime-derived");
1313
+ (call.providerReceiptRecordedAt === null || !hasReceiptTimestamp) &&
1314
+ call.identitySemantics === "runtime-derived" &&
1315
+ (call.name !== "publish_artifact"
1316
+ ? !hasArtifactReadback &&
1317
+ !startedArtifactSchemaPresent &&
1318
+ !settlementArtifactDigestPresent
1319
+ : hasArtifactReadback
1320
+ ? startedLineage.length === 1 &&
1321
+ startedLineage[0]?.details?.artifactReadbackSchema ===
1322
+ "cc-dynamic-workflow-artifact-readback/v1" &&
1323
+ (call.status === "completed"
1324
+ ? validArtifactReadback(call.artifactReadback) &&
1325
+ settlementLineage.length === 1 &&
1326
+ settlementLineage[0]?.details?.artifactReadbackDigest ===
1327
+ artifactReadbackDigest(call.artifactReadback)
1328
+ : call.artifactReadback === null &&
1329
+ (["failed", "outcome_unknown"].includes(call.status)
1330
+ ? settlementLineage.length === 1 &&
1331
+ settlementLineage[0]?.details?.artifactReadbackDigest ===
1332
+ null
1333
+ : !settlementArtifactDigestPresent))
1334
+ : !startedArtifactSchemaPresent &&
1335
+ !settlementArtifactDigestPresent) &&
1336
+ (managedToolCheckpointRequired(call.name)
1337
+ ? hasCheckpointReadback
1338
+ ? checkpointBindingValid &&
1339
+ startedLineage.length === 1 &&
1340
+ startedLineage[0]?.details?.checkpointReadbackSchema ===
1341
+ "cc-dynamic-workflow-checkpoint-readback/v1" &&
1342
+ (["completed", "failed", "outcome_unknown"].includes(call.status)
1343
+ ? (call.checkpointReadback === null ||
1344
+ (validCheckpointReadback(call.checkpointReadback) &&
1345
+ checkpointReadbackMatchesBinding(
1346
+ call.checkpointReadback,
1347
+ hasCheckpointBinding ? call.checkpointBinding : null,
1348
+ ))) &&
1349
+ settlementLineage.length === 1 &&
1350
+ settlementLineage[0]?.details?.checkpointReadbackDigest ===
1351
+ (call.checkpointReadback === null
1352
+ ? null
1353
+ : checkpointReadbackDigest(call.checkpointReadback))
1354
+ : call.checkpointReadback === null &&
1355
+ !settlementCheckpointDigestPresent)
1356
+ : !hasCheckpointBinding &&
1357
+ !startedCheckpointSchemaPresent &&
1358
+ !startedCheckpointBindingDigestPresent &&
1359
+ !settlementCheckpointDigestPresent
1360
+ : !hasCheckpointReadback &&
1361
+ !hasCheckpointBinding &&
1362
+ !startedCheckpointSchemaPresent &&
1363
+ !startedCheckpointBindingDigestPresent &&
1364
+ !settlementCheckpointDigestPresent));
383
1365
  const settlementValid = terminal
384
1366
  ? call.settlementCode !== null || call.status === "completed"
385
1367
  : call.settlementCode === null &&
386
1368
  call.outcomeUnknown === false &&
387
- call.providerReceiptPersisted === false &&
388
- call.providerReceiptRequestId === null &&
389
- call.providerReceiptResponseId === null &&
390
1369
  call.mcpLedgerId === null &&
391
1370
  call.mcpLedgerPrewritePersisted === false &&
392
1371
  call.mcpLedgerSettlementPersisted === false;
@@ -416,6 +1395,105 @@ function verifyEffectCalls(effect, effectIndex, issues) {
416
1395
  }
417
1396
  }
418
1397
 
1398
+ function verifyInputRequests(state, issues) {
1399
+ if (state.inputRequests === undefined) {
1400
+ if (["input_requested", "needs_input"].includes(state.status)) {
1401
+ issues.push("input-requests-missing");
1402
+ }
1403
+ return;
1404
+ }
1405
+ if (
1406
+ !Array.isArray(state.inputRequests) ||
1407
+ state.inputRequests.length > MAX_INPUT_REQUESTS
1408
+ ) {
1409
+ issues.push("input-requests-invalid");
1410
+ return;
1411
+ }
1412
+ const ids = new Set();
1413
+ const stepIds = new Set();
1414
+ let pendingCount = 0;
1415
+ for (const [index, request] of state.inputRequests.entries()) {
1416
+ let valid = true;
1417
+ let normalized = null;
1418
+ const plainRequest =
1419
+ request && typeof request === "object" && !Array.isArray(request);
1420
+ try {
1421
+ normalized = normalizeStageInputRequest({
1422
+ stepId: request?.stepId,
1423
+ prompt: request?.prompt,
1424
+ options: request?.options,
1425
+ multiSelect: request?.multiSelect,
1426
+ });
1427
+ } catch {
1428
+ valid = false;
1429
+ }
1430
+ const expectedId = normalized
1431
+ ? stageInputRequestIdentity(state.runId, normalized)
1432
+ : null;
1433
+ const pending = request?.status === "pending";
1434
+ const answered = request?.status === "answered";
1435
+ const cancelled = request?.status === "cancelled";
1436
+ if (pending) pendingCount += 1;
1437
+ if (
1438
+ !plainRequest ||
1439
+ request?.schema !== DYNAMIC_WORKFLOW_INPUT_REQUEST_SCHEMA ||
1440
+ (plainRequest && Reflect.ownKeys(request).length !== 11) ||
1441
+ request?.id !== expectedId ||
1442
+ ids.has(request?.id) ||
1443
+ stepIds.has(request?.stepId) ||
1444
+ (!pending && !answered && !cancelled) ||
1445
+ isoNow(request?.requestedAt) !== request.requestedAt ||
1446
+ (pending &&
1447
+ (request.resolvedAt !== null ||
1448
+ request.responseDigest !== null ||
1449
+ request.response !== null))
1450
+ ) {
1451
+ valid = false;
1452
+ }
1453
+ if (answered) {
1454
+ let answer = null;
1455
+ try {
1456
+ answer = normalizeStageInputAnswer(request.response, request);
1457
+ } catch {
1458
+ valid = false;
1459
+ }
1460
+ if (
1461
+ answer === null ||
1462
+ !SHA256_RE.test(request.responseDigest || "") ||
1463
+ request.responseDigest !==
1464
+ stageInputResponseDigest(request.id, request.response) ||
1465
+ typeof request.resolvedAt !== "string" ||
1466
+ isoNow(request.resolvedAt) !== request.resolvedAt ||
1467
+ Date.parse(request.resolvedAt) < Date.parse(request.requestedAt)
1468
+ ) {
1469
+ valid = false;
1470
+ }
1471
+ }
1472
+ if (
1473
+ cancelled &&
1474
+ (typeof request.resolvedAt !== "string" ||
1475
+ isoNow(request.resolvedAt) !== request.resolvedAt ||
1476
+ Date.parse(request.resolvedAt) < Date.parse(request.requestedAt) ||
1477
+ request.responseDigest !== null ||
1478
+ request.response !== null)
1479
+ ) {
1480
+ valid = false;
1481
+ }
1482
+ if (!valid) issues.push(`input-request-${index}-invalid`);
1483
+ ids.add(request?.id);
1484
+ stepIds.add(request?.stepId);
1485
+ }
1486
+ const inputStatus = ["input_requested", "needs_input"].includes(state.status);
1487
+ if (
1488
+ pendingCount > 1 ||
1489
+ inputStatus !== (pendingCount === 1) ||
1490
+ (state.status === "needs_input" &&
1491
+ state.effects?.some((effect) => effect?.status === "pending"))
1492
+ ) {
1493
+ issues.push("input-request-state-invalid");
1494
+ }
1495
+ }
1496
+
419
1497
  export function verifyDynamicWorkflowRuntimeState(value) {
420
1498
  const state = snapshotJson(value, "workflow runtime state");
421
1499
  const issues = [];
@@ -441,6 +1519,31 @@ export function verifyDynamicWorkflowRuntimeState(value) {
441
1519
  ) {
442
1520
  issues.push("authority-binding-invalid");
443
1521
  }
1522
+ if (state.executionBudget !== undefined && state.executionBudget !== null) {
1523
+ const budget = state.executionBudget;
1524
+ const fields = [
1525
+ "maxExpandedTasks",
1526
+ "maxParallel",
1527
+ "maxTokens",
1528
+ "maxUsd",
1529
+ "maxDurationMs",
1530
+ ];
1531
+ const validBudget =
1532
+ budget &&
1533
+ typeof budget === "object" &&
1534
+ !Array.isArray(budget) &&
1535
+ Object.keys(budget).length === fields.length &&
1536
+ fields.every((field) => Object.hasOwn(budget, field)) &&
1537
+ fields.every((field) => {
1538
+ const amount = budget[field];
1539
+ if (amount === null) return true;
1540
+ if (!Number.isFinite(amount) || amount < 0) return false;
1541
+ return (
1542
+ field === "maxUsd" || (Number.isSafeInteger(amount) && amount >= 1)
1543
+ );
1544
+ });
1545
+ if (!validBudget) issues.push("execution-budget-invalid");
1546
+ }
444
1547
  if (
445
1548
  !Number.isSafeInteger(state.revision) ||
446
1549
  state.revision < 1 ||
@@ -452,6 +1555,43 @@ export function verifyDynamicWorkflowRuntimeState(value) {
452
1555
  if (!Array.isArray(state.effects) || state.effects.length > MAX_EFFECTS) {
453
1556
  issues.push("effects-invalid");
454
1557
  } else {
1558
+ const startedLineageByCallId = new Map();
1559
+ const receiptLineageByCallId = new Map();
1560
+ const settlementLineageByCallId = new Map();
1561
+ for (const event of state.lineage) {
1562
+ const callRecordId = event?.details?.callRecordId;
1563
+ const target =
1564
+ event.type === "effect-call-started"
1565
+ ? startedLineageByCallId
1566
+ : event.type === "effect-call-receipt-recorded"
1567
+ ? receiptLineageByCallId
1568
+ : event.type === "effect-call-settled"
1569
+ ? settlementLineageByCallId
1570
+ : null;
1571
+ if (!target) continue;
1572
+ if (!target.has(callRecordId)) {
1573
+ target.set(callRecordId, []);
1574
+ }
1575
+ target.get(callRecordId).push(event);
1576
+ }
1577
+ const knownCallIds = new Set(
1578
+ state.effects.flatMap((effect) =>
1579
+ Array.isArray(effect?.calls)
1580
+ ? effect.calls.map((call) => call?.id)
1581
+ : [],
1582
+ ),
1583
+ );
1584
+ let receiptLineageIndex = 0;
1585
+ for (const [callRecordId, events] of receiptLineageByCallId) {
1586
+ if (
1587
+ !SHA256_RE.test(callRecordId || "") ||
1588
+ !knownCallIds.has(callRecordId) ||
1589
+ events.length !== 1
1590
+ ) {
1591
+ issues.push(`receipt-lineage-${receiptLineageIndex}-invalid`);
1592
+ }
1593
+ receiptLineageIndex += 1;
1594
+ }
455
1595
  const ids = new Set();
456
1596
  const keys = new Set();
457
1597
  const batches = new Map();
@@ -569,7 +1709,14 @@ export function verifyDynamicWorkflowRuntimeState(value) {
569
1709
  }
570
1710
  ids.add(effect?.id);
571
1711
  keys.add(effect?.key);
572
- verifyEffectCalls(effect, index, issues);
1712
+ verifyEffectCalls(
1713
+ effect,
1714
+ index,
1715
+ issues,
1716
+ startedLineageByCallId,
1717
+ receiptLineageByCallId,
1718
+ settlementLineageByCallId,
1719
+ );
573
1720
  if (batchMetadataValid && hasBatchMetadata) {
574
1721
  if (!batches.has(effect.batchId)) batches.set(effect.batchId, []);
575
1722
  batches.get(effect.batchId).push(effect);
@@ -596,6 +1743,7 @@ export function verifyDynamicWorkflowRuntimeState(value) {
596
1743
  batchIndex += 1;
597
1744
  }
598
1745
  }
1746
+ verifyInputRequests(state, issues);
599
1747
  if (
600
1748
  (state.status === "completed") !== (state.finalRecord != null) ||
601
1749
  (state.status === "stopped" && state.finalRecord != null)
@@ -800,6 +1948,22 @@ export function readDynamicWorkflowEffectResultFile(filePath) {
800
1948
  );
801
1949
  }
802
1950
 
1951
+ export function readDynamicWorkflowInputResponseFile(filePath) {
1952
+ const payload = readDynamicWorkflowEffectResultFile(filePath);
1953
+ if (
1954
+ !payload ||
1955
+ typeof payload !== "object" ||
1956
+ Array.isArray(payload) ||
1957
+ Reflect.ownKeys(payload).length !== 1 ||
1958
+ !Object.hasOwn(payload, "answer")
1959
+ ) {
1960
+ throw new Error(
1961
+ 'workflow input response file must contain exactly {"answer": ...}',
1962
+ );
1963
+ }
1964
+ return payload.answer;
1965
+ }
1966
+
803
1967
  function stateBindings(execution, runId) {
804
1968
  const admission = execution.runAdmission;
805
1969
  if (!admission || typeof admission !== "object") {
@@ -843,6 +2007,11 @@ function stateBindings(execution, runId) {
843
2007
  "workflow execution policy",
844
2008
  64 * 1024,
845
2009
  ),
2010
+ executionBudget: snapshotJson(
2011
+ execution.workflow?.facade?.budget || null,
2012
+ "workflow execution budget",
2013
+ 16 * 1024,
2014
+ ),
846
2015
  };
847
2016
  }
848
2017
 
@@ -856,7 +2025,10 @@ function bindingsMatch(state, bindings) {
856
2025
  "executionAuthoritySessionId",
857
2026
  ].every((field) => state[field] === bindings[field]) &&
858
2027
  canonicalJson(state.executionPolicy) ===
859
- canonicalJson(bindings.executionPolicy)
2028
+ canonicalJson(bindings.executionPolicy) &&
2029
+ (state.executionBudget === undefined ||
2030
+ canonicalJson(state.executionBudget) ===
2031
+ canonicalJson(bindings.executionBudget))
860
2032
  );
861
2033
  }
862
2034
 
@@ -869,6 +2041,7 @@ function createState(bindings, now) {
869
2041
  revision: 0,
870
2042
  status: "ready",
871
2043
  effects: [],
2044
+ inputRequests: [],
872
2045
  finalRecord: null,
873
2046
  createdAt: at,
874
2047
  updatedAt: at,
@@ -892,14 +2065,110 @@ function controlError(message, reason, state, effect = null, cause = null) {
892
2065
  return error;
893
2066
  }
894
2067
 
895
- function startRun(statePath, bindings, now) {
896
- return withStateMutation(statePath, (current) => {
897
- let state = current || createState(bindings, now);
898
- if (!bindingsMatch(state, bindings)) {
899
- throw new Error(
900
- "durable workflow run authority does not match stored state",
901
- );
902
- }
2068
+ function runtimeInputRequests(state) {
2069
+ return Array.isArray(state?.inputRequests) ? state.inputRequests : [];
2070
+ }
2071
+
2072
+ function normalizeStageInputRequest(value) {
2073
+ const request = snapshotJson(
2074
+ value,
2075
+ "workflow stage input request",
2076
+ 64 * 1024,
2077
+ );
2078
+ const fields = Object.keys(request || {});
2079
+ if (
2080
+ !request ||
2081
+ typeof request !== "object" ||
2082
+ Array.isArray(request) ||
2083
+ fields.length !== 4 ||
2084
+ !["stepId", "prompt", "options", "multiSelect"].every((field) =>
2085
+ Object.hasOwn(request, field),
2086
+ ) ||
2087
+ typeof request.stepId !== "string" ||
2088
+ request.stepId.length === 0 ||
2089
+ request.stepId.length > 512 ||
2090
+ typeof request.prompt !== "string" ||
2091
+ request.prompt.trim() !== request.prompt ||
2092
+ request.prompt.length === 0 ||
2093
+ request.prompt.length > 4096 ||
2094
+ typeof request.multiSelect !== "boolean" ||
2095
+ !(
2096
+ request.options === null ||
2097
+ (Array.isArray(request.options) &&
2098
+ request.options.length > 0 &&
2099
+ request.options.length <= MAX_INPUT_OPTIONS &&
2100
+ request.options.every(
2101
+ (option) =>
2102
+ typeof option === "string" &&
2103
+ option.trim() === option &&
2104
+ option.length > 0 &&
2105
+ option.length <= 512,
2106
+ ) &&
2107
+ new Set(request.options).size === request.options.length)
2108
+ ) ||
2109
+ (request.multiSelect && request.options === null)
2110
+ ) {
2111
+ throw new TypeError("workflow stage input request is malformed");
2112
+ }
2113
+ if (containsSecret(JSON.stringify(request))) {
2114
+ throw new Error("workflow stage input request contains secret-shaped data");
2115
+ }
2116
+ return request;
2117
+ }
2118
+
2119
+ function stageInputRequestIdentity(runId, request) {
2120
+ return digest("chainlesschain.dynamic-workflow.input-request.v1\0", {
2121
+ runId,
2122
+ request,
2123
+ });
2124
+ }
2125
+
2126
+ function normalizeStageInputAnswer(value, request) {
2127
+ const answer = snapshotJson(
2128
+ value,
2129
+ "workflow stage input answer",
2130
+ MAX_INPUT_RESPONSE_BYTES,
2131
+ );
2132
+ if (request.multiSelect) {
2133
+ if (
2134
+ !Array.isArray(answer) ||
2135
+ answer.length === 0 ||
2136
+ answer.length > MAX_INPUT_OPTIONS ||
2137
+ answer.some((item) => typeof item !== "string") ||
2138
+ new Set(answer).size !== answer.length ||
2139
+ answer.some((item) => !request.options.includes(item))
2140
+ ) {
2141
+ throw new TypeError("workflow stage input answer is malformed");
2142
+ }
2143
+ } else if (
2144
+ typeof answer !== "string" ||
2145
+ answer.length === 0 ||
2146
+ Buffer.byteLength(answer, "utf8") > MAX_INPUT_RESPONSE_BYTES ||
2147
+ (request.options !== null && !request.options.includes(answer))
2148
+ ) {
2149
+ throw new TypeError("workflow stage input answer is malformed");
2150
+ }
2151
+ if (containsSecret(JSON.stringify(answer))) {
2152
+ throw new Error("workflow stage input answer contains secret-shaped data");
2153
+ }
2154
+ return answer;
2155
+ }
2156
+
2157
+ function stageInputResponseDigest(requestId, answer) {
2158
+ return digest("chainlesschain.dynamic-workflow.input-response.v1\0", {
2159
+ requestId,
2160
+ answer,
2161
+ });
2162
+ }
2163
+
2164
+ function startRun(statePath, bindings, now) {
2165
+ return withStateMutation(statePath, (current) => {
2166
+ let state = current || createState(bindings, now);
2167
+ if (!bindingsMatch(state, bindings)) {
2168
+ throw new Error(
2169
+ "durable workflow run authority does not match stored state",
2170
+ );
2171
+ }
903
2172
  if (state.status === "completed") return { value: state };
904
2173
  if (state.status !== "ready") {
905
2174
  throw new Error(
@@ -919,6 +2188,78 @@ function startRun(statePath, bindings, now) {
919
2188
  });
920
2189
  }
921
2190
 
2191
+ function resolveDurableWorkflowStageInput(statePath, runId, rawRequest, now) {
2192
+ const request = normalizeStageInputRequest(rawRequest);
2193
+ const requestId = stageInputRequestIdentity(runId, request);
2194
+ const outcome = withStateMutation(statePath, (current) => {
2195
+ if (!current || current.runId !== runId) {
2196
+ throw new Error("durable workflow run disappeared before input request");
2197
+ }
2198
+ const inputRequests = runtimeInputRequests(current);
2199
+ const matchingStep = inputRequests.find(
2200
+ (entry) => entry.stepId === request.stepId,
2201
+ );
2202
+ if (matchingStep && matchingStep.id !== requestId) {
2203
+ throw new Error("workflow stage input request drifted during replay");
2204
+ }
2205
+ if (matchingStep?.status === "answered") {
2206
+ return { value: { answer: matchingStep.response, state: current } };
2207
+ }
2208
+ if (matchingStep?.status === "pending") {
2209
+ return {
2210
+ value: { pending: matchingStep, state: current },
2211
+ };
2212
+ }
2213
+ const existingPending = inputRequests.find(
2214
+ (entry) => entry.status === "pending",
2215
+ );
2216
+ if (existingPending) {
2217
+ return {
2218
+ value: { pending: existingPending, state: current },
2219
+ };
2220
+ }
2221
+ if (current.status !== "running") {
2222
+ throw new Error(
2223
+ `durable workflow run cannot request input while ${current.status}`,
2224
+ );
2225
+ }
2226
+ if (inputRequests.length >= MAX_INPUT_REQUESTS) {
2227
+ throw new Error("durable workflow input request limit exceeded");
2228
+ }
2229
+ const requestedAt = isoNow(now);
2230
+ const pending = {
2231
+ schema: DYNAMIC_WORKFLOW_INPUT_REQUEST_SCHEMA,
2232
+ id: requestId,
2233
+ stepId: request.stepId,
2234
+ prompt: request.prompt,
2235
+ options: request.options,
2236
+ multiSelect: request.multiSelect,
2237
+ status: "pending",
2238
+ requestedAt,
2239
+ resolvedAt: null,
2240
+ responseDigest: null,
2241
+ response: null,
2242
+ };
2243
+ const state = transition(
2244
+ current,
2245
+ "input-requested",
2246
+ { requestId, stepId: request.stepId },
2247
+ (draft) => {
2248
+ draft.inputRequests = [...runtimeInputRequests(draft), pending];
2249
+ draft.status = "input_requested";
2250
+ },
2251
+ now,
2252
+ );
2253
+ return { state, value: { pending, state } };
2254
+ });
2255
+ if (outcome.answer !== undefined) return outcome.answer;
2256
+ throw controlError(
2257
+ `workflow stage ${outcome.pending.stepId} needs input`,
2258
+ "needs-input",
2259
+ outcome.state,
2260
+ );
2261
+ }
2262
+
922
2263
  function effectKey(effect) {
923
2264
  const stepId = String(effect?.stepId || "");
924
2265
  const iteration = Number(effect?.iteration);
@@ -983,6 +2324,11 @@ function requestEffectBatch(statePath, runId, argsList, now) {
983
2324
  value: { control: "paused", state: current, results: null },
984
2325
  };
985
2326
  }
2327
+ if (["input_requested", "needs_input"].includes(current.status)) {
2328
+ return {
2329
+ value: { control: "needs-input", state: current, results: null },
2330
+ };
2331
+ }
986
2332
  if (current.status === "stopped") {
987
2333
  return {
988
2334
  value: { control: "stopped", state: current, results: null },
@@ -1169,7 +2515,11 @@ function markEffectProviderDispatched(statePath, runId, effectId, now) {
1169
2515
  if (current.status === "stopped") {
1170
2516
  return { value: { control: "stopped", state: current, effect } };
1171
2517
  }
1172
- if (!["running", "pause_requested", "blocked"].includes(current.status)) {
2518
+ if (
2519
+ !["running", "pause_requested", "input_requested", "blocked"].includes(
2520
+ current.status,
2521
+ )
2522
+ ) {
1173
2523
  throw new Error(
1174
2524
  `workflow effect cannot be dispatched while ${current.status}`,
1175
2525
  );
@@ -1243,10 +2593,90 @@ function ownDataValue(value, property) {
1243
2593
  }
1244
2594
  }
1245
2595
 
2596
+ function providerUsageSettlement(event) {
2597
+ const usage = ownDataValue(event, "usage");
2598
+ let descriptors;
2599
+ try {
2600
+ if (
2601
+ !usage ||
2602
+ typeof usage !== "object" ||
2603
+ Array.isArray(usage) ||
2604
+ utilTypes.isProxy(usage)
2605
+ ) {
2606
+ throw new TypeError();
2607
+ }
2608
+ descriptors = Object.getOwnPropertyDescriptors(usage);
2609
+ } catch {
2610
+ throw new TypeError("workflow provider token usage is malformed");
2611
+ }
2612
+ const fields = Reflect.ownKeys(descriptors);
2613
+ if (
2614
+ fields.length > 32 ||
2615
+ fields.some(
2616
+ (field) =>
2617
+ typeof field !== "string" ||
2618
+ descriptors[field].enumerable !== true ||
2619
+ !Object.hasOwn(descriptors[field], "value"),
2620
+ )
2621
+ ) {
2622
+ throw new TypeError("workflow provider token usage is malformed");
2623
+ }
2624
+ const count = (canonical, alias, required) => {
2625
+ const canonicalField = descriptors[canonical];
2626
+ const aliasField = descriptors[alias];
2627
+ if (!canonicalField && !aliasField) {
2628
+ if (required) {
2629
+ throw new TypeError("workflow provider token usage is incomplete");
2630
+ }
2631
+ return 0;
2632
+ }
2633
+ const canonicalValue = canonicalField?.value;
2634
+ const aliasValue = aliasField?.value;
2635
+ if (
2636
+ (canonicalField && aliasField && canonicalValue !== aliasValue) ||
2637
+ nonNegativeSafeInteger(canonicalField ? canonicalValue : aliasValue) ===
2638
+ null ||
2639
+ (canonicalField ? canonicalValue : aliasValue) > MAX_PROVIDER_TOKEN_COUNT
2640
+ ) {
2641
+ throw new TypeError("workflow provider token usage is malformed");
2642
+ }
2643
+ return canonicalField ? canonicalValue : aliasValue;
2644
+ };
2645
+ const inputTokens = count("input_tokens", "prompt_tokens", true);
2646
+ const outputTokens = count("output_tokens", "completion_tokens", true);
2647
+ const cacheReadInputTokens = count(
2648
+ "cache_read_input_tokens",
2649
+ "cache_read_tokens",
2650
+ false,
2651
+ );
2652
+ const cacheCreationInputTokens = count(
2653
+ "cache_creation_input_tokens",
2654
+ "cache_creation_tokens",
2655
+ false,
2656
+ );
2657
+ const totalTokens =
2658
+ inputTokens +
2659
+ outputTokens +
2660
+ cacheReadInputTokens +
2661
+ cacheCreationInputTokens;
2662
+ if (!Number.isSafeInteger(totalTokens)) {
2663
+ throw new TypeError("workflow provider token usage exceeds its limit");
2664
+ }
2665
+ return {
2666
+ schema: "cc-provider-token-usage/v1",
2667
+ inputTokens,
2668
+ outputTokens,
2669
+ cacheReadInputTokens,
2670
+ cacheCreationInputTokens,
2671
+ totalTokens,
2672
+ };
2673
+ }
2674
+
1246
2675
  function workflowCallAttempt(effectId, kind, event, now) {
1247
2676
  const startedAt = isoNow(now);
1248
2677
  const ownerEffectId = event?.workflowEffectId;
1249
2678
  if (kind === "provider") {
2679
+ const providerModel = event?.model == null ? null : event.model;
1250
2680
  const requestSource =
1251
2681
  event?.workflowRequestSource ||
1252
2682
  (event?.source === "subagent" ? "model" : event?.source);
@@ -1255,6 +2685,8 @@ function workflowCallAttempt(effectId, kind, event, now) {
1255
2685
  !SHA256_RE.test(ownerEffectId || "") ||
1256
2686
  !WORKFLOW_PROVIDER_CALL_ID_RE.test(event.callId || "") ||
1257
2687
  !PROVIDER_NAME_RE.test(event.provider || "") ||
2688
+ (providerModel !== null &&
2689
+ !PROVIDER_MODEL_RE.test(providerModel || "")) ||
1258
2690
  !["model", "semantic-compaction", "subagent"].includes(event.source) ||
1259
2691
  !["model", "semantic-compaction"].includes(requestSource) ||
1260
2692
  (event.source === "subagent"
@@ -1296,6 +2728,11 @@ function workflowCallAttempt(effectId, kind, event, now) {
1296
2728
  providerReceiptPersisted: false,
1297
2729
  providerReceiptRequestId: null,
1298
2730
  providerReceiptResponseId: null,
2731
+ providerReceiptRecordedAt: null,
2732
+ providerUsage: null,
2733
+ providerModel,
2734
+ providerPricing: providerPricingSnapshot(event.provider, providerModel),
2735
+ providerCostEstimate: null,
1299
2736
  mcpLedgerId: null,
1300
2737
  mcpLedgerPrewritePersisted: false,
1301
2738
  mcpLedgerSettlementPersisted: false,
@@ -1321,6 +2758,18 @@ function workflowCallAttempt(effectId, kind, event, now) {
1321
2758
  ) {
1322
2759
  throw new TypeError("workflow tool call boundary is malformed");
1323
2760
  }
2761
+ const checkpointRequired = managedToolCheckpointRequired(event.tool);
2762
+ const checkpointBindingValue = ownDataValue(
2763
+ event,
2764
+ "managedCheckpointBinding",
2765
+ );
2766
+ const checkpointBinding = checkpointBindingSnapshot(checkpointBindingValue);
2767
+ if (
2768
+ (!checkpointRequired && checkpointBindingValue !== undefined) ||
2769
+ (checkpointBinding !== null && !validCheckpointBinding(checkpointBinding))
2770
+ ) {
2771
+ throw new TypeError("workflow tool checkpoint boundary is malformed");
2772
+ }
1324
2773
  return {
1325
2774
  schema: DYNAMIC_WORKFLOW_CALL_SCHEMA,
1326
2775
  id: workflowCallId(
@@ -1350,6 +2799,11 @@ function workflowCallAttempt(effectId, kind, event, now) {
1350
2799
  providerReceiptPersisted: false,
1351
2800
  providerReceiptRequestId: null,
1352
2801
  providerReceiptResponseId: null,
2802
+ providerReceiptRecordedAt: null,
2803
+ ...(event.tool === "publish_artifact" ? { artifactReadback: null } : {}),
2804
+ ...(checkpointRequired
2805
+ ? { checkpointBinding, checkpointReadback: null }
2806
+ : {}),
1353
2807
  mcpLedgerId: null,
1354
2808
  mcpLedgerPrewritePersisted: false,
1355
2809
  mcpLedgerSettlementPersisted: false,
@@ -1406,6 +2860,32 @@ function beginEffectCall(statePath, runId, effectId, kind, event, now) {
1406
2860
  ownerEffectId: attempt.ownerEffectId,
1407
2861
  callRecordId: attempt.id,
1408
2862
  kind,
2863
+ ...(kind === "provider"
2864
+ ? {
2865
+ providerModel: attempt.providerModel,
2866
+ providerPricingDigest:
2867
+ attempt.providerPricing === null
2868
+ ? null
2869
+ : providerPricingDigest(attempt.providerPricing),
2870
+ }
2871
+ : {}),
2872
+ ...(kind === "tool" && attempt.name === "publish_artifact"
2873
+ ? {
2874
+ artifactReadbackSchema:
2875
+ "cc-dynamic-workflow-artifact-readback/v1",
2876
+ }
2877
+ : {}),
2878
+ ...(kind === "tool" &&
2879
+ Object.prototype.hasOwnProperty.call(attempt, "checkpointReadback")
2880
+ ? {
2881
+ checkpointReadbackSchema:
2882
+ "cc-dynamic-workflow-checkpoint-readback/v1",
2883
+ checkpointBindingDigest:
2884
+ attempt.checkpointBinding === null
2885
+ ? null
2886
+ : checkpointBindingDigest(attempt.checkpointBinding),
2887
+ }
2888
+ : {}),
1409
2889
  },
1410
2890
  (draft) => {
1411
2891
  draft.effects[index] = {
@@ -1492,12 +2972,88 @@ function providerReceiptSettlement(call, event) {
1492
2972
  return { persisted: true, requestId, responseId };
1493
2973
  }
1494
2974
 
1495
- function effectCallSettlement(call, event, now) {
2975
+ function recordEffectCallReceipt(statePath, runId, effectId, event, now) {
2976
+ return withStateMutation(statePath, (current) => {
2977
+ if (!current || current.runId !== runId) {
2978
+ throw new Error(
2979
+ "durable workflow run disappeared before provider receipt prewrite",
2980
+ );
2981
+ }
2982
+ const effectIndex = current.effects.findIndex(
2983
+ (effect) => effect.id === effectId,
2984
+ );
2985
+ const effect = current.effects[effectIndex];
2986
+ const callIndex = (effect?.calls || []).findIndex(
2987
+ (call) => call.kind === "provider" && call.callId === event?.callId,
2988
+ );
2989
+ const call = effect?.calls?.[callIndex];
2990
+ if (
2991
+ !effect ||
2992
+ effect.status !== "pending" ||
2993
+ !call ||
2994
+ call.status !== "started" ||
2995
+ call.providerReceiptPersisted === true
2996
+ ) {
2997
+ throw new Error("workflow provider call cannot record this receipt");
2998
+ }
2999
+ if (
3000
+ event?.type !== "provider-request-receipt" ||
3001
+ event.provider !== call.name ||
3002
+ event.source !== call.source ||
3003
+ event.workflowRequestSource !== call.requestSource ||
3004
+ event.workflowEffectId !== (call.ownerEffectId ?? call.effectId)
3005
+ ) {
3006
+ throw new TypeError("workflow provider receipt envelope is malformed");
3007
+ }
3008
+ const receipt = providerReceiptSettlement(call, event);
3009
+ if (!receipt.persisted) {
3010
+ throw new TypeError("workflow provider receipt envelope is empty");
3011
+ }
3012
+ const providerReceiptRecordedAt = isoNow(now);
3013
+ const state = transition(
3014
+ current,
3015
+ "effect-call-receipt-recorded",
3016
+ {
3017
+ effectId,
3018
+ ownerEffectId: call.ownerEffectId ?? call.effectId,
3019
+ callRecordId: call.id,
3020
+ },
3021
+ (draft) => {
3022
+ const calls = [...(draft.effects[effectIndex].calls || [])];
3023
+ calls[callIndex] = {
3024
+ ...calls[callIndex],
3025
+ providerReceiptPersisted: true,
3026
+ providerReceiptRequestId: receipt.requestId,
3027
+ providerReceiptResponseId: receipt.responseId,
3028
+ providerReceiptRecordedAt,
3029
+ };
3030
+ draft.effects[effectIndex] = {
3031
+ ...draft.effects[effectIndex],
3032
+ calls,
3033
+ };
3034
+ },
3035
+ now,
3036
+ );
3037
+ return { state, value: state.effects[effectIndex].calls[callIndex] };
3038
+ });
3039
+ }
3040
+
3041
+ function effectCallSettlement(
3042
+ call,
3043
+ event,
3044
+ now,
3045
+ artifactStore,
3046
+ checkpointStore,
3047
+ ) {
1496
3048
  let status;
1497
3049
  let settlementCode = null;
1498
- let providerReceiptPersisted = false;
1499
- let providerReceiptRequestId = null;
1500
- let providerReceiptResponseId = null;
3050
+ let providerUsage = call.providerUsage;
3051
+ let providerCostEstimate = call.providerCostEstimate;
3052
+ let providerReceiptPersisted = call.providerReceiptPersisted;
3053
+ let providerReceiptRequestId = call.providerReceiptRequestId;
3054
+ let providerReceiptResponseId = call.providerReceiptResponseId;
3055
+ let artifactReadback = call.artifactReadback;
3056
+ let checkpointReadback = call.checkpointReadback;
1501
3057
  let mcpLedgerId = null;
1502
3058
  let mcpLedgerPrewritePersisted = false;
1503
3059
  let mcpLedgerSettlementPersisted = false;
@@ -1506,19 +3062,35 @@ function effectCallSettlement(call, event, now) {
1506
3062
  !["token-usage", "model-usage-unknown"].includes(event?.type) ||
1507
3063
  event.callId !== call.callId ||
1508
3064
  (event.provider && event.provider !== call.name) ||
3065
+ (Object.prototype.hasOwnProperty.call(call, "providerModel") &&
3066
+ event.model != null &&
3067
+ event.model !== call.providerModel) ||
1509
3068
  (event.source && event.source !== call.source)
1510
3069
  ) {
1511
3070
  throw new TypeError("workflow provider call settlement is malformed");
1512
3071
  }
1513
3072
  status = event.type === "token-usage" ? "completed" : "outcome_unknown";
3073
+ providerUsage =
3074
+ status === "completed" ? providerUsageSettlement(event) : null;
3075
+ providerCostEstimate =
3076
+ status === "completed"
3077
+ ? estimateProviderCost(call.providerPricing, providerUsage)
3078
+ : null;
1514
3079
  settlementCode =
1515
3080
  status === "outcome_unknown"
1516
3081
  ? String(event.code || "provider_outcome_unknown")
1517
3082
  : null;
1518
3083
  const providerReceipt = providerReceiptSettlement(call, event);
1519
- providerReceiptPersisted = providerReceipt.persisted;
1520
- providerReceiptRequestId = providerReceipt.requestId;
1521
- providerReceiptResponseId = providerReceipt.responseId;
3084
+ if (
3085
+ providerReceipt.persisted &&
3086
+ (!call.providerReceiptPersisted ||
3087
+ providerReceipt.requestId !== call.providerReceiptRequestId ||
3088
+ providerReceipt.responseId !== call.providerReceiptResponseId)
3089
+ ) {
3090
+ throw new TypeError(
3091
+ "workflow provider receipt was not durably prewritten",
3092
+ );
3093
+ }
1522
3094
  } else {
1523
3095
  if (
1524
3096
  event?.type !== "tool-result" ||
@@ -1556,6 +3128,28 @@ function effectCallSettlement(call, event, now) {
1556
3128
  ) {
1557
3129
  throw new TypeError("workflow MCP call settlement is malformed");
1558
3130
  }
3131
+ if (Object.prototype.hasOwnProperty.call(call, "artifactReadback")) {
3132
+ const published = ownDataValue(event.result, "published");
3133
+ if (status === "completed") {
3134
+ artifactReadback = artifactReadbackSettlement(event, artifactStore);
3135
+ } else {
3136
+ if (published !== undefined && published !== null) {
3137
+ throw new TypeError(
3138
+ "failed workflow artifact settlement cannot claim a published artifact",
3139
+ );
3140
+ }
3141
+ artifactReadback = null;
3142
+ }
3143
+ }
3144
+ if (Object.prototype.hasOwnProperty.call(call, "checkpointReadback")) {
3145
+ checkpointReadback = checkpointReadbackSettlement(
3146
+ event,
3147
+ checkpointStore,
3148
+ Object.prototype.hasOwnProperty.call(call, "checkpointBinding")
3149
+ ? call.checkpointBinding
3150
+ : null,
3151
+ );
3152
+ }
1559
3153
  }
1560
3154
  if (
1561
3155
  settlementCode !== null &&
@@ -1573,13 +3167,35 @@ function effectCallSettlement(call, event, now) {
1573
3167
  providerReceiptPersisted,
1574
3168
  providerReceiptRequestId,
1575
3169
  providerReceiptResponseId,
3170
+ ...(call.kind === "provider" ? { providerUsage } : {}),
3171
+ ...(call.kind === "provider" &&
3172
+ Object.prototype.hasOwnProperty.call(call, "providerCostEstimate")
3173
+ ? { providerCostEstimate }
3174
+ : {}),
3175
+ ...(call.kind === "tool" &&
3176
+ Object.prototype.hasOwnProperty.call(call, "artifactReadback")
3177
+ ? { artifactReadback }
3178
+ : {}),
3179
+ ...(call.kind === "tool" &&
3180
+ Object.prototype.hasOwnProperty.call(call, "checkpointReadback")
3181
+ ? { checkpointReadback }
3182
+ : {}),
1576
3183
  mcpLedgerId,
1577
3184
  mcpLedgerPrewritePersisted,
1578
3185
  mcpLedgerSettlementPersisted,
1579
3186
  };
1580
3187
  }
1581
3188
 
1582
- function settleEffectCall(statePath, runId, effectId, kind, event, now) {
3189
+ function settleEffectCall(
3190
+ statePath,
3191
+ runId,
3192
+ effectId,
3193
+ kind,
3194
+ event,
3195
+ now,
3196
+ artifactStore,
3197
+ checkpointStore,
3198
+ ) {
1583
3199
  return withStateMutation(statePath, (current) => {
1584
3200
  if (!current || current.runId !== runId) {
1585
3201
  throw new Error(
@@ -1605,7 +3221,47 @@ function settleEffectCall(statePath, runId, effectId, kind, event, now) {
1605
3221
  ) {
1606
3222
  throw new Error("workflow child call is not started at settlement");
1607
3223
  }
1608
- const settlement = effectCallSettlement(call, event, now);
3224
+ const settlement = effectCallSettlement(
3225
+ call,
3226
+ event,
3227
+ now,
3228
+ artifactStore,
3229
+ checkpointStore,
3230
+ );
3231
+ const usageDigest =
3232
+ kind === "provider" && settlement.providerUsage !== null
3233
+ ? providerUsageDigest(settlement.providerUsage)
3234
+ : null;
3235
+ const hasProviderCostSchema = Object.prototype.hasOwnProperty.call(
3236
+ settlement,
3237
+ "providerCostEstimate",
3238
+ );
3239
+ const costEstimateDigest =
3240
+ kind === "provider" &&
3241
+ hasProviderCostSchema &&
3242
+ settlement.providerCostEstimate !== null
3243
+ ? providerCostEstimateDigest(settlement.providerCostEstimate)
3244
+ : null;
3245
+ const hasArtifactReadback = Object.prototype.hasOwnProperty.call(
3246
+ settlement,
3247
+ "artifactReadback",
3248
+ );
3249
+ const settledArtifactReadbackDigest =
3250
+ kind === "tool" &&
3251
+ hasArtifactReadback &&
3252
+ settlement.artifactReadback !== null
3253
+ ? artifactReadbackDigest(settlement.artifactReadback)
3254
+ : null;
3255
+ const hasCheckpointReadback = Object.prototype.hasOwnProperty.call(
3256
+ settlement,
3257
+ "checkpointReadback",
3258
+ );
3259
+ const settledCheckpointReadbackDigest =
3260
+ kind === "tool" &&
3261
+ hasCheckpointReadback &&
3262
+ settlement.checkpointReadback !== null
3263
+ ? checkpointReadbackDigest(settlement.checkpointReadback)
3264
+ : null;
1609
3265
  const state = transition(
1610
3266
  current,
1611
3267
  "effect-call-settled",
@@ -1614,6 +3270,16 @@ function settleEffectCall(statePath, runId, effectId, kind, event, now) {
1614
3270
  callRecordId: call.id,
1615
3271
  kind,
1616
3272
  status: settlement.status,
3273
+ ...(kind === "provider" ? { providerUsageDigest: usageDigest } : {}),
3274
+ ...(kind === "provider" && hasProviderCostSchema
3275
+ ? { providerCostEstimateDigest: costEstimateDigest }
3276
+ : {}),
3277
+ ...(kind === "tool" && hasArtifactReadback
3278
+ ? { artifactReadbackDigest: settledArtifactReadbackDigest }
3279
+ : {}),
3280
+ ...(kind === "tool" && hasCheckpointReadback
3281
+ ? { checkpointReadbackDigest: settledCheckpointReadbackDigest }
3282
+ : {}),
1617
3283
  },
1618
3284
  (draft) => {
1619
3285
  const calls = [...(draft.effects[effectIndex].calls || [])];
@@ -1629,7 +3295,14 @@ function settleEffectCall(statePath, runId, effectId, kind, event, now) {
1629
3295
  });
1630
3296
  }
1631
3297
 
1632
- function createDurableCallObservers(statePath, runId, effectId, now) {
3298
+ function createDurableCallObservers(
3299
+ statePath,
3300
+ runId,
3301
+ effectId,
3302
+ now,
3303
+ artifactStore,
3304
+ checkpointStore,
3305
+ ) {
1633
3306
  return Object.freeze({
1634
3307
  strictUsageTelemetry: true,
1635
3308
  onUsageBoundary(event) {
@@ -1638,11 +3311,23 @@ function createDurableCallObservers(statePath, runId, effectId, now) {
1638
3311
  onUsageSettlement(event) {
1639
3312
  settleEffectCall(statePath, runId, effectId, "provider", event, now);
1640
3313
  },
3314
+ onProviderReceipt(event) {
3315
+ recordEffectCallReceipt(statePath, runId, effectId, event, now);
3316
+ },
1641
3317
  onToolCallBoundary(event) {
1642
3318
  beginEffectCall(statePath, runId, effectId, "tool", event, now);
1643
3319
  },
1644
3320
  onToolCallSettlement(event) {
1645
- settleEffectCall(statePath, runId, effectId, "tool", event, now);
3321
+ settleEffectCall(
3322
+ statePath,
3323
+ runId,
3324
+ effectId,
3325
+ "tool",
3326
+ event,
3327
+ now,
3328
+ artifactStore,
3329
+ checkpointStore,
3330
+ );
1646
3331
  },
1647
3332
  });
1648
3333
  }
@@ -1812,6 +3497,32 @@ function finalizePauseAfterSettlementBarrier(statePath, runId, now) {
1812
3497
  });
1813
3498
  }
1814
3499
 
3500
+ function finalizeInputAfterSettlementBarrier(statePath, runId, now) {
3501
+ return withStateMutation(statePath, (current) => {
3502
+ if (!current || current.runId !== runId) return { value: current };
3503
+ if (current.status !== "input_requested") return { value: current };
3504
+ if (current.effects.some((effect) => effect.status === "pending")) {
3505
+ return { value: current };
3506
+ }
3507
+ const pending = runtimeInputRequests(current).find(
3508
+ (request) => request.status === "pending",
3509
+ );
3510
+ if (!pending) {
3511
+ throw new Error("workflow input barrier has no pending request");
3512
+ }
3513
+ const state = transition(
3514
+ current,
3515
+ "run-needs-input",
3516
+ { requestId: pending.id, barrier: "all-requested-effects-settled" },
3517
+ (draft) => {
3518
+ draft.status = "needs_input";
3519
+ },
3520
+ now,
3521
+ );
3522
+ return { state, value: state };
3523
+ });
3524
+ }
3525
+
1815
3526
  function completeRun(statePath, runId, record, now) {
1816
3527
  const finalRecord = snapshotJson(
1817
3528
  record,
@@ -1834,6 +3545,18 @@ function completeRun(statePath, runId, record, now) {
1834
3545
  );
1835
3546
  return { state, value: { control: "paused", state } };
1836
3547
  }
3548
+ if (current.status === "input_requested") {
3549
+ const state = transition(
3550
+ current,
3551
+ "run-needs-input",
3552
+ { barrier: "workflow-completion" },
3553
+ (draft) => {
3554
+ draft.status = "needs_input";
3555
+ },
3556
+ now,
3557
+ );
3558
+ return { state, value: { control: "needs-input", state } };
3559
+ }
1837
3560
  if (current.status === "stopped") {
1838
3561
  return { value: { control: "stopped", state: current } };
1839
3562
  }
@@ -1862,7 +3585,13 @@ function markFailed(statePath, runId, error, now) {
1862
3585
  !current ||
1863
3586
  current.runId !== runId ||
1864
3587
  TERMINAL_STATUSES.has(current.status) ||
1865
- ["paused", "blocked", "pause_requested"].includes(current.status)
3588
+ [
3589
+ "paused",
3590
+ "blocked",
3591
+ "pause_requested",
3592
+ "input_requested",
3593
+ "needs_input",
3594
+ ].includes(current.status)
1866
3595
  ) {
1867
3596
  return { value: current };
1868
3597
  }
@@ -1879,6 +3608,26 @@ function markFailed(statePath, runId, error, now) {
1879
3608
  });
1880
3609
  }
1881
3610
 
3611
+ function managedCheckpointRuntimeExclusions(cwd, statePath, existing = []) {
3612
+ const exclusions = Array.isArray(existing) ? [...existing] : [];
3613
+ if (typeof cwd !== "string" || !cwd.trim()) return exclusions;
3614
+ const workspaceRoot = path.resolve(cwd);
3615
+ const runtimeStatePath = path.resolve(statePath);
3616
+ const relativeState = path.relative(workspaceRoot, runtimeStatePath);
3617
+ if (
3618
+ !relativeState ||
3619
+ path.isAbsolute(relativeState) ||
3620
+ relativeState === ".." ||
3621
+ relativeState.startsWith(`..${path.sep}`)
3622
+ ) {
3623
+ return exclusions;
3624
+ }
3625
+ const relativeDirectory = path.dirname(relativeState);
3626
+ const exclusion =
3627
+ relativeDirectory === "." ? relativeState : relativeDirectory;
3628
+ return [...new Set([...exclusions, exclusion.replaceAll("\\", "/")])];
3629
+ }
3630
+
1882
3631
  export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
1883
3632
  const statePath = path.resolve(options.statePath);
1884
3633
  const runId = safeId(options.runId, "runId");
@@ -1894,6 +3643,9 @@ export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
1894
3643
  runId,
1895
3644
  deps.now,
1896
3645
  );
3646
+ const artifactStore = deps.artifactStore || new ArtifactStore();
3647
+ const checkpointStore =
3648
+ deps.checkpointStore || new WorkspaceTransactionManager();
1897
3649
  const durableRunTask = async (args) => {
1898
3650
  const requested = await requestEffectForDispatch(args);
1899
3651
  if (requested.control) {
@@ -1975,10 +3727,18 @@ export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
1975
3727
  runId,
1976
3728
  effect.id,
1977
3729
  deps.now,
3730
+ artifactStore,
3731
+ checkpointStore,
1978
3732
  );
1979
3733
  result = await deps.runTask({
1980
3734
  ...args,
1981
3735
  workflowEffectId: effect.id,
3736
+ managedCheckpoint: true,
3737
+ managedCheckpointExclusions: managedCheckpointRuntimeExclusions(
3738
+ args.cwd,
3739
+ statePath,
3740
+ args.managedCheckpointExclusions,
3741
+ ),
1982
3742
  ...durableCallObservers,
1983
3743
  });
1984
3744
  if (typeof deps.afterProvider === "function") {
@@ -2015,6 +3775,8 @@ export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
2015
3775
  ...execution,
2016
3776
  maxParallel: execution.runAdmission.maxParallel,
2017
3777
  runTask: durableRunTask,
3778
+ resolveInput: (request) =>
3779
+ resolveDurableWorkflowStageInput(statePath, runId, request, deps.now),
2018
3780
  });
2019
3781
  const completed = completeRun(statePath, runId, record, deps.now);
2020
3782
  if (completed.control) {
@@ -2026,6 +3788,35 @@ export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
2026
3788
  }
2027
3789
  return completed.record;
2028
3790
  } catch (error) {
3791
+ if (
3792
+ error?.code === DYNAMIC_WORKFLOW_RUNTIME_CONTROL_CODE &&
3793
+ error.reason === "needs-input"
3794
+ ) {
3795
+ const state = finalizeInputAfterSettlementBarrier(
3796
+ statePath,
3797
+ runId,
3798
+ deps.now,
3799
+ );
3800
+ const pendingEffect = state?.effects?.find(
3801
+ (effect) => effect.status === "pending",
3802
+ );
3803
+ if (pendingEffect) {
3804
+ throw controlError(
3805
+ `workflow effect ${pendingEffect.id} requires reconciliation before input can settle`,
3806
+ "reconciliation-required",
3807
+ state,
3808
+ pendingEffect,
3809
+ error,
3810
+ );
3811
+ }
3812
+ throw controlError(
3813
+ "dynamic workflow run needs input after its settlement barrier",
3814
+ "needs-input",
3815
+ state,
3816
+ null,
3817
+ error,
3818
+ );
3819
+ }
2029
3820
  if (
2030
3821
  error?.code === DYNAMIC_WORKFLOW_RUNTIME_CONTROL_CODE &&
2031
3822
  error.reason === "paused"
@@ -2095,6 +3886,9 @@ export function requestDurableWorkflowPause(
2095
3886
  if (TERMINAL_STATUSES.has(current.status)) {
2096
3887
  throw new Error(`cannot pause a ${current.status} workflow run`);
2097
3888
  }
3889
+ if (["input_requested", "needs_input"].includes(current.status)) {
3890
+ throw new Error(`cannot pause a ${current.status} workflow run`);
3891
+ }
2098
3892
  const immediate = ["ready", "paused", "failed"].includes(current.status);
2099
3893
  const state = transition(
2100
3894
  current,
@@ -2127,6 +3921,12 @@ export function requestDurableWorkflowStop(
2127
3921
  "run-stopped",
2128
3922
  {},
2129
3923
  (draft) => {
3924
+ const resolvedAt = isoNow(deps.now);
3925
+ draft.inputRequests = runtimeInputRequests(draft).map((request) =>
3926
+ request.status === "pending"
3927
+ ? { ...request, status: "cancelled", resolvedAt }
3928
+ : request,
3929
+ );
2130
3930
  draft.status = "stopped";
2131
3931
  draft.finalRecord = null;
2132
3932
  },
@@ -2148,6 +3948,17 @@ export function prepareDurableWorkflowResume(
2148
3948
  if (TERMINAL_STATUSES.has(current.status)) {
2149
3949
  throw new Error(`cannot resume a ${current.status} workflow run`);
2150
3950
  }
3951
+ const pendingInput = runtimeInputRequests(current).find(
3952
+ (request) => request.status === "pending",
3953
+ );
3954
+ if (pendingInput) {
3955
+ const error = new Error(
3956
+ `workflow input request ${pendingInput.id} must be answered before resume`,
3957
+ );
3958
+ error.code = "CC_DYNAMIC_WORKFLOW_INPUT_REQUIRED";
3959
+ error.pendingInputRequest = pendingInput;
3960
+ throw error;
3961
+ }
2151
3962
  const pending = current.effects.find(
2152
3963
  (effect) => effect.status === "pending" && !isKnownUndispatched(effect),
2153
3964
  );
@@ -2172,36 +3983,313 @@ export function prepareDurableWorkflowResume(
2172
3983
  });
2173
3984
  }
2174
3985
 
2175
- export function reconcileDurableWorkflowEffect(
2176
- statePath,
2177
- input = {},
2178
- deps = {},
2179
- ) {
2180
- const effectId = String(input.effectId || "")
3986
+ export function submitDurableWorkflowInput(statePath, input = {}, deps = {}) {
3987
+ const requestId = String(input.requestId || "")
2181
3988
  .trim()
2182
3989
  .toLowerCase();
2183
- if (!SHA256_RE.test(effectId)) throw new TypeError("effectId is invalid");
2184
- const result = snapshotJson(
2185
- input.result,
2186
- "workflow reconciled effect result",
2187
- 1024 * 1024,
2188
- );
2189
- if (containsSecret(JSON.stringify(result))) {
2190
- throw new Error("workflow reconciled result contains secret-shaped data");
3990
+ if (!SHA256_RE.test(requestId)) {
3991
+ throw new TypeError("workflow input request id is invalid");
2191
3992
  }
2192
- const resultDigest = effectResultDigest(result);
2193
3993
  return withStateMutation(path.resolve(statePath), (current) => {
2194
3994
  if (!current)
2195
3995
  throw new Error("dynamic workflow runtime state was not found");
2196
3996
  requireRevision(current, input.expectedRevision);
2197
- const index = current.effects.findIndex((effect) => effect.id === effectId);
2198
- const effect = current.effects[index];
2199
- if (!effect || effect.status !== "pending") {
2200
- throw new Error("workflow effect is not pending reconciliation");
2201
- }
2202
- if (isKnownUndispatched(effect)) {
3997
+ if (current.status !== "needs_input") {
2203
3998
  throw new Error(
2204
- "workflow effect was not dispatched and must be resumed instead of reconciled",
3999
+ `cannot answer workflow input while run is ${current.status}`,
4000
+ );
4001
+ }
4002
+ if (current.effects.some((effect) => effect.status === "pending")) {
4003
+ throw new Error(
4004
+ "workflow input cannot be answered before the effect settlement barrier",
4005
+ );
4006
+ }
4007
+ const requests = runtimeInputRequests(current);
4008
+ const requestIndex = requests.findIndex(
4009
+ (request) => request.id === requestId,
4010
+ );
4011
+ const request = requests[requestIndex];
4012
+ if (!request || request.status !== "pending") {
4013
+ throw new Error("workflow input request is not pending");
4014
+ }
4015
+ const answer = normalizeStageInputAnswer(input.answer, request);
4016
+ const responseDigest = stageInputResponseDigest(requestId, answer);
4017
+ const resolvedAt = isoNow(deps.now);
4018
+ const state = transition(
4019
+ current,
4020
+ "input-answered",
4021
+ { requestId, stepId: request.stepId, responseDigest },
4022
+ (draft) => {
4023
+ draft.inputRequests = [...runtimeInputRequests(draft)];
4024
+ draft.inputRequests[requestIndex] = {
4025
+ ...draft.inputRequests[requestIndex],
4026
+ status: "answered",
4027
+ resolvedAt,
4028
+ responseDigest,
4029
+ response: answer,
4030
+ };
4031
+ draft.status = "ready";
4032
+ },
4033
+ deps.now,
4034
+ );
4035
+ return { state, value: state };
4036
+ });
4037
+ }
4038
+
4039
+ function checkpointReadbackFromPreparedBinding(binding, checkpointStore) {
4040
+ if (!validCheckpointBinding(binding)) {
4041
+ throw new TypeError("workflow checkpoint prepared binding is malformed");
4042
+ }
4043
+ if (!checkpointStore || typeof checkpointStore.inspect !== "function") {
4044
+ throw new TypeError("workflow checkpoint store readback is unavailable");
4045
+ }
4046
+ let stored;
4047
+ try {
4048
+ stored = checkpointStore.inspect(binding.transactionId);
4049
+ } catch {
4050
+ throw new TypeError("workflow checkpoint store readback failed");
4051
+ }
4052
+ if (
4053
+ ![
4054
+ WORKSPACE_TRANSACTION_STATE.COMMITTED,
4055
+ WORKSPACE_TRANSACTION_STATE.ROLLED_BACK,
4056
+ ].includes(stored?.state)
4057
+ ) {
4058
+ return null;
4059
+ }
4060
+ const evidence = checkpointEvidenceSnapshot(stored.evidence);
4061
+ if (!validCheckpointEvidence(evidence)) {
4062
+ throw new TypeError("workflow checkpoint terminal evidence is malformed");
4063
+ }
4064
+ return checkpointStoreReadback(evidence, checkpointStore, binding);
4065
+ }
4066
+
4067
+ export function recoverDurableWorkflowCheckpointCall(
4068
+ statePath,
4069
+ input = {},
4070
+ deps = {},
4071
+ ) {
4072
+ const callRecordId = String(input.callRecordId || "")
4073
+ .trim()
4074
+ .toLowerCase();
4075
+ if (!SHA256_RE.test(callRecordId)) {
4076
+ throw new TypeError("callRecordId is invalid");
4077
+ }
4078
+ const checkpointStore =
4079
+ deps.checkpointStore || new WorkspaceTransactionManager();
4080
+ return withStateMutation(path.resolve(statePath), (current) => {
4081
+ if (!current) {
4082
+ throw new Error("dynamic workflow runtime state was not found");
4083
+ }
4084
+ requireRevision(current, input.expectedRevision);
4085
+ let effectIndex = -1;
4086
+ let callIndex = -1;
4087
+ for (const [candidateEffectIndex, effect] of current.effects.entries()) {
4088
+ const candidateCallIndex = (effect.calls || []).findIndex(
4089
+ (call) => call.id === callRecordId,
4090
+ );
4091
+ if (candidateCallIndex >= 0) {
4092
+ effectIndex = candidateEffectIndex;
4093
+ callIndex = candidateCallIndex;
4094
+ break;
4095
+ }
4096
+ }
4097
+ const effect = current.effects[effectIndex];
4098
+ const call = effect?.calls?.[callIndex];
4099
+ if (
4100
+ !effect ||
4101
+ effect.status !== "pending" ||
4102
+ !call ||
4103
+ call.kind !== "tool" ||
4104
+ call.status !== "started"
4105
+ ) {
4106
+ throw new Error(
4107
+ "workflow checkpoint call is not pending terminal recovery",
4108
+ );
4109
+ }
4110
+ if (
4111
+ Object.prototype.hasOwnProperty.call(call, "artifactReadback") ||
4112
+ !Object.prototype.hasOwnProperty.call(call, "checkpointBinding") ||
4113
+ !validCheckpointBinding(call.checkpointBinding)
4114
+ ) {
4115
+ throw new Error(
4116
+ "workflow checkpoint call has no recoverable prepared binding",
4117
+ );
4118
+ }
4119
+ const checkpointReadback = checkpointReadbackFromPreparedBinding(
4120
+ call.checkpointBinding,
4121
+ checkpointStore,
4122
+ );
4123
+ if (checkpointReadback === null) {
4124
+ const error = new Error(
4125
+ "workflow checkpoint transaction has not reached a recoverable terminal state",
4126
+ );
4127
+ error.code = "CC_DYNAMIC_WORKFLOW_CHECKPOINT_NOT_TERMINAL";
4128
+ throw error;
4129
+ }
4130
+ const committed = checkpointReadback.outcome === "committed";
4131
+ const settledAt = isoNow(deps.now);
4132
+ const settlementCode = committed
4133
+ ? "checkpoint_store_recovered_commit"
4134
+ : "checkpoint_store_recovered_rollback";
4135
+ const state = transition(
4136
+ current,
4137
+ "effect-call-settled",
4138
+ {
4139
+ effectId: effect.id,
4140
+ callRecordId: call.id,
4141
+ kind: "tool",
4142
+ status: committed ? "completed" : "failed",
4143
+ checkpointReadbackDigest: checkpointReadbackDigest(checkpointReadback),
4144
+ checkpointRecovery: "terminal-store",
4145
+ },
4146
+ (draft) => {
4147
+ const calls = [...(draft.effects[effectIndex].calls || [])];
4148
+ calls[callIndex] = {
4149
+ ...calls[callIndex],
4150
+ status: committed ? "completed" : "failed",
4151
+ settledAt,
4152
+ outcomeUnknown: false,
4153
+ settlementCode,
4154
+ checkpointReadback,
4155
+ };
4156
+ draft.effects[effectIndex] = {
4157
+ ...draft.effects[effectIndex],
4158
+ calls,
4159
+ };
4160
+ },
4161
+ deps.now,
4162
+ );
4163
+ return { state, value: state };
4164
+ });
4165
+ }
4166
+
4167
+ export function recoverDurableWorkflowCheckpointCalls(
4168
+ statePath,
4169
+ input = {},
4170
+ deps = {},
4171
+ ) {
4172
+ const checkpointStore =
4173
+ deps.checkpointStore || new WorkspaceTransactionManager();
4174
+ return withStateMutation(path.resolve(statePath), (current) => {
4175
+ if (!current) {
4176
+ throw new Error("dynamic workflow runtime state was not found");
4177
+ }
4178
+ requireRevision(current, input.expectedRevision);
4179
+ const recoveries = [];
4180
+ for (const [effectIndex, effect] of current.effects.entries()) {
4181
+ if (effect.status !== "pending") continue;
4182
+ for (const [callIndex, call] of (effect.calls || []).entries()) {
4183
+ if (
4184
+ call.kind !== "tool" ||
4185
+ call.status !== "started" ||
4186
+ Object.prototype.hasOwnProperty.call(call, "artifactReadback") ||
4187
+ !Object.prototype.hasOwnProperty.call(call, "checkpointBinding") ||
4188
+ !validCheckpointBinding(call.checkpointBinding)
4189
+ ) {
4190
+ continue;
4191
+ }
4192
+ const checkpointReadback = checkpointReadbackFromPreparedBinding(
4193
+ call.checkpointBinding,
4194
+ checkpointStore,
4195
+ );
4196
+ if (checkpointReadback === null) continue;
4197
+ const committed = checkpointReadback.outcome === "committed";
4198
+ recoveries.push({
4199
+ effectIndex,
4200
+ callIndex,
4201
+ effectId: effect.id,
4202
+ callRecordId: call.id,
4203
+ committed,
4204
+ checkpointReadback,
4205
+ settlementCode: committed
4206
+ ? "checkpoint_store_recovered_commit"
4207
+ : "checkpoint_store_recovered_rollback",
4208
+ });
4209
+ }
4210
+ }
4211
+ if (recoveries.length === 0) {
4212
+ const error = new Error(
4213
+ "no workflow checkpoint calls have a recoverable terminal store state",
4214
+ );
4215
+ error.code = "CC_DYNAMIC_WORKFLOW_CHECKPOINT_BATCH_EMPTY";
4216
+ throw error;
4217
+ }
4218
+ if (current.lineage.length + recoveries.length > MAX_LINEAGE_EVENTS) {
4219
+ throw new Error(
4220
+ "dynamic workflow runtime lineage limit prevents batch recovery",
4221
+ );
4222
+ }
4223
+ const settledAt = isoNow(deps.now);
4224
+ const draft = snapshotJson(
4225
+ stateMaterial(current),
4226
+ "workflow checkpoint batch recovery",
4227
+ );
4228
+ for (const recovery of recoveries) {
4229
+ const calls = [...(draft.effects[recovery.effectIndex].calls || [])];
4230
+ calls[recovery.callIndex] = {
4231
+ ...calls[recovery.callIndex],
4232
+ status: recovery.committed ? "completed" : "failed",
4233
+ settledAt,
4234
+ outcomeUnknown: false,
4235
+ settlementCode: recovery.settlementCode,
4236
+ checkpointReadback: recovery.checkpointReadback,
4237
+ };
4238
+ draft.effects[recovery.effectIndex] = {
4239
+ ...draft.effects[recovery.effectIndex],
4240
+ calls,
4241
+ };
4242
+ appendLineage(
4243
+ draft,
4244
+ "effect-call-settled",
4245
+ {
4246
+ effectId: recovery.effectId,
4247
+ callRecordId: recovery.callRecordId,
4248
+ kind: "tool",
4249
+ status: recovery.committed ? "completed" : "failed",
4250
+ checkpointReadbackDigest: checkpointReadbackDigest(
4251
+ recovery.checkpointReadback,
4252
+ ),
4253
+ checkpointRecovery: "terminal-store-batch",
4254
+ },
4255
+ settledAt,
4256
+ );
4257
+ }
4258
+ const state = finalizeState(draft);
4259
+ return { state, value: state };
4260
+ });
4261
+ }
4262
+
4263
+ export function reconcileDurableWorkflowEffect(
4264
+ statePath,
4265
+ input = {},
4266
+ deps = {},
4267
+ ) {
4268
+ const effectId = String(input.effectId || "")
4269
+ .trim()
4270
+ .toLowerCase();
4271
+ if (!SHA256_RE.test(effectId)) throw new TypeError("effectId is invalid");
4272
+ const result = snapshotJson(
4273
+ input.result,
4274
+ "workflow reconciled effect result",
4275
+ 1024 * 1024,
4276
+ );
4277
+ if (containsSecret(JSON.stringify(result))) {
4278
+ throw new Error("workflow reconciled result contains secret-shaped data");
4279
+ }
4280
+ const resultDigest = effectResultDigest(result);
4281
+ return withStateMutation(path.resolve(statePath), (current) => {
4282
+ if (!current)
4283
+ throw new Error("dynamic workflow runtime state was not found");
4284
+ requireRevision(current, input.expectedRevision);
4285
+ const index = current.effects.findIndex((effect) => effect.id === effectId);
4286
+ const effect = current.effects[index];
4287
+ if (!effect || effect.status !== "pending") {
4288
+ throw new Error("workflow effect is not pending reconciliation");
4289
+ }
4290
+ if (isKnownUndispatched(effect)) {
4291
+ throw new Error(
4292
+ "workflow effect was not dispatched and must be resumed instead of reconciled",
2205
4293
  );
2206
4294
  }
2207
4295
  const firstPending = current.effects.find(
@@ -2286,7 +4374,7 @@ function projectResultValues(effect, field, domain) {
2286
4374
  };
2287
4375
  }
2288
4376
 
2289
- function projectProviderRequestReceipts(effect) {
4377
+ function projectLegacyProviderRequestReceipts(effect) {
2290
4378
  const attemptValues = Array.isArray(effect.result?.providerRequestAttempts)
2291
4379
  ? effect.result.providerRequestAttempts
2292
4380
  : [];
@@ -2408,6 +4496,185 @@ function projectProviderRequestReceipts(effect) {
2408
4496
  };
2409
4497
  }
2410
4498
 
4499
+ function projectProviderRequestReceipts(state) {
4500
+ const attemptLineage = [];
4501
+ const receiptLineage = [];
4502
+ const requestAttemptEffectIds = new Set();
4503
+ const receiptEffectIds = new Set();
4504
+ const providerReturnedReceiptEffectIds = new Set();
4505
+ let attemptCount = 0;
4506
+ let receiptCount = 0;
4507
+ let invalidAttempts = 0;
4508
+ let invalidReceipts = 0;
4509
+ let missingReceipts = 0;
4510
+ let truncatedEffects = 0;
4511
+ let durableCallEffects = 0;
4512
+ let legacyResultFallbackEffects = 0;
4513
+ let conflictingOuterResultEffects = 0;
4514
+
4515
+ for (const effect of state.effects) {
4516
+ const providerCalls = (effect.calls || []).filter(
4517
+ (call) => call.kind === "provider",
4518
+ );
4519
+ if (providerCalls.length > 0) {
4520
+ durableCallEffects += 1;
4521
+ attemptCount += providerCalls.length;
4522
+ receiptCount += providerCalls.filter(
4523
+ (call) => call.providerReceiptPersisted,
4524
+ ).length;
4525
+ missingReceipts += providerCalls.filter(
4526
+ (call) => !call.providerReceiptPersisted,
4527
+ ).length;
4528
+ requestAttemptEffectIds.add(effect.id);
4529
+ if (providerCalls.some((call) => call.providerReceiptPersisted)) {
4530
+ receiptEffectIds.add(effect.id);
4531
+ if (effect.settlementAuthority === "provider-return") {
4532
+ providerReturnedReceiptEffectIds.add(effect.id);
4533
+ }
4534
+ }
4535
+ const hasOuterResultEvidence =
4536
+ Array.isArray(effect.result?.providerRequestAttempts) ||
4537
+ Array.isArray(effect.result?.providerRequestReceipts);
4538
+ if (hasOuterResultEvidence) {
4539
+ const reported = projectLegacyProviderRequestReceipts(effect);
4540
+ const directCalls = providerCalls.filter(
4541
+ (call) => call.ownerEffectId === effect.id,
4542
+ );
4543
+ const attemptMatches =
4544
+ reported.invalidAttempts === 0 &&
4545
+ reported.attemptLineage.length === directCalls.length &&
4546
+ directCalls.every((call) => {
4547
+ const attempt = reported.attemptLineage.find(
4548
+ (candidate) => candidate.callId === call.callId,
4549
+ );
4550
+ return (
4551
+ attempt?.provider === call.name &&
4552
+ attempt?.callSequence === call.sequence &&
4553
+ attempt?.source === call.requestSource &&
4554
+ attempt?.clientRequestId === call.clientRequestId
4555
+ );
4556
+ });
4557
+ const directReceiptCalls = directCalls.filter(
4558
+ (call) => call.providerReceiptPersisted,
4559
+ );
4560
+ const receiptMatches =
4561
+ reported.invalidReceipts === 0 &&
4562
+ reported.receiptLineage.length === directReceiptCalls.length &&
4563
+ directReceiptCalls.every((call) => {
4564
+ const receipt = reported.receiptLineage.find(
4565
+ (candidate) => candidate.callId === call.callId,
4566
+ );
4567
+ return (
4568
+ receipt?.requestId === call.providerReceiptRequestId &&
4569
+ receipt?.responseId === call.providerReceiptResponseId
4570
+ );
4571
+ });
4572
+ if (!attemptMatches || !receiptMatches) {
4573
+ conflictingOuterResultEffects += 1;
4574
+ }
4575
+ }
4576
+ const visibleCalls = providerCalls.slice(
4577
+ 0,
4578
+ MAX_PROJECTED_ARTIFACTS_PER_EFFECT,
4579
+ );
4580
+ if (providerCalls.length > visibleCalls.length) truncatedEffects += 1;
4581
+ for (let ordinal = 0; ordinal < visibleCalls.length; ordinal += 1) {
4582
+ const call = visibleCalls[ordinal];
4583
+ const attempt = {
4584
+ effectId: effect.id,
4585
+ ownerEffectId: call.ownerEffectId,
4586
+ callRecordId: call.id,
4587
+ ordinal,
4588
+ provider: call.name,
4589
+ callId: call.callId,
4590
+ callSequence: call.sequence,
4591
+ source: call.requestSource,
4592
+ attributionSource: call.source,
4593
+ clientRequestId: call.clientRequestId,
4594
+ status: call.status,
4595
+ startedAt: call.startedAt,
4596
+ requestIdentitySemantics: "trace-only",
4597
+ authoritySource: "durable-call-store",
4598
+ };
4599
+ attemptLineage.push(attempt);
4600
+ if (!call.providerReceiptPersisted) continue;
4601
+ receiptLineage.push({
4602
+ ...attempt,
4603
+ requestId: call.providerReceiptRequestId,
4604
+ responseId: call.providerReceiptResponseId,
4605
+ recordedAt:
4606
+ call.providerReceiptRecordedAt === undefined
4607
+ ? call.settledAt
4608
+ : call.providerReceiptRecordedAt,
4609
+ independentlyReadable: false,
4610
+ });
4611
+ }
4612
+ continue;
4613
+ }
4614
+
4615
+ if (effect.status !== "settled") continue;
4616
+ const hasOuterResultEvidence =
4617
+ Array.isArray(effect.result?.providerRequestAttempts) ||
4618
+ Array.isArray(effect.result?.providerRequestReceipts);
4619
+ if (effect.calls !== undefined) {
4620
+ if (
4621
+ hasOuterResultEvidence &&
4622
+ ((effect.result?.providerRequestAttempts?.length || 0) > 0 ||
4623
+ (effect.result?.providerRequestReceipts?.length || 0) > 0)
4624
+ ) {
4625
+ conflictingOuterResultEffects += 1;
4626
+ }
4627
+ continue;
4628
+ }
4629
+ const legacy = projectLegacyProviderRequestReceipts(effect);
4630
+ if (legacy.attemptCount === 0 && legacy.receiptCount === 0) continue;
4631
+ legacyResultFallbackEffects += 1;
4632
+ attemptCount += legacy.attemptCount;
4633
+ receiptCount += legacy.receiptCount;
4634
+ invalidAttempts += legacy.invalidAttempts;
4635
+ invalidReceipts += legacy.invalidReceipts;
4636
+ missingReceipts += legacy.missingReceipts;
4637
+ if (legacy.truncated) truncatedEffects += 1;
4638
+ if (legacy.attemptLineage.length > 0) {
4639
+ requestAttemptEffectIds.add(effect.id);
4640
+ }
4641
+ if (legacy.receiptLineage.length > 0) {
4642
+ receiptEffectIds.add(effect.id);
4643
+ providerReturnedReceiptEffectIds.add(effect.id);
4644
+ }
4645
+ attemptLineage.push(
4646
+ ...legacy.attemptLineage.map((attempt) => ({
4647
+ ...attempt,
4648
+ authoritySource: "legacy-task-result",
4649
+ })),
4650
+ );
4651
+ receiptLineage.push(
4652
+ ...legacy.receiptLineage.map((receipt) => ({
4653
+ ...receipt,
4654
+ recordedAt: effect.settledAt,
4655
+ authoritySource: "legacy-task-result",
4656
+ })),
4657
+ );
4658
+ }
4659
+
4660
+ return {
4661
+ attemptCount,
4662
+ receiptCount,
4663
+ invalidAttempts,
4664
+ invalidReceipts,
4665
+ missingReceipts,
4666
+ truncatedEffects,
4667
+ durableCallEffects,
4668
+ legacyResultFallbackEffects,
4669
+ conflictingOuterResultEffects,
4670
+ requestAttemptEffects: requestAttemptEffectIds.size,
4671
+ receiptEffects: receiptEffectIds.size,
4672
+ providerReturnedReceiptEffects: providerReturnedReceiptEffectIds.size,
4673
+ attemptLineage,
4674
+ receiptLineage,
4675
+ };
4676
+ }
4677
+
2411
4678
  function expectedNestedToolEffectId(effectId, sequence, toolUseId, tool) {
2412
4679
  return `sha256:${createHash("sha256")
2413
4680
  .update(
@@ -2417,7 +4684,7 @@ function expectedNestedToolEffectId(effectId, sequence, toolUseId, tool) {
2417
4684
  .digest("hex")}`;
2418
4685
  }
2419
4686
 
2420
- function projectNestedToolEffects(effect) {
4687
+ function projectLegacyNestedToolEffects(effect) {
2421
4688
  const attemptValues = Array.isArray(effect.result?.nestedEffectAttempts)
2422
4689
  ? effect.result.nestedEffectAttempts
2423
4690
  : [];
@@ -2565,6 +4832,177 @@ function projectNestedToolEffects(effect) {
2565
4832
  };
2566
4833
  }
2567
4834
 
4835
+ function projectNestedToolEffects(state) {
4836
+ const attemptLineage = [];
4837
+ const settlementLineage = [];
4838
+ let attemptCount = 0;
4839
+ let settlementCount = 0;
4840
+ let invalidAttempts = 0;
4841
+ let invalidSettlements = 0;
4842
+ let missingSettlements = 0;
4843
+ let durableMcpSettlements = 0;
4844
+ let truncatedEffects = 0;
4845
+ let durableCallEffects = 0;
4846
+ let legacyResultFallbackEffects = 0;
4847
+ let conflictingOuterResultEffects = 0;
4848
+
4849
+ for (const effect of state.effects) {
4850
+ const toolCalls = (effect.calls || []).filter(
4851
+ (call) => call.kind === "tool",
4852
+ );
4853
+ if (toolCalls.length > 0) {
4854
+ durableCallEffects += 1;
4855
+ attemptCount += toolCalls.length;
4856
+ settlementCount += toolCalls.filter(
4857
+ (call) => call.status !== "started",
4858
+ ).length;
4859
+ missingSettlements += toolCalls.filter(
4860
+ (call) => call.status === "started",
4861
+ ).length;
4862
+ durableMcpSettlements += toolCalls.filter(
4863
+ (call) =>
4864
+ call.mcpLedgerId &&
4865
+ call.mcpLedgerPrewritePersisted &&
4866
+ call.mcpLedgerSettlementPersisted,
4867
+ ).length;
4868
+
4869
+ const hasOuterResultEvidence =
4870
+ Array.isArray(effect.result?.nestedEffectAttempts) ||
4871
+ Array.isArray(effect.result?.nestedEffectSettlements);
4872
+ if (hasOuterResultEvidence) {
4873
+ const reported = projectLegacyNestedToolEffects(effect);
4874
+ const directCalls = toolCalls.filter(
4875
+ (call) => call.ownerEffectId === effect.id,
4876
+ );
4877
+ const attemptMatches =
4878
+ reported.invalidAttempts === 0 &&
4879
+ reported.attemptLineage.length === directCalls.length &&
4880
+ directCalls.every((call) => {
4881
+ const attempt = reported.attemptLineage.find(
4882
+ (candidate) => candidate.childEffectId === call.childEffectId,
4883
+ );
4884
+ return (
4885
+ attempt?.childSequence === call.sequence &&
4886
+ attempt?.tool === call.name &&
4887
+ attempt?.toolUseId === call.callId
4888
+ );
4889
+ });
4890
+ const directTerminalCalls = directCalls.filter(
4891
+ (call) => call.status !== "started",
4892
+ );
4893
+ const settlementMatches =
4894
+ reported.invalidSettlements === 0 &&
4895
+ reported.settlementLineage.length === directTerminalCalls.length &&
4896
+ directTerminalCalls.every((call) => {
4897
+ const settlement = reported.settlementLineage.find(
4898
+ (candidate) => candidate.childEffectId === call.childEffectId,
4899
+ );
4900
+ return (
4901
+ settlement?.status === call.status &&
4902
+ settlement?.mcpLedgerId === call.mcpLedgerId &&
4903
+ settlement?.mcpLedgerPrewritePersisted ===
4904
+ call.mcpLedgerPrewritePersisted &&
4905
+ settlement?.mcpLedgerSettlementPersisted ===
4906
+ call.mcpLedgerSettlementPersisted
4907
+ );
4908
+ });
4909
+ if (!attemptMatches || !settlementMatches) {
4910
+ conflictingOuterResultEffects += 1;
4911
+ }
4912
+ }
4913
+
4914
+ const visibleCalls = toolCalls.slice(
4915
+ 0,
4916
+ MAX_PROJECTED_ARTIFACTS_PER_EFFECT,
4917
+ );
4918
+ if (toolCalls.length > visibleCalls.length) truncatedEffects += 1;
4919
+ for (let ordinal = 0; ordinal < visibleCalls.length; ordinal += 1) {
4920
+ const call = visibleCalls[ordinal];
4921
+ const attempt = {
4922
+ effectId: effect.id,
4923
+ ownerEffectId: call.ownerEffectId,
4924
+ callRecordId: call.id,
4925
+ ordinal,
4926
+ childEffectId: call.childEffectId,
4927
+ childSequence: call.sequence,
4928
+ kind: "tool",
4929
+ tool: call.name,
4930
+ toolUseId: call.callId,
4931
+ status: call.status,
4932
+ startedAt: call.startedAt,
4933
+ identitySemantics: "runtime-derived",
4934
+ authoritySource: "durable-call-store",
4935
+ };
4936
+ attemptLineage.push(attempt);
4937
+ if (call.status === "started") continue;
4938
+ settlementLineage.push({
4939
+ ...attempt,
4940
+ settledAt: call.settledAt,
4941
+ outcomeUnknown: call.outcomeUnknown,
4942
+ settlementCode: call.settlementCode,
4943
+ mcpLedgerId: call.mcpLedgerId,
4944
+ mcpLedgerPrewritePersisted: call.mcpLedgerPrewritePersisted,
4945
+ mcpLedgerSettlementPersisted: call.mcpLedgerSettlementPersisted,
4946
+ });
4947
+ }
4948
+ continue;
4949
+ }
4950
+
4951
+ if (effect.status !== "settled") continue;
4952
+ const hasOuterResultEvidence =
4953
+ Array.isArray(effect.result?.nestedEffectAttempts) ||
4954
+ Array.isArray(effect.result?.nestedEffectSettlements);
4955
+ if (effect.calls !== undefined) {
4956
+ if (
4957
+ hasOuterResultEvidence &&
4958
+ ((effect.result?.nestedEffectAttempts?.length || 0) > 0 ||
4959
+ (effect.result?.nestedEffectSettlements?.length || 0) > 0)
4960
+ ) {
4961
+ conflictingOuterResultEffects += 1;
4962
+ }
4963
+ continue;
4964
+ }
4965
+ const legacy = projectLegacyNestedToolEffects(effect);
4966
+ if (legacy.attemptCount === 0 && legacy.settlementCount === 0) continue;
4967
+ legacyResultFallbackEffects += 1;
4968
+ attemptCount += legacy.attemptCount;
4969
+ settlementCount += legacy.settlementCount;
4970
+ invalidAttempts += legacy.invalidAttempts;
4971
+ invalidSettlements += legacy.invalidSettlements;
4972
+ missingSettlements += legacy.missingSettlements;
4973
+ durableMcpSettlements += legacy.durableMcpSettlements;
4974
+ if (legacy.truncated) truncatedEffects += 1;
4975
+ attemptLineage.push(
4976
+ ...legacy.attemptLineage.map((attempt) => ({
4977
+ ...attempt,
4978
+ authoritySource: "legacy-task-result",
4979
+ })),
4980
+ );
4981
+ settlementLineage.push(
4982
+ ...legacy.settlementLineage.map((settlement) => ({
4983
+ ...settlement,
4984
+ settledAt: effect.settledAt,
4985
+ authoritySource: "legacy-task-result",
4986
+ })),
4987
+ );
4988
+ }
4989
+
4990
+ return {
4991
+ attemptCount,
4992
+ settlementCount,
4993
+ invalidAttempts,
4994
+ invalidSettlements,
4995
+ missingSettlements,
4996
+ durableMcpSettlements,
4997
+ truncatedEffects,
4998
+ durableCallEffects,
4999
+ legacyResultFallbackEffects,
5000
+ conflictingOuterResultEffects,
5001
+ attemptLineage,
5002
+ settlementLineage,
5003
+ };
5004
+ }
5005
+
2568
5006
  function projectDurableWorkflowCalls(state) {
2569
5007
  const lineage = state.effects.flatMap((effect) =>
2570
5008
  (effect.calls || []).map((call) => ({
@@ -2590,10 +5028,73 @@ function projectDurableWorkflowCalls(state) {
2590
5028
  protocol: "cc-provider-request-receipt/v1",
2591
5029
  requestId: call.providerReceiptRequestId,
2592
5030
  responseId: call.providerReceiptResponseId,
5031
+ recordedAt:
5032
+ call.providerReceiptRecordedAt === undefined
5033
+ ? call.settledAt
5034
+ : call.providerReceiptRecordedAt,
2593
5035
  requestIdentitySemantics: "trace-only",
2594
5036
  independentlyReadable: false,
2595
5037
  }
2596
5038
  : null,
5039
+ providerUsage:
5040
+ call.kind === "provider" && validProviderUsage(call.providerUsage)
5041
+ ? {
5042
+ ...call.providerUsage,
5043
+ recordedAt: call.settledAt,
5044
+ }
5045
+ : null,
5046
+ providerUsageSchemaPresent:
5047
+ call.kind === "provider" &&
5048
+ Object.prototype.hasOwnProperty.call(call, "providerUsage"),
5049
+ providerModel:
5050
+ call.kind === "provider" &&
5051
+ Object.prototype.hasOwnProperty.call(call, "providerModel")
5052
+ ? call.providerModel
5053
+ : null,
5054
+ providerPricing:
5055
+ call.kind === "provider" &&
5056
+ validProviderPricing(
5057
+ call.providerPricing,
5058
+ call.name,
5059
+ call.providerModel,
5060
+ )
5061
+ ? call.providerPricing
5062
+ : null,
5063
+ providerCostEstimate:
5064
+ call.kind === "provider" &&
5065
+ validProviderCostEstimate(
5066
+ call.providerCostEstimate,
5067
+ call.providerPricing,
5068
+ call.providerUsage,
5069
+ )
5070
+ ? call.providerCostEstimate
5071
+ : null,
5072
+ providerCostSchemaPresent:
5073
+ call.kind === "provider" &&
5074
+ ["providerModel", "providerPricing", "providerCostEstimate"].every(
5075
+ (field) => Object.prototype.hasOwnProperty.call(call, field),
5076
+ ),
5077
+ artifactReadback:
5078
+ call.kind === "tool" && validArtifactReadback(call.artifactReadback)
5079
+ ? call.artifactReadback
5080
+ : null,
5081
+ artifactReadbackSchemaPresent:
5082
+ call.kind === "tool" &&
5083
+ Object.prototype.hasOwnProperty.call(call, "artifactReadback"),
5084
+ checkpointReadback:
5085
+ call.kind === "tool" && validCheckpointReadback(call.checkpointReadback)
5086
+ ? call.checkpointReadback
5087
+ : null,
5088
+ checkpointReadbackSchemaPresent:
5089
+ call.kind === "tool" &&
5090
+ Object.prototype.hasOwnProperty.call(call, "checkpointReadback"),
5091
+ checkpointBinding:
5092
+ call.kind === "tool" && validCheckpointBinding(call.checkpointBinding)
5093
+ ? call.checkpointBinding
5094
+ : null,
5095
+ checkpointBindingSchemaPresent:
5096
+ call.kind === "tool" &&
5097
+ Object.prototype.hasOwnProperty.call(call, "checkpointBinding"),
2597
5098
  mcpLedgerId: call.mcpLedgerId,
2598
5099
  mcpLedgerPrewritePersisted: call.mcpLedgerPrewritePersisted,
2599
5100
  mcpLedgerSettlementPersisted: call.mcpLedgerSettlementPersisted,
@@ -2613,6 +5114,20 @@ function projectDurableWorkflowCalls(state) {
2613
5114
  descendants: lineage.filter((call) => call.descendant).length,
2614
5115
  providerReceipts: lineage.filter((call) => call.providerReceipt !== null)
2615
5116
  .length,
5117
+ providerUsageRecords: lineage.filter((call) => call.providerUsage !== null)
5118
+ .length,
5119
+ providerCostEstimateRecords: lineage.filter(
5120
+ (call) => call.providerCostEstimate !== null,
5121
+ ).length,
5122
+ artifactReadbackRecords: lineage.filter(
5123
+ (call) => call.artifactReadback !== null,
5124
+ ).length,
5125
+ checkpointReadbackRecords: lineage.filter(
5126
+ (call) => call.checkpointReadback !== null,
5127
+ ).length,
5128
+ checkpointBindingRecords: lineage.filter(
5129
+ (call) => call.checkpointBinding !== null,
5130
+ ).length,
2616
5131
  providerNativeIdempotencyProven: false,
2617
5132
  providerReceiptsIndependentlyReadable: false,
2618
5133
  lineageDigest: digest(
@@ -2623,6 +5138,371 @@ function projectDurableWorkflowCalls(state) {
2623
5138
  };
2624
5139
  }
2625
5140
 
5141
+ function projectProviderTokenUsage(state) {
5142
+ const totals = {
5143
+ inputTokens: 0,
5144
+ outputTokens: 0,
5145
+ cacheReadInputTokens: 0,
5146
+ cacheCreationInputTokens: 0,
5147
+ totalTokens: 0,
5148
+ };
5149
+ const lineage = [];
5150
+ const observedEffectIds = new Set();
5151
+ let providerCalls = 0;
5152
+ let pendingCalls = 0;
5153
+ let outcomeUnknownCalls = 0;
5154
+ let operatorReconciledCalls = 0;
5155
+ let legacyCalls = 0;
5156
+ for (const effect of state.effects) {
5157
+ for (const call of effect.calls || []) {
5158
+ if (call.kind !== "provider") continue;
5159
+ providerCalls += 1;
5160
+ if (call.status === "started") pendingCalls += 1;
5161
+ if (call.status === "outcome_unknown") outcomeUnknownCalls += 1;
5162
+ if (call.status === "operator_reconciled") operatorReconciledCalls += 1;
5163
+ if (!Object.prototype.hasOwnProperty.call(call, "providerUsage")) {
5164
+ legacyCalls += 1;
5165
+ continue;
5166
+ }
5167
+ if (!validProviderUsage(call.providerUsage)) continue;
5168
+ observedEffectIds.add(effect.id);
5169
+ totals.inputTokens += call.providerUsage.inputTokens;
5170
+ totals.outputTokens += call.providerUsage.outputTokens;
5171
+ totals.cacheReadInputTokens += call.providerUsage.cacheReadInputTokens;
5172
+ totals.cacheCreationInputTokens +=
5173
+ call.providerUsage.cacheCreationInputTokens;
5174
+ totals.totalTokens += call.providerUsage.totalTokens;
5175
+ lineage.push({
5176
+ effectId: effect.id,
5177
+ ownerEffectId: call.ownerEffectId ?? effect.id,
5178
+ callRecordId: call.id,
5179
+ callId: call.callId,
5180
+ sequence: call.sequence,
5181
+ provider: call.name,
5182
+ model: Object.prototype.hasOwnProperty.call(call, "providerModel")
5183
+ ? call.providerModel
5184
+ : null,
5185
+ source: call.source,
5186
+ requestSource: call.requestSource ?? call.source,
5187
+ descendant: (call.ownerEffectId ?? effect.id) !== effect.id,
5188
+ settledAt: call.settledAt,
5189
+ usage: call.providerUsage,
5190
+ usageDigest: providerUsageDigest(call.providerUsage),
5191
+ });
5192
+ }
5193
+ }
5194
+ return {
5195
+ authority:
5196
+ legacyCalls > 0
5197
+ ? "runtime-state-hash-chain-fsync-with-legacy-call-schema"
5198
+ : "runtime-state-hash-chain-fsync",
5199
+ crashVisible: true,
5200
+ providerCalls,
5201
+ providerReportedCalls: lineage.length,
5202
+ providerReportedEffects: observedEffectIds.size,
5203
+ missingProviderReportedCalls: providerCalls - lineage.length,
5204
+ pendingCalls,
5205
+ outcomeUnknownCalls,
5206
+ operatorReconciledCalls,
5207
+ legacyCalls,
5208
+ providerReported: lineage.length > 0 ? totals : null,
5209
+ lineageDigest: digest(
5210
+ "chainlesschain.dynamic-workflow.provider-token-usage-lineage.v1\0",
5211
+ lineage,
5212
+ ),
5213
+ lineage,
5214
+ };
5215
+ }
5216
+
5217
+ function projectProviderCostEstimates(state) {
5218
+ const lineage = [];
5219
+ const pricedEffectIds = new Set();
5220
+ let providerCalls = 0;
5221
+ let pricingSnapshotCalls = 0;
5222
+ let pendingCalls = 0;
5223
+ let outcomeUnknownCalls = 0;
5224
+ let operatorReconciledCalls = 0;
5225
+ let unpricedCalls = 0;
5226
+ let modelMissingCalls = 0;
5227
+ let legacyCalls = 0;
5228
+ let estimatedUsd = 0;
5229
+ for (const effect of state.effects) {
5230
+ for (const call of effect.calls || []) {
5231
+ if (call.kind !== "provider") continue;
5232
+ providerCalls += 1;
5233
+ if (call.status === "started") pendingCalls += 1;
5234
+ if (call.status === "outcome_unknown") outcomeUnknownCalls += 1;
5235
+ if (call.status === "operator_reconciled") operatorReconciledCalls += 1;
5236
+ const hasSchema = [
5237
+ "providerModel",
5238
+ "providerPricing",
5239
+ "providerCostEstimate",
5240
+ ].every((field) => Object.prototype.hasOwnProperty.call(call, field));
5241
+ if (!hasSchema) {
5242
+ legacyCalls += 1;
5243
+ continue;
5244
+ }
5245
+ if (call.providerModel === null) modelMissingCalls += 1;
5246
+ if (
5247
+ validProviderPricing(
5248
+ call.providerPricing,
5249
+ call.name,
5250
+ call.providerModel,
5251
+ )
5252
+ ) {
5253
+ pricingSnapshotCalls += 1;
5254
+ }
5255
+ if (
5256
+ call.status === "completed" &&
5257
+ validProviderUsage(call.providerUsage) &&
5258
+ call.providerPricing === null
5259
+ ) {
5260
+ unpricedCalls += 1;
5261
+ }
5262
+ if (
5263
+ !validProviderCostEstimate(
5264
+ call.providerCostEstimate,
5265
+ call.providerPricing,
5266
+ call.providerUsage,
5267
+ )
5268
+ ) {
5269
+ continue;
5270
+ }
5271
+ pricedEffectIds.add(effect.id);
5272
+ estimatedUsd += call.providerCostEstimate.totalUsd;
5273
+ lineage.push({
5274
+ effectId: effect.id,
5275
+ ownerEffectId: call.ownerEffectId ?? effect.id,
5276
+ callRecordId: call.id,
5277
+ callId: call.callId,
5278
+ sequence: call.sequence,
5279
+ provider: call.name,
5280
+ model: call.providerModel,
5281
+ source: call.source,
5282
+ requestSource: call.requestSource ?? call.source,
5283
+ descendant: (call.ownerEffectId ?? effect.id) !== effect.id,
5284
+ settledAt: call.settledAt,
5285
+ pricing: call.providerPricing,
5286
+ estimate: call.providerCostEstimate,
5287
+ estimateDigest: providerCostEstimateDigest(call.providerCostEstimate),
5288
+ });
5289
+ }
5290
+ }
5291
+ return {
5292
+ authority:
5293
+ legacyCalls > 0
5294
+ ? "durable-pricing-snapshot-estimate-with-legacy-call-schema"
5295
+ : "durable-pricing-snapshot-estimate",
5296
+ currency: "USD",
5297
+ reportedUsd: null,
5298
+ estimatedUsd: lineage.length > 0 ? estimatedUsd : null,
5299
+ providerCalls,
5300
+ pricingSnapshotCalls,
5301
+ pricedCalls: lineage.length,
5302
+ pricedEffects: pricedEffectIds.size,
5303
+ missingEstimateCalls: providerCalls - lineage.length,
5304
+ pendingCalls,
5305
+ outcomeUnknownCalls,
5306
+ operatorReconciledCalls,
5307
+ unpricedCalls,
5308
+ modelMissingCalls,
5309
+ legacyCalls,
5310
+ lineageDigest: digest(
5311
+ "chainlesschain.dynamic-workflow.provider-cost-estimate-lineage.v1\0",
5312
+ lineage,
5313
+ ),
5314
+ lineage,
5315
+ };
5316
+ }
5317
+
5318
+ function projectArtifactStoreReadbacks(state) {
5319
+ const lineage = [];
5320
+ const verifiedEffectIds = new Set();
5321
+ let artifactCalls = 0;
5322
+ let completedCalls = 0;
5323
+ let failedCalls = 0;
5324
+ let pendingCalls = 0;
5325
+ let outcomeUnknownCalls = 0;
5326
+ let operatorReconciledCalls = 0;
5327
+ let missingReadbacks = 0;
5328
+ let legacyCalls = 0;
5329
+ for (const effect of state.effects) {
5330
+ for (const call of effect.calls || []) {
5331
+ if (call.kind !== "tool" || call.name !== "publish_artifact") continue;
5332
+ artifactCalls += 1;
5333
+ if (call.status === "completed") completedCalls += 1;
5334
+ if (call.status === "failed") failedCalls += 1;
5335
+ if (call.status === "started") pendingCalls += 1;
5336
+ if (call.status === "outcome_unknown") outcomeUnknownCalls += 1;
5337
+ if (call.status === "operator_reconciled") operatorReconciledCalls += 1;
5338
+ if (!Object.prototype.hasOwnProperty.call(call, "artifactReadback")) {
5339
+ legacyCalls += 1;
5340
+ if (call.status !== "failed") missingReadbacks += 1;
5341
+ continue;
5342
+ }
5343
+ if (!validArtifactReadback(call.artifactReadback)) {
5344
+ if (call.status !== "failed") missingReadbacks += 1;
5345
+ continue;
5346
+ }
5347
+ verifiedEffectIds.add(effect.id);
5348
+ lineage.push({
5349
+ effectId: effect.id,
5350
+ ownerEffectId: call.ownerEffectId ?? effect.id,
5351
+ callRecordId: call.id,
5352
+ callId: call.callId,
5353
+ sequence: call.sequence,
5354
+ descendant: (call.ownerEffectId ?? effect.id) !== effect.id,
5355
+ settledAt: call.settledAt,
5356
+ readback: call.artifactReadback,
5357
+ readbackDigest: artifactReadbackDigest(call.artifactReadback),
5358
+ });
5359
+ }
5360
+ }
5361
+ return {
5362
+ authority:
5363
+ legacyCalls > 0
5364
+ ? "artifact-store-index-and-bytes-at-settlement-with-legacy-call-schema"
5365
+ : "artifact-store-index-and-bytes-at-settlement",
5366
+ verificationTiming: "tool-settlement",
5367
+ immutableRetentionProven: false,
5368
+ artifactCalls,
5369
+ completedCalls,
5370
+ failedCalls,
5371
+ verifiedCalls: lineage.length,
5372
+ verifiedEffects: verifiedEffectIds.size,
5373
+ missingReadbacks,
5374
+ pendingCalls,
5375
+ outcomeUnknownCalls,
5376
+ operatorReconciledCalls,
5377
+ legacyCalls,
5378
+ lineageDigest: digest(
5379
+ "chainlesschain.dynamic-workflow.artifact-store-readback-lineage.v1\0",
5380
+ lineage,
5381
+ ),
5382
+ lineage,
5383
+ };
5384
+ }
5385
+
5386
+ function projectCheckpointStoreReadbacks(state) {
5387
+ const lineage = [];
5388
+ const verifiedEffectIds = new Set();
5389
+ let toolCalls = 0;
5390
+ let completedCalls = 0;
5391
+ let failedCalls = 0;
5392
+ let pendingCalls = 0;
5393
+ let outcomeUnknownCalls = 0;
5394
+ let operatorReconciledCalls = 0;
5395
+ let committedCalls = 0;
5396
+ let rolledBackCalls = 0;
5397
+ let fullCoverageCalls = 0;
5398
+ let partialCoverageCalls = 0;
5399
+ let externalSideEffectCalls = 0;
5400
+ let preparedBindingCalls = 0;
5401
+ let terminalStoreRecoveredCalls = 0;
5402
+ let bindingLegacyCalls = 0;
5403
+ let missingReadbacks = 0;
5404
+ let legacyCalls = 0;
5405
+ for (const effect of state.effects) {
5406
+ for (const call of effect.calls || []) {
5407
+ if (call.kind !== "tool" || !managedToolCheckpointRequired(call.name)) {
5408
+ continue;
5409
+ }
5410
+ toolCalls += 1;
5411
+ if (call.status === "completed") completedCalls += 1;
5412
+ if (call.status === "failed") failedCalls += 1;
5413
+ if (call.status === "started") pendingCalls += 1;
5414
+ if (call.status === "outcome_unknown") outcomeUnknownCalls += 1;
5415
+ if (call.status === "operator_reconciled") operatorReconciledCalls += 1;
5416
+ if (!Object.prototype.hasOwnProperty.call(call, "checkpointBinding")) {
5417
+ bindingLegacyCalls += 1;
5418
+ }
5419
+ if (validCheckpointBinding(call.checkpointBinding)) {
5420
+ preparedBindingCalls += 1;
5421
+ }
5422
+ if (
5423
+ [
5424
+ "checkpoint_store_recovered_commit",
5425
+ "checkpoint_store_recovered_rollback",
5426
+ ].includes(call.settlementCode)
5427
+ ) {
5428
+ terminalStoreRecoveredCalls += 1;
5429
+ }
5430
+ if (!Object.prototype.hasOwnProperty.call(call, "checkpointReadback")) {
5431
+ legacyCalls += 1;
5432
+ missingReadbacks += 1;
5433
+ continue;
5434
+ }
5435
+ if (!validCheckpointReadback(call.checkpointReadback)) {
5436
+ missingReadbacks += 1;
5437
+ continue;
5438
+ }
5439
+ if (call.checkpointReadback.outcome === "committed") committedCalls += 1;
5440
+ if (call.checkpointReadback.outcome === "rolled_back") {
5441
+ rolledBackCalls += 1;
5442
+ }
5443
+ if (
5444
+ call.checkpointReadback.coverage === WORKSPACE_TRANSACTION_COVERAGE.FULL
5445
+ ) {
5446
+ fullCoverageCalls += 1;
5447
+ }
5448
+ if (
5449
+ call.checkpointReadback.coverage ===
5450
+ WORKSPACE_TRANSACTION_COVERAGE.PARTIAL
5451
+ ) {
5452
+ partialCoverageCalls += 1;
5453
+ }
5454
+ if (call.checkpointReadback.externalSideEffects) {
5455
+ externalSideEffectCalls += 1;
5456
+ }
5457
+ verifiedEffectIds.add(effect.id);
5458
+ lineage.push({
5459
+ effectId: effect.id,
5460
+ ownerEffectId: call.ownerEffectId ?? effect.id,
5461
+ callRecordId: call.id,
5462
+ callId: call.callId,
5463
+ sequence: call.sequence,
5464
+ tool: call.name,
5465
+ descendant: (call.ownerEffectId ?? effect.id) !== effect.id,
5466
+ settledAt: call.settledAt,
5467
+ readback: call.checkpointReadback,
5468
+ readbackDigest: checkpointReadbackDigest(call.checkpointReadback),
5469
+ });
5470
+ }
5471
+ }
5472
+ return {
5473
+ authority:
5474
+ legacyCalls > 0
5475
+ ? "workspace-transaction-store-terminal-readback-with-legacy-call-schema"
5476
+ : "workspace-transaction-store-terminal-readback",
5477
+ verificationTiming: "tool-settlement",
5478
+ rollbackScope: "workspace-files-only",
5479
+ externalSideEffectsRollbackProven: false,
5480
+ toolCalls,
5481
+ completedCalls,
5482
+ failedCalls,
5483
+ verifiedCalls: lineage.length,
5484
+ verifiedEffects: verifiedEffectIds.size,
5485
+ committedCalls,
5486
+ rolledBackCalls,
5487
+ fullCoverageCalls,
5488
+ partialCoverageCalls,
5489
+ externalSideEffectCalls,
5490
+ preparedBindingCalls,
5491
+ terminalStoreRecoveredCalls,
5492
+ bindingLegacyCalls,
5493
+ missingReadbacks,
5494
+ pendingCalls,
5495
+ outcomeUnknownCalls,
5496
+ operatorReconciledCalls,
5497
+ legacyCalls,
5498
+ lineageDigest: digest(
5499
+ "chainlesschain.dynamic-workflow.checkpoint-store-readback-lineage.v1\0",
5500
+ lineage,
5501
+ ),
5502
+ lineage,
5503
+ };
5504
+ }
5505
+
2626
5506
  function projectDynamicWorkflowObservability(state) {
2627
5507
  const settled = state.effects.filter((effect) => effect.status === "settled");
2628
5508
  const effectLineage = [];
@@ -2633,6 +5513,12 @@ function projectDynamicWorkflowObservability(state) {
2633
5513
  const nestedEffectAttemptLineage = [];
2634
5514
  const nestedEffectSettlementLineage = [];
2635
5515
  const durableCalls = projectDurableWorkflowCalls(state);
5516
+ const providerTokenUsage = projectProviderTokenUsage(state);
5517
+ const providerCostEstimates = projectProviderCostEstimates(state);
5518
+ const artifactStoreReadbacks = projectArtifactStoreReadbacks(state);
5519
+ const checkpointStoreReadbacks = projectCheckpointStoreReadbacks(state);
5520
+ const providerReceipts = projectProviderRequestReceipts(state);
5521
+ const nestedEffects = projectNestedToolEffects(state);
2636
5522
  let requestToSettlementMs = 0;
2637
5523
  let maxRequestToSettlementMs = 0;
2638
5524
  let estimatedTokens = 0;
@@ -2644,21 +5530,21 @@ function projectDynamicWorkflowObservability(state) {
2644
5530
  let checkpointCount = 0;
2645
5531
  let artifactTruncatedEffects = 0;
2646
5532
  let checkpointTruncatedEffects = 0;
2647
- let providerRequestAttemptCount = 0;
2648
- let providerRequestAttemptEffects = 0;
2649
- let providerReceiptCount = 0;
2650
- let providerReceiptEffects = 0;
2651
- let invalidProviderRequestAttempts = 0;
2652
- let invalidProviderReceipts = 0;
2653
- let missingProviderRequestReceipts = 0;
2654
- let providerReceiptTruncatedEffects = 0;
2655
- let nestedEffectAttemptCount = 0;
2656
- let nestedEffectSettlementCount = 0;
2657
- let nestedEffectDurableMcpSettlements = 0;
2658
- let invalidNestedEffectAttempts = 0;
2659
- let invalidNestedEffectSettlements = 0;
2660
- let missingNestedEffectSettlements = 0;
2661
- let nestedEffectTruncatedEffects = 0;
5533
+ const providerRequestAttemptCount = providerReceipts.attemptCount;
5534
+ const providerRequestAttemptEffects = providerReceipts.requestAttemptEffects;
5535
+ const providerReceiptCount = providerReceipts.receiptCount;
5536
+ const providerReceiptEffects = providerReceipts.receiptEffects;
5537
+ const invalidProviderRequestAttempts = providerReceipts.invalidAttempts;
5538
+ const invalidProviderReceipts = providerReceipts.invalidReceipts;
5539
+ const missingProviderRequestReceipts = providerReceipts.missingReceipts;
5540
+ const providerReceiptTruncatedEffects = providerReceipts.truncatedEffects;
5541
+ const nestedEffectAttemptCount = nestedEffects.attemptCount;
5542
+ const nestedEffectSettlementCount = nestedEffects.settlementCount;
5543
+ const nestedEffectDurableMcpSettlements = nestedEffects.durableMcpSettlements;
5544
+ const invalidNestedEffectAttempts = nestedEffects.invalidAttempts;
5545
+ const invalidNestedEffectSettlements = nestedEffects.invalidSettlements;
5546
+ const missingNestedEffectSettlements = nestedEffects.missingSettlements;
5547
+ const nestedEffectTruncatedEffects = nestedEffects.truncatedEffects;
2662
5548
 
2663
5549
  for (const effect of settled) {
2664
5550
  const elapsed = Math.max(
@@ -2699,33 +5585,6 @@ function projectDynamicWorkflowObservability(state) {
2699
5585
  checkpointLineage.push(...checkpoints.lineage);
2700
5586
  if (checkpoints.truncated) checkpointTruncatedEffects += 1;
2701
5587
 
2702
- const providerReceipts = projectProviderRequestReceipts(effect);
2703
- providerRequestAttemptCount += providerReceipts.attemptCount;
2704
- providerReceiptCount += providerReceipts.receiptCount;
2705
- providerRequestAttemptLineage.push(...providerReceipts.attemptLineage);
2706
- providerReceiptLineage.push(...providerReceipts.receiptLineage);
2707
- invalidProviderRequestAttempts += providerReceipts.invalidAttempts;
2708
- invalidProviderReceipts += providerReceipts.invalidReceipts;
2709
- missingProviderRequestReceipts += providerReceipts.missingReceipts;
2710
- if (providerReceipts.attemptLineage.length > 0) {
2711
- providerRequestAttemptEffects += 1;
2712
- }
2713
- if (providerReceipts.receiptLineage.length > 0) {
2714
- providerReceiptEffects += 1;
2715
- }
2716
- if (providerReceipts.truncated) providerReceiptTruncatedEffects += 1;
2717
-
2718
- const nestedEffects = projectNestedToolEffects(effect);
2719
- nestedEffectAttemptCount += nestedEffects.attemptCount;
2720
- nestedEffectSettlementCount += nestedEffects.settlementCount;
2721
- nestedEffectDurableMcpSettlements += nestedEffects.durableMcpSettlements;
2722
- invalidNestedEffectAttempts += nestedEffects.invalidAttempts;
2723
- invalidNestedEffectSettlements += nestedEffects.invalidSettlements;
2724
- missingNestedEffectSettlements += nestedEffects.missingSettlements;
2725
- nestedEffectAttemptLineage.push(...nestedEffects.attemptLineage);
2726
- nestedEffectSettlementLineage.push(...nestedEffects.settlementLineage);
2727
- if (nestedEffects.truncated) nestedEffectTruncatedEffects += 1;
2728
-
2729
5588
  effectLineage.push({
2730
5589
  effectId: effect.id,
2731
5590
  stepId: effect.stepId,
@@ -2752,14 +5611,70 @@ function projectDynamicWorkflowObservability(state) {
2752
5611
  }
2753
5612
 
2754
5613
  const gaps = [
2755
- "provider-token-usage-unavailable",
2756
5614
  "provider-cost-usd-unavailable",
2757
5615
  "provider-native-idempotency-unavailable",
2758
5616
  "provider-receipt-independent-readback-unavailable",
2759
- "checkpoint-provider-readback-unavailable",
2760
- "artifact-store-readback-unavailable",
2761
- "nested-tool-independent-ledger-incomplete",
5617
+ "artifact-store-immutable-retention-unavailable",
2762
5618
  ];
5619
+ providerRequestAttemptLineage.push(...providerReceipts.attemptLineage);
5620
+ providerReceiptLineage.push(...providerReceipts.receiptLineage);
5621
+ nestedEffectAttemptLineage.push(...nestedEffects.attemptLineage);
5622
+ nestedEffectSettlementLineage.push(...nestedEffects.settlementLineage);
5623
+ if (providerTokenUsage.providerReportedCalls === 0) {
5624
+ gaps.push("provider-token-usage-unavailable");
5625
+ }
5626
+ if (providerTokenUsage.missingProviderReportedCalls > 0) {
5627
+ gaps.push("provider-token-usage-incomplete");
5628
+ }
5629
+ if (providerTokenUsage.legacyCalls > 0) {
5630
+ gaps.push("provider-token-usage-legacy-call-schema");
5631
+ }
5632
+ if (providerCostEstimates.pricedCalls === 0) {
5633
+ gaps.push("provider-cost-estimate-unavailable");
5634
+ }
5635
+ if (providerCostEstimates.missingEstimateCalls > 0) {
5636
+ gaps.push("provider-cost-estimate-incomplete");
5637
+ }
5638
+ if (providerCostEstimates.legacyCalls > 0) {
5639
+ gaps.push("provider-cost-estimate-legacy-call-schema");
5640
+ }
5641
+ if (artifactStoreReadbacks.verifiedCalls === 0) {
5642
+ gaps.push("artifact-store-readback-unavailable");
5643
+ }
5644
+ if (artifactStoreReadbacks.missingReadbacks > 0) {
5645
+ gaps.push("artifact-store-readback-incomplete");
5646
+ }
5647
+ if (artifactStoreReadbacks.legacyCalls > 0) {
5648
+ gaps.push("artifact-store-readback-legacy-call-schema");
5649
+ }
5650
+ if (checkpointStoreReadbacks.verifiedCalls === 0) {
5651
+ gaps.push("checkpoint-provider-readback-unavailable");
5652
+ }
5653
+ if (checkpointStoreReadbacks.missingReadbacks > 0) {
5654
+ gaps.push("checkpoint-store-readback-incomplete");
5655
+ }
5656
+ if (checkpointStoreReadbacks.legacyCalls > 0) {
5657
+ gaps.push("checkpoint-store-readback-legacy-call-schema");
5658
+ }
5659
+ if (
5660
+ checkpointStoreReadbacks.preparedBindingCalls <
5661
+ checkpointStoreReadbacks.toolCalls
5662
+ ) {
5663
+ gaps.push("checkpoint-prepared-binding-incomplete");
5664
+ }
5665
+ if (checkpointStoreReadbacks.bindingLegacyCalls > 0) {
5666
+ gaps.push("checkpoint-prepared-binding-legacy-call-schema");
5667
+ }
5668
+ if (
5669
+ checkpointStoreReadbacks.verifiedCalls > 0 &&
5670
+ checkpointStoreReadbacks.fullCoverageCalls <
5671
+ checkpointStoreReadbacks.verifiedCalls
5672
+ ) {
5673
+ gaps.push("checkpoint-full-coverage-incomplete");
5674
+ }
5675
+ if (checkpointStoreReadbacks.externalSideEffectCalls > 0) {
5676
+ gaps.push("checkpoint-external-side-effect-rollback-unavailable");
5677
+ }
2763
5678
  if (tokenEstimateEffects !== settled.length) {
2764
5679
  gaps.push("cowork-token-estimate-incomplete");
2765
5680
  }
@@ -2773,7 +5688,8 @@ function projectDynamicWorkflowObservability(state) {
2773
5688
  (effect) => effect.settlementAuthority === "provider-return",
2774
5689
  ).length;
2775
5690
  if (
2776
- providerReceiptEffects !== providerReturnedEffects ||
5691
+ providerReceipts.providerReturnedReceiptEffects !==
5692
+ providerReturnedEffects ||
2777
5693
  missingProviderRequestReceipts > 0
2778
5694
  ) {
2779
5695
  gaps.push("provider-request-receipt-incomplete");
@@ -2787,6 +5703,12 @@ function projectDynamicWorkflowObservability(state) {
2787
5703
  if (providerReceiptTruncatedEffects > 0) {
2788
5704
  gaps.push("provider-request-receipt-projection-truncated");
2789
5705
  }
5706
+ if (providerReceipts.legacyResultFallbackEffects > 0) {
5707
+ gaps.push("provider-request-receipt-legacy-result-fallback");
5708
+ }
5709
+ if (providerReceipts.conflictingOuterResultEffects > 0) {
5710
+ gaps.push("provider-request-result-disagrees-with-durable-store");
5711
+ }
2790
5712
  if (invalidNestedEffectAttempts > 0) {
2791
5713
  gaps.push("nested-tool-effect-attempt-invalid");
2792
5714
  }
@@ -2799,6 +5721,13 @@ function projectDynamicWorkflowObservability(state) {
2799
5721
  if (nestedEffectTruncatedEffects > 0) {
2800
5722
  gaps.push("nested-tool-effect-projection-truncated");
2801
5723
  }
5724
+ if (nestedEffects.legacyResultFallbackEffects > 0) {
5725
+ gaps.push("nested-tool-independent-ledger-incomplete");
5726
+ gaps.push("nested-tool-effect-legacy-result-fallback");
5727
+ }
5728
+ if (nestedEffects.conflictingOuterResultEffects > 0) {
5729
+ gaps.push("nested-tool-result-disagrees-with-durable-store");
5730
+ }
2802
5731
 
2803
5732
  return snapshotJson(
2804
5733
  {
@@ -2839,27 +5768,42 @@ function projectDynamicWorkflowObservability(state) {
2839
5768
  maxMs: maxRequestToSettlementMs,
2840
5769
  },
2841
5770
  tokens: {
2842
- authority: "cowork-result-heuristic",
5771
+ ...providerTokenUsage,
5772
+ estimateAuthority: "cowork-result-heuristic",
2843
5773
  estimated: estimatedTokens,
2844
5774
  observedEffects: tokenEstimateEffects,
2845
5775
  missingEffects: settled.length - tokenEstimateEffects,
2846
- providerReported: null,
2847
5776
  },
2848
5777
  cost: {
2849
- authority: "unavailable",
2850
- reportedUsd: null,
2851
- observedEffects: 0,
5778
+ ...providerCostEstimates,
5779
+ observedEffects: providerCostEstimates.pricedEffects,
5780
+ projectedRecords: providerCostEstimates.lineage.length,
2852
5781
  },
2853
5782
  providerReceipts: {
2854
- authority: "provider-returned-trace-only",
5783
+ authority:
5784
+ providerReceipts.legacyResultFallbackEffects > 0
5785
+ ? "runtime-state-hash-chain-fsync-with-legacy-task-result-fallback"
5786
+ : "runtime-state-hash-chain-fsync",
5787
+ receiptSemantics: "provider-returned-trace-only",
5788
+ crashVisible: true,
5789
+ durableCallEffects: providerReceipts.durableCallEffects,
5790
+ legacyResultFallbackEffects:
5791
+ providerReceipts.legacyResultFallbackEffects,
5792
+ conflictingOuterResultEffects:
5793
+ providerReceipts.conflictingOuterResultEffects,
2855
5794
  count: providerReceiptCount,
2856
5795
  projectedRecords: providerReceiptLineage.length,
2857
5796
  requestAttempts: providerRequestAttemptCount,
2858
5797
  projectedRequestAttempts: providerRequestAttemptLineage.length,
2859
5798
  requestAttemptEffects: providerRequestAttemptEffects,
2860
5799
  observedEffects: providerReceiptEffects,
2861
- missingProviderReturnedEffects:
2862
- providerReturnedEffects - providerReceiptEffects,
5800
+ providerReturnedObservedEffects:
5801
+ providerReceipts.providerReturnedReceiptEffects,
5802
+ missingProviderReturnedEffects: Math.max(
5803
+ 0,
5804
+ providerReturnedEffects -
5805
+ providerReceipts.providerReturnedReceiptEffects,
5806
+ ),
2863
5807
  missingRequestReceipts: missingProviderRequestReceipts,
2864
5808
  invalidRequestAttempts: invalidProviderRequestAttempts,
2865
5809
  invalidRecords: invalidProviderReceipts,
@@ -2879,7 +5823,15 @@ function projectDynamicWorkflowObservability(state) {
2879
5823
  },
2880
5824
  durableCalls,
2881
5825
  nestedEffects: {
2882
- authority: "task-result-bound-with-mcp-session-ledger-flags",
5826
+ authority:
5827
+ nestedEffects.legacyResultFallbackEffects > 0
5828
+ ? "runtime-state-hash-chain-fsync-with-legacy-task-result-fallback"
5829
+ : "runtime-state-hash-chain-fsync",
5830
+ crashVisible: true,
5831
+ durableCallEffects: nestedEffects.durableCallEffects,
5832
+ legacyResultFallbackEffects: nestedEffects.legacyResultFallbackEffects,
5833
+ conflictingOuterResultEffects:
5834
+ nestedEffects.conflictingOuterResultEffects,
2883
5835
  attempts: nestedEffectAttemptCount,
2884
5836
  settlements: nestedEffectSettlementCount,
2885
5837
  projectedAttempts: nestedEffectAttemptLineage.length,
@@ -2889,7 +5841,8 @@ function projectDynamicWorkflowObservability(state) {
2889
5841
  invalidAttempts: invalidNestedEffectAttempts,
2890
5842
  invalidSettlements: invalidNestedEffectSettlements,
2891
5843
  truncatedEffects: nestedEffectTruncatedEffects,
2892
- allEffectsIndependentlyDurable: false,
5844
+ allEffectsIndependentlyDurable:
5845
+ nestedEffects.legacyResultFallbackEffects === 0,
2893
5846
  attemptLineageDigest: digest(
2894
5847
  "chainlesschain.dynamic-workflow.nested-effect-attempt-lineage.v1\0",
2895
5848
  nestedEffectAttemptLineage,
@@ -2911,6 +5864,7 @@ function projectDynamicWorkflowObservability(state) {
2911
5864
  artifactLineage,
2912
5865
  ),
2913
5866
  lineage: artifactLineage,
5867
+ storeReadbacks: artifactStoreReadbacks,
2914
5868
  },
2915
5869
  checkpoints: {
2916
5870
  authority: "task-result-digest-only",
@@ -2922,6 +5876,7 @@ function projectDynamicWorkflowObservability(state) {
2922
5876
  checkpointLineage,
2923
5877
  ),
2924
5878
  lineage: checkpointLineage,
5879
+ storeReadbacks: checkpointStoreReadbacks,
2925
5880
  },
2926
5881
  gaps,
2927
5882
  },
@@ -2930,7 +5885,135 @@ function projectDynamicWorkflowObservability(state) {
2930
5885
  );
2931
5886
  }
2932
5887
 
2933
- export function projectDynamicWorkflowRuntime(stateOrPath) {
5888
+ function projectCurrentWorkflowStoreReadbacks(state, deps = {}) {
5889
+ const artifactStore = deps.artifactStore || new ArtifactStore();
5890
+ const checkpointStore =
5891
+ deps.checkpointStore || new WorkspaceTransactionManager();
5892
+ const artifacts = {
5893
+ eligibleCalls: 0,
5894
+ verifiedCalls: 0,
5895
+ mismatchedCalls: 0,
5896
+ unavailableCalls: 0,
5897
+ lineage: [],
5898
+ };
5899
+ const checkpoints = {
5900
+ eligibleCalls: 0,
5901
+ verifiedCalls: 0,
5902
+ mismatchedCalls: 0,
5903
+ unavailableCalls: 0,
5904
+ lineage: [],
5905
+ };
5906
+
5907
+ for (const effect of state.effects) {
5908
+ for (const call of effect.calls || []) {
5909
+ if (validArtifactReadback(call.artifactReadback)) {
5910
+ artifacts.eligibleCalls += 1;
5911
+ const storedDigest = artifactReadbackDigest(call.artifactReadback);
5912
+ let currentDigest = null;
5913
+ let status = "unavailable";
5914
+ try {
5915
+ const current = artifactReadbackSettlement(
5916
+ { result: { published: call.artifactReadback.metadata } },
5917
+ artifactStore,
5918
+ );
5919
+ currentDigest = artifactReadbackDigest(current);
5920
+ status = currentDigest === storedDigest ? "verified" : "mismatch";
5921
+ } catch {
5922
+ status = "unavailable";
5923
+ }
5924
+ if (status === "verified") artifacts.verifiedCalls += 1;
5925
+ else if (status === "mismatch") artifacts.mismatchedCalls += 1;
5926
+ else artifacts.unavailableCalls += 1;
5927
+ artifacts.lineage.push({
5928
+ effectId: effect.id,
5929
+ callRecordId: call.id,
5930
+ artifactId: call.artifactReadback.metadata.id,
5931
+ storedReadbackDigest: storedDigest,
5932
+ currentReadbackDigest: currentDigest,
5933
+ status,
5934
+ });
5935
+ }
5936
+
5937
+ if (validCheckpointReadback(call.checkpointReadback)) {
5938
+ checkpoints.eligibleCalls += 1;
5939
+ const storedDigest = checkpointReadbackDigest(call.checkpointReadback);
5940
+ let currentDigest = null;
5941
+ let status = "unavailable";
5942
+ if (validCheckpointBinding(call.checkpointBinding)) {
5943
+ try {
5944
+ const current = checkpointReadbackFromPreparedBinding(
5945
+ call.checkpointBinding,
5946
+ checkpointStore,
5947
+ );
5948
+ if (current !== null) {
5949
+ currentDigest = checkpointReadbackDigest(current);
5950
+ status = currentDigest === storedDigest ? "verified" : "mismatch";
5951
+ }
5952
+ } catch {
5953
+ status = "unavailable";
5954
+ }
5955
+ }
5956
+ if (status === "verified") checkpoints.verifiedCalls += 1;
5957
+ else if (status === "mismatch") checkpoints.mismatchedCalls += 1;
5958
+ else checkpoints.unavailableCalls += 1;
5959
+ checkpoints.lineage.push({
5960
+ effectId: effect.id,
5961
+ callRecordId: call.id,
5962
+ transactionId: call.checkpointReadback.transactionId,
5963
+ checkpointId: call.checkpointReadback.checkpointId,
5964
+ storedReadbackDigest: storedDigest,
5965
+ currentReadbackDigest: currentDigest,
5966
+ status,
5967
+ });
5968
+ }
5969
+ }
5970
+ }
5971
+
5972
+ const gaps = [];
5973
+ if (artifacts.mismatchedCalls > 0) {
5974
+ gaps.push("artifact-store-current-readback-mismatch");
5975
+ }
5976
+ if (artifacts.unavailableCalls > 0) {
5977
+ gaps.push("artifact-store-current-readback-unavailable");
5978
+ }
5979
+ if (checkpoints.mismatchedCalls > 0) {
5980
+ gaps.push("checkpoint-store-current-readback-mismatch");
5981
+ }
5982
+ if (checkpoints.unavailableCalls > 0) {
5983
+ gaps.push("checkpoint-store-current-readback-unavailable");
5984
+ }
5985
+ const eligibleCalls = artifacts.eligibleCalls + checkpoints.eligibleCalls;
5986
+ const verifiedCalls = artifacts.verifiedCalls + checkpoints.verifiedCalls;
5987
+ return snapshotJson(
5988
+ {
5989
+ schema: "cc-dynamic-workflow-current-store-readback/v1",
5990
+ authority: "artifact-and-checkpoint-store-at-status-projection",
5991
+ verificationTiming: "runtime-status",
5992
+ complete: eligibleCalls > 0 && eligibleCalls === verifiedCalls,
5993
+ eligibleCalls,
5994
+ verifiedCalls,
5995
+ artifacts: {
5996
+ ...artifacts,
5997
+ lineageDigest: digest(
5998
+ "chainlesschain.dynamic-workflow.current-artifact-readback-lineage.v1\0",
5999
+ artifacts.lineage,
6000
+ ),
6001
+ },
6002
+ checkpoints: {
6003
+ ...checkpoints,
6004
+ lineageDigest: digest(
6005
+ "chainlesschain.dynamic-workflow.current-checkpoint-readback-lineage.v1\0",
6006
+ checkpoints.lineage,
6007
+ ),
6008
+ },
6009
+ gaps,
6010
+ },
6011
+ "dynamic workflow current store readback projection",
6012
+ 2 * 1024 * 1024,
6013
+ );
6014
+ }
6015
+
6016
+ export function projectDynamicWorkflowRuntime(stateOrPath, options = {}) {
2934
6017
  const state =
2935
6018
  typeof stateOrPath === "string"
2936
6019
  ? readDynamicWorkflowRuntimeState(stateOrPath)
@@ -2966,9 +6049,406 @@ export function projectDynamicWorkflowRuntime(stateOrPath) {
2966
6049
  }),
2967
6050
  ),
2968
6051
  ),
6052
+ inputRequestCount: runtimeInputRequests(state).length,
6053
+ answeredInputRequestCount: runtimeInputRequests(state).filter(
6054
+ (request) => request.status === "answered",
6055
+ ).length,
6056
+ pendingInputRequests: Object.freeze(
6057
+ runtimeInputRequests(state)
6058
+ .filter((request) => request.status === "pending")
6059
+ .map((request) =>
6060
+ Object.freeze({
6061
+ id: request.id,
6062
+ stepId: request.stepId,
6063
+ prompt: request.prompt,
6064
+ options: request.options
6065
+ ? Object.freeze([...request.options])
6066
+ : null,
6067
+ multiSelect: request.multiSelect,
6068
+ requestedAt: request.requestedAt,
6069
+ }),
6070
+ ),
6071
+ ),
2969
6072
  finalRecordStatus: state.finalRecord?.status || null,
2970
6073
  observability: projectDynamicWorkflowObservability(state),
6074
+ currentStoreReadbacks:
6075
+ options.currentStoreReadback === true
6076
+ ? projectCurrentWorkflowStoreReadbacks(state, options)
6077
+ : null,
2971
6078
  updatedAt: state.updatedAt,
2972
6079
  stateDigest: state.stateDigest,
2973
6080
  });
2974
6081
  }
6082
+
6083
+ function checkpointRecoverySummary(state, options = {}) {
6084
+ const checkpointStore =
6085
+ options.checkpointStore || new WorkspaceTransactionManager();
6086
+ let prepared = 0;
6087
+ let terminal = 0;
6088
+ let pending = 0;
6089
+ let unavailable = 0;
6090
+ for (const effect of state.effects) {
6091
+ if (effect.status !== "pending") continue;
6092
+ for (const call of effect.calls || []) {
6093
+ if (
6094
+ call.kind !== "tool" ||
6095
+ call.status !== "started" ||
6096
+ Object.hasOwn(call, "artifactReadback") ||
6097
+ !validCheckpointBinding(call.checkpointBinding)
6098
+ ) {
6099
+ continue;
6100
+ }
6101
+ prepared += 1;
6102
+ try {
6103
+ if (
6104
+ checkpointReadbackFromPreparedBinding(
6105
+ call.checkpointBinding,
6106
+ checkpointStore,
6107
+ ) === null
6108
+ ) {
6109
+ pending += 1;
6110
+ } else {
6111
+ terminal += 1;
6112
+ }
6113
+ } catch {
6114
+ unavailable += 1;
6115
+ }
6116
+ }
6117
+ }
6118
+ return Object.freeze({ prepared, terminal, pending, unavailable });
6119
+ }
6120
+
6121
+ function observedBudgetStatus(observed, limit, complete) {
6122
+ if (!Number.isFinite(limit) || !Number.isFinite(observed) || !complete) {
6123
+ return "unknown";
6124
+ }
6125
+ return observed > limit ? "exceeded" : "within";
6126
+ }
6127
+
6128
+ const WORKFLOW_RECOVERY_BACKOFF_MS = Object.freeze([
6129
+ 15_000,
6130
+ 60_000,
6131
+ 5 * 60_000,
6132
+ 15 * 60_000,
6133
+ ]);
6134
+
6135
+ function dynamicWorkflowRecoveryPolicy(state, recovery) {
6136
+ const pendingInput = runtimeInputRequests(state).filter(
6137
+ (request) => request.status === "pending",
6138
+ ).length;
6139
+ const pendingEffects = state.effects.filter(
6140
+ (effect) => effect.status === "pending",
6141
+ );
6142
+ const uncertainEffects = pendingEffects.filter(
6143
+ (effect) => !isKnownUndispatched(effect),
6144
+ ).length;
6145
+ let risk = "none";
6146
+ let severity = "info";
6147
+ let recommendedAction = "none";
6148
+ let requiresApproval = false;
6149
+ let automaticallyExecutable = false;
6150
+
6151
+ if (recovery.terminal > 0) {
6152
+ risk = "terminal_checkpoint_recovery";
6153
+ severity = "warning";
6154
+ recommendedAction = "recover";
6155
+ requiresApproval = true;
6156
+ automaticallyExecutable = true;
6157
+ } else if (pendingInput > 0) {
6158
+ risk = "input_required";
6159
+ severity = "warning";
6160
+ recommendedAction = "reply";
6161
+ requiresApproval = true;
6162
+ } else if (uncertainEffects > 0) {
6163
+ risk = "operator_adjudication_required";
6164
+ severity = "critical";
6165
+ recommendedAction = "reconcile";
6166
+ requiresApproval = true;
6167
+ } else if (state.status === "pause_requested") {
6168
+ risk = "settlement_barrier_pending";
6169
+ severity = "info";
6170
+ recommendedAction = "wait";
6171
+ } else if (
6172
+ ["paused", "failed", "blocked"].includes(state.status) &&
6173
+ pendingEffects.length === 0
6174
+ ) {
6175
+ risk = "restart_ready";
6176
+ severity = "warning";
6177
+ recommendedAction = "resume";
6178
+ requiresApproval = true;
6179
+ automaticallyExecutable = true;
6180
+ }
6181
+
6182
+ const notificationKey = digest(
6183
+ "chainlesschain.dynamic-workflow.recovery-notification.v1\0",
6184
+ {
6185
+ runId: state.runId,
6186
+ revision: state.revision,
6187
+ stateDigest: state.stateDigest,
6188
+ risk,
6189
+ recommendedAction,
6190
+ },
6191
+ );
6192
+ return Object.freeze({
6193
+ schema: "cc-dynamic-workflow-recovery-policy/v1",
6194
+ risk,
6195
+ severity,
6196
+ recommendedAction,
6197
+ requiresApproval,
6198
+ automaticallyExecutable,
6199
+ unattendedMutationAllowed: false,
6200
+ pendingInput,
6201
+ pendingEffects: pendingEffects.length,
6202
+ uncertainEffects,
6203
+ notification: Object.freeze({
6204
+ key: notificationKey,
6205
+ backoffMs: WORKFLOW_RECOVERY_BACKOFF_MS,
6206
+ resetOnStateDigestChange: true,
6207
+ }),
6208
+ });
6209
+ }
6210
+
6211
+ /**
6212
+ * Bounded, content-free view used by the cross-IDE Sessions Workbench. Tool
6213
+ * arguments, prompts, provider output, result bodies, and filesystem bindings
6214
+ * deliberately never cross this projection boundary.
6215
+ */
6216
+ export function projectDynamicWorkflowWorkbenchState(
6217
+ stateOrPath,
6218
+ options = {},
6219
+ ) {
6220
+ const state =
6221
+ typeof stateOrPath === "string"
6222
+ ? readDynamicWorkflowRuntimeState(stateOrPath)
6223
+ : verifyDynamicWorkflowRuntimeState(stateOrPath);
6224
+ const observability = projectDynamicWorkflowObservability(state);
6225
+ const recovery = checkpointRecoverySummary(state, options);
6226
+ const recoveryPolicy = dynamicWorkflowRecoveryPolicy(state, recovery);
6227
+ const recentEffect = state.effects.at(-1) || null;
6228
+ const recentCall = recentEffect?.calls?.at(-1) || null;
6229
+ const lastTransition = state.lineage.at(-1) || null;
6230
+ const budget = state.executionBudget || null;
6231
+ const providerTokens = observability.tokens.providerReported?.totalTokens;
6232
+ const observedTokens = Number.isFinite(providerTokens)
6233
+ ? providerTokens
6234
+ : observability.tokens.missingEffects === 0
6235
+ ? observability.tokens.estimated
6236
+ : null;
6237
+ const tokenComplete =
6238
+ Number.isFinite(providerTokens) ||
6239
+ observability.tokens.missingEffects === 0;
6240
+ const observedUsd = Number.isFinite(observability.cost.estimatedUsd)
6241
+ ? observability.cost.estimatedUsd
6242
+ : null;
6243
+ const costComplete =
6244
+ observability.cost.providerCalls === observability.cost.pricedCalls;
6245
+ const observedDurationMs = observability.duration.totalMs;
6246
+ const durationComplete = observability.effects.pending === 0;
6247
+ const budgetStatus = {
6248
+ tokens: observedBudgetStatus(
6249
+ observedTokens,
6250
+ budget?.maxTokens,
6251
+ tokenComplete,
6252
+ ),
6253
+ usd: observedBudgetStatus(observedUsd, budget?.maxUsd, costComplete),
6254
+ duration: observedBudgetStatus(
6255
+ observedDurationMs,
6256
+ budget?.maxDurationMs,
6257
+ durationComplete,
6258
+ ),
6259
+ };
6260
+ const overallBudgetStatus = Object.values(budgetStatus).includes("exceeded")
6261
+ ? "exceeded"
6262
+ : Object.values(budgetStatus).includes("unknown")
6263
+ ? "unknown"
6264
+ : "within";
6265
+
6266
+ return Object.freeze({
6267
+ schema: "cc-dynamic-workflow-workbench-state/v1",
6268
+ runId: state.runId,
6269
+ workflowId: state.workflowId,
6270
+ status: state.status,
6271
+ revision: state.revision,
6272
+ stateDigest: state.stateDigest,
6273
+ definitionDigest: state.definitionDigest,
6274
+ admissionDigest: state.admissionDigest,
6275
+ executionAuthoritySessionId: state.executionAuthoritySessionId,
6276
+ createdAt: state.createdAt,
6277
+ updatedAt: state.updatedAt,
6278
+ phase: Object.freeze({
6279
+ status: state.status,
6280
+ transition: lastTransition?.type || "run-created",
6281
+ at: lastTransition?.at || state.createdAt,
6282
+ }),
6283
+ agents: Object.freeze({
6284
+ requested: observability.effects.requested,
6285
+ settled: observability.effects.settled,
6286
+ pending: observability.effects.pending,
6287
+ completed: observability.effects.completedTasks,
6288
+ failed: observability.effects.failedTasks,
6289
+ }),
6290
+ input: Object.freeze({
6291
+ requested: runtimeInputRequests(state).length,
6292
+ pending: runtimeInputRequests(state).filter(
6293
+ (request) => request.status === "pending",
6294
+ ).length,
6295
+ }),
6296
+ budget: Object.freeze({
6297
+ limits: budget,
6298
+ observed: Object.freeze({
6299
+ tokens: observedTokens,
6300
+ usd: observedUsd,
6301
+ durationMs: observedDurationMs,
6302
+ }),
6303
+ status: Object.freeze(budgetStatus),
6304
+ overall: overallBudgetStatus,
6305
+ }),
6306
+ artifacts: Object.freeze({ count: observability.artifacts.count }),
6307
+ checkpoints: Object.freeze({ count: observability.checkpoints.count }),
6308
+ recovery,
6309
+ recoveryPolicy,
6310
+ recent: recentEffect
6311
+ ? Object.freeze({
6312
+ effectId: recentEffect.id,
6313
+ stepId: recentEffect.stepId,
6314
+ status: recentEffect.status,
6315
+ taskStatus:
6316
+ recentEffect.result?.status === "completed" ||
6317
+ recentEffect.result?.status === "failed"
6318
+ ? recentEffect.result.status
6319
+ : null,
6320
+ requestedAt: recentEffect.requestedAt,
6321
+ settledAt: recentEffect.settledAt || null,
6322
+ resultDigest: recentEffect.resultDigest || null,
6323
+ call: recentCall
6324
+ ? Object.freeze({
6325
+ id: recentCall.id,
6326
+ kind: recentCall.kind,
6327
+ name: recentCall.name,
6328
+ status: recentCall.status,
6329
+ settlementCode: recentCall.settlementCode,
6330
+ settledAt: recentCall.settledAt,
6331
+ })
6332
+ : null,
6333
+ })
6334
+ : null,
6335
+ });
6336
+ }
6337
+
6338
+ /** Securely discover durable workflow state files below one project root. */
6339
+ export function listDynamicWorkflowWorkbenchStates(cwd, options = {}) {
6340
+ const limit = Math.max(1, Math.min(256, Number(options.limit) || 100));
6341
+ const projectionOptions = Object.hasOwn(options, "checkpointStore")
6342
+ ? options
6343
+ : { ...options, checkpointStore: new WorkspaceTransactionManager() };
6344
+ const directory = path.join(
6345
+ path.resolve(cwd),
6346
+ ".chainlesschain",
6347
+ "cowork",
6348
+ "workflow-runs",
6349
+ );
6350
+ let stat;
6351
+ try {
6352
+ stat = fs.lstatSync(directory);
6353
+ } catch (error) {
6354
+ if (error?.code === "ENOENT") {
6355
+ return Object.freeze({ runs: Object.freeze([]), invalidCount: 0 });
6356
+ }
6357
+ throw error;
6358
+ }
6359
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
6360
+ throw new Error("dynamic workflow runtime directory is not trusted");
6361
+ }
6362
+ const entries = fs.readdirSync(directory, { withFileTypes: true });
6363
+ if (entries.length > MAX_WORKBENCH_RUN_FILES) {
6364
+ throw new Error("dynamic workflow runtime directory exceeds safe limits");
6365
+ }
6366
+ const runs = [];
6367
+ let invalidCount = 0;
6368
+ for (const entry of entries) {
6369
+ if (
6370
+ !entry.isFile() ||
6371
+ entry.isSymbolicLink() ||
6372
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,255}\.json$/u.test(entry.name)
6373
+ ) {
6374
+ continue;
6375
+ }
6376
+ const runId = entry.name.slice(0, -".json".length);
6377
+ try {
6378
+ const statePath = dynamicWorkflowRunStatePath(cwd, runId);
6379
+ const state = readDynamicWorkflowRuntimeState(statePath);
6380
+ if (state.runId !== runId) throw new Error("run identity mismatch");
6381
+ runs.push(projectDynamicWorkflowWorkbenchState(state, projectionOptions));
6382
+ } catch {
6383
+ invalidCount += 1;
6384
+ }
6385
+ }
6386
+ runs.sort(
6387
+ (left, right) =>
6388
+ (Date.parse(right.updatedAt) || 0) - (Date.parse(left.updatedAt) || 0) ||
6389
+ left.runId.localeCompare(right.runId),
6390
+ );
6391
+ return Object.freeze({
6392
+ runs: Object.freeze(runs.slice(0, limit)),
6393
+ invalidCount,
6394
+ });
6395
+ }
6396
+
6397
+ /** Deterministic, non-mutating batch inventory for startup/periodic recovery. */
6398
+ export function buildDynamicWorkflowRecoveryPlan(cwd, options = {}) {
6399
+ const discovered = listDynamicWorkflowWorkbenchStates(cwd, options);
6400
+ const policy = Object.freeze({
6401
+ schema: "cc-dynamic-workflow-recovery-policy-set/v1",
6402
+ unattendedMutationAllowed: false,
6403
+ approvalRequiredFor: Object.freeze([
6404
+ "recover",
6405
+ "resume",
6406
+ "reply",
6407
+ "reconcile",
6408
+ ]),
6409
+ backoffMs: WORKFLOW_RECOVERY_BACKOFF_MS,
6410
+ });
6411
+ const items = discovered.runs.map((run) =>
6412
+ Object.freeze({
6413
+ runId: run.runId,
6414
+ workflowId: run.workflowId,
6415
+ revision: run.revision,
6416
+ stateDigest: run.stateDigest,
6417
+ status: run.status,
6418
+ updatedAt: run.updatedAt,
6419
+ risk: run.recoveryPolicy.risk,
6420
+ severity: run.recoveryPolicy.severity,
6421
+ recommendedAction: run.recoveryPolicy.recommendedAction,
6422
+ requiresApproval: run.recoveryPolicy.requiresApproval,
6423
+ automaticallyExecutable: run.recoveryPolicy.automaticallyExecutable,
6424
+ notificationKey: run.recoveryPolicy.notification.key,
6425
+ recovery: run.recovery,
6426
+ }),
6427
+ );
6428
+ const summary = Object.freeze({
6429
+ total: items.length,
6430
+ invalid: discovered.invalidCount,
6431
+ attention: items.filter((item) => item.risk !== "none").length,
6432
+ critical: items.filter((item) => item.severity === "critical").length,
6433
+ approvalRequired: items.filter((item) => item.requiresApproval).length,
6434
+ automaticallyExecutable: items.filter(
6435
+ (item) => item.automaticallyExecutable,
6436
+ ).length,
6437
+ });
6438
+ const planMaterial = { policy, summary, items };
6439
+ return Object.freeze({
6440
+ schema: "cc-dynamic-workflow-recovery-plan/v1",
6441
+ authority: "cli",
6442
+ mode: "dry-run",
6443
+ generatedAt: isoNow(options.now),
6444
+ projectDigest: digest(
6445
+ "chainlesschain.dynamic-workflow.recovery-project.v1\0",
6446
+ path.resolve(cwd),
6447
+ ),
6448
+ planDigest: digest(
6449
+ "chainlesschain.dynamic-workflow.recovery-plan.v1\0",
6450
+ planMaterial,
6451
+ ),
6452
+ ...planMaterial,
6453
+ });
6454
+ }