chainlesschain 0.165.2 → 0.165.4

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 (210) hide show
  1. package/README.md +151 -29
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/assets/{AIOps-lS1R0Cza.js → AIOps-C__nnraW.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BmlgBPsj.js → ActionButton-CsKWHEZ-.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CNKczv1S.js → Analytics-BhIjfjWO.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-BuZGJARQ.js → AppLayout-BjuXFzWZ.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-Dm73Sl-O.js → Artifacts-k6whDkZJ.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-BGMW7A5e.js → Audit-C4FBMYRh.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BQ6M54JR.js → BackgroundAgents-C_vRrSXk.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DBzGP0bR.js → Backup-CngFZeVp.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-D1mLCoOV.js → BaseInput-B_H9EBRm.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-BUfcN7p8.js → Chat-DLEC5kt7.js} +4 -4
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-xijcWkJe.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-Du0Yg5Pi.js → Checkbox-m3ClR4xh.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-B2RXziHW.js → Codegen-CmLSFrgp.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-DWhekOrR.js → Col-DFGQteHN.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-HQhrkon4.js → Community-EUUVORHE.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-izHNb4uz.js → Compact-2PSg0Yr7.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BDwLy41-.js → Compliance-CcKur7gB.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DFJhLa7K.js → Cowork-DK_Fezfa.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DYY9RG7-.js → Cron-Dbw89xl7.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-rr7tJTtv.js → Crosschain-CH66h_Ec.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-D2sfHRLi.js → DID-D1igRb17.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-q_eGdhha.js → Dashboard-DVAsrobk.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-Cufrh2Bz.js → Dropdown-Dl7W8AQs.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-B-kdv10f.js → EmailListRenderer-D5tpaEQC.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D21XjgfG.js → FamilyGuardDashboard-O_VQf8gq.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-CAwRTLZH.js → Federation-BTVcgS9n.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-vx3daNDF.js → FormItemContext-BRnkJAIz.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-CRWY2wU4.js → GenericCardRenderer-8OWSCkDj.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Q6SwauyD.js → Git-WXLYTQj2.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-BJ1q4IQZ.js → Governance-BezaUhtc.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CSWjROgD.js → Inference-CuRay3Gu.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-fzuQsWuY.js → KnowledgeGraph-DfWLDW1m.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-DVF8Mnns.js → Logs-C55y2pPY.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-hDpWdiBc.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-BQP7LOA4.js → Marketplace-Dk08Uycg.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-BNlJCmNc.js → McpTools-C11fSB5V.js} +4 -4
  39. package/src/assets/web-panel/assets/{Memory-DLLg5WQ4.js → Memory-mr0eZc97.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CR2skNAF.js → MobileBridge-C0iCSRbb.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-BxVavObl.js → MobileProjects-CY-xMG8Z.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-B17pgqMI.js → Mtc-Bplqc_Wc.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-4UmV5cIL.js → MtcAudit-BlzwsoSM.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-DoMEXEcU.js → Multisig-BBLmTty6.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-h9DMXFX4.js → NLProgramming-DtT1uf1W.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-Cihn-xX8.js → Notes-DcqQAxTK.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-BE6ZGa3R.js → NotificationSettings-cb0kWoZe.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-CnwojoSI.js → OrderTableRenderer-kw9HZQNN.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-B2vpwmjb.js → Organization-DlTlbkUN.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-C4z28R0-.js → Overflow-pp1qs-oY.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BVlFkwhW.js → P2P-BESLnyYz.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BXbEdL5B.js → PdhVaultBrowser-B-J1mGuf.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-C28xs4lp.js → Permissions-B36PeT1_.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-Dgl_rBjM.js → PersonalDataHub-CTK2ad-3.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-3QUIhrKI.js → Pipeline-zJNGXsac.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-8Gh33OZ5.js → Privacy-b9fz0Qf0.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-BNmg4tZi.js → ProjectInit-DjZbZgiB.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-vgMfeCf9.js → ProjectSettings-C-hHJXlD.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-BC9KSDM6.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-CXoOa8eX.js → Providers-Dz-CAYYU.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-BhfTF0oS.js → QrScannerModal-A0yoQ3-3.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-C746HEAj.js → QuickAsk-DrFgFJNC.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-BSej8Eo0.js → Recommend-KC8hF1g4.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-f3gmnlG3.js → RemoteSession-DGTPIk6u.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-CyvybpkN.js → Reputation-QmbwM7Yk.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-DwV2PeYn.js → Row-BxjJylia.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-11GrzjZy.js → RssFeed-B7Wda1J6.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-BjKXkrAY.js → Search-_plQ_YSA.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-eWiFb9Yk.js → Security-CD2YJZfx.js} +2 -2
  70. package/src/assets/web-panel/assets/{Services-CdCsMAtt.js → Services-qQE7NmwE.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-4Q2UTKQ5.js → Skeleton-DgQqkFf7.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-v4rtYrRj.js → Skills-C4C0oFFt.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-CRhsFvd_.js → Sla-Px2N2B5c.js} +1 -1
  74. package/src/assets/web-panel/assets/SpeechSettings-DN6C3lQd.js +1 -0
  75. package/src/assets/web-panel/assets/{SyncSettings-D16zonl7.js → SyncSettings-jm5aH_m7.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-Xxozz0i_.js → Tasks-DlkJcv67.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-CXJpPtCT.js → Templates-DyrTHUBs.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-XRV0L1ZF.js → Tenant-mC434PtL.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-CYuO1i_w.js → Terminal-o0_s3NgK.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-BXqitWsu.js → TimelineRenderer-DAGlAXOw.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-BvPrDHIy.js → Tokens-BnulS_bs.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DhCrSTf5.js → Trigger-suQbVXYM.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DmsuZD6K.js → Trust-DbIJAPo5.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-D_yrts5C.js → UkeySign-CQNpbIHi.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-B01ihPrm.js → VideoEditing-D8aWCERk.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-DpUjKa-N.js → Wallet-CuLpYfZc.js} +2 -2
  87. package/src/assets/web-panel/assets/{WebAuthn-CL07yON6.js → WebAuthn-CsfG8ZMc.js} +2 -2
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CLMflnRr.js → WorkflowEditor-BYsEAZbd.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DtUqe8h_.js → chat-FT4Um3-I.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-DPKrgVfy.js → colors-CKbS_Wlt.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BTt2GtOm.js → compact-item-CFJtzuJM.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-D_VeUc1J.js → createContext-B9kSRW6K.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DJZ0gAMu.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-1kndiylj.js → hasIn-DR-x0Xpi.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-dVc-CXY4.js → index-7jHr_A_k.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DFIZ_1Sf.js → index-B3ZX81mK.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-fk2Rjacw.js → index-B7osrXqy.js} +3 -3
  98. package/src/assets/web-panel/assets/{index-CHG-KcS4.js → index-BGbPnC5x.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Dbbymxpj.js → index-BI5hP_qy.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BaZAbw9A.js → index-BK3TiKVq.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CzjkKUmR.js → index-BVPU43b1.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BAXfRKfG.js → index-BW-t9n-q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-8nz_fmlj.js → index-BfFCQHL3.js} +1 -1
  104. package/src/assets/web-panel/assets/index-Bizb4hkw.js +1 -0
  105. package/src/assets/web-panel/assets/{index-DdkplL9R.js → index-Bt4fS2eL.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DG2MZQ3f.js → index-BwR7yc2h.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DfKBRdSS.js → index-Bzz0C1BZ.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-GsoUQ2M7.js → index-C2JvmYRc.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-l1BETrDb.js → index-CGaXUsIs.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-4bna2_SR.js → index-CJmIQ9XR.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DfGl8CqG.js → index-C_enk9I_.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D95OFht7.js → index-Cr2FLUsD.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DsJCDUJG.js → index-CvrCCqJt.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-sDoMHTNA.js → index-D4LfPvWA.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-By5n8up9.js → index-DMGmg497.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-ocf06EVQ.js → index-DShSkww-.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bhudc_Ez.js → index-DTxNy0ZU.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CX81qSM4.js → index-DW4BWjTk.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BlDNVgBt.js → index-DWdqx7zI.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CcO04eDn.js → index-Dap9c3eD.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Dwqmtk65.js → index-DcfxhKXR.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-2eyAXE3d.js → index-DdkIhfi-.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-brCInrzq.js → index-DeSgw-WV.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DJ8vXGOa.js → index-DiNUEpSQ.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BHpBMWCh.js → index-DjEP7WkH.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-m31Ka8hZ.js → index-DtkFqPVA.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CtF7EN49.js → index-KqDaCApw.js} +1 -1
  128. package/src/assets/web-panel/assets/index-OHSl-xCh.js +1 -0
  129. package/src/assets/web-panel/assets/{index-B9KvcS7R.js → index-OSzjTzGM.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CauoBQkH.js → index-QBSeNVO3.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-FzdwcSy2.js → index-WE08gMJP.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DxdTdBix.js → index-r2VI1Ikk.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-BepLZwgB.js → index-tqSEHPRT.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-CvyYJd7z.js → initDefaultProps-Dd7n7VgI.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-DYZkhUdz.js → motion-BwxouRw1.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-slnJRVT1.js → move-DObN0PG9.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-DVc0CKmy.js → mtc-parser-B3nOUQc7.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-BCXAtHCq.js → omit-o9WU2Nbl.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DBsemz_K.js → pickAttrs-CL_vvfQ_.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-BRtkJ-nv.js → placementArrow-CYrM2tyK.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-Cebq7EZn.js → responsiveObserve-k_06cQ96.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-DT7qvId2.js → slide-CPKG0pxS.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-pQiITLkG.js → statusUtils-BDDEeLTw.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-c-19BVKQ.js → styleChecker-Cmb7RTh7.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DsS5-0bE.js → useFlexGapSupport-e2IedC4N.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CxGiMe5n.js → useFs-CE_gxVnm.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-D9btVID5.js → usePersonalDataHub-BkMIIKMz.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-CUYFsFEN.js → vnode-DW886XV1.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-CXmn_35j.js → zoom-Dwo_adaf.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +3 -3
  152. package/src/commands/agent.js +105 -35
  153. package/src/commands/artifacts.js +363 -33
  154. package/src/commands/cowork.js +202 -36
  155. package/src/commands/session-budget.js +155 -0
  156. package/src/commands/session-location.js +948 -0
  157. package/src/commands/session.js +72 -46
  158. package/src/data/changelog.json +26 -0
  159. package/src/gateways/ws/artifact-protocol.js +77 -13
  160. package/src/gateways/ws/session-protocol.js +391 -24
  161. package/src/gateways/ws/ws-agent-handler.js +191 -14
  162. package/src/gateways/ws/ws-server.js +14 -3
  163. package/src/gateways/ws/ws-session-gateway.js +101 -5
  164. package/src/harness/jsonl-session-store.js +1210 -0
  165. package/src/lib/agent-team/team-runner.js +43 -13
  166. package/src/lib/artifact-access-ledger.js +461 -0
  167. package/src/lib/artifact-cleanup-ledger.js +861 -0
  168. package/src/lib/artifact-deletion-ledger.js +677 -0
  169. package/src/lib/artifact-store.js +256 -51
  170. package/src/lib/background-agent-keeper-protocol.js +75 -4
  171. package/src/lib/background-agent-launch-barrier.js +74 -0
  172. package/src/lib/background-agent-supervisor.js +149 -5
  173. package/src/lib/background-turn-bootstrap-protocol.js +4 -0
  174. package/src/lib/command-lifecycle-report.js +8 -4
  175. package/src/lib/cowork-adapter.js +9 -5
  176. package/src/lib/cowork-task-runner.js +20 -0
  177. package/src/lib/cowork-workflow.js +178 -20
  178. package/src/lib/direct-model-usage.js +62 -22
  179. package/src/lib/dynamic-workflow-runtime.js +3184 -111
  180. package/src/lib/execution-location-result-apply.js +339 -0
  181. package/src/lib/execution-location-result-artifact.js +271 -0
  182. package/src/lib/execution-location-result-review.js +253 -0
  183. package/src/lib/execution-location-result-store.js +331 -0
  184. package/src/lib/execution-location-result.js +617 -0
  185. package/src/lib/execution-location-target.js +236 -1
  186. package/src/lib/managed-tool-checkpoint.js +47 -4
  187. package/src/lib/process-execution-broker/workspace-transaction.js +3 -0
  188. package/src/lib/session-budget-production-root.js +376 -0
  189. package/src/lib/session-budget-runtime.js +109 -18
  190. package/src/lib/session-budget-usage.js +63 -0
  191. package/src/lib/session-host-snapshot.js +19 -2
  192. package/src/lib/session-resource-budget.js +766 -105
  193. package/src/lib/sub-agent-context.js +69 -22
  194. package/src/lib/web-ui-server.js +12 -1
  195. package/src/lib/with-file-lock.js +175 -26
  196. package/src/repl/agent-repl.js +237 -16
  197. package/src/runtime/agent-core.js +123 -0
  198. package/src/runtime/headless-runner.js +135 -46
  199. package/src/runtime/headless-stream.js +133 -25
  200. package/src/runtime/policies/agent-policy.js +10 -0
  201. package/src/workers/background-agent-keeper.js +112 -19
  202. package/src/workers/background-agent-worker.js +383 -134
  203. package/src/workers/background-turn-bootstrap.js +13 -8
  204. package/src/assets/web-panel/assets/ChatBubbleRenderer-CY5m8ZHg.js +0 -1
  205. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +0 -1
  206. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +0 -1
  207. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +0 -1
  208. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +0 -1
  209. package/src/assets/web-panel/assets/index-CPcM-eqo.js +0 -1
  210. package/src/assets/web-panel/assets/index-xeSyVoRe.js +0 -1
@@ -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,19 @@ 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;
45
74
  const MAX_PROJECTED_ARTIFACTS_PER_EFFECT = 256;
75
+ const MAX_PROVIDER_TOKEN_COUNT = 1_000_000_000;
76
+ const MAX_PROVIDER_USD_RATE_PER_MILLION = 1_000_000;
77
+ const MAX_PROVIDER_CACHE_RATE_MULTIPLIER = 100;
46
78
  const PROVIDER_CLIENT_REQUEST_ID_RE = /^ccwf_[a-f0-9]{64}$/u;
47
79
  const PROVIDER_RECEIPT_ID_RE = /^[A-Za-z0-9._:-]{1,256}$/u;
48
80
  const PROVIDER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
81
+ const PROVIDER_MODEL_RE = /^[\x21-\x7e]{1,256}$/u;
82
+ const ARTIFACT_ID_RE = /^art_[a-z0-9]+_[a-f0-9]{8}$/u;
83
+ const ARTIFACT_SHA256_RE = /^[a-f0-9]{64}$/u;
49
84
  const WORKFLOW_TOOL_CALL_ID_RE = /^[\x21-\x7e]{1,512}$/u;
50
85
  const WORKFLOW_TOOL_NAME_RE = /^[A-Za-z0-9._:-]{1,256}$/u;
51
86
  const WORKFLOW_CHILD_EFFECT_PROTOCOL = "cc-workflow-child-effect/v1";
@@ -53,6 +88,20 @@ const WORKFLOW_PROVIDER_ATTEMPT_PROTOCOL = "cc-provider-request-attempt/v1";
53
88
  const WORKFLOW_PROVIDER_CALL_ID_RE = /^[A-Za-z0-9._:-]{1,256}$/u;
54
89
  const WORKFLOW_PROVIDER_REQUEST_ID_RE = /^ccwf_[a-f0-9]{64}$/u;
55
90
  const DESCENDANT_OWNER_TOOL_NAMES = new Set(["run_skill", "spawn_sub_agent"]);
91
+ const ARTIFACT_METADATA_FIELDS = Object.freeze([
92
+ "id",
93
+ "title",
94
+ "kind",
95
+ "mime",
96
+ "size",
97
+ "sha256",
98
+ "file",
99
+ "sessionId",
100
+ "createdAt",
101
+ "expiresAt",
102
+ "immutable",
103
+ "recordDigest",
104
+ ]);
56
105
  const WORKFLOW_CALL_STATUSES = new Set([
57
106
  "started",
58
107
  "completed",
@@ -110,6 +159,709 @@ function nonNegativeSafeInteger(value) {
110
159
  return Number.isSafeInteger(value) && value >= 0 ? value : null;
111
160
  }
112
161
 
162
+ function providerUsageDigest(usage) {
163
+ return digest(
164
+ "chainlesschain.dynamic-workflow.provider-token-usage.v1\0",
165
+ usage,
166
+ );
167
+ }
168
+
169
+ function validProviderUsage(value) {
170
+ const fields =
171
+ value && typeof value === "object" ? Object.keys(value).sort() : [];
172
+ return (
173
+ fields.length === 6 &&
174
+ [
175
+ "cacheCreationInputTokens",
176
+ "cacheReadInputTokens",
177
+ "inputTokens",
178
+ "outputTokens",
179
+ "schema",
180
+ "totalTokens",
181
+ ].every((field, index) => fields[index] === field) &&
182
+ value?.schema === "cc-provider-token-usage/v1" &&
183
+ nonNegativeSafeInteger(value.inputTokens) !== null &&
184
+ nonNegativeSafeInteger(value.outputTokens) !== null &&
185
+ nonNegativeSafeInteger(value.cacheReadInputTokens) !== null &&
186
+ nonNegativeSafeInteger(value.cacheCreationInputTokens) !== null &&
187
+ nonNegativeSafeInteger(value.totalTokens) !== null &&
188
+ value.inputTokens <= MAX_PROVIDER_TOKEN_COUNT &&
189
+ value.outputTokens <= MAX_PROVIDER_TOKEN_COUNT &&
190
+ value.cacheReadInputTokens <= MAX_PROVIDER_TOKEN_COUNT &&
191
+ value.cacheCreationInputTokens <= MAX_PROVIDER_TOKEN_COUNT &&
192
+ value.totalTokens ===
193
+ value.inputTokens +
194
+ value.outputTokens +
195
+ value.cacheReadInputTokens +
196
+ value.cacheCreationInputTokens
197
+ );
198
+ }
199
+
200
+ function exactObjectFields(value, expected) {
201
+ const fields =
202
+ value && typeof value === "object" && !Array.isArray(value)
203
+ ? Object.keys(value).sort()
204
+ : [];
205
+ const sortedExpected = [...expected].sort();
206
+ return (
207
+ fields.length === sortedExpected.length &&
208
+ sortedExpected.every((field, index) => fields[index] === field)
209
+ );
210
+ }
211
+
212
+ function nonNegativeFinite(value, max = Number.MAX_VALUE) {
213
+ return Number.isFinite(value) && value >= 0 && value <= max;
214
+ }
215
+
216
+ function providerPricingDigest(pricing) {
217
+ return digest(
218
+ "chainlesschain.dynamic-workflow.provider-pricing-snapshot.v1\0",
219
+ pricing,
220
+ );
221
+ }
222
+
223
+ function providerCostEstimateDigest(cost) {
224
+ return digest(
225
+ "chainlesschain.dynamic-workflow.provider-cost-estimate.v1\0",
226
+ cost,
227
+ );
228
+ }
229
+
230
+ function providerPricingCatalogDigest() {
231
+ return digest(
232
+ "chainlesschain.dynamic-workflow.provider-pricing-catalog.v1\0",
233
+ {
234
+ freeProviders: FREE_PROVIDERS,
235
+ priceTable: PRICE_TABLE,
236
+ cacheReadMultiplier: CACHE_READ_MULTIPLIER,
237
+ cacheReadMultiplierByProvider: CACHE_READ_MULTIPLIER_BY_PROVIDER,
238
+ cacheCreationMultiplier: CACHE_WRITE_MULTIPLIER,
239
+ },
240
+ );
241
+ }
242
+
243
+ function providerPricingSnapshot(provider, model) {
244
+ const rate = lookupRate(provider, model);
245
+ if (!rate) return null;
246
+ const cacheReadMultiplier =
247
+ CACHE_READ_MULTIPLIER_BY_PROVIDER[provider] ?? CACHE_READ_MULTIPLIER;
248
+ return {
249
+ schema: "cc-provider-pricing-snapshot/v1",
250
+ currency: "USD",
251
+ authority: "builtin-public-list-estimate",
252
+ provider,
253
+ model,
254
+ pattern: rate.pattern,
255
+ inputUsdPerMillion: rate.in,
256
+ outputUsdPerMillion: rate.out,
257
+ cacheReadMultiplier,
258
+ cacheCreationMultiplier: CACHE_WRITE_MULTIPLIER,
259
+ free: rate.pattern === "free",
260
+ catalogDigest: providerPricingCatalogDigest(),
261
+ };
262
+ }
263
+
264
+ function validProviderPricing(value, provider, model) {
265
+ return (
266
+ exactObjectFields(value, [
267
+ "schema",
268
+ "currency",
269
+ "authority",
270
+ "provider",
271
+ "model",
272
+ "pattern",
273
+ "inputUsdPerMillion",
274
+ "outputUsdPerMillion",
275
+ "cacheReadMultiplier",
276
+ "cacheCreationMultiplier",
277
+ "free",
278
+ "catalogDigest",
279
+ ]) &&
280
+ value.schema === "cc-provider-pricing-snapshot/v1" &&
281
+ value.currency === "USD" &&
282
+ value.authority === "builtin-public-list-estimate" &&
283
+ value.provider === provider &&
284
+ value.model === model &&
285
+ typeof value.pattern === "string" &&
286
+ /^[A-Za-z0-9._:-]{1,128}$/u.test(value.pattern) &&
287
+ nonNegativeFinite(
288
+ value.inputUsdPerMillion,
289
+ MAX_PROVIDER_USD_RATE_PER_MILLION,
290
+ ) &&
291
+ nonNegativeFinite(
292
+ value.outputUsdPerMillion,
293
+ MAX_PROVIDER_USD_RATE_PER_MILLION,
294
+ ) &&
295
+ nonNegativeFinite(
296
+ value.cacheReadMultiplier,
297
+ MAX_PROVIDER_CACHE_RATE_MULTIPLIER,
298
+ ) &&
299
+ nonNegativeFinite(
300
+ value.cacheCreationMultiplier,
301
+ MAX_PROVIDER_CACHE_RATE_MULTIPLIER,
302
+ ) &&
303
+ typeof value.free === "boolean" &&
304
+ value.free ===
305
+ (value.inputUsdPerMillion === 0 && value.outputUsdPerMillion === 0) &&
306
+ SHA256_RE.test(value.catalogDigest || "")
307
+ );
308
+ }
309
+
310
+ function estimateProviderCost(pricing, usage) {
311
+ if (!pricing || !usage) return null;
312
+ const inputUsd = (usage.inputTokens / 1_000_000) * pricing.inputUsdPerMillion;
313
+ const outputUsd =
314
+ (usage.outputTokens / 1_000_000) * pricing.outputUsdPerMillion;
315
+ const cacheReadUsd =
316
+ (usage.cacheReadInputTokens / 1_000_000) *
317
+ pricing.inputUsdPerMillion *
318
+ pricing.cacheReadMultiplier;
319
+ const cacheCreationUsd =
320
+ (usage.cacheCreationInputTokens / 1_000_000) *
321
+ pricing.inputUsdPerMillion *
322
+ pricing.cacheCreationMultiplier;
323
+ return {
324
+ schema: "cc-provider-cost-estimate/v1",
325
+ currency: "USD",
326
+ authority: "durable-pricing-snapshot-estimate",
327
+ inputUsd,
328
+ outputUsd,
329
+ cacheReadUsd,
330
+ cacheCreationUsd,
331
+ totalUsd: inputUsd + outputUsd + cacheReadUsd + cacheCreationUsd,
332
+ pricingDigest: providerPricingDigest(pricing),
333
+ };
334
+ }
335
+
336
+ function validProviderCostEstimate(value, pricing, usage) {
337
+ if (
338
+ !exactObjectFields(value, [
339
+ "schema",
340
+ "currency",
341
+ "authority",
342
+ "inputUsd",
343
+ "outputUsd",
344
+ "cacheReadUsd",
345
+ "cacheCreationUsd",
346
+ "totalUsd",
347
+ "pricingDigest",
348
+ ]) ||
349
+ value.schema !== "cc-provider-cost-estimate/v1" ||
350
+ value.currency !== "USD" ||
351
+ value.authority !== "durable-pricing-snapshot-estimate" ||
352
+ ![
353
+ value.inputUsd,
354
+ value.outputUsd,
355
+ value.cacheReadUsd,
356
+ value.cacheCreationUsd,
357
+ value.totalUsd,
358
+ ].every((amount) => nonNegativeFinite(amount)) ||
359
+ value.pricingDigest !== providerPricingDigest(pricing)
360
+ ) {
361
+ return false;
362
+ }
363
+ const expected = estimateProviderCost(pricing, usage);
364
+ return (
365
+ canonicalJson(value, "providerCostEstimate") ===
366
+ canonicalJson(expected, "providerCostEstimate")
367
+ );
368
+ }
369
+
370
+ function strictDataObjectSnapshot(value, fields, field, maxBytes = 64 * 1024) {
371
+ let descriptors;
372
+ try {
373
+ if (
374
+ !value ||
375
+ typeof value !== "object" ||
376
+ Array.isArray(value) ||
377
+ utilTypes.isProxy(value)
378
+ ) {
379
+ throw new TypeError(`${field} must be a plain data object`);
380
+ }
381
+ descriptors = Object.getOwnPropertyDescriptors(value);
382
+ const keys = Reflect.ownKeys(descriptors);
383
+ if (
384
+ keys.length !== fields.length ||
385
+ keys.some((key) => typeof key !== "string") ||
386
+ fields.some(
387
+ (name) =>
388
+ !Object.hasOwn(descriptors, name) ||
389
+ !Object.hasOwn(descriptors[name], "value"),
390
+ )
391
+ ) {
392
+ throw new TypeError(`${field} has an invalid field set`);
393
+ }
394
+ } catch (cause) {
395
+ if (cause instanceof TypeError && cause.message.startsWith(field)) {
396
+ throw cause;
397
+ }
398
+ throw new TypeError(`${field} must be a plain data object`);
399
+ }
400
+ return snapshotJson(
401
+ Object.fromEntries(fields.map((name) => [name, descriptors[name].value])),
402
+ field,
403
+ maxBytes,
404
+ );
405
+ }
406
+
407
+ function validArtifactMetadata(value) {
408
+ if (!exactObjectFields(value, ARTIFACT_METADATA_FIELDS)) return false;
409
+ let timestampsValid = false;
410
+ try {
411
+ timestampsValid =
412
+ isoNow(value.createdAt) === value.createdAt &&
413
+ isoNow(value.expiresAt) === value.expiresAt &&
414
+ Date.parse(value.expiresAt) >= Date.parse(value.createdAt);
415
+ } catch {
416
+ timestampsValid = false;
417
+ }
418
+ return (
419
+ ARTIFACT_ID_RE.test(value.id || "") &&
420
+ typeof value.title === "string" &&
421
+ value.title.length >= 1 &&
422
+ value.title.length <= 1024 &&
423
+ !value.title.includes("\0") &&
424
+ ARTIFACT_KINDS.includes(value.kind) &&
425
+ typeof value.mime === "string" &&
426
+ /^[\x21-\x7e]{1,256}$/u.test(value.mime) &&
427
+ Number.isSafeInteger(value.size) &&
428
+ value.size >= 0 &&
429
+ value.size <= MAX_ARTIFACT_BYTES &&
430
+ ARTIFACT_SHA256_RE.test(value.sha256 || "") &&
431
+ typeof value.file === "string" &&
432
+ value.file.length >= 1 &&
433
+ value.file.length <= 512 &&
434
+ path.basename(value.file) === value.file &&
435
+ !/[\\/\0-\x1f\x7f]/u.test(value.file) &&
436
+ (value.sessionId === null ||
437
+ (typeof value.sessionId === "string" &&
438
+ value.sessionId.length >= 1 &&
439
+ value.sessionId.length <= 256)) &&
440
+ timestampsValid &&
441
+ typeof value.immutable === "boolean" &&
442
+ (value.recordDigest === null || SHA256_RE.test(value.recordDigest || ""))
443
+ );
444
+ }
445
+
446
+ function artifactMetadataDigest(metadata) {
447
+ return digest(
448
+ "chainlesschain.dynamic-workflow.artifact-store-metadata.v1\0",
449
+ metadata,
450
+ );
451
+ }
452
+
453
+ function artifactReadbackDigest(readback) {
454
+ return digest(
455
+ "chainlesschain.dynamic-workflow.artifact-store-readback.v1\0",
456
+ readback,
457
+ );
458
+ }
459
+
460
+ function validArtifactReadback(value) {
461
+ return (
462
+ exactObjectFields(value, [
463
+ "schema",
464
+ "authority",
465
+ "metadata",
466
+ "metadataDigest",
467
+ "contentDigest",
468
+ ]) &&
469
+ value.schema === "cc-dynamic-workflow-artifact-readback/v1" &&
470
+ value.authority === "artifact-store-index-and-bytes-at-settlement" &&
471
+ validArtifactMetadata(value.metadata) &&
472
+ value.metadataDigest === artifactMetadataDigest(value.metadata) &&
473
+ value.contentDigest === `sha256:${value.metadata.sha256}`
474
+ );
475
+ }
476
+
477
+ function artifactReadbackSettlement(event, artifactStore) {
478
+ const supplied = strictDataObjectSnapshot(
479
+ ownDataValue(event?.result, "published"),
480
+ ARTIFACT_METADATA_FIELDS,
481
+ "workflow published artifact metadata",
482
+ );
483
+ if (!validArtifactMetadata(supplied)) {
484
+ throw new TypeError("workflow published artifact metadata is malformed");
485
+ }
486
+ if (containsSecret(JSON.stringify(supplied))) {
487
+ throw new TypeError(
488
+ "workflow published artifact metadata contains secret-shaped data",
489
+ );
490
+ }
491
+ if (
492
+ !artifactStore ||
493
+ typeof artifactStore.get !== "function" ||
494
+ typeof artifactStore.verifyIntegrity !== "function"
495
+ ) {
496
+ throw new TypeError("workflow artifact store readback is unavailable");
497
+ }
498
+ let stored;
499
+ let integrity;
500
+ try {
501
+ stored = artifactStore.get(supplied.id);
502
+ integrity = stored ? artifactStore.verifyIntegrity(stored) : null;
503
+ } catch {
504
+ throw new TypeError("workflow artifact store readback failed");
505
+ }
506
+ if (!stored || !integrity?.ok) {
507
+ throw new TypeError(
508
+ "workflow artifact store bytes are unavailable or invalid",
509
+ );
510
+ }
511
+ const canonical = strictDataObjectSnapshot(
512
+ publicArtifactMetadata(stored),
513
+ ARTIFACT_METADATA_FIELDS,
514
+ "workflow artifact store metadata",
515
+ );
516
+ if (
517
+ !validArtifactMetadata(canonical) ||
518
+ canonicalJson(canonical, "workflowArtifactMetadata") !==
519
+ canonicalJson(supplied, "workflowArtifactMetadata") ||
520
+ integrity.expectedSha256 !== supplied.sha256 ||
521
+ integrity.actualSha256 !== supplied.sha256
522
+ ) {
523
+ throw new TypeError(
524
+ "workflow artifact store readback does not match settlement",
525
+ );
526
+ }
527
+ return {
528
+ schema: "cc-dynamic-workflow-artifact-readback/v1",
529
+ authority: "artifact-store-index-and-bytes-at-settlement",
530
+ metadata: canonical,
531
+ metadataDigest: artifactMetadataDigest(canonical),
532
+ contentDigest: `sha256:${integrity.actualSha256}`,
533
+ };
534
+ }
535
+
536
+ const CHECKPOINT_EVIDENCE_BASE_FIELDS = Object.freeze([
537
+ "version",
538
+ "transactionId",
539
+ "checkpointId",
540
+ "checkpointDigest",
541
+ "writeManifestDigest",
542
+ "fileCoverage",
543
+ "coverage",
544
+ "externalSideEffects",
545
+ "outcome",
546
+ "executions",
547
+ "exclusions",
548
+ "uncoveredPaths",
549
+ "evidenceDigest",
550
+ ]);
551
+
552
+ const CHECKPOINT_BINDING_FIELDS = Object.freeze([
553
+ "schema",
554
+ "authority",
555
+ "transactionId",
556
+ "checkpointId",
557
+ "checkpointDigest",
558
+ "preparedStateDigest",
559
+ "coverage",
560
+ "fileCoverage",
561
+ "externalSideEffects",
562
+ ]);
563
+
564
+ function checkpointBindingSnapshot(value) {
565
+ if (value === undefined || value === null) return null;
566
+ return strictDataObjectSnapshot(
567
+ value,
568
+ CHECKPOINT_BINDING_FIELDS,
569
+ "workflow managed checkpoint binding",
570
+ );
571
+ }
572
+
573
+ function validCheckpointBinding(value) {
574
+ return (
575
+ exactObjectFields(value, CHECKPOINT_BINDING_FIELDS) &&
576
+ value.schema === "cc-managed-tool-checkpoint-binding/v1" &&
577
+ value.authority === "process-broker-workspace-transaction-prepared" &&
578
+ /^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.transactionId || "") &&
579
+ /^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.checkpointId || "") &&
580
+ SHA256_RE.test(value.checkpointDigest || "") &&
581
+ SHA256_RE.test(value.preparedStateDigest || "") &&
582
+ Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(value.coverage) &&
583
+ Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(
584
+ value.fileCoverage,
585
+ ) &&
586
+ typeof value.externalSideEffects === "boolean"
587
+ );
588
+ }
589
+
590
+ function checkpointBindingDigest(binding) {
591
+ return digest(
592
+ "chainlesschain.dynamic-workflow.checkpoint-prepared-binding.v1\0",
593
+ binding,
594
+ );
595
+ }
596
+
597
+ function checkpointReadbackMatchesBinding(readback, binding) {
598
+ return (
599
+ binding === null ||
600
+ (validCheckpointBinding(binding) &&
601
+ readback.transactionId === binding.transactionId &&
602
+ readback.checkpointId === binding.checkpointId &&
603
+ readback.checkpointDigest === binding.checkpointDigest &&
604
+ readback.coverage === binding.coverage &&
605
+ readback.fileCoverage === binding.fileCoverage &&
606
+ readback.externalSideEffects === binding.externalSideEffects)
607
+ );
608
+ }
609
+
610
+ function checkpointEvidenceSnapshot(value) {
611
+ const outcome = ownDataValue(value, "outcome");
612
+ const fields =
613
+ outcome === "rolled_back"
614
+ ? [
615
+ ...CHECKPOINT_EVIDENCE_BASE_FIELDS,
616
+ "rollbackReason",
617
+ "verificationDigest",
618
+ ]
619
+ : CHECKPOINT_EVIDENCE_BASE_FIELDS;
620
+ return strictDataObjectSnapshot(
621
+ value,
622
+ fields,
623
+ "workflow managed checkpoint evidence",
624
+ 512 * 1024,
625
+ );
626
+ }
627
+
628
+ function boundedStringArray(value, maxItems = 4096, maxLength = 4096) {
629
+ return (
630
+ Array.isArray(value) &&
631
+ value.length <= maxItems &&
632
+ value.every(
633
+ (entry) =>
634
+ typeof entry === "string" &&
635
+ entry.length >= 1 &&
636
+ entry.length <= maxLength &&
637
+ !entry.includes("\0"),
638
+ )
639
+ );
640
+ }
641
+
642
+ function validCheckpointEvidence(value) {
643
+ if (
644
+ !value ||
645
+ value.version !== WORKSPACE_TRANSACTION_VERSION ||
646
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.transactionId || "") ||
647
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.checkpointId || "") ||
648
+ !SHA256_RE.test(value.checkpointDigest || "") ||
649
+ !SHA256_RE.test(value.writeManifestDigest || "") ||
650
+ !Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(
651
+ value.fileCoverage,
652
+ ) ||
653
+ !Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(value.coverage) ||
654
+ typeof value.externalSideEffects !== "boolean" ||
655
+ !["committed", "rolled_back"].includes(value.outcome) ||
656
+ !boundedStringArray(value.executions, 4096, 256) ||
657
+ !boundedStringArray(value.exclusions) ||
658
+ !boundedStringArray(value.uncoveredPaths) ||
659
+ !SHA256_RE.test(value.evidenceDigest || "") ||
660
+ (value.outcome === "rolled_back" &&
661
+ (typeof value.rollbackReason !== "string" ||
662
+ value.rollbackReason.length < 1 ||
663
+ value.rollbackReason.length > 1000 ||
664
+ !SHA256_RE.test(value.verificationDigest || "")))
665
+ ) {
666
+ return false;
667
+ }
668
+ const unsigned = { ...value };
669
+ delete unsigned.evidenceDigest;
670
+ return value.evidenceDigest === digestWorkspaceEvidence(unsigned);
671
+ }
672
+
673
+ function checkpointReadbackDigest(readback) {
674
+ return digest(
675
+ "chainlesschain.dynamic-workflow.checkpoint-store-readback.v1\0",
676
+ readback,
677
+ );
678
+ }
679
+
680
+ function validCheckpointReadback(value) {
681
+ return (
682
+ exactObjectFields(value, [
683
+ "schema",
684
+ "authority",
685
+ "transactionId",
686
+ "checkpointId",
687
+ "outcome",
688
+ "coverage",
689
+ "fileCoverage",
690
+ "externalSideEffects",
691
+ "checkpointDigest",
692
+ "writeManifestDigest",
693
+ "evidenceDigest",
694
+ "stateDigest",
695
+ ]) &&
696
+ value.schema === "cc-dynamic-workflow-checkpoint-readback/v1" &&
697
+ value.authority === "workspace-transaction-store-terminal-readback" &&
698
+ /^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.transactionId || "") &&
699
+ /^[A-Za-z0-9][A-Za-z0-9._-]{0,191}$/u.test(value.checkpointId || "") &&
700
+ ["committed", "rolled_back"].includes(value.outcome) &&
701
+ Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(value.coverage) &&
702
+ Object.values(WORKSPACE_TRANSACTION_COVERAGE).includes(
703
+ value.fileCoverage,
704
+ ) &&
705
+ typeof value.externalSideEffects === "boolean" &&
706
+ SHA256_RE.test(value.checkpointDigest || "") &&
707
+ SHA256_RE.test(value.writeManifestDigest || "") &&
708
+ SHA256_RE.test(value.evidenceDigest || "") &&
709
+ SHA256_RE.test(value.stateDigest || "")
710
+ );
711
+ }
712
+
713
+ function checkpointStoreReadback(evidence, checkpointStore, binding = null) {
714
+ if (
715
+ binding !== null &&
716
+ (!validCheckpointBinding(binding) ||
717
+ evidence.transactionId !== binding.transactionId ||
718
+ evidence.checkpointId !== binding.checkpointId ||
719
+ evidence.checkpointDigest !== binding.checkpointDigest ||
720
+ evidence.coverage !== binding.coverage ||
721
+ evidence.fileCoverage !== binding.fileCoverage ||
722
+ evidence.externalSideEffects !== binding.externalSideEffects)
723
+ ) {
724
+ throw new TypeError(
725
+ "workflow managed checkpoint settlement does not match its prepared binding",
726
+ );
727
+ }
728
+ if (!checkpointStore || typeof checkpointStore.inspect !== "function") {
729
+ throw new TypeError("workflow checkpoint store readback is unavailable");
730
+ }
731
+ let stored;
732
+ try {
733
+ stored = snapshotJson(
734
+ checkpointStore.inspect(evidence.transactionId),
735
+ "workflow checkpoint store state",
736
+ 2 * 1024 * 1024,
737
+ );
738
+ } catch {
739
+ throw new TypeError("workflow checkpoint store readback failed");
740
+ }
741
+ const unsignedState = { ...stored };
742
+ delete unsignedState.stateDigest;
743
+ const expectedState =
744
+ evidence.outcome === "committed"
745
+ ? WORKSPACE_TRANSACTION_STATE.COMMITTED
746
+ : WORKSPACE_TRANSACTION_STATE.ROLLED_BACK;
747
+ if (
748
+ stored.id !== evidence.transactionId ||
749
+ stored.checkpointId !== evidence.checkpointId ||
750
+ stored.state !== expectedState ||
751
+ !SHA256_RE.test(stored.stateDigest || "") ||
752
+ stored.stateDigest !== digestWorkspaceEvidence(unsignedState) ||
753
+ canonicalJson(stored.evidence, "workflowCheckpointEvidence") !==
754
+ canonicalJson(evidence, "workflowCheckpointEvidence")
755
+ ) {
756
+ throw new TypeError(
757
+ "workflow checkpoint store readback does not match settlement",
758
+ );
759
+ }
760
+ const readback = {
761
+ schema: "cc-dynamic-workflow-checkpoint-readback/v1",
762
+ authority: "workspace-transaction-store-terminal-readback",
763
+ transactionId: evidence.transactionId,
764
+ checkpointId: evidence.checkpointId,
765
+ outcome: evidence.outcome,
766
+ coverage: evidence.coverage,
767
+ fileCoverage: evidence.fileCoverage,
768
+ externalSideEffects: evidence.externalSideEffects,
769
+ checkpointDigest: evidence.checkpointDigest,
770
+ writeManifestDigest: evidence.writeManifestDigest,
771
+ evidenceDigest: evidence.evidenceDigest,
772
+ stateDigest: stored.stateDigest,
773
+ };
774
+ if (!checkpointReadbackMatchesBinding(readback, binding)) {
775
+ throw new TypeError(
776
+ "workflow checkpoint store readback does not match its prepared binding",
777
+ );
778
+ }
779
+ return readback;
780
+ }
781
+
782
+ function checkpointReadbackSettlement(event, checkpointStore, binding = null) {
783
+ const managed = ownDataValue(event?.result, "managedCheckpoint");
784
+ if (managed === undefined || managed === null) return null;
785
+ const skipped = ownDataValue(managed, "skipped");
786
+ const status = ownDataValue(managed, "status");
787
+ if (skipped === true) {
788
+ const observation = strictDataObjectSnapshot(
789
+ managed,
790
+ ["skipped", "toolName", "coverage", "fileCoverage", "reason"],
791
+ "workflow unavailable managed checkpoint",
792
+ );
793
+ if (
794
+ observation.toolName !== event.tool ||
795
+ observation.coverage !== WORKSPACE_TRANSACTION_COVERAGE.NONE ||
796
+ observation.fileCoverage !== WORKSPACE_TRANSACTION_COVERAGE.NONE ||
797
+ typeof observation.reason !== "string" ||
798
+ observation.reason.length < 1 ||
799
+ observation.reason.length > 256
800
+ ) {
801
+ throw new TypeError(
802
+ "workflow unavailable managed checkpoint is malformed",
803
+ );
804
+ }
805
+ return null;
806
+ }
807
+ if (["not_started", "recovery_required"].includes(status)) {
808
+ const observation = strictDataObjectSnapshot(
809
+ managed,
810
+ status === "not_started"
811
+ ? ["status", "coverage", "code"]
812
+ : [
813
+ "status",
814
+ "coverage",
815
+ "code",
816
+ "transactionId",
817
+ "checkpointId",
818
+ "settlement",
819
+ "originalToolError",
820
+ ],
821
+ "workflow unavailable managed checkpoint",
822
+ );
823
+ const hasToolError = Boolean(
824
+ event?.error || ownDataValue(event?.result, "error"),
825
+ );
826
+ if (
827
+ observation.coverage !== WORKSPACE_TRANSACTION_COVERAGE.NONE ||
828
+ !hasToolError
829
+ ) {
830
+ throw new TypeError(
831
+ "workflow unavailable managed checkpoint is malformed",
832
+ );
833
+ }
834
+ return null;
835
+ }
836
+ const settlement = strictDataObjectSnapshot(
837
+ managed,
838
+ [
839
+ "skipped",
840
+ "toolName",
841
+ "transactionId",
842
+ "checkpointId",
843
+ "evidence",
844
+ "coverage",
845
+ "fileCoverage",
846
+ ],
847
+ "workflow managed checkpoint settlement",
848
+ 1024 * 1024,
849
+ );
850
+ const evidence = checkpointEvidenceSnapshot(settlement.evidence);
851
+ if (
852
+ settlement.skipped !== false ||
853
+ settlement.toolName !== event.tool ||
854
+ settlement.transactionId !== evidence.transactionId ||
855
+ settlement.checkpointId !== evidence.checkpointId ||
856
+ settlement.coverage !== evidence.coverage ||
857
+ settlement.fileCoverage !== evidence.fileCoverage ||
858
+ !validCheckpointEvidence(evidence)
859
+ ) {
860
+ throw new TypeError("workflow managed checkpoint settlement is malformed");
861
+ }
862
+ return checkpointStoreReadback(evidence, checkpointStore, binding);
863
+ }
864
+
113
865
  function effectResultDigest(result) {
114
866
  return digest("chainlesschain.dynamic-workflow.effect-result.v1\0", result);
115
867
  }
@@ -248,7 +1000,14 @@ function expectedWorkflowProviderRequestId(effectId, source, sequence) {
248
1000
  .digest("hex")}`;
249
1001
  }
250
1002
 
251
- function verifyEffectCalls(effect, effectIndex, issues) {
1003
+ function verifyEffectCalls(
1004
+ effect,
1005
+ effectIndex,
1006
+ issues,
1007
+ startedLineageByCallId,
1008
+ receiptLineageByCallId,
1009
+ settlementLineageByCallId,
1010
+ ) {
252
1011
  const calls = effect?.calls === undefined ? [] : effect.calls;
253
1012
  if (!Array.isArray(calls) || calls.length > MAX_CALLS_PER_EFFECT) {
254
1013
  issues.push(`effect-${effectIndex}-calls-invalid`);
@@ -266,11 +1025,112 @@ function verifyEffectCalls(effect, effectIndex, issues) {
266
1025
  ? call?.source
267
1026
  : null
268
1027
  : call.requestSource;
1028
+ const startedLineage = startedLineageByCallId.get(call?.id) || [];
1029
+ const receiptLineage = receiptLineageByCallId.get(call?.id) || [];
1030
+ const settlementLineage = settlementLineageByCallId.get(call?.id) || [];
1031
+ const receiptLineageValid =
1032
+ receiptLineage.length === 1 &&
1033
+ receiptLineage[0]?.details?.effectId === effect.id &&
1034
+ receiptLineage[0]?.details?.ownerEffectId === ownerEffectId &&
1035
+ receiptLineage[0]?.details?.callRecordId === call?.id;
1036
+ const hasReceiptTimestamp = Object.prototype.hasOwnProperty.call(
1037
+ call || {},
1038
+ "providerReceiptRecordedAt",
1039
+ );
1040
+ const hasProviderUsage = Object.prototype.hasOwnProperty.call(
1041
+ call || {},
1042
+ "providerUsage",
1043
+ );
1044
+ const settlementUsageDigestPresent = settlementLineage.some((event) =>
1045
+ Object.prototype.hasOwnProperty.call(
1046
+ event?.details || {},
1047
+ "providerUsageDigest",
1048
+ ),
1049
+ );
1050
+ const providerCostFields = [
1051
+ "providerModel",
1052
+ "providerPricing",
1053
+ "providerCostEstimate",
1054
+ ];
1055
+ const providerCostFieldCount = providerCostFields.filter((field) =>
1056
+ Object.prototype.hasOwnProperty.call(call || {}, field),
1057
+ ).length;
1058
+ const hasProviderCostSchema =
1059
+ providerCostFieldCount === providerCostFields.length;
1060
+ const startedPricingFieldsPresent = startedLineage.some(
1061
+ (event) =>
1062
+ Object.prototype.hasOwnProperty.call(
1063
+ event?.details || {},
1064
+ "providerModel",
1065
+ ) ||
1066
+ Object.prototype.hasOwnProperty.call(
1067
+ event?.details || {},
1068
+ "providerPricingDigest",
1069
+ ),
1070
+ );
1071
+ const settlementCostDigestPresent = settlementLineage.some((event) =>
1072
+ Object.prototype.hasOwnProperty.call(
1073
+ event?.details || {},
1074
+ "providerCostEstimateDigest",
1075
+ ),
1076
+ );
1077
+ const hasArtifactReadback = Object.prototype.hasOwnProperty.call(
1078
+ call || {},
1079
+ "artifactReadback",
1080
+ );
1081
+ const startedArtifactSchemaPresent = startedLineage.some((event) =>
1082
+ Object.prototype.hasOwnProperty.call(
1083
+ event?.details || {},
1084
+ "artifactReadbackSchema",
1085
+ ),
1086
+ );
1087
+ const settlementArtifactDigestPresent = settlementLineage.some((event) =>
1088
+ Object.prototype.hasOwnProperty.call(
1089
+ event?.details || {},
1090
+ "artifactReadbackDigest",
1091
+ ),
1092
+ );
1093
+ const hasCheckpointReadback = Object.prototype.hasOwnProperty.call(
1094
+ call || {},
1095
+ "checkpointReadback",
1096
+ );
1097
+ const startedCheckpointSchemaPresent = startedLineage.some((event) =>
1098
+ Object.prototype.hasOwnProperty.call(
1099
+ event?.details || {},
1100
+ "checkpointReadbackSchema",
1101
+ ),
1102
+ );
1103
+ const settlementCheckpointDigestPresent = settlementLineage.some((event) =>
1104
+ Object.prototype.hasOwnProperty.call(
1105
+ event?.details || {},
1106
+ "checkpointReadbackDigest",
1107
+ ),
1108
+ );
1109
+ const hasCheckpointBinding = Object.prototype.hasOwnProperty.call(
1110
+ call || {},
1111
+ "checkpointBinding",
1112
+ );
1113
+ const startedCheckpointBindingDigestPresent = startedLineage.some((event) =>
1114
+ Object.prototype.hasOwnProperty.call(
1115
+ event?.details || {},
1116
+ "checkpointBindingDigest",
1117
+ ),
1118
+ );
1119
+ const checkpointBindingValid = hasCheckpointBinding
1120
+ ? startedLineage.length === 1 &&
1121
+ (call.checkpointBinding === null ||
1122
+ validCheckpointBinding(call.checkpointBinding)) &&
1123
+ startedLineage[0]?.details?.checkpointBindingDigest ===
1124
+ (call.checkpointBinding === null
1125
+ ? null
1126
+ : checkpointBindingDigest(call.checkpointBinding))
1127
+ : !startedCheckpointBindingDigestPresent;
269
1128
  const terminal = call?.status !== "started";
270
1129
  const identityKey = `${call?.kind || ""}\0${
271
1130
  call?.kind === "tool" ? call?.childEffectId : call?.callId
272
1131
  }`;
273
1132
  let timestampsValid = false;
1133
+ let receiptTimestampValid = false;
274
1134
  try {
275
1135
  timestampsValid =
276
1136
  isoNow(call?.startedAt) === call.startedAt &&
@@ -281,8 +1141,22 @@ function verifyEffectCalls(effect, effectIndex, issues) {
281
1141
  (effect.settledAt === null ||
282
1142
  Date.parse(call.settledAt) <= Date.parse(effect.settledAt))
283
1143
  : call?.settledAt === null);
1144
+ receiptTimestampValid = call?.providerReceiptPersisted
1145
+ ? receiptLineageValid
1146
+ ? hasReceiptTimestamp &&
1147
+ isoNow(call.providerReceiptRecordedAt) ===
1148
+ call.providerReceiptRecordedAt &&
1149
+ Date.parse(call.providerReceiptRecordedAt) >=
1150
+ Date.parse(call.startedAt) &&
1151
+ (!terminal ||
1152
+ Date.parse(call.providerReceiptRecordedAt) <=
1153
+ Date.parse(call.settledAt))
1154
+ : receiptLineage.length === 0 && !hasReceiptTimestamp && terminal
1155
+ : receiptLineage.length === 0 &&
1156
+ (call?.providerReceiptRecordedAt === null || !hasReceiptTimestamp);
284
1157
  } catch {
285
1158
  timestampsValid = false;
1159
+ receiptTimestampValid = false;
286
1160
  }
287
1161
  const commonValid =
288
1162
  call?.schema === DYNAMIC_WORKFLOW_CALL_SCHEMA &&
@@ -328,6 +1202,7 @@ function verifyEffectCalls(effect, effectIndex, issues) {
328
1202
  Boolean(
329
1203
  call.providerReceiptRequestId || call.providerReceiptResponseId,
330
1204
  ) &&
1205
+ receiptTimestampValid &&
331
1206
  (call.mcpLedgerId === null ||
332
1207
  PROVIDER_RECEIPT_ID_RE.test(call.mcpLedgerId || "")) &&
333
1208
  typeof call.mcpLedgerPrewritePersisted === "boolean" &&
@@ -355,8 +1230,63 @@ function verifyEffectCalls(effect, effectIndex, issues) {
355
1230
  call.sequence,
356
1231
  ) &&
357
1232
  call.identitySemantics === "trace-only" &&
358
- (call.providerReceiptPersisted === false ||
359
- ["completed", "outcome_unknown"].includes(call.status)) &&
1233
+ call.status !== "failed" &&
1234
+ (providerCostFieldCount === 0
1235
+ ? !startedPricingFieldsPresent && !settlementCostDigestPresent
1236
+ : hasProviderCostSchema &&
1237
+ (call.providerModel === null ||
1238
+ PROVIDER_MODEL_RE.test(call.providerModel || "")) &&
1239
+ (call.providerPricing === null ||
1240
+ validProviderPricing(
1241
+ call.providerPricing,
1242
+ call.name,
1243
+ call.providerModel,
1244
+ )) &&
1245
+ startedLineage.length === 1 &&
1246
+ startedLineage[0]?.details?.providerModel === call.providerModel &&
1247
+ startedLineage[0]?.details?.providerPricingDigest ===
1248
+ (call.providerPricing === null
1249
+ ? null
1250
+ : providerPricingDigest(call.providerPricing)) &&
1251
+ (call.status === "completed"
1252
+ ? (call.providerPricing === null
1253
+ ? call.providerCostEstimate === null
1254
+ : validProviderCostEstimate(
1255
+ call.providerCostEstimate,
1256
+ call.providerPricing,
1257
+ call.providerUsage,
1258
+ )) &&
1259
+ settlementLineage.length === 1 &&
1260
+ settlementLineage[0]?.details?.providerCostEstimateDigest ===
1261
+ (call.providerCostEstimate === null
1262
+ ? null
1263
+ : providerCostEstimateDigest(call.providerCostEstimate))
1264
+ : call.providerCostEstimate === null &&
1265
+ (call.status === "outcome_unknown"
1266
+ ? settlementLineage.length === 1 &&
1267
+ settlementLineage[0]?.details
1268
+ ?.providerCostEstimateDigest === null
1269
+ : !settlementCostDigestPresent))) &&
1270
+ (hasProviderUsage
1271
+ ? call.status === "completed"
1272
+ ? validProviderUsage(call.providerUsage) &&
1273
+ settlementLineage.length === 1 &&
1274
+ settlementLineage[0]?.details?.providerUsageDigest ===
1275
+ providerUsageDigest(call.providerUsage)
1276
+ : call.providerUsage === null &&
1277
+ (call.status === "outcome_unknown"
1278
+ ? settlementLineage.length === 1 &&
1279
+ settlementLineage[0]?.details?.providerUsageDigest === null
1280
+ : !settlementUsageDigestPresent)
1281
+ : !settlementUsageDigestPresent) &&
1282
+ !hasArtifactReadback &&
1283
+ !startedArtifactSchemaPresent &&
1284
+ !settlementArtifactDigestPresent &&
1285
+ !hasCheckpointReadback &&
1286
+ !startedCheckpointSchemaPresent &&
1287
+ !settlementCheckpointDigestPresent &&
1288
+ !hasCheckpointBinding &&
1289
+ !startedCheckpointBindingDigestPresent &&
360
1290
  call.mcpLedgerId === null &&
361
1291
  call.mcpLedgerPrewritePersisted === false &&
362
1292
  call.mcpLedgerSettlementPersisted === false);
@@ -379,14 +1309,62 @@ function verifyEffectCalls(effect, effectIndex, issues) {
379
1309
  call.providerReceiptPersisted === false &&
380
1310
  call.providerReceiptRequestId === null &&
381
1311
  call.providerReceiptResponseId === null &&
382
- call.identitySemantics === "runtime-derived");
1312
+ (call.providerReceiptRecordedAt === null || !hasReceiptTimestamp) &&
1313
+ call.identitySemantics === "runtime-derived" &&
1314
+ (call.name !== "publish_artifact"
1315
+ ? !hasArtifactReadback &&
1316
+ !startedArtifactSchemaPresent &&
1317
+ !settlementArtifactDigestPresent
1318
+ : hasArtifactReadback
1319
+ ? startedLineage.length === 1 &&
1320
+ startedLineage[0]?.details?.artifactReadbackSchema ===
1321
+ "cc-dynamic-workflow-artifact-readback/v1" &&
1322
+ (call.status === "completed"
1323
+ ? validArtifactReadback(call.artifactReadback) &&
1324
+ settlementLineage.length === 1 &&
1325
+ settlementLineage[0]?.details?.artifactReadbackDigest ===
1326
+ artifactReadbackDigest(call.artifactReadback)
1327
+ : call.artifactReadback === null &&
1328
+ (["failed", "outcome_unknown"].includes(call.status)
1329
+ ? settlementLineage.length === 1 &&
1330
+ settlementLineage[0]?.details?.artifactReadbackDigest ===
1331
+ null
1332
+ : !settlementArtifactDigestPresent))
1333
+ : !startedArtifactSchemaPresent &&
1334
+ !settlementArtifactDigestPresent) &&
1335
+ (managedToolCheckpointRequired(call.name)
1336
+ ? hasCheckpointReadback
1337
+ ? checkpointBindingValid &&
1338
+ startedLineage.length === 1 &&
1339
+ startedLineage[0]?.details?.checkpointReadbackSchema ===
1340
+ "cc-dynamic-workflow-checkpoint-readback/v1" &&
1341
+ (["completed", "failed", "outcome_unknown"].includes(call.status)
1342
+ ? (call.checkpointReadback === null ||
1343
+ (validCheckpointReadback(call.checkpointReadback) &&
1344
+ checkpointReadbackMatchesBinding(
1345
+ call.checkpointReadback,
1346
+ hasCheckpointBinding ? call.checkpointBinding : null,
1347
+ ))) &&
1348
+ settlementLineage.length === 1 &&
1349
+ settlementLineage[0]?.details?.checkpointReadbackDigest ===
1350
+ (call.checkpointReadback === null
1351
+ ? null
1352
+ : checkpointReadbackDigest(call.checkpointReadback))
1353
+ : call.checkpointReadback === null &&
1354
+ !settlementCheckpointDigestPresent)
1355
+ : !hasCheckpointBinding &&
1356
+ !startedCheckpointSchemaPresent &&
1357
+ !startedCheckpointBindingDigestPresent &&
1358
+ !settlementCheckpointDigestPresent
1359
+ : !hasCheckpointReadback &&
1360
+ !hasCheckpointBinding &&
1361
+ !startedCheckpointSchemaPresent &&
1362
+ !startedCheckpointBindingDigestPresent &&
1363
+ !settlementCheckpointDigestPresent));
383
1364
  const settlementValid = terminal
384
1365
  ? call.settlementCode !== null || call.status === "completed"
385
1366
  : call.settlementCode === null &&
386
1367
  call.outcomeUnknown === false &&
387
- call.providerReceiptPersisted === false &&
388
- call.providerReceiptRequestId === null &&
389
- call.providerReceiptResponseId === null &&
390
1368
  call.mcpLedgerId === null &&
391
1369
  call.mcpLedgerPrewritePersisted === false &&
392
1370
  call.mcpLedgerSettlementPersisted === false;
@@ -416,6 +1394,105 @@ function verifyEffectCalls(effect, effectIndex, issues) {
416
1394
  }
417
1395
  }
418
1396
 
1397
+ function verifyInputRequests(state, issues) {
1398
+ if (state.inputRequests === undefined) {
1399
+ if (["input_requested", "needs_input"].includes(state.status)) {
1400
+ issues.push("input-requests-missing");
1401
+ }
1402
+ return;
1403
+ }
1404
+ if (
1405
+ !Array.isArray(state.inputRequests) ||
1406
+ state.inputRequests.length > MAX_INPUT_REQUESTS
1407
+ ) {
1408
+ issues.push("input-requests-invalid");
1409
+ return;
1410
+ }
1411
+ const ids = new Set();
1412
+ const stepIds = new Set();
1413
+ let pendingCount = 0;
1414
+ for (const [index, request] of state.inputRequests.entries()) {
1415
+ let valid = true;
1416
+ let normalized = null;
1417
+ const plainRequest =
1418
+ request && typeof request === "object" && !Array.isArray(request);
1419
+ try {
1420
+ normalized = normalizeStageInputRequest({
1421
+ stepId: request?.stepId,
1422
+ prompt: request?.prompt,
1423
+ options: request?.options,
1424
+ multiSelect: request?.multiSelect,
1425
+ });
1426
+ } catch {
1427
+ valid = false;
1428
+ }
1429
+ const expectedId = normalized
1430
+ ? stageInputRequestIdentity(state.runId, normalized)
1431
+ : null;
1432
+ const pending = request?.status === "pending";
1433
+ const answered = request?.status === "answered";
1434
+ const cancelled = request?.status === "cancelled";
1435
+ if (pending) pendingCount += 1;
1436
+ if (
1437
+ !plainRequest ||
1438
+ request?.schema !== DYNAMIC_WORKFLOW_INPUT_REQUEST_SCHEMA ||
1439
+ (plainRequest && Reflect.ownKeys(request).length !== 11) ||
1440
+ request?.id !== expectedId ||
1441
+ ids.has(request?.id) ||
1442
+ stepIds.has(request?.stepId) ||
1443
+ (!pending && !answered && !cancelled) ||
1444
+ isoNow(request?.requestedAt) !== request.requestedAt ||
1445
+ (pending &&
1446
+ (request.resolvedAt !== null ||
1447
+ request.responseDigest !== null ||
1448
+ request.response !== null))
1449
+ ) {
1450
+ valid = false;
1451
+ }
1452
+ if (answered) {
1453
+ let answer = null;
1454
+ try {
1455
+ answer = normalizeStageInputAnswer(request.response, request);
1456
+ } catch {
1457
+ valid = false;
1458
+ }
1459
+ if (
1460
+ answer === null ||
1461
+ !SHA256_RE.test(request.responseDigest || "") ||
1462
+ request.responseDigest !==
1463
+ stageInputResponseDigest(request.id, request.response) ||
1464
+ typeof request.resolvedAt !== "string" ||
1465
+ isoNow(request.resolvedAt) !== request.resolvedAt ||
1466
+ Date.parse(request.resolvedAt) < Date.parse(request.requestedAt)
1467
+ ) {
1468
+ valid = false;
1469
+ }
1470
+ }
1471
+ if (
1472
+ cancelled &&
1473
+ (typeof request.resolvedAt !== "string" ||
1474
+ isoNow(request.resolvedAt) !== request.resolvedAt ||
1475
+ Date.parse(request.resolvedAt) < Date.parse(request.requestedAt) ||
1476
+ request.responseDigest !== null ||
1477
+ request.response !== null)
1478
+ ) {
1479
+ valid = false;
1480
+ }
1481
+ if (!valid) issues.push(`input-request-${index}-invalid`);
1482
+ ids.add(request?.id);
1483
+ stepIds.add(request?.stepId);
1484
+ }
1485
+ const inputStatus = ["input_requested", "needs_input"].includes(state.status);
1486
+ if (
1487
+ pendingCount > 1 ||
1488
+ inputStatus !== (pendingCount === 1) ||
1489
+ (state.status === "needs_input" &&
1490
+ state.effects?.some((effect) => effect?.status === "pending"))
1491
+ ) {
1492
+ issues.push("input-request-state-invalid");
1493
+ }
1494
+ }
1495
+
419
1496
  export function verifyDynamicWorkflowRuntimeState(value) {
420
1497
  const state = snapshotJson(value, "workflow runtime state");
421
1498
  const issues = [];
@@ -452,6 +1529,43 @@ export function verifyDynamicWorkflowRuntimeState(value) {
452
1529
  if (!Array.isArray(state.effects) || state.effects.length > MAX_EFFECTS) {
453
1530
  issues.push("effects-invalid");
454
1531
  } else {
1532
+ const startedLineageByCallId = new Map();
1533
+ const receiptLineageByCallId = new Map();
1534
+ const settlementLineageByCallId = new Map();
1535
+ for (const event of state.lineage) {
1536
+ const callRecordId = event?.details?.callRecordId;
1537
+ const target =
1538
+ event.type === "effect-call-started"
1539
+ ? startedLineageByCallId
1540
+ : event.type === "effect-call-receipt-recorded"
1541
+ ? receiptLineageByCallId
1542
+ : event.type === "effect-call-settled"
1543
+ ? settlementLineageByCallId
1544
+ : null;
1545
+ if (!target) continue;
1546
+ if (!target.has(callRecordId)) {
1547
+ target.set(callRecordId, []);
1548
+ }
1549
+ target.get(callRecordId).push(event);
1550
+ }
1551
+ const knownCallIds = new Set(
1552
+ state.effects.flatMap((effect) =>
1553
+ Array.isArray(effect?.calls)
1554
+ ? effect.calls.map((call) => call?.id)
1555
+ : [],
1556
+ ),
1557
+ );
1558
+ let receiptLineageIndex = 0;
1559
+ for (const [callRecordId, events] of receiptLineageByCallId) {
1560
+ if (
1561
+ !SHA256_RE.test(callRecordId || "") ||
1562
+ !knownCallIds.has(callRecordId) ||
1563
+ events.length !== 1
1564
+ ) {
1565
+ issues.push(`receipt-lineage-${receiptLineageIndex}-invalid`);
1566
+ }
1567
+ receiptLineageIndex += 1;
1568
+ }
455
1569
  const ids = new Set();
456
1570
  const keys = new Set();
457
1571
  const batches = new Map();
@@ -569,7 +1683,14 @@ export function verifyDynamicWorkflowRuntimeState(value) {
569
1683
  }
570
1684
  ids.add(effect?.id);
571
1685
  keys.add(effect?.key);
572
- verifyEffectCalls(effect, index, issues);
1686
+ verifyEffectCalls(
1687
+ effect,
1688
+ index,
1689
+ issues,
1690
+ startedLineageByCallId,
1691
+ receiptLineageByCallId,
1692
+ settlementLineageByCallId,
1693
+ );
573
1694
  if (batchMetadataValid && hasBatchMetadata) {
574
1695
  if (!batches.has(effect.batchId)) batches.set(effect.batchId, []);
575
1696
  batches.get(effect.batchId).push(effect);
@@ -596,6 +1717,7 @@ export function verifyDynamicWorkflowRuntimeState(value) {
596
1717
  batchIndex += 1;
597
1718
  }
598
1719
  }
1720
+ verifyInputRequests(state, issues);
599
1721
  if (
600
1722
  (state.status === "completed") !== (state.finalRecord != null) ||
601
1723
  (state.status === "stopped" && state.finalRecord != null)
@@ -800,6 +1922,22 @@ export function readDynamicWorkflowEffectResultFile(filePath) {
800
1922
  );
801
1923
  }
802
1924
 
1925
+ export function readDynamicWorkflowInputResponseFile(filePath) {
1926
+ const payload = readDynamicWorkflowEffectResultFile(filePath);
1927
+ if (
1928
+ !payload ||
1929
+ typeof payload !== "object" ||
1930
+ Array.isArray(payload) ||
1931
+ Reflect.ownKeys(payload).length !== 1 ||
1932
+ !Object.hasOwn(payload, "answer")
1933
+ ) {
1934
+ throw new Error(
1935
+ 'workflow input response file must contain exactly {"answer": ...}',
1936
+ );
1937
+ }
1938
+ return payload.answer;
1939
+ }
1940
+
803
1941
  function stateBindings(execution, runId) {
804
1942
  const admission = execution.runAdmission;
805
1943
  if (!admission || typeof admission !== "object") {
@@ -869,6 +2007,7 @@ function createState(bindings, now) {
869
2007
  revision: 0,
870
2008
  status: "ready",
871
2009
  effects: [],
2010
+ inputRequests: [],
872
2011
  finalRecord: null,
873
2012
  createdAt: at,
874
2013
  updatedAt: at,
@@ -892,18 +2031,114 @@ function controlError(message, reason, state, effect = null, cause = null) {
892
2031
  return error;
893
2032
  }
894
2033
 
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
- }
903
- if (state.status === "completed") return { value: state };
904
- if (state.status !== "ready") {
905
- throw new Error(
906
- `durable workflow run is ${state.status}; use explicit control or reconciliation`,
2034
+ function runtimeInputRequests(state) {
2035
+ return Array.isArray(state?.inputRequests) ? state.inputRequests : [];
2036
+ }
2037
+
2038
+ function normalizeStageInputRequest(value) {
2039
+ const request = snapshotJson(
2040
+ value,
2041
+ "workflow stage input request",
2042
+ 64 * 1024,
2043
+ );
2044
+ const fields = Object.keys(request || {});
2045
+ if (
2046
+ !request ||
2047
+ typeof request !== "object" ||
2048
+ Array.isArray(request) ||
2049
+ fields.length !== 4 ||
2050
+ !["stepId", "prompt", "options", "multiSelect"].every((field) =>
2051
+ Object.hasOwn(request, field),
2052
+ ) ||
2053
+ typeof request.stepId !== "string" ||
2054
+ request.stepId.length === 0 ||
2055
+ request.stepId.length > 512 ||
2056
+ typeof request.prompt !== "string" ||
2057
+ request.prompt.trim() !== request.prompt ||
2058
+ request.prompt.length === 0 ||
2059
+ request.prompt.length > 4096 ||
2060
+ typeof request.multiSelect !== "boolean" ||
2061
+ !(
2062
+ request.options === null ||
2063
+ (Array.isArray(request.options) &&
2064
+ request.options.length > 0 &&
2065
+ request.options.length <= MAX_INPUT_OPTIONS &&
2066
+ request.options.every(
2067
+ (option) =>
2068
+ typeof option === "string" &&
2069
+ option.trim() === option &&
2070
+ option.length > 0 &&
2071
+ option.length <= 512,
2072
+ ) &&
2073
+ new Set(request.options).size === request.options.length)
2074
+ ) ||
2075
+ (request.multiSelect && request.options === null)
2076
+ ) {
2077
+ throw new TypeError("workflow stage input request is malformed");
2078
+ }
2079
+ if (containsSecret(JSON.stringify(request))) {
2080
+ throw new Error("workflow stage input request contains secret-shaped data");
2081
+ }
2082
+ return request;
2083
+ }
2084
+
2085
+ function stageInputRequestIdentity(runId, request) {
2086
+ return digest("chainlesschain.dynamic-workflow.input-request.v1\0", {
2087
+ runId,
2088
+ request,
2089
+ });
2090
+ }
2091
+
2092
+ function normalizeStageInputAnswer(value, request) {
2093
+ const answer = snapshotJson(
2094
+ value,
2095
+ "workflow stage input answer",
2096
+ MAX_INPUT_RESPONSE_BYTES,
2097
+ );
2098
+ if (request.multiSelect) {
2099
+ if (
2100
+ !Array.isArray(answer) ||
2101
+ answer.length === 0 ||
2102
+ answer.length > MAX_INPUT_OPTIONS ||
2103
+ answer.some((item) => typeof item !== "string") ||
2104
+ new Set(answer).size !== answer.length ||
2105
+ answer.some((item) => !request.options.includes(item))
2106
+ ) {
2107
+ throw new TypeError("workflow stage input answer is malformed");
2108
+ }
2109
+ } else if (
2110
+ typeof answer !== "string" ||
2111
+ answer.length === 0 ||
2112
+ Buffer.byteLength(answer, "utf8") > MAX_INPUT_RESPONSE_BYTES ||
2113
+ (request.options !== null && !request.options.includes(answer))
2114
+ ) {
2115
+ throw new TypeError("workflow stage input answer is malformed");
2116
+ }
2117
+ if (containsSecret(JSON.stringify(answer))) {
2118
+ throw new Error("workflow stage input answer contains secret-shaped data");
2119
+ }
2120
+ return answer;
2121
+ }
2122
+
2123
+ function stageInputResponseDigest(requestId, answer) {
2124
+ return digest("chainlesschain.dynamic-workflow.input-response.v1\0", {
2125
+ requestId,
2126
+ answer,
2127
+ });
2128
+ }
2129
+
2130
+ function startRun(statePath, bindings, now) {
2131
+ return withStateMutation(statePath, (current) => {
2132
+ let state = current || createState(bindings, now);
2133
+ if (!bindingsMatch(state, bindings)) {
2134
+ throw new Error(
2135
+ "durable workflow run authority does not match stored state",
2136
+ );
2137
+ }
2138
+ if (state.status === "completed") return { value: state };
2139
+ if (state.status !== "ready") {
2140
+ throw new Error(
2141
+ `durable workflow run is ${state.status}; use explicit control or reconciliation`,
907
2142
  );
908
2143
  }
909
2144
  state = transition(
@@ -919,6 +2154,78 @@ function startRun(statePath, bindings, now) {
919
2154
  });
920
2155
  }
921
2156
 
2157
+ function resolveDurableWorkflowStageInput(statePath, runId, rawRequest, now) {
2158
+ const request = normalizeStageInputRequest(rawRequest);
2159
+ const requestId = stageInputRequestIdentity(runId, request);
2160
+ const outcome = withStateMutation(statePath, (current) => {
2161
+ if (!current || current.runId !== runId) {
2162
+ throw new Error("durable workflow run disappeared before input request");
2163
+ }
2164
+ const inputRequests = runtimeInputRequests(current);
2165
+ const matchingStep = inputRequests.find(
2166
+ (entry) => entry.stepId === request.stepId,
2167
+ );
2168
+ if (matchingStep && matchingStep.id !== requestId) {
2169
+ throw new Error("workflow stage input request drifted during replay");
2170
+ }
2171
+ if (matchingStep?.status === "answered") {
2172
+ return { value: { answer: matchingStep.response, state: current } };
2173
+ }
2174
+ if (matchingStep?.status === "pending") {
2175
+ return {
2176
+ value: { pending: matchingStep, state: current },
2177
+ };
2178
+ }
2179
+ const existingPending = inputRequests.find(
2180
+ (entry) => entry.status === "pending",
2181
+ );
2182
+ if (existingPending) {
2183
+ return {
2184
+ value: { pending: existingPending, state: current },
2185
+ };
2186
+ }
2187
+ if (current.status !== "running") {
2188
+ throw new Error(
2189
+ `durable workflow run cannot request input while ${current.status}`,
2190
+ );
2191
+ }
2192
+ if (inputRequests.length >= MAX_INPUT_REQUESTS) {
2193
+ throw new Error("durable workflow input request limit exceeded");
2194
+ }
2195
+ const requestedAt = isoNow(now);
2196
+ const pending = {
2197
+ schema: DYNAMIC_WORKFLOW_INPUT_REQUEST_SCHEMA,
2198
+ id: requestId,
2199
+ stepId: request.stepId,
2200
+ prompt: request.prompt,
2201
+ options: request.options,
2202
+ multiSelect: request.multiSelect,
2203
+ status: "pending",
2204
+ requestedAt,
2205
+ resolvedAt: null,
2206
+ responseDigest: null,
2207
+ response: null,
2208
+ };
2209
+ const state = transition(
2210
+ current,
2211
+ "input-requested",
2212
+ { requestId, stepId: request.stepId },
2213
+ (draft) => {
2214
+ draft.inputRequests = [...runtimeInputRequests(draft), pending];
2215
+ draft.status = "input_requested";
2216
+ },
2217
+ now,
2218
+ );
2219
+ return { state, value: { pending, state } };
2220
+ });
2221
+ if (outcome.answer !== undefined) return outcome.answer;
2222
+ throw controlError(
2223
+ `workflow stage ${outcome.pending.stepId} needs input`,
2224
+ "needs-input",
2225
+ outcome.state,
2226
+ );
2227
+ }
2228
+
922
2229
  function effectKey(effect) {
923
2230
  const stepId = String(effect?.stepId || "");
924
2231
  const iteration = Number(effect?.iteration);
@@ -983,6 +2290,11 @@ function requestEffectBatch(statePath, runId, argsList, now) {
983
2290
  value: { control: "paused", state: current, results: null },
984
2291
  };
985
2292
  }
2293
+ if (["input_requested", "needs_input"].includes(current.status)) {
2294
+ return {
2295
+ value: { control: "needs-input", state: current, results: null },
2296
+ };
2297
+ }
986
2298
  if (current.status === "stopped") {
987
2299
  return {
988
2300
  value: { control: "stopped", state: current, results: null },
@@ -1169,7 +2481,11 @@ function markEffectProviderDispatched(statePath, runId, effectId, now) {
1169
2481
  if (current.status === "stopped") {
1170
2482
  return { value: { control: "stopped", state: current, effect } };
1171
2483
  }
1172
- if (!["running", "pause_requested", "blocked"].includes(current.status)) {
2484
+ if (
2485
+ !["running", "pause_requested", "input_requested", "blocked"].includes(
2486
+ current.status,
2487
+ )
2488
+ ) {
1173
2489
  throw new Error(
1174
2490
  `workflow effect cannot be dispatched while ${current.status}`,
1175
2491
  );
@@ -1243,10 +2559,90 @@ function ownDataValue(value, property) {
1243
2559
  }
1244
2560
  }
1245
2561
 
2562
+ function providerUsageSettlement(event) {
2563
+ const usage = ownDataValue(event, "usage");
2564
+ let descriptors;
2565
+ try {
2566
+ if (
2567
+ !usage ||
2568
+ typeof usage !== "object" ||
2569
+ Array.isArray(usage) ||
2570
+ utilTypes.isProxy(usage)
2571
+ ) {
2572
+ throw new TypeError();
2573
+ }
2574
+ descriptors = Object.getOwnPropertyDescriptors(usage);
2575
+ } catch {
2576
+ throw new TypeError("workflow provider token usage is malformed");
2577
+ }
2578
+ const fields = Reflect.ownKeys(descriptors);
2579
+ if (
2580
+ fields.length > 32 ||
2581
+ fields.some(
2582
+ (field) =>
2583
+ typeof field !== "string" ||
2584
+ descriptors[field].enumerable !== true ||
2585
+ !Object.hasOwn(descriptors[field], "value"),
2586
+ )
2587
+ ) {
2588
+ throw new TypeError("workflow provider token usage is malformed");
2589
+ }
2590
+ const count = (canonical, alias, required) => {
2591
+ const canonicalField = descriptors[canonical];
2592
+ const aliasField = descriptors[alias];
2593
+ if (!canonicalField && !aliasField) {
2594
+ if (required) {
2595
+ throw new TypeError("workflow provider token usage is incomplete");
2596
+ }
2597
+ return 0;
2598
+ }
2599
+ const canonicalValue = canonicalField?.value;
2600
+ const aliasValue = aliasField?.value;
2601
+ if (
2602
+ (canonicalField && aliasField && canonicalValue !== aliasValue) ||
2603
+ nonNegativeSafeInteger(canonicalField ? canonicalValue : aliasValue) ===
2604
+ null ||
2605
+ (canonicalField ? canonicalValue : aliasValue) > MAX_PROVIDER_TOKEN_COUNT
2606
+ ) {
2607
+ throw new TypeError("workflow provider token usage is malformed");
2608
+ }
2609
+ return canonicalField ? canonicalValue : aliasValue;
2610
+ };
2611
+ const inputTokens = count("input_tokens", "prompt_tokens", true);
2612
+ const outputTokens = count("output_tokens", "completion_tokens", true);
2613
+ const cacheReadInputTokens = count(
2614
+ "cache_read_input_tokens",
2615
+ "cache_read_tokens",
2616
+ false,
2617
+ );
2618
+ const cacheCreationInputTokens = count(
2619
+ "cache_creation_input_tokens",
2620
+ "cache_creation_tokens",
2621
+ false,
2622
+ );
2623
+ const totalTokens =
2624
+ inputTokens +
2625
+ outputTokens +
2626
+ cacheReadInputTokens +
2627
+ cacheCreationInputTokens;
2628
+ if (!Number.isSafeInteger(totalTokens)) {
2629
+ throw new TypeError("workflow provider token usage exceeds its limit");
2630
+ }
2631
+ return {
2632
+ schema: "cc-provider-token-usage/v1",
2633
+ inputTokens,
2634
+ outputTokens,
2635
+ cacheReadInputTokens,
2636
+ cacheCreationInputTokens,
2637
+ totalTokens,
2638
+ };
2639
+ }
2640
+
1246
2641
  function workflowCallAttempt(effectId, kind, event, now) {
1247
2642
  const startedAt = isoNow(now);
1248
2643
  const ownerEffectId = event?.workflowEffectId;
1249
2644
  if (kind === "provider") {
2645
+ const providerModel = event?.model == null ? null : event.model;
1250
2646
  const requestSource =
1251
2647
  event?.workflowRequestSource ||
1252
2648
  (event?.source === "subagent" ? "model" : event?.source);
@@ -1255,6 +2651,8 @@ function workflowCallAttempt(effectId, kind, event, now) {
1255
2651
  !SHA256_RE.test(ownerEffectId || "") ||
1256
2652
  !WORKFLOW_PROVIDER_CALL_ID_RE.test(event.callId || "") ||
1257
2653
  !PROVIDER_NAME_RE.test(event.provider || "") ||
2654
+ (providerModel !== null &&
2655
+ !PROVIDER_MODEL_RE.test(providerModel || "")) ||
1258
2656
  !["model", "semantic-compaction", "subagent"].includes(event.source) ||
1259
2657
  !["model", "semantic-compaction"].includes(requestSource) ||
1260
2658
  (event.source === "subagent"
@@ -1296,6 +2694,11 @@ function workflowCallAttempt(effectId, kind, event, now) {
1296
2694
  providerReceiptPersisted: false,
1297
2695
  providerReceiptRequestId: null,
1298
2696
  providerReceiptResponseId: null,
2697
+ providerReceiptRecordedAt: null,
2698
+ providerUsage: null,
2699
+ providerModel,
2700
+ providerPricing: providerPricingSnapshot(event.provider, providerModel),
2701
+ providerCostEstimate: null,
1299
2702
  mcpLedgerId: null,
1300
2703
  mcpLedgerPrewritePersisted: false,
1301
2704
  mcpLedgerSettlementPersisted: false,
@@ -1321,6 +2724,18 @@ function workflowCallAttempt(effectId, kind, event, now) {
1321
2724
  ) {
1322
2725
  throw new TypeError("workflow tool call boundary is malformed");
1323
2726
  }
2727
+ const checkpointRequired = managedToolCheckpointRequired(event.tool);
2728
+ const checkpointBindingValue = ownDataValue(
2729
+ event,
2730
+ "managedCheckpointBinding",
2731
+ );
2732
+ const checkpointBinding = checkpointBindingSnapshot(checkpointBindingValue);
2733
+ if (
2734
+ (!checkpointRequired && checkpointBindingValue !== undefined) ||
2735
+ (checkpointBinding !== null && !validCheckpointBinding(checkpointBinding))
2736
+ ) {
2737
+ throw new TypeError("workflow tool checkpoint boundary is malformed");
2738
+ }
1324
2739
  return {
1325
2740
  schema: DYNAMIC_WORKFLOW_CALL_SCHEMA,
1326
2741
  id: workflowCallId(
@@ -1350,6 +2765,11 @@ function workflowCallAttempt(effectId, kind, event, now) {
1350
2765
  providerReceiptPersisted: false,
1351
2766
  providerReceiptRequestId: null,
1352
2767
  providerReceiptResponseId: null,
2768
+ providerReceiptRecordedAt: null,
2769
+ ...(event.tool === "publish_artifact" ? { artifactReadback: null } : {}),
2770
+ ...(checkpointRequired
2771
+ ? { checkpointBinding, checkpointReadback: null }
2772
+ : {}),
1353
2773
  mcpLedgerId: null,
1354
2774
  mcpLedgerPrewritePersisted: false,
1355
2775
  mcpLedgerSettlementPersisted: false,
@@ -1406,6 +2826,32 @@ function beginEffectCall(statePath, runId, effectId, kind, event, now) {
1406
2826
  ownerEffectId: attempt.ownerEffectId,
1407
2827
  callRecordId: attempt.id,
1408
2828
  kind,
2829
+ ...(kind === "provider"
2830
+ ? {
2831
+ providerModel: attempt.providerModel,
2832
+ providerPricingDigest:
2833
+ attempt.providerPricing === null
2834
+ ? null
2835
+ : providerPricingDigest(attempt.providerPricing),
2836
+ }
2837
+ : {}),
2838
+ ...(kind === "tool" && attempt.name === "publish_artifact"
2839
+ ? {
2840
+ artifactReadbackSchema:
2841
+ "cc-dynamic-workflow-artifact-readback/v1",
2842
+ }
2843
+ : {}),
2844
+ ...(kind === "tool" &&
2845
+ Object.prototype.hasOwnProperty.call(attempt, "checkpointReadback")
2846
+ ? {
2847
+ checkpointReadbackSchema:
2848
+ "cc-dynamic-workflow-checkpoint-readback/v1",
2849
+ checkpointBindingDigest:
2850
+ attempt.checkpointBinding === null
2851
+ ? null
2852
+ : checkpointBindingDigest(attempt.checkpointBinding),
2853
+ }
2854
+ : {}),
1409
2855
  },
1410
2856
  (draft) => {
1411
2857
  draft.effects[index] = {
@@ -1492,12 +2938,88 @@ function providerReceiptSettlement(call, event) {
1492
2938
  return { persisted: true, requestId, responseId };
1493
2939
  }
1494
2940
 
1495
- function effectCallSettlement(call, event, now) {
2941
+ function recordEffectCallReceipt(statePath, runId, effectId, event, now) {
2942
+ return withStateMutation(statePath, (current) => {
2943
+ if (!current || current.runId !== runId) {
2944
+ throw new Error(
2945
+ "durable workflow run disappeared before provider receipt prewrite",
2946
+ );
2947
+ }
2948
+ const effectIndex = current.effects.findIndex(
2949
+ (effect) => effect.id === effectId,
2950
+ );
2951
+ const effect = current.effects[effectIndex];
2952
+ const callIndex = (effect?.calls || []).findIndex(
2953
+ (call) => call.kind === "provider" && call.callId === event?.callId,
2954
+ );
2955
+ const call = effect?.calls?.[callIndex];
2956
+ if (
2957
+ !effect ||
2958
+ effect.status !== "pending" ||
2959
+ !call ||
2960
+ call.status !== "started" ||
2961
+ call.providerReceiptPersisted === true
2962
+ ) {
2963
+ throw new Error("workflow provider call cannot record this receipt");
2964
+ }
2965
+ if (
2966
+ event?.type !== "provider-request-receipt" ||
2967
+ event.provider !== call.name ||
2968
+ event.source !== call.source ||
2969
+ event.workflowRequestSource !== call.requestSource ||
2970
+ event.workflowEffectId !== (call.ownerEffectId ?? call.effectId)
2971
+ ) {
2972
+ throw new TypeError("workflow provider receipt envelope is malformed");
2973
+ }
2974
+ const receipt = providerReceiptSettlement(call, event);
2975
+ if (!receipt.persisted) {
2976
+ throw new TypeError("workflow provider receipt envelope is empty");
2977
+ }
2978
+ const providerReceiptRecordedAt = isoNow(now);
2979
+ const state = transition(
2980
+ current,
2981
+ "effect-call-receipt-recorded",
2982
+ {
2983
+ effectId,
2984
+ ownerEffectId: call.ownerEffectId ?? call.effectId,
2985
+ callRecordId: call.id,
2986
+ },
2987
+ (draft) => {
2988
+ const calls = [...(draft.effects[effectIndex].calls || [])];
2989
+ calls[callIndex] = {
2990
+ ...calls[callIndex],
2991
+ providerReceiptPersisted: true,
2992
+ providerReceiptRequestId: receipt.requestId,
2993
+ providerReceiptResponseId: receipt.responseId,
2994
+ providerReceiptRecordedAt,
2995
+ };
2996
+ draft.effects[effectIndex] = {
2997
+ ...draft.effects[effectIndex],
2998
+ calls,
2999
+ };
3000
+ },
3001
+ now,
3002
+ );
3003
+ return { state, value: state.effects[effectIndex].calls[callIndex] };
3004
+ });
3005
+ }
3006
+
3007
+ function effectCallSettlement(
3008
+ call,
3009
+ event,
3010
+ now,
3011
+ artifactStore,
3012
+ checkpointStore,
3013
+ ) {
1496
3014
  let status;
1497
3015
  let settlementCode = null;
1498
- let providerReceiptPersisted = false;
1499
- let providerReceiptRequestId = null;
1500
- let providerReceiptResponseId = null;
3016
+ let providerUsage = call.providerUsage;
3017
+ let providerCostEstimate = call.providerCostEstimate;
3018
+ let providerReceiptPersisted = call.providerReceiptPersisted;
3019
+ let providerReceiptRequestId = call.providerReceiptRequestId;
3020
+ let providerReceiptResponseId = call.providerReceiptResponseId;
3021
+ let artifactReadback = call.artifactReadback;
3022
+ let checkpointReadback = call.checkpointReadback;
1501
3023
  let mcpLedgerId = null;
1502
3024
  let mcpLedgerPrewritePersisted = false;
1503
3025
  let mcpLedgerSettlementPersisted = false;
@@ -1506,19 +3028,35 @@ function effectCallSettlement(call, event, now) {
1506
3028
  !["token-usage", "model-usage-unknown"].includes(event?.type) ||
1507
3029
  event.callId !== call.callId ||
1508
3030
  (event.provider && event.provider !== call.name) ||
3031
+ (Object.prototype.hasOwnProperty.call(call, "providerModel") &&
3032
+ event.model != null &&
3033
+ event.model !== call.providerModel) ||
1509
3034
  (event.source && event.source !== call.source)
1510
3035
  ) {
1511
3036
  throw new TypeError("workflow provider call settlement is malformed");
1512
3037
  }
1513
3038
  status = event.type === "token-usage" ? "completed" : "outcome_unknown";
3039
+ providerUsage =
3040
+ status === "completed" ? providerUsageSettlement(event) : null;
3041
+ providerCostEstimate =
3042
+ status === "completed"
3043
+ ? estimateProviderCost(call.providerPricing, providerUsage)
3044
+ : null;
1514
3045
  settlementCode =
1515
3046
  status === "outcome_unknown"
1516
3047
  ? String(event.code || "provider_outcome_unknown")
1517
3048
  : null;
1518
3049
  const providerReceipt = providerReceiptSettlement(call, event);
1519
- providerReceiptPersisted = providerReceipt.persisted;
1520
- providerReceiptRequestId = providerReceipt.requestId;
1521
- providerReceiptResponseId = providerReceipt.responseId;
3050
+ if (
3051
+ providerReceipt.persisted &&
3052
+ (!call.providerReceiptPersisted ||
3053
+ providerReceipt.requestId !== call.providerReceiptRequestId ||
3054
+ providerReceipt.responseId !== call.providerReceiptResponseId)
3055
+ ) {
3056
+ throw new TypeError(
3057
+ "workflow provider receipt was not durably prewritten",
3058
+ );
3059
+ }
1522
3060
  } else {
1523
3061
  if (
1524
3062
  event?.type !== "tool-result" ||
@@ -1556,6 +3094,28 @@ function effectCallSettlement(call, event, now) {
1556
3094
  ) {
1557
3095
  throw new TypeError("workflow MCP call settlement is malformed");
1558
3096
  }
3097
+ if (Object.prototype.hasOwnProperty.call(call, "artifactReadback")) {
3098
+ const published = ownDataValue(event.result, "published");
3099
+ if (status === "completed") {
3100
+ artifactReadback = artifactReadbackSettlement(event, artifactStore);
3101
+ } else {
3102
+ if (published !== undefined && published !== null) {
3103
+ throw new TypeError(
3104
+ "failed workflow artifact settlement cannot claim a published artifact",
3105
+ );
3106
+ }
3107
+ artifactReadback = null;
3108
+ }
3109
+ }
3110
+ if (Object.prototype.hasOwnProperty.call(call, "checkpointReadback")) {
3111
+ checkpointReadback = checkpointReadbackSettlement(
3112
+ event,
3113
+ checkpointStore,
3114
+ Object.prototype.hasOwnProperty.call(call, "checkpointBinding")
3115
+ ? call.checkpointBinding
3116
+ : null,
3117
+ );
3118
+ }
1559
3119
  }
1560
3120
  if (
1561
3121
  settlementCode !== null &&
@@ -1573,13 +3133,35 @@ function effectCallSettlement(call, event, now) {
1573
3133
  providerReceiptPersisted,
1574
3134
  providerReceiptRequestId,
1575
3135
  providerReceiptResponseId,
3136
+ ...(call.kind === "provider" ? { providerUsage } : {}),
3137
+ ...(call.kind === "provider" &&
3138
+ Object.prototype.hasOwnProperty.call(call, "providerCostEstimate")
3139
+ ? { providerCostEstimate }
3140
+ : {}),
3141
+ ...(call.kind === "tool" &&
3142
+ Object.prototype.hasOwnProperty.call(call, "artifactReadback")
3143
+ ? { artifactReadback }
3144
+ : {}),
3145
+ ...(call.kind === "tool" &&
3146
+ Object.prototype.hasOwnProperty.call(call, "checkpointReadback")
3147
+ ? { checkpointReadback }
3148
+ : {}),
1576
3149
  mcpLedgerId,
1577
3150
  mcpLedgerPrewritePersisted,
1578
3151
  mcpLedgerSettlementPersisted,
1579
3152
  };
1580
3153
  }
1581
3154
 
1582
- function settleEffectCall(statePath, runId, effectId, kind, event, now) {
3155
+ function settleEffectCall(
3156
+ statePath,
3157
+ runId,
3158
+ effectId,
3159
+ kind,
3160
+ event,
3161
+ now,
3162
+ artifactStore,
3163
+ checkpointStore,
3164
+ ) {
1583
3165
  return withStateMutation(statePath, (current) => {
1584
3166
  if (!current || current.runId !== runId) {
1585
3167
  throw new Error(
@@ -1605,7 +3187,47 @@ function settleEffectCall(statePath, runId, effectId, kind, event, now) {
1605
3187
  ) {
1606
3188
  throw new Error("workflow child call is not started at settlement");
1607
3189
  }
1608
- const settlement = effectCallSettlement(call, event, now);
3190
+ const settlement = effectCallSettlement(
3191
+ call,
3192
+ event,
3193
+ now,
3194
+ artifactStore,
3195
+ checkpointStore,
3196
+ );
3197
+ const usageDigest =
3198
+ kind === "provider" && settlement.providerUsage !== null
3199
+ ? providerUsageDigest(settlement.providerUsage)
3200
+ : null;
3201
+ const hasProviderCostSchema = Object.prototype.hasOwnProperty.call(
3202
+ settlement,
3203
+ "providerCostEstimate",
3204
+ );
3205
+ const costEstimateDigest =
3206
+ kind === "provider" &&
3207
+ hasProviderCostSchema &&
3208
+ settlement.providerCostEstimate !== null
3209
+ ? providerCostEstimateDigest(settlement.providerCostEstimate)
3210
+ : null;
3211
+ const hasArtifactReadback = Object.prototype.hasOwnProperty.call(
3212
+ settlement,
3213
+ "artifactReadback",
3214
+ );
3215
+ const settledArtifactReadbackDigest =
3216
+ kind === "tool" &&
3217
+ hasArtifactReadback &&
3218
+ settlement.artifactReadback !== null
3219
+ ? artifactReadbackDigest(settlement.artifactReadback)
3220
+ : null;
3221
+ const hasCheckpointReadback = Object.prototype.hasOwnProperty.call(
3222
+ settlement,
3223
+ "checkpointReadback",
3224
+ );
3225
+ const settledCheckpointReadbackDigest =
3226
+ kind === "tool" &&
3227
+ hasCheckpointReadback &&
3228
+ settlement.checkpointReadback !== null
3229
+ ? checkpointReadbackDigest(settlement.checkpointReadback)
3230
+ : null;
1609
3231
  const state = transition(
1610
3232
  current,
1611
3233
  "effect-call-settled",
@@ -1614,6 +3236,16 @@ function settleEffectCall(statePath, runId, effectId, kind, event, now) {
1614
3236
  callRecordId: call.id,
1615
3237
  kind,
1616
3238
  status: settlement.status,
3239
+ ...(kind === "provider" ? { providerUsageDigest: usageDigest } : {}),
3240
+ ...(kind === "provider" && hasProviderCostSchema
3241
+ ? { providerCostEstimateDigest: costEstimateDigest }
3242
+ : {}),
3243
+ ...(kind === "tool" && hasArtifactReadback
3244
+ ? { artifactReadbackDigest: settledArtifactReadbackDigest }
3245
+ : {}),
3246
+ ...(kind === "tool" && hasCheckpointReadback
3247
+ ? { checkpointReadbackDigest: settledCheckpointReadbackDigest }
3248
+ : {}),
1617
3249
  },
1618
3250
  (draft) => {
1619
3251
  const calls = [...(draft.effects[effectIndex].calls || [])];
@@ -1629,7 +3261,14 @@ function settleEffectCall(statePath, runId, effectId, kind, event, now) {
1629
3261
  });
1630
3262
  }
1631
3263
 
1632
- function createDurableCallObservers(statePath, runId, effectId, now) {
3264
+ function createDurableCallObservers(
3265
+ statePath,
3266
+ runId,
3267
+ effectId,
3268
+ now,
3269
+ artifactStore,
3270
+ checkpointStore,
3271
+ ) {
1633
3272
  return Object.freeze({
1634
3273
  strictUsageTelemetry: true,
1635
3274
  onUsageBoundary(event) {
@@ -1638,11 +3277,23 @@ function createDurableCallObservers(statePath, runId, effectId, now) {
1638
3277
  onUsageSettlement(event) {
1639
3278
  settleEffectCall(statePath, runId, effectId, "provider", event, now);
1640
3279
  },
3280
+ onProviderReceipt(event) {
3281
+ recordEffectCallReceipt(statePath, runId, effectId, event, now);
3282
+ },
1641
3283
  onToolCallBoundary(event) {
1642
3284
  beginEffectCall(statePath, runId, effectId, "tool", event, now);
1643
3285
  },
1644
3286
  onToolCallSettlement(event) {
1645
- settleEffectCall(statePath, runId, effectId, "tool", event, now);
3287
+ settleEffectCall(
3288
+ statePath,
3289
+ runId,
3290
+ effectId,
3291
+ "tool",
3292
+ event,
3293
+ now,
3294
+ artifactStore,
3295
+ checkpointStore,
3296
+ );
1646
3297
  },
1647
3298
  });
1648
3299
  }
@@ -1812,6 +3463,32 @@ function finalizePauseAfterSettlementBarrier(statePath, runId, now) {
1812
3463
  });
1813
3464
  }
1814
3465
 
3466
+ function finalizeInputAfterSettlementBarrier(statePath, runId, now) {
3467
+ return withStateMutation(statePath, (current) => {
3468
+ if (!current || current.runId !== runId) return { value: current };
3469
+ if (current.status !== "input_requested") return { value: current };
3470
+ if (current.effects.some((effect) => effect.status === "pending")) {
3471
+ return { value: current };
3472
+ }
3473
+ const pending = runtimeInputRequests(current).find(
3474
+ (request) => request.status === "pending",
3475
+ );
3476
+ if (!pending) {
3477
+ throw new Error("workflow input barrier has no pending request");
3478
+ }
3479
+ const state = transition(
3480
+ current,
3481
+ "run-needs-input",
3482
+ { requestId: pending.id, barrier: "all-requested-effects-settled" },
3483
+ (draft) => {
3484
+ draft.status = "needs_input";
3485
+ },
3486
+ now,
3487
+ );
3488
+ return { state, value: state };
3489
+ });
3490
+ }
3491
+
1815
3492
  function completeRun(statePath, runId, record, now) {
1816
3493
  const finalRecord = snapshotJson(
1817
3494
  record,
@@ -1834,6 +3511,18 @@ function completeRun(statePath, runId, record, now) {
1834
3511
  );
1835
3512
  return { state, value: { control: "paused", state } };
1836
3513
  }
3514
+ if (current.status === "input_requested") {
3515
+ const state = transition(
3516
+ current,
3517
+ "run-needs-input",
3518
+ { barrier: "workflow-completion" },
3519
+ (draft) => {
3520
+ draft.status = "needs_input";
3521
+ },
3522
+ now,
3523
+ );
3524
+ return { state, value: { control: "needs-input", state } };
3525
+ }
1837
3526
  if (current.status === "stopped") {
1838
3527
  return { value: { control: "stopped", state: current } };
1839
3528
  }
@@ -1862,7 +3551,13 @@ function markFailed(statePath, runId, error, now) {
1862
3551
  !current ||
1863
3552
  current.runId !== runId ||
1864
3553
  TERMINAL_STATUSES.has(current.status) ||
1865
- ["paused", "blocked", "pause_requested"].includes(current.status)
3554
+ [
3555
+ "paused",
3556
+ "blocked",
3557
+ "pause_requested",
3558
+ "input_requested",
3559
+ "needs_input",
3560
+ ].includes(current.status)
1866
3561
  ) {
1867
3562
  return { value: current };
1868
3563
  }
@@ -1879,6 +3574,26 @@ function markFailed(statePath, runId, error, now) {
1879
3574
  });
1880
3575
  }
1881
3576
 
3577
+ function managedCheckpointRuntimeExclusions(cwd, statePath, existing = []) {
3578
+ const exclusions = Array.isArray(existing) ? [...existing] : [];
3579
+ if (typeof cwd !== "string" || !cwd.trim()) return exclusions;
3580
+ const workspaceRoot = path.resolve(cwd);
3581
+ const runtimeStatePath = path.resolve(statePath);
3582
+ const relativeState = path.relative(workspaceRoot, runtimeStatePath);
3583
+ if (
3584
+ !relativeState ||
3585
+ path.isAbsolute(relativeState) ||
3586
+ relativeState === ".." ||
3587
+ relativeState.startsWith(`..${path.sep}`)
3588
+ ) {
3589
+ return exclusions;
3590
+ }
3591
+ const relativeDirectory = path.dirname(relativeState);
3592
+ const exclusion =
3593
+ relativeDirectory === "." ? relativeState : relativeDirectory;
3594
+ return [...new Set([...exclusions, exclusion.replaceAll("\\", "/")])];
3595
+ }
3596
+
1882
3597
  export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
1883
3598
  const statePath = path.resolve(options.statePath);
1884
3599
  const runId = safeId(options.runId, "runId");
@@ -1894,6 +3609,9 @@ export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
1894
3609
  runId,
1895
3610
  deps.now,
1896
3611
  );
3612
+ const artifactStore = deps.artifactStore || new ArtifactStore();
3613
+ const checkpointStore =
3614
+ deps.checkpointStore || new WorkspaceTransactionManager();
1897
3615
  const durableRunTask = async (args) => {
1898
3616
  const requested = await requestEffectForDispatch(args);
1899
3617
  if (requested.control) {
@@ -1975,10 +3693,18 @@ export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
1975
3693
  runId,
1976
3694
  effect.id,
1977
3695
  deps.now,
3696
+ artifactStore,
3697
+ checkpointStore,
1978
3698
  );
1979
3699
  result = await deps.runTask({
1980
3700
  ...args,
1981
3701
  workflowEffectId: effect.id,
3702
+ managedCheckpoint: true,
3703
+ managedCheckpointExclusions: managedCheckpointRuntimeExclusions(
3704
+ args.cwd,
3705
+ statePath,
3706
+ args.managedCheckpointExclusions,
3707
+ ),
1982
3708
  ...durableCallObservers,
1983
3709
  });
1984
3710
  if (typeof deps.afterProvider === "function") {
@@ -2015,6 +3741,8 @@ export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
2015
3741
  ...execution,
2016
3742
  maxParallel: execution.runAdmission.maxParallel,
2017
3743
  runTask: durableRunTask,
3744
+ resolveInput: (request) =>
3745
+ resolveDurableWorkflowStageInput(statePath, runId, request, deps.now),
2018
3746
  });
2019
3747
  const completed = completeRun(statePath, runId, record, deps.now);
2020
3748
  if (completed.control) {
@@ -2026,6 +3754,35 @@ export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
2026
3754
  }
2027
3755
  return completed.record;
2028
3756
  } catch (error) {
3757
+ if (
3758
+ error?.code === DYNAMIC_WORKFLOW_RUNTIME_CONTROL_CODE &&
3759
+ error.reason === "needs-input"
3760
+ ) {
3761
+ const state = finalizeInputAfterSettlementBarrier(
3762
+ statePath,
3763
+ runId,
3764
+ deps.now,
3765
+ );
3766
+ const pendingEffect = state?.effects?.find(
3767
+ (effect) => effect.status === "pending",
3768
+ );
3769
+ if (pendingEffect) {
3770
+ throw controlError(
3771
+ `workflow effect ${pendingEffect.id} requires reconciliation before input can settle`,
3772
+ "reconciliation-required",
3773
+ state,
3774
+ pendingEffect,
3775
+ error,
3776
+ );
3777
+ }
3778
+ throw controlError(
3779
+ "dynamic workflow run needs input after its settlement barrier",
3780
+ "needs-input",
3781
+ state,
3782
+ null,
3783
+ error,
3784
+ );
3785
+ }
2029
3786
  if (
2030
3787
  error?.code === DYNAMIC_WORKFLOW_RUNTIME_CONTROL_CODE &&
2031
3788
  error.reason === "paused"
@@ -2095,6 +3852,9 @@ export function requestDurableWorkflowPause(
2095
3852
  if (TERMINAL_STATUSES.has(current.status)) {
2096
3853
  throw new Error(`cannot pause a ${current.status} workflow run`);
2097
3854
  }
3855
+ if (["input_requested", "needs_input"].includes(current.status)) {
3856
+ throw new Error(`cannot pause a ${current.status} workflow run`);
3857
+ }
2098
3858
  const immediate = ["ready", "paused", "failed"].includes(current.status);
2099
3859
  const state = transition(
2100
3860
  current,
@@ -2127,6 +3887,12 @@ export function requestDurableWorkflowStop(
2127
3887
  "run-stopped",
2128
3888
  {},
2129
3889
  (draft) => {
3890
+ const resolvedAt = isoNow(deps.now);
3891
+ draft.inputRequests = runtimeInputRequests(draft).map((request) =>
3892
+ request.status === "pending"
3893
+ ? { ...request, status: "cancelled", resolvedAt }
3894
+ : request,
3895
+ );
2130
3896
  draft.status = "stopped";
2131
3897
  draft.finalRecord = null;
2132
3898
  },
@@ -2148,6 +3914,17 @@ export function prepareDurableWorkflowResume(
2148
3914
  if (TERMINAL_STATUSES.has(current.status)) {
2149
3915
  throw new Error(`cannot resume a ${current.status} workflow run`);
2150
3916
  }
3917
+ const pendingInput = runtimeInputRequests(current).find(
3918
+ (request) => request.status === "pending",
3919
+ );
3920
+ if (pendingInput) {
3921
+ const error = new Error(
3922
+ `workflow input request ${pendingInput.id} must be answered before resume`,
3923
+ );
3924
+ error.code = "CC_DYNAMIC_WORKFLOW_INPUT_REQUIRED";
3925
+ error.pendingInputRequest = pendingInput;
3926
+ throw error;
3927
+ }
2151
3928
  const pending = current.effects.find(
2152
3929
  (effect) => effect.status === "pending" && !isKnownUndispatched(effect),
2153
3930
  );
@@ -2172,31 +3949,308 @@ export function prepareDurableWorkflowResume(
2172
3949
  });
2173
3950
  }
2174
3951
 
2175
- export function reconcileDurableWorkflowEffect(
2176
- statePath,
2177
- input = {},
2178
- deps = {},
2179
- ) {
2180
- const effectId = String(input.effectId || "")
3952
+ export function submitDurableWorkflowInput(statePath, input = {}, deps = {}) {
3953
+ const requestId = String(input.requestId || "")
2181
3954
  .trim()
2182
3955
  .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");
3956
+ if (!SHA256_RE.test(requestId)) {
3957
+ throw new TypeError("workflow input request id is invalid");
2191
3958
  }
2192
- const resultDigest = effectResultDigest(result);
2193
3959
  return withStateMutation(path.resolve(statePath), (current) => {
2194
3960
  if (!current)
2195
3961
  throw new Error("dynamic workflow runtime state was not found");
2196
3962
  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") {
3963
+ if (current.status !== "needs_input") {
3964
+ throw new Error(
3965
+ `cannot answer workflow input while run is ${current.status}`,
3966
+ );
3967
+ }
3968
+ if (current.effects.some((effect) => effect.status === "pending")) {
3969
+ throw new Error(
3970
+ "workflow input cannot be answered before the effect settlement barrier",
3971
+ );
3972
+ }
3973
+ const requests = runtimeInputRequests(current);
3974
+ const requestIndex = requests.findIndex(
3975
+ (request) => request.id === requestId,
3976
+ );
3977
+ const request = requests[requestIndex];
3978
+ if (!request || request.status !== "pending") {
3979
+ throw new Error("workflow input request is not pending");
3980
+ }
3981
+ const answer = normalizeStageInputAnswer(input.answer, request);
3982
+ const responseDigest = stageInputResponseDigest(requestId, answer);
3983
+ const resolvedAt = isoNow(deps.now);
3984
+ const state = transition(
3985
+ current,
3986
+ "input-answered",
3987
+ { requestId, stepId: request.stepId, responseDigest },
3988
+ (draft) => {
3989
+ draft.inputRequests = [...runtimeInputRequests(draft)];
3990
+ draft.inputRequests[requestIndex] = {
3991
+ ...draft.inputRequests[requestIndex],
3992
+ status: "answered",
3993
+ resolvedAt,
3994
+ responseDigest,
3995
+ response: answer,
3996
+ };
3997
+ draft.status = "ready";
3998
+ },
3999
+ deps.now,
4000
+ );
4001
+ return { state, value: state };
4002
+ });
4003
+ }
4004
+
4005
+ function checkpointReadbackFromPreparedBinding(binding, checkpointStore) {
4006
+ if (!validCheckpointBinding(binding)) {
4007
+ throw new TypeError("workflow checkpoint prepared binding is malformed");
4008
+ }
4009
+ if (!checkpointStore || typeof checkpointStore.inspect !== "function") {
4010
+ throw new TypeError("workflow checkpoint store readback is unavailable");
4011
+ }
4012
+ let stored;
4013
+ try {
4014
+ stored = checkpointStore.inspect(binding.transactionId);
4015
+ } catch {
4016
+ throw new TypeError("workflow checkpoint store readback failed");
4017
+ }
4018
+ if (
4019
+ ![
4020
+ WORKSPACE_TRANSACTION_STATE.COMMITTED,
4021
+ WORKSPACE_TRANSACTION_STATE.ROLLED_BACK,
4022
+ ].includes(stored?.state)
4023
+ ) {
4024
+ return null;
4025
+ }
4026
+ const evidence = checkpointEvidenceSnapshot(stored.evidence);
4027
+ if (!validCheckpointEvidence(evidence)) {
4028
+ throw new TypeError("workflow checkpoint terminal evidence is malformed");
4029
+ }
4030
+ return checkpointStoreReadback(evidence, checkpointStore, binding);
4031
+ }
4032
+
4033
+ export function recoverDurableWorkflowCheckpointCall(
4034
+ statePath,
4035
+ input = {},
4036
+ deps = {},
4037
+ ) {
4038
+ const callRecordId = String(input.callRecordId || "")
4039
+ .trim()
4040
+ .toLowerCase();
4041
+ if (!SHA256_RE.test(callRecordId)) {
4042
+ throw new TypeError("callRecordId is invalid");
4043
+ }
4044
+ const checkpointStore =
4045
+ deps.checkpointStore || new WorkspaceTransactionManager();
4046
+ return withStateMutation(path.resolve(statePath), (current) => {
4047
+ if (!current) {
4048
+ throw new Error("dynamic workflow runtime state was not found");
4049
+ }
4050
+ requireRevision(current, input.expectedRevision);
4051
+ let effectIndex = -1;
4052
+ let callIndex = -1;
4053
+ for (const [candidateEffectIndex, effect] of current.effects.entries()) {
4054
+ const candidateCallIndex = (effect.calls || []).findIndex(
4055
+ (call) => call.id === callRecordId,
4056
+ );
4057
+ if (candidateCallIndex >= 0) {
4058
+ effectIndex = candidateEffectIndex;
4059
+ callIndex = candidateCallIndex;
4060
+ break;
4061
+ }
4062
+ }
4063
+ const effect = current.effects[effectIndex];
4064
+ const call = effect?.calls?.[callIndex];
4065
+ if (
4066
+ !effect ||
4067
+ effect.status !== "pending" ||
4068
+ !call ||
4069
+ call.kind !== "tool" ||
4070
+ call.status !== "started"
4071
+ ) {
4072
+ throw new Error(
4073
+ "workflow checkpoint call is not pending terminal recovery",
4074
+ );
4075
+ }
4076
+ if (
4077
+ Object.prototype.hasOwnProperty.call(call, "artifactReadback") ||
4078
+ !Object.prototype.hasOwnProperty.call(call, "checkpointBinding") ||
4079
+ !validCheckpointBinding(call.checkpointBinding)
4080
+ ) {
4081
+ throw new Error(
4082
+ "workflow checkpoint call has no recoverable prepared binding",
4083
+ );
4084
+ }
4085
+ const checkpointReadback = checkpointReadbackFromPreparedBinding(
4086
+ call.checkpointBinding,
4087
+ checkpointStore,
4088
+ );
4089
+ if (checkpointReadback === null) {
4090
+ const error = new Error(
4091
+ "workflow checkpoint transaction has not reached a recoverable terminal state",
4092
+ );
4093
+ error.code = "CC_DYNAMIC_WORKFLOW_CHECKPOINT_NOT_TERMINAL";
4094
+ throw error;
4095
+ }
4096
+ const committed = checkpointReadback.outcome === "committed";
4097
+ const settledAt = isoNow(deps.now);
4098
+ const settlementCode = committed
4099
+ ? "checkpoint_store_recovered_commit"
4100
+ : "checkpoint_store_recovered_rollback";
4101
+ const state = transition(
4102
+ current,
4103
+ "effect-call-settled",
4104
+ {
4105
+ effectId: effect.id,
4106
+ callRecordId: call.id,
4107
+ kind: "tool",
4108
+ status: committed ? "completed" : "failed",
4109
+ checkpointReadbackDigest: checkpointReadbackDigest(checkpointReadback),
4110
+ checkpointRecovery: "terminal-store",
4111
+ },
4112
+ (draft) => {
4113
+ const calls = [...(draft.effects[effectIndex].calls || [])];
4114
+ calls[callIndex] = {
4115
+ ...calls[callIndex],
4116
+ status: committed ? "completed" : "failed",
4117
+ settledAt,
4118
+ outcomeUnknown: false,
4119
+ settlementCode,
4120
+ checkpointReadback,
4121
+ };
4122
+ draft.effects[effectIndex] = {
4123
+ ...draft.effects[effectIndex],
4124
+ calls,
4125
+ };
4126
+ },
4127
+ deps.now,
4128
+ );
4129
+ return { state, value: state };
4130
+ });
4131
+ }
4132
+
4133
+ export function recoverDurableWorkflowCheckpointCalls(
4134
+ statePath,
4135
+ input = {},
4136
+ deps = {},
4137
+ ) {
4138
+ const checkpointStore =
4139
+ deps.checkpointStore || new WorkspaceTransactionManager();
4140
+ return withStateMutation(path.resolve(statePath), (current) => {
4141
+ if (!current) {
4142
+ throw new Error("dynamic workflow runtime state was not found");
4143
+ }
4144
+ requireRevision(current, input.expectedRevision);
4145
+ const recoveries = [];
4146
+ for (const [effectIndex, effect] of current.effects.entries()) {
4147
+ if (effect.status !== "pending") continue;
4148
+ for (const [callIndex, call] of (effect.calls || []).entries()) {
4149
+ if (
4150
+ call.kind !== "tool" ||
4151
+ call.status !== "started" ||
4152
+ Object.prototype.hasOwnProperty.call(call, "artifactReadback") ||
4153
+ !Object.prototype.hasOwnProperty.call(call, "checkpointBinding") ||
4154
+ !validCheckpointBinding(call.checkpointBinding)
4155
+ ) {
4156
+ continue;
4157
+ }
4158
+ const checkpointReadback = checkpointReadbackFromPreparedBinding(
4159
+ call.checkpointBinding,
4160
+ checkpointStore,
4161
+ );
4162
+ if (checkpointReadback === null) continue;
4163
+ const committed = checkpointReadback.outcome === "committed";
4164
+ recoveries.push({
4165
+ effectIndex,
4166
+ callIndex,
4167
+ effectId: effect.id,
4168
+ callRecordId: call.id,
4169
+ committed,
4170
+ checkpointReadback,
4171
+ settlementCode: committed
4172
+ ? "checkpoint_store_recovered_commit"
4173
+ : "checkpoint_store_recovered_rollback",
4174
+ });
4175
+ }
4176
+ }
4177
+ if (recoveries.length === 0) {
4178
+ const error = new Error(
4179
+ "no workflow checkpoint calls have a recoverable terminal store state",
4180
+ );
4181
+ error.code = "CC_DYNAMIC_WORKFLOW_CHECKPOINT_BATCH_EMPTY";
4182
+ throw error;
4183
+ }
4184
+ if (current.lineage.length + recoveries.length > MAX_LINEAGE_EVENTS) {
4185
+ throw new Error(
4186
+ "dynamic workflow runtime lineage limit prevents batch recovery",
4187
+ );
4188
+ }
4189
+ const settledAt = isoNow(deps.now);
4190
+ const draft = snapshotJson(
4191
+ stateMaterial(current),
4192
+ "workflow checkpoint batch recovery",
4193
+ );
4194
+ for (const recovery of recoveries) {
4195
+ const calls = [...(draft.effects[recovery.effectIndex].calls || [])];
4196
+ calls[recovery.callIndex] = {
4197
+ ...calls[recovery.callIndex],
4198
+ status: recovery.committed ? "completed" : "failed",
4199
+ settledAt,
4200
+ outcomeUnknown: false,
4201
+ settlementCode: recovery.settlementCode,
4202
+ checkpointReadback: recovery.checkpointReadback,
4203
+ };
4204
+ draft.effects[recovery.effectIndex] = {
4205
+ ...draft.effects[recovery.effectIndex],
4206
+ calls,
4207
+ };
4208
+ appendLineage(
4209
+ draft,
4210
+ "effect-call-settled",
4211
+ {
4212
+ effectId: recovery.effectId,
4213
+ callRecordId: recovery.callRecordId,
4214
+ kind: "tool",
4215
+ status: recovery.committed ? "completed" : "failed",
4216
+ checkpointReadbackDigest: checkpointReadbackDigest(
4217
+ recovery.checkpointReadback,
4218
+ ),
4219
+ checkpointRecovery: "terminal-store-batch",
4220
+ },
4221
+ settledAt,
4222
+ );
4223
+ }
4224
+ const state = finalizeState(draft);
4225
+ return { state, value: state };
4226
+ });
4227
+ }
4228
+
4229
+ export function reconcileDurableWorkflowEffect(
4230
+ statePath,
4231
+ input = {},
4232
+ deps = {},
4233
+ ) {
4234
+ const effectId = String(input.effectId || "")
4235
+ .trim()
4236
+ .toLowerCase();
4237
+ if (!SHA256_RE.test(effectId)) throw new TypeError("effectId is invalid");
4238
+ const result = snapshotJson(
4239
+ input.result,
4240
+ "workflow reconciled effect result",
4241
+ 1024 * 1024,
4242
+ );
4243
+ if (containsSecret(JSON.stringify(result))) {
4244
+ throw new Error("workflow reconciled result contains secret-shaped data");
4245
+ }
4246
+ const resultDigest = effectResultDigest(result);
4247
+ return withStateMutation(path.resolve(statePath), (current) => {
4248
+ if (!current)
4249
+ throw new Error("dynamic workflow runtime state was not found");
4250
+ requireRevision(current, input.expectedRevision);
4251
+ const index = current.effects.findIndex((effect) => effect.id === effectId);
4252
+ const effect = current.effects[index];
4253
+ if (!effect || effect.status !== "pending") {
2200
4254
  throw new Error("workflow effect is not pending reconciliation");
2201
4255
  }
2202
4256
  if (isKnownUndispatched(effect)) {
@@ -2286,7 +4340,7 @@ function projectResultValues(effect, field, domain) {
2286
4340
  };
2287
4341
  }
2288
4342
 
2289
- function projectProviderRequestReceipts(effect) {
4343
+ function projectLegacyProviderRequestReceipts(effect) {
2290
4344
  const attemptValues = Array.isArray(effect.result?.providerRequestAttempts)
2291
4345
  ? effect.result.providerRequestAttempts
2292
4346
  : [];
@@ -2408,6 +4462,185 @@ function projectProviderRequestReceipts(effect) {
2408
4462
  };
2409
4463
  }
2410
4464
 
4465
+ function projectProviderRequestReceipts(state) {
4466
+ const attemptLineage = [];
4467
+ const receiptLineage = [];
4468
+ const requestAttemptEffectIds = new Set();
4469
+ const receiptEffectIds = new Set();
4470
+ const providerReturnedReceiptEffectIds = new Set();
4471
+ let attemptCount = 0;
4472
+ let receiptCount = 0;
4473
+ let invalidAttempts = 0;
4474
+ let invalidReceipts = 0;
4475
+ let missingReceipts = 0;
4476
+ let truncatedEffects = 0;
4477
+ let durableCallEffects = 0;
4478
+ let legacyResultFallbackEffects = 0;
4479
+ let conflictingOuterResultEffects = 0;
4480
+
4481
+ for (const effect of state.effects) {
4482
+ const providerCalls = (effect.calls || []).filter(
4483
+ (call) => call.kind === "provider",
4484
+ );
4485
+ if (providerCalls.length > 0) {
4486
+ durableCallEffects += 1;
4487
+ attemptCount += providerCalls.length;
4488
+ receiptCount += providerCalls.filter(
4489
+ (call) => call.providerReceiptPersisted,
4490
+ ).length;
4491
+ missingReceipts += providerCalls.filter(
4492
+ (call) => !call.providerReceiptPersisted,
4493
+ ).length;
4494
+ requestAttemptEffectIds.add(effect.id);
4495
+ if (providerCalls.some((call) => call.providerReceiptPersisted)) {
4496
+ receiptEffectIds.add(effect.id);
4497
+ if (effect.settlementAuthority === "provider-return") {
4498
+ providerReturnedReceiptEffectIds.add(effect.id);
4499
+ }
4500
+ }
4501
+ const hasOuterResultEvidence =
4502
+ Array.isArray(effect.result?.providerRequestAttempts) ||
4503
+ Array.isArray(effect.result?.providerRequestReceipts);
4504
+ if (hasOuterResultEvidence) {
4505
+ const reported = projectLegacyProviderRequestReceipts(effect);
4506
+ const directCalls = providerCalls.filter(
4507
+ (call) => call.ownerEffectId === effect.id,
4508
+ );
4509
+ const attemptMatches =
4510
+ reported.invalidAttempts === 0 &&
4511
+ reported.attemptLineage.length === directCalls.length &&
4512
+ directCalls.every((call) => {
4513
+ const attempt = reported.attemptLineage.find(
4514
+ (candidate) => candidate.callId === call.callId,
4515
+ );
4516
+ return (
4517
+ attempt?.provider === call.name &&
4518
+ attempt?.callSequence === call.sequence &&
4519
+ attempt?.source === call.requestSource &&
4520
+ attempt?.clientRequestId === call.clientRequestId
4521
+ );
4522
+ });
4523
+ const directReceiptCalls = directCalls.filter(
4524
+ (call) => call.providerReceiptPersisted,
4525
+ );
4526
+ const receiptMatches =
4527
+ reported.invalidReceipts === 0 &&
4528
+ reported.receiptLineage.length === directReceiptCalls.length &&
4529
+ directReceiptCalls.every((call) => {
4530
+ const receipt = reported.receiptLineage.find(
4531
+ (candidate) => candidate.callId === call.callId,
4532
+ );
4533
+ return (
4534
+ receipt?.requestId === call.providerReceiptRequestId &&
4535
+ receipt?.responseId === call.providerReceiptResponseId
4536
+ );
4537
+ });
4538
+ if (!attemptMatches || !receiptMatches) {
4539
+ conflictingOuterResultEffects += 1;
4540
+ }
4541
+ }
4542
+ const visibleCalls = providerCalls.slice(
4543
+ 0,
4544
+ MAX_PROJECTED_ARTIFACTS_PER_EFFECT,
4545
+ );
4546
+ if (providerCalls.length > visibleCalls.length) truncatedEffects += 1;
4547
+ for (let ordinal = 0; ordinal < visibleCalls.length; ordinal += 1) {
4548
+ const call = visibleCalls[ordinal];
4549
+ const attempt = {
4550
+ effectId: effect.id,
4551
+ ownerEffectId: call.ownerEffectId,
4552
+ callRecordId: call.id,
4553
+ ordinal,
4554
+ provider: call.name,
4555
+ callId: call.callId,
4556
+ callSequence: call.sequence,
4557
+ source: call.requestSource,
4558
+ attributionSource: call.source,
4559
+ clientRequestId: call.clientRequestId,
4560
+ status: call.status,
4561
+ startedAt: call.startedAt,
4562
+ requestIdentitySemantics: "trace-only",
4563
+ authoritySource: "durable-call-store",
4564
+ };
4565
+ attemptLineage.push(attempt);
4566
+ if (!call.providerReceiptPersisted) continue;
4567
+ receiptLineage.push({
4568
+ ...attempt,
4569
+ requestId: call.providerReceiptRequestId,
4570
+ responseId: call.providerReceiptResponseId,
4571
+ recordedAt:
4572
+ call.providerReceiptRecordedAt === undefined
4573
+ ? call.settledAt
4574
+ : call.providerReceiptRecordedAt,
4575
+ independentlyReadable: false,
4576
+ });
4577
+ }
4578
+ continue;
4579
+ }
4580
+
4581
+ if (effect.status !== "settled") continue;
4582
+ const hasOuterResultEvidence =
4583
+ Array.isArray(effect.result?.providerRequestAttempts) ||
4584
+ Array.isArray(effect.result?.providerRequestReceipts);
4585
+ if (effect.calls !== undefined) {
4586
+ if (
4587
+ hasOuterResultEvidence &&
4588
+ ((effect.result?.providerRequestAttempts?.length || 0) > 0 ||
4589
+ (effect.result?.providerRequestReceipts?.length || 0) > 0)
4590
+ ) {
4591
+ conflictingOuterResultEffects += 1;
4592
+ }
4593
+ continue;
4594
+ }
4595
+ const legacy = projectLegacyProviderRequestReceipts(effect);
4596
+ if (legacy.attemptCount === 0 && legacy.receiptCount === 0) continue;
4597
+ legacyResultFallbackEffects += 1;
4598
+ attemptCount += legacy.attemptCount;
4599
+ receiptCount += legacy.receiptCount;
4600
+ invalidAttempts += legacy.invalidAttempts;
4601
+ invalidReceipts += legacy.invalidReceipts;
4602
+ missingReceipts += legacy.missingReceipts;
4603
+ if (legacy.truncated) truncatedEffects += 1;
4604
+ if (legacy.attemptLineage.length > 0) {
4605
+ requestAttemptEffectIds.add(effect.id);
4606
+ }
4607
+ if (legacy.receiptLineage.length > 0) {
4608
+ receiptEffectIds.add(effect.id);
4609
+ providerReturnedReceiptEffectIds.add(effect.id);
4610
+ }
4611
+ attemptLineage.push(
4612
+ ...legacy.attemptLineage.map((attempt) => ({
4613
+ ...attempt,
4614
+ authoritySource: "legacy-task-result",
4615
+ })),
4616
+ );
4617
+ receiptLineage.push(
4618
+ ...legacy.receiptLineage.map((receipt) => ({
4619
+ ...receipt,
4620
+ recordedAt: effect.settledAt,
4621
+ authoritySource: "legacy-task-result",
4622
+ })),
4623
+ );
4624
+ }
4625
+
4626
+ return {
4627
+ attemptCount,
4628
+ receiptCount,
4629
+ invalidAttempts,
4630
+ invalidReceipts,
4631
+ missingReceipts,
4632
+ truncatedEffects,
4633
+ durableCallEffects,
4634
+ legacyResultFallbackEffects,
4635
+ conflictingOuterResultEffects,
4636
+ requestAttemptEffects: requestAttemptEffectIds.size,
4637
+ receiptEffects: receiptEffectIds.size,
4638
+ providerReturnedReceiptEffects: providerReturnedReceiptEffectIds.size,
4639
+ attemptLineage,
4640
+ receiptLineage,
4641
+ };
4642
+ }
4643
+
2411
4644
  function expectedNestedToolEffectId(effectId, sequence, toolUseId, tool) {
2412
4645
  return `sha256:${createHash("sha256")
2413
4646
  .update(
@@ -2417,7 +4650,7 @@ function expectedNestedToolEffectId(effectId, sequence, toolUseId, tool) {
2417
4650
  .digest("hex")}`;
2418
4651
  }
2419
4652
 
2420
- function projectNestedToolEffects(effect) {
4653
+ function projectLegacyNestedToolEffects(effect) {
2421
4654
  const attemptValues = Array.isArray(effect.result?.nestedEffectAttempts)
2422
4655
  ? effect.result.nestedEffectAttempts
2423
4656
  : [];
@@ -2565,6 +4798,177 @@ function projectNestedToolEffects(effect) {
2565
4798
  };
2566
4799
  }
2567
4800
 
4801
+ function projectNestedToolEffects(state) {
4802
+ const attemptLineage = [];
4803
+ const settlementLineage = [];
4804
+ let attemptCount = 0;
4805
+ let settlementCount = 0;
4806
+ let invalidAttempts = 0;
4807
+ let invalidSettlements = 0;
4808
+ let missingSettlements = 0;
4809
+ let durableMcpSettlements = 0;
4810
+ let truncatedEffects = 0;
4811
+ let durableCallEffects = 0;
4812
+ let legacyResultFallbackEffects = 0;
4813
+ let conflictingOuterResultEffects = 0;
4814
+
4815
+ for (const effect of state.effects) {
4816
+ const toolCalls = (effect.calls || []).filter(
4817
+ (call) => call.kind === "tool",
4818
+ );
4819
+ if (toolCalls.length > 0) {
4820
+ durableCallEffects += 1;
4821
+ attemptCount += toolCalls.length;
4822
+ settlementCount += toolCalls.filter(
4823
+ (call) => call.status !== "started",
4824
+ ).length;
4825
+ missingSettlements += toolCalls.filter(
4826
+ (call) => call.status === "started",
4827
+ ).length;
4828
+ durableMcpSettlements += toolCalls.filter(
4829
+ (call) =>
4830
+ call.mcpLedgerId &&
4831
+ call.mcpLedgerPrewritePersisted &&
4832
+ call.mcpLedgerSettlementPersisted,
4833
+ ).length;
4834
+
4835
+ const hasOuterResultEvidence =
4836
+ Array.isArray(effect.result?.nestedEffectAttempts) ||
4837
+ Array.isArray(effect.result?.nestedEffectSettlements);
4838
+ if (hasOuterResultEvidence) {
4839
+ const reported = projectLegacyNestedToolEffects(effect);
4840
+ const directCalls = toolCalls.filter(
4841
+ (call) => call.ownerEffectId === effect.id,
4842
+ );
4843
+ const attemptMatches =
4844
+ reported.invalidAttempts === 0 &&
4845
+ reported.attemptLineage.length === directCalls.length &&
4846
+ directCalls.every((call) => {
4847
+ const attempt = reported.attemptLineage.find(
4848
+ (candidate) => candidate.childEffectId === call.childEffectId,
4849
+ );
4850
+ return (
4851
+ attempt?.childSequence === call.sequence &&
4852
+ attempt?.tool === call.name &&
4853
+ attempt?.toolUseId === call.callId
4854
+ );
4855
+ });
4856
+ const directTerminalCalls = directCalls.filter(
4857
+ (call) => call.status !== "started",
4858
+ );
4859
+ const settlementMatches =
4860
+ reported.invalidSettlements === 0 &&
4861
+ reported.settlementLineage.length === directTerminalCalls.length &&
4862
+ directTerminalCalls.every((call) => {
4863
+ const settlement = reported.settlementLineage.find(
4864
+ (candidate) => candidate.childEffectId === call.childEffectId,
4865
+ );
4866
+ return (
4867
+ settlement?.status === call.status &&
4868
+ settlement?.mcpLedgerId === call.mcpLedgerId &&
4869
+ settlement?.mcpLedgerPrewritePersisted ===
4870
+ call.mcpLedgerPrewritePersisted &&
4871
+ settlement?.mcpLedgerSettlementPersisted ===
4872
+ call.mcpLedgerSettlementPersisted
4873
+ );
4874
+ });
4875
+ if (!attemptMatches || !settlementMatches) {
4876
+ conflictingOuterResultEffects += 1;
4877
+ }
4878
+ }
4879
+
4880
+ const visibleCalls = toolCalls.slice(
4881
+ 0,
4882
+ MAX_PROJECTED_ARTIFACTS_PER_EFFECT,
4883
+ );
4884
+ if (toolCalls.length > visibleCalls.length) truncatedEffects += 1;
4885
+ for (let ordinal = 0; ordinal < visibleCalls.length; ordinal += 1) {
4886
+ const call = visibleCalls[ordinal];
4887
+ const attempt = {
4888
+ effectId: effect.id,
4889
+ ownerEffectId: call.ownerEffectId,
4890
+ callRecordId: call.id,
4891
+ ordinal,
4892
+ childEffectId: call.childEffectId,
4893
+ childSequence: call.sequence,
4894
+ kind: "tool",
4895
+ tool: call.name,
4896
+ toolUseId: call.callId,
4897
+ status: call.status,
4898
+ startedAt: call.startedAt,
4899
+ identitySemantics: "runtime-derived",
4900
+ authoritySource: "durable-call-store",
4901
+ };
4902
+ attemptLineage.push(attempt);
4903
+ if (call.status === "started") continue;
4904
+ settlementLineage.push({
4905
+ ...attempt,
4906
+ settledAt: call.settledAt,
4907
+ outcomeUnknown: call.outcomeUnknown,
4908
+ settlementCode: call.settlementCode,
4909
+ mcpLedgerId: call.mcpLedgerId,
4910
+ mcpLedgerPrewritePersisted: call.mcpLedgerPrewritePersisted,
4911
+ mcpLedgerSettlementPersisted: call.mcpLedgerSettlementPersisted,
4912
+ });
4913
+ }
4914
+ continue;
4915
+ }
4916
+
4917
+ if (effect.status !== "settled") continue;
4918
+ const hasOuterResultEvidence =
4919
+ Array.isArray(effect.result?.nestedEffectAttempts) ||
4920
+ Array.isArray(effect.result?.nestedEffectSettlements);
4921
+ if (effect.calls !== undefined) {
4922
+ if (
4923
+ hasOuterResultEvidence &&
4924
+ ((effect.result?.nestedEffectAttempts?.length || 0) > 0 ||
4925
+ (effect.result?.nestedEffectSettlements?.length || 0) > 0)
4926
+ ) {
4927
+ conflictingOuterResultEffects += 1;
4928
+ }
4929
+ continue;
4930
+ }
4931
+ const legacy = projectLegacyNestedToolEffects(effect);
4932
+ if (legacy.attemptCount === 0 && legacy.settlementCount === 0) continue;
4933
+ legacyResultFallbackEffects += 1;
4934
+ attemptCount += legacy.attemptCount;
4935
+ settlementCount += legacy.settlementCount;
4936
+ invalidAttempts += legacy.invalidAttempts;
4937
+ invalidSettlements += legacy.invalidSettlements;
4938
+ missingSettlements += legacy.missingSettlements;
4939
+ durableMcpSettlements += legacy.durableMcpSettlements;
4940
+ if (legacy.truncated) truncatedEffects += 1;
4941
+ attemptLineage.push(
4942
+ ...legacy.attemptLineage.map((attempt) => ({
4943
+ ...attempt,
4944
+ authoritySource: "legacy-task-result",
4945
+ })),
4946
+ );
4947
+ settlementLineage.push(
4948
+ ...legacy.settlementLineage.map((settlement) => ({
4949
+ ...settlement,
4950
+ settledAt: effect.settledAt,
4951
+ authoritySource: "legacy-task-result",
4952
+ })),
4953
+ );
4954
+ }
4955
+
4956
+ return {
4957
+ attemptCount,
4958
+ settlementCount,
4959
+ invalidAttempts,
4960
+ invalidSettlements,
4961
+ missingSettlements,
4962
+ durableMcpSettlements,
4963
+ truncatedEffects,
4964
+ durableCallEffects,
4965
+ legacyResultFallbackEffects,
4966
+ conflictingOuterResultEffects,
4967
+ attemptLineage,
4968
+ settlementLineage,
4969
+ };
4970
+ }
4971
+
2568
4972
  function projectDurableWorkflowCalls(state) {
2569
4973
  const lineage = state.effects.flatMap((effect) =>
2570
4974
  (effect.calls || []).map((call) => ({
@@ -2590,10 +4994,73 @@ function projectDurableWorkflowCalls(state) {
2590
4994
  protocol: "cc-provider-request-receipt/v1",
2591
4995
  requestId: call.providerReceiptRequestId,
2592
4996
  responseId: call.providerReceiptResponseId,
4997
+ recordedAt:
4998
+ call.providerReceiptRecordedAt === undefined
4999
+ ? call.settledAt
5000
+ : call.providerReceiptRecordedAt,
2593
5001
  requestIdentitySemantics: "trace-only",
2594
5002
  independentlyReadable: false,
2595
5003
  }
2596
5004
  : null,
5005
+ providerUsage:
5006
+ call.kind === "provider" && validProviderUsage(call.providerUsage)
5007
+ ? {
5008
+ ...call.providerUsage,
5009
+ recordedAt: call.settledAt,
5010
+ }
5011
+ : null,
5012
+ providerUsageSchemaPresent:
5013
+ call.kind === "provider" &&
5014
+ Object.prototype.hasOwnProperty.call(call, "providerUsage"),
5015
+ providerModel:
5016
+ call.kind === "provider" &&
5017
+ Object.prototype.hasOwnProperty.call(call, "providerModel")
5018
+ ? call.providerModel
5019
+ : null,
5020
+ providerPricing:
5021
+ call.kind === "provider" &&
5022
+ validProviderPricing(
5023
+ call.providerPricing,
5024
+ call.name,
5025
+ call.providerModel,
5026
+ )
5027
+ ? call.providerPricing
5028
+ : null,
5029
+ providerCostEstimate:
5030
+ call.kind === "provider" &&
5031
+ validProviderCostEstimate(
5032
+ call.providerCostEstimate,
5033
+ call.providerPricing,
5034
+ call.providerUsage,
5035
+ )
5036
+ ? call.providerCostEstimate
5037
+ : null,
5038
+ providerCostSchemaPresent:
5039
+ call.kind === "provider" &&
5040
+ ["providerModel", "providerPricing", "providerCostEstimate"].every(
5041
+ (field) => Object.prototype.hasOwnProperty.call(call, field),
5042
+ ),
5043
+ artifactReadback:
5044
+ call.kind === "tool" && validArtifactReadback(call.artifactReadback)
5045
+ ? call.artifactReadback
5046
+ : null,
5047
+ artifactReadbackSchemaPresent:
5048
+ call.kind === "tool" &&
5049
+ Object.prototype.hasOwnProperty.call(call, "artifactReadback"),
5050
+ checkpointReadback:
5051
+ call.kind === "tool" && validCheckpointReadback(call.checkpointReadback)
5052
+ ? call.checkpointReadback
5053
+ : null,
5054
+ checkpointReadbackSchemaPresent:
5055
+ call.kind === "tool" &&
5056
+ Object.prototype.hasOwnProperty.call(call, "checkpointReadback"),
5057
+ checkpointBinding:
5058
+ call.kind === "tool" && validCheckpointBinding(call.checkpointBinding)
5059
+ ? call.checkpointBinding
5060
+ : null,
5061
+ checkpointBindingSchemaPresent:
5062
+ call.kind === "tool" &&
5063
+ Object.prototype.hasOwnProperty.call(call, "checkpointBinding"),
2597
5064
  mcpLedgerId: call.mcpLedgerId,
2598
5065
  mcpLedgerPrewritePersisted: call.mcpLedgerPrewritePersisted,
2599
5066
  mcpLedgerSettlementPersisted: call.mcpLedgerSettlementPersisted,
@@ -2613,6 +5080,20 @@ function projectDurableWorkflowCalls(state) {
2613
5080
  descendants: lineage.filter((call) => call.descendant).length,
2614
5081
  providerReceipts: lineage.filter((call) => call.providerReceipt !== null)
2615
5082
  .length,
5083
+ providerUsageRecords: lineage.filter((call) => call.providerUsage !== null)
5084
+ .length,
5085
+ providerCostEstimateRecords: lineage.filter(
5086
+ (call) => call.providerCostEstimate !== null,
5087
+ ).length,
5088
+ artifactReadbackRecords: lineage.filter(
5089
+ (call) => call.artifactReadback !== null,
5090
+ ).length,
5091
+ checkpointReadbackRecords: lineage.filter(
5092
+ (call) => call.checkpointReadback !== null,
5093
+ ).length,
5094
+ checkpointBindingRecords: lineage.filter(
5095
+ (call) => call.checkpointBinding !== null,
5096
+ ).length,
2616
5097
  providerNativeIdempotencyProven: false,
2617
5098
  providerReceiptsIndependentlyReadable: false,
2618
5099
  lineageDigest: digest(
@@ -2623,6 +5104,371 @@ function projectDurableWorkflowCalls(state) {
2623
5104
  };
2624
5105
  }
2625
5106
 
5107
+ function projectProviderTokenUsage(state) {
5108
+ const totals = {
5109
+ inputTokens: 0,
5110
+ outputTokens: 0,
5111
+ cacheReadInputTokens: 0,
5112
+ cacheCreationInputTokens: 0,
5113
+ totalTokens: 0,
5114
+ };
5115
+ const lineage = [];
5116
+ const observedEffectIds = new Set();
5117
+ let providerCalls = 0;
5118
+ let pendingCalls = 0;
5119
+ let outcomeUnknownCalls = 0;
5120
+ let operatorReconciledCalls = 0;
5121
+ let legacyCalls = 0;
5122
+ for (const effect of state.effects) {
5123
+ for (const call of effect.calls || []) {
5124
+ if (call.kind !== "provider") continue;
5125
+ providerCalls += 1;
5126
+ if (call.status === "started") pendingCalls += 1;
5127
+ if (call.status === "outcome_unknown") outcomeUnknownCalls += 1;
5128
+ if (call.status === "operator_reconciled") operatorReconciledCalls += 1;
5129
+ if (!Object.prototype.hasOwnProperty.call(call, "providerUsage")) {
5130
+ legacyCalls += 1;
5131
+ continue;
5132
+ }
5133
+ if (!validProviderUsage(call.providerUsage)) continue;
5134
+ observedEffectIds.add(effect.id);
5135
+ totals.inputTokens += call.providerUsage.inputTokens;
5136
+ totals.outputTokens += call.providerUsage.outputTokens;
5137
+ totals.cacheReadInputTokens += call.providerUsage.cacheReadInputTokens;
5138
+ totals.cacheCreationInputTokens +=
5139
+ call.providerUsage.cacheCreationInputTokens;
5140
+ totals.totalTokens += call.providerUsage.totalTokens;
5141
+ lineage.push({
5142
+ effectId: effect.id,
5143
+ ownerEffectId: call.ownerEffectId ?? effect.id,
5144
+ callRecordId: call.id,
5145
+ callId: call.callId,
5146
+ sequence: call.sequence,
5147
+ provider: call.name,
5148
+ model: Object.prototype.hasOwnProperty.call(call, "providerModel")
5149
+ ? call.providerModel
5150
+ : null,
5151
+ source: call.source,
5152
+ requestSource: call.requestSource ?? call.source,
5153
+ descendant: (call.ownerEffectId ?? effect.id) !== effect.id,
5154
+ settledAt: call.settledAt,
5155
+ usage: call.providerUsage,
5156
+ usageDigest: providerUsageDigest(call.providerUsage),
5157
+ });
5158
+ }
5159
+ }
5160
+ return {
5161
+ authority:
5162
+ legacyCalls > 0
5163
+ ? "runtime-state-hash-chain-fsync-with-legacy-call-schema"
5164
+ : "runtime-state-hash-chain-fsync",
5165
+ crashVisible: true,
5166
+ providerCalls,
5167
+ providerReportedCalls: lineage.length,
5168
+ providerReportedEffects: observedEffectIds.size,
5169
+ missingProviderReportedCalls: providerCalls - lineage.length,
5170
+ pendingCalls,
5171
+ outcomeUnknownCalls,
5172
+ operatorReconciledCalls,
5173
+ legacyCalls,
5174
+ providerReported: lineage.length > 0 ? totals : null,
5175
+ lineageDigest: digest(
5176
+ "chainlesschain.dynamic-workflow.provider-token-usage-lineage.v1\0",
5177
+ lineage,
5178
+ ),
5179
+ lineage,
5180
+ };
5181
+ }
5182
+
5183
+ function projectProviderCostEstimates(state) {
5184
+ const lineage = [];
5185
+ const pricedEffectIds = new Set();
5186
+ let providerCalls = 0;
5187
+ let pricingSnapshotCalls = 0;
5188
+ let pendingCalls = 0;
5189
+ let outcomeUnknownCalls = 0;
5190
+ let operatorReconciledCalls = 0;
5191
+ let unpricedCalls = 0;
5192
+ let modelMissingCalls = 0;
5193
+ let legacyCalls = 0;
5194
+ let estimatedUsd = 0;
5195
+ for (const effect of state.effects) {
5196
+ for (const call of effect.calls || []) {
5197
+ if (call.kind !== "provider") continue;
5198
+ providerCalls += 1;
5199
+ if (call.status === "started") pendingCalls += 1;
5200
+ if (call.status === "outcome_unknown") outcomeUnknownCalls += 1;
5201
+ if (call.status === "operator_reconciled") operatorReconciledCalls += 1;
5202
+ const hasSchema = [
5203
+ "providerModel",
5204
+ "providerPricing",
5205
+ "providerCostEstimate",
5206
+ ].every((field) => Object.prototype.hasOwnProperty.call(call, field));
5207
+ if (!hasSchema) {
5208
+ legacyCalls += 1;
5209
+ continue;
5210
+ }
5211
+ if (call.providerModel === null) modelMissingCalls += 1;
5212
+ if (
5213
+ validProviderPricing(
5214
+ call.providerPricing,
5215
+ call.name,
5216
+ call.providerModel,
5217
+ )
5218
+ ) {
5219
+ pricingSnapshotCalls += 1;
5220
+ }
5221
+ if (
5222
+ call.status === "completed" &&
5223
+ validProviderUsage(call.providerUsage) &&
5224
+ call.providerPricing === null
5225
+ ) {
5226
+ unpricedCalls += 1;
5227
+ }
5228
+ if (
5229
+ !validProviderCostEstimate(
5230
+ call.providerCostEstimate,
5231
+ call.providerPricing,
5232
+ call.providerUsage,
5233
+ )
5234
+ ) {
5235
+ continue;
5236
+ }
5237
+ pricedEffectIds.add(effect.id);
5238
+ estimatedUsd += call.providerCostEstimate.totalUsd;
5239
+ lineage.push({
5240
+ effectId: effect.id,
5241
+ ownerEffectId: call.ownerEffectId ?? effect.id,
5242
+ callRecordId: call.id,
5243
+ callId: call.callId,
5244
+ sequence: call.sequence,
5245
+ provider: call.name,
5246
+ model: call.providerModel,
5247
+ source: call.source,
5248
+ requestSource: call.requestSource ?? call.source,
5249
+ descendant: (call.ownerEffectId ?? effect.id) !== effect.id,
5250
+ settledAt: call.settledAt,
5251
+ pricing: call.providerPricing,
5252
+ estimate: call.providerCostEstimate,
5253
+ estimateDigest: providerCostEstimateDigest(call.providerCostEstimate),
5254
+ });
5255
+ }
5256
+ }
5257
+ return {
5258
+ authority:
5259
+ legacyCalls > 0
5260
+ ? "durable-pricing-snapshot-estimate-with-legacy-call-schema"
5261
+ : "durable-pricing-snapshot-estimate",
5262
+ currency: "USD",
5263
+ reportedUsd: null,
5264
+ estimatedUsd: lineage.length > 0 ? estimatedUsd : null,
5265
+ providerCalls,
5266
+ pricingSnapshotCalls,
5267
+ pricedCalls: lineage.length,
5268
+ pricedEffects: pricedEffectIds.size,
5269
+ missingEstimateCalls: providerCalls - lineage.length,
5270
+ pendingCalls,
5271
+ outcomeUnknownCalls,
5272
+ operatorReconciledCalls,
5273
+ unpricedCalls,
5274
+ modelMissingCalls,
5275
+ legacyCalls,
5276
+ lineageDigest: digest(
5277
+ "chainlesschain.dynamic-workflow.provider-cost-estimate-lineage.v1\0",
5278
+ lineage,
5279
+ ),
5280
+ lineage,
5281
+ };
5282
+ }
5283
+
5284
+ function projectArtifactStoreReadbacks(state) {
5285
+ const lineage = [];
5286
+ const verifiedEffectIds = new Set();
5287
+ let artifactCalls = 0;
5288
+ let completedCalls = 0;
5289
+ let failedCalls = 0;
5290
+ let pendingCalls = 0;
5291
+ let outcomeUnknownCalls = 0;
5292
+ let operatorReconciledCalls = 0;
5293
+ let missingReadbacks = 0;
5294
+ let legacyCalls = 0;
5295
+ for (const effect of state.effects) {
5296
+ for (const call of effect.calls || []) {
5297
+ if (call.kind !== "tool" || call.name !== "publish_artifact") continue;
5298
+ artifactCalls += 1;
5299
+ if (call.status === "completed") completedCalls += 1;
5300
+ if (call.status === "failed") failedCalls += 1;
5301
+ if (call.status === "started") pendingCalls += 1;
5302
+ if (call.status === "outcome_unknown") outcomeUnknownCalls += 1;
5303
+ if (call.status === "operator_reconciled") operatorReconciledCalls += 1;
5304
+ if (!Object.prototype.hasOwnProperty.call(call, "artifactReadback")) {
5305
+ legacyCalls += 1;
5306
+ if (call.status !== "failed") missingReadbacks += 1;
5307
+ continue;
5308
+ }
5309
+ if (!validArtifactReadback(call.artifactReadback)) {
5310
+ if (call.status !== "failed") missingReadbacks += 1;
5311
+ continue;
5312
+ }
5313
+ verifiedEffectIds.add(effect.id);
5314
+ lineage.push({
5315
+ effectId: effect.id,
5316
+ ownerEffectId: call.ownerEffectId ?? effect.id,
5317
+ callRecordId: call.id,
5318
+ callId: call.callId,
5319
+ sequence: call.sequence,
5320
+ descendant: (call.ownerEffectId ?? effect.id) !== effect.id,
5321
+ settledAt: call.settledAt,
5322
+ readback: call.artifactReadback,
5323
+ readbackDigest: artifactReadbackDigest(call.artifactReadback),
5324
+ });
5325
+ }
5326
+ }
5327
+ return {
5328
+ authority:
5329
+ legacyCalls > 0
5330
+ ? "artifact-store-index-and-bytes-at-settlement-with-legacy-call-schema"
5331
+ : "artifact-store-index-and-bytes-at-settlement",
5332
+ verificationTiming: "tool-settlement",
5333
+ immutableRetentionProven: false,
5334
+ artifactCalls,
5335
+ completedCalls,
5336
+ failedCalls,
5337
+ verifiedCalls: lineage.length,
5338
+ verifiedEffects: verifiedEffectIds.size,
5339
+ missingReadbacks,
5340
+ pendingCalls,
5341
+ outcomeUnknownCalls,
5342
+ operatorReconciledCalls,
5343
+ legacyCalls,
5344
+ lineageDigest: digest(
5345
+ "chainlesschain.dynamic-workflow.artifact-store-readback-lineage.v1\0",
5346
+ lineage,
5347
+ ),
5348
+ lineage,
5349
+ };
5350
+ }
5351
+
5352
+ function projectCheckpointStoreReadbacks(state) {
5353
+ const lineage = [];
5354
+ const verifiedEffectIds = new Set();
5355
+ let toolCalls = 0;
5356
+ let completedCalls = 0;
5357
+ let failedCalls = 0;
5358
+ let pendingCalls = 0;
5359
+ let outcomeUnknownCalls = 0;
5360
+ let operatorReconciledCalls = 0;
5361
+ let committedCalls = 0;
5362
+ let rolledBackCalls = 0;
5363
+ let fullCoverageCalls = 0;
5364
+ let partialCoverageCalls = 0;
5365
+ let externalSideEffectCalls = 0;
5366
+ let preparedBindingCalls = 0;
5367
+ let terminalStoreRecoveredCalls = 0;
5368
+ let bindingLegacyCalls = 0;
5369
+ let missingReadbacks = 0;
5370
+ let legacyCalls = 0;
5371
+ for (const effect of state.effects) {
5372
+ for (const call of effect.calls || []) {
5373
+ if (call.kind !== "tool" || !managedToolCheckpointRequired(call.name)) {
5374
+ continue;
5375
+ }
5376
+ toolCalls += 1;
5377
+ if (call.status === "completed") completedCalls += 1;
5378
+ if (call.status === "failed") failedCalls += 1;
5379
+ if (call.status === "started") pendingCalls += 1;
5380
+ if (call.status === "outcome_unknown") outcomeUnknownCalls += 1;
5381
+ if (call.status === "operator_reconciled") operatorReconciledCalls += 1;
5382
+ if (!Object.prototype.hasOwnProperty.call(call, "checkpointBinding")) {
5383
+ bindingLegacyCalls += 1;
5384
+ }
5385
+ if (validCheckpointBinding(call.checkpointBinding)) {
5386
+ preparedBindingCalls += 1;
5387
+ }
5388
+ if (
5389
+ [
5390
+ "checkpoint_store_recovered_commit",
5391
+ "checkpoint_store_recovered_rollback",
5392
+ ].includes(call.settlementCode)
5393
+ ) {
5394
+ terminalStoreRecoveredCalls += 1;
5395
+ }
5396
+ if (!Object.prototype.hasOwnProperty.call(call, "checkpointReadback")) {
5397
+ legacyCalls += 1;
5398
+ missingReadbacks += 1;
5399
+ continue;
5400
+ }
5401
+ if (!validCheckpointReadback(call.checkpointReadback)) {
5402
+ missingReadbacks += 1;
5403
+ continue;
5404
+ }
5405
+ if (call.checkpointReadback.outcome === "committed") committedCalls += 1;
5406
+ if (call.checkpointReadback.outcome === "rolled_back") {
5407
+ rolledBackCalls += 1;
5408
+ }
5409
+ if (
5410
+ call.checkpointReadback.coverage === WORKSPACE_TRANSACTION_COVERAGE.FULL
5411
+ ) {
5412
+ fullCoverageCalls += 1;
5413
+ }
5414
+ if (
5415
+ call.checkpointReadback.coverage ===
5416
+ WORKSPACE_TRANSACTION_COVERAGE.PARTIAL
5417
+ ) {
5418
+ partialCoverageCalls += 1;
5419
+ }
5420
+ if (call.checkpointReadback.externalSideEffects) {
5421
+ externalSideEffectCalls += 1;
5422
+ }
5423
+ verifiedEffectIds.add(effect.id);
5424
+ lineage.push({
5425
+ effectId: effect.id,
5426
+ ownerEffectId: call.ownerEffectId ?? effect.id,
5427
+ callRecordId: call.id,
5428
+ callId: call.callId,
5429
+ sequence: call.sequence,
5430
+ tool: call.name,
5431
+ descendant: (call.ownerEffectId ?? effect.id) !== effect.id,
5432
+ settledAt: call.settledAt,
5433
+ readback: call.checkpointReadback,
5434
+ readbackDigest: checkpointReadbackDigest(call.checkpointReadback),
5435
+ });
5436
+ }
5437
+ }
5438
+ return {
5439
+ authority:
5440
+ legacyCalls > 0
5441
+ ? "workspace-transaction-store-terminal-readback-with-legacy-call-schema"
5442
+ : "workspace-transaction-store-terminal-readback",
5443
+ verificationTiming: "tool-settlement",
5444
+ rollbackScope: "workspace-files-only",
5445
+ externalSideEffectsRollbackProven: false,
5446
+ toolCalls,
5447
+ completedCalls,
5448
+ failedCalls,
5449
+ verifiedCalls: lineage.length,
5450
+ verifiedEffects: verifiedEffectIds.size,
5451
+ committedCalls,
5452
+ rolledBackCalls,
5453
+ fullCoverageCalls,
5454
+ partialCoverageCalls,
5455
+ externalSideEffectCalls,
5456
+ preparedBindingCalls,
5457
+ terminalStoreRecoveredCalls,
5458
+ bindingLegacyCalls,
5459
+ missingReadbacks,
5460
+ pendingCalls,
5461
+ outcomeUnknownCalls,
5462
+ operatorReconciledCalls,
5463
+ legacyCalls,
5464
+ lineageDigest: digest(
5465
+ "chainlesschain.dynamic-workflow.checkpoint-store-readback-lineage.v1\0",
5466
+ lineage,
5467
+ ),
5468
+ lineage,
5469
+ };
5470
+ }
5471
+
2626
5472
  function projectDynamicWorkflowObservability(state) {
2627
5473
  const settled = state.effects.filter((effect) => effect.status === "settled");
2628
5474
  const effectLineage = [];
@@ -2633,6 +5479,12 @@ function projectDynamicWorkflowObservability(state) {
2633
5479
  const nestedEffectAttemptLineage = [];
2634
5480
  const nestedEffectSettlementLineage = [];
2635
5481
  const durableCalls = projectDurableWorkflowCalls(state);
5482
+ const providerTokenUsage = projectProviderTokenUsage(state);
5483
+ const providerCostEstimates = projectProviderCostEstimates(state);
5484
+ const artifactStoreReadbacks = projectArtifactStoreReadbacks(state);
5485
+ const checkpointStoreReadbacks = projectCheckpointStoreReadbacks(state);
5486
+ const providerReceipts = projectProviderRequestReceipts(state);
5487
+ const nestedEffects = projectNestedToolEffects(state);
2636
5488
  let requestToSettlementMs = 0;
2637
5489
  let maxRequestToSettlementMs = 0;
2638
5490
  let estimatedTokens = 0;
@@ -2644,21 +5496,21 @@ function projectDynamicWorkflowObservability(state) {
2644
5496
  let checkpointCount = 0;
2645
5497
  let artifactTruncatedEffects = 0;
2646
5498
  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;
5499
+ const providerRequestAttemptCount = providerReceipts.attemptCount;
5500
+ const providerRequestAttemptEffects = providerReceipts.requestAttemptEffects;
5501
+ const providerReceiptCount = providerReceipts.receiptCount;
5502
+ const providerReceiptEffects = providerReceipts.receiptEffects;
5503
+ const invalidProviderRequestAttempts = providerReceipts.invalidAttempts;
5504
+ const invalidProviderReceipts = providerReceipts.invalidReceipts;
5505
+ const missingProviderRequestReceipts = providerReceipts.missingReceipts;
5506
+ const providerReceiptTruncatedEffects = providerReceipts.truncatedEffects;
5507
+ const nestedEffectAttemptCount = nestedEffects.attemptCount;
5508
+ const nestedEffectSettlementCount = nestedEffects.settlementCount;
5509
+ const nestedEffectDurableMcpSettlements = nestedEffects.durableMcpSettlements;
5510
+ const invalidNestedEffectAttempts = nestedEffects.invalidAttempts;
5511
+ const invalidNestedEffectSettlements = nestedEffects.invalidSettlements;
5512
+ const missingNestedEffectSettlements = nestedEffects.missingSettlements;
5513
+ const nestedEffectTruncatedEffects = nestedEffects.truncatedEffects;
2662
5514
 
2663
5515
  for (const effect of settled) {
2664
5516
  const elapsed = Math.max(
@@ -2699,33 +5551,6 @@ function projectDynamicWorkflowObservability(state) {
2699
5551
  checkpointLineage.push(...checkpoints.lineage);
2700
5552
  if (checkpoints.truncated) checkpointTruncatedEffects += 1;
2701
5553
 
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
5554
  effectLineage.push({
2730
5555
  effectId: effect.id,
2731
5556
  stepId: effect.stepId,
@@ -2752,14 +5577,70 @@ function projectDynamicWorkflowObservability(state) {
2752
5577
  }
2753
5578
 
2754
5579
  const gaps = [
2755
- "provider-token-usage-unavailable",
2756
5580
  "provider-cost-usd-unavailable",
2757
5581
  "provider-native-idempotency-unavailable",
2758
5582
  "provider-receipt-independent-readback-unavailable",
2759
- "checkpoint-provider-readback-unavailable",
2760
- "artifact-store-readback-unavailable",
2761
- "nested-tool-independent-ledger-incomplete",
5583
+ "artifact-store-immutable-retention-unavailable",
2762
5584
  ];
5585
+ providerRequestAttemptLineage.push(...providerReceipts.attemptLineage);
5586
+ providerReceiptLineage.push(...providerReceipts.receiptLineage);
5587
+ nestedEffectAttemptLineage.push(...nestedEffects.attemptLineage);
5588
+ nestedEffectSettlementLineage.push(...nestedEffects.settlementLineage);
5589
+ if (providerTokenUsage.providerReportedCalls === 0) {
5590
+ gaps.push("provider-token-usage-unavailable");
5591
+ }
5592
+ if (providerTokenUsage.missingProviderReportedCalls > 0) {
5593
+ gaps.push("provider-token-usage-incomplete");
5594
+ }
5595
+ if (providerTokenUsage.legacyCalls > 0) {
5596
+ gaps.push("provider-token-usage-legacy-call-schema");
5597
+ }
5598
+ if (providerCostEstimates.pricedCalls === 0) {
5599
+ gaps.push("provider-cost-estimate-unavailable");
5600
+ }
5601
+ if (providerCostEstimates.missingEstimateCalls > 0) {
5602
+ gaps.push("provider-cost-estimate-incomplete");
5603
+ }
5604
+ if (providerCostEstimates.legacyCalls > 0) {
5605
+ gaps.push("provider-cost-estimate-legacy-call-schema");
5606
+ }
5607
+ if (artifactStoreReadbacks.verifiedCalls === 0) {
5608
+ gaps.push("artifact-store-readback-unavailable");
5609
+ }
5610
+ if (artifactStoreReadbacks.missingReadbacks > 0) {
5611
+ gaps.push("artifact-store-readback-incomplete");
5612
+ }
5613
+ if (artifactStoreReadbacks.legacyCalls > 0) {
5614
+ gaps.push("artifact-store-readback-legacy-call-schema");
5615
+ }
5616
+ if (checkpointStoreReadbacks.verifiedCalls === 0) {
5617
+ gaps.push("checkpoint-provider-readback-unavailable");
5618
+ }
5619
+ if (checkpointStoreReadbacks.missingReadbacks > 0) {
5620
+ gaps.push("checkpoint-store-readback-incomplete");
5621
+ }
5622
+ if (checkpointStoreReadbacks.legacyCalls > 0) {
5623
+ gaps.push("checkpoint-store-readback-legacy-call-schema");
5624
+ }
5625
+ if (
5626
+ checkpointStoreReadbacks.preparedBindingCalls <
5627
+ checkpointStoreReadbacks.toolCalls
5628
+ ) {
5629
+ gaps.push("checkpoint-prepared-binding-incomplete");
5630
+ }
5631
+ if (checkpointStoreReadbacks.bindingLegacyCalls > 0) {
5632
+ gaps.push("checkpoint-prepared-binding-legacy-call-schema");
5633
+ }
5634
+ if (
5635
+ checkpointStoreReadbacks.verifiedCalls > 0 &&
5636
+ checkpointStoreReadbacks.fullCoverageCalls <
5637
+ checkpointStoreReadbacks.verifiedCalls
5638
+ ) {
5639
+ gaps.push("checkpoint-full-coverage-incomplete");
5640
+ }
5641
+ if (checkpointStoreReadbacks.externalSideEffectCalls > 0) {
5642
+ gaps.push("checkpoint-external-side-effect-rollback-unavailable");
5643
+ }
2763
5644
  if (tokenEstimateEffects !== settled.length) {
2764
5645
  gaps.push("cowork-token-estimate-incomplete");
2765
5646
  }
@@ -2773,7 +5654,8 @@ function projectDynamicWorkflowObservability(state) {
2773
5654
  (effect) => effect.settlementAuthority === "provider-return",
2774
5655
  ).length;
2775
5656
  if (
2776
- providerReceiptEffects !== providerReturnedEffects ||
5657
+ providerReceipts.providerReturnedReceiptEffects !==
5658
+ providerReturnedEffects ||
2777
5659
  missingProviderRequestReceipts > 0
2778
5660
  ) {
2779
5661
  gaps.push("provider-request-receipt-incomplete");
@@ -2787,6 +5669,12 @@ function projectDynamicWorkflowObservability(state) {
2787
5669
  if (providerReceiptTruncatedEffects > 0) {
2788
5670
  gaps.push("provider-request-receipt-projection-truncated");
2789
5671
  }
5672
+ if (providerReceipts.legacyResultFallbackEffects > 0) {
5673
+ gaps.push("provider-request-receipt-legacy-result-fallback");
5674
+ }
5675
+ if (providerReceipts.conflictingOuterResultEffects > 0) {
5676
+ gaps.push("provider-request-result-disagrees-with-durable-store");
5677
+ }
2790
5678
  if (invalidNestedEffectAttempts > 0) {
2791
5679
  gaps.push("nested-tool-effect-attempt-invalid");
2792
5680
  }
@@ -2799,6 +5687,13 @@ function projectDynamicWorkflowObservability(state) {
2799
5687
  if (nestedEffectTruncatedEffects > 0) {
2800
5688
  gaps.push("nested-tool-effect-projection-truncated");
2801
5689
  }
5690
+ if (nestedEffects.legacyResultFallbackEffects > 0) {
5691
+ gaps.push("nested-tool-independent-ledger-incomplete");
5692
+ gaps.push("nested-tool-effect-legacy-result-fallback");
5693
+ }
5694
+ if (nestedEffects.conflictingOuterResultEffects > 0) {
5695
+ gaps.push("nested-tool-result-disagrees-with-durable-store");
5696
+ }
2802
5697
 
2803
5698
  return snapshotJson(
2804
5699
  {
@@ -2839,27 +5734,42 @@ function projectDynamicWorkflowObservability(state) {
2839
5734
  maxMs: maxRequestToSettlementMs,
2840
5735
  },
2841
5736
  tokens: {
2842
- authority: "cowork-result-heuristic",
5737
+ ...providerTokenUsage,
5738
+ estimateAuthority: "cowork-result-heuristic",
2843
5739
  estimated: estimatedTokens,
2844
5740
  observedEffects: tokenEstimateEffects,
2845
5741
  missingEffects: settled.length - tokenEstimateEffects,
2846
- providerReported: null,
2847
5742
  },
2848
5743
  cost: {
2849
- authority: "unavailable",
2850
- reportedUsd: null,
2851
- observedEffects: 0,
5744
+ ...providerCostEstimates,
5745
+ observedEffects: providerCostEstimates.pricedEffects,
5746
+ projectedRecords: providerCostEstimates.lineage.length,
2852
5747
  },
2853
5748
  providerReceipts: {
2854
- authority: "provider-returned-trace-only",
5749
+ authority:
5750
+ providerReceipts.legacyResultFallbackEffects > 0
5751
+ ? "runtime-state-hash-chain-fsync-with-legacy-task-result-fallback"
5752
+ : "runtime-state-hash-chain-fsync",
5753
+ receiptSemantics: "provider-returned-trace-only",
5754
+ crashVisible: true,
5755
+ durableCallEffects: providerReceipts.durableCallEffects,
5756
+ legacyResultFallbackEffects:
5757
+ providerReceipts.legacyResultFallbackEffects,
5758
+ conflictingOuterResultEffects:
5759
+ providerReceipts.conflictingOuterResultEffects,
2855
5760
  count: providerReceiptCount,
2856
5761
  projectedRecords: providerReceiptLineage.length,
2857
5762
  requestAttempts: providerRequestAttemptCount,
2858
5763
  projectedRequestAttempts: providerRequestAttemptLineage.length,
2859
5764
  requestAttemptEffects: providerRequestAttemptEffects,
2860
5765
  observedEffects: providerReceiptEffects,
2861
- missingProviderReturnedEffects:
2862
- providerReturnedEffects - providerReceiptEffects,
5766
+ providerReturnedObservedEffects:
5767
+ providerReceipts.providerReturnedReceiptEffects,
5768
+ missingProviderReturnedEffects: Math.max(
5769
+ 0,
5770
+ providerReturnedEffects -
5771
+ providerReceipts.providerReturnedReceiptEffects,
5772
+ ),
2863
5773
  missingRequestReceipts: missingProviderRequestReceipts,
2864
5774
  invalidRequestAttempts: invalidProviderRequestAttempts,
2865
5775
  invalidRecords: invalidProviderReceipts,
@@ -2879,7 +5789,15 @@ function projectDynamicWorkflowObservability(state) {
2879
5789
  },
2880
5790
  durableCalls,
2881
5791
  nestedEffects: {
2882
- authority: "task-result-bound-with-mcp-session-ledger-flags",
5792
+ authority:
5793
+ nestedEffects.legacyResultFallbackEffects > 0
5794
+ ? "runtime-state-hash-chain-fsync-with-legacy-task-result-fallback"
5795
+ : "runtime-state-hash-chain-fsync",
5796
+ crashVisible: true,
5797
+ durableCallEffects: nestedEffects.durableCallEffects,
5798
+ legacyResultFallbackEffects: nestedEffects.legacyResultFallbackEffects,
5799
+ conflictingOuterResultEffects:
5800
+ nestedEffects.conflictingOuterResultEffects,
2883
5801
  attempts: nestedEffectAttemptCount,
2884
5802
  settlements: nestedEffectSettlementCount,
2885
5803
  projectedAttempts: nestedEffectAttemptLineage.length,
@@ -2889,7 +5807,8 @@ function projectDynamicWorkflowObservability(state) {
2889
5807
  invalidAttempts: invalidNestedEffectAttempts,
2890
5808
  invalidSettlements: invalidNestedEffectSettlements,
2891
5809
  truncatedEffects: nestedEffectTruncatedEffects,
2892
- allEffectsIndependentlyDurable: false,
5810
+ allEffectsIndependentlyDurable:
5811
+ nestedEffects.legacyResultFallbackEffects === 0,
2893
5812
  attemptLineageDigest: digest(
2894
5813
  "chainlesschain.dynamic-workflow.nested-effect-attempt-lineage.v1\0",
2895
5814
  nestedEffectAttemptLineage,
@@ -2911,6 +5830,7 @@ function projectDynamicWorkflowObservability(state) {
2911
5830
  artifactLineage,
2912
5831
  ),
2913
5832
  lineage: artifactLineage,
5833
+ storeReadbacks: artifactStoreReadbacks,
2914
5834
  },
2915
5835
  checkpoints: {
2916
5836
  authority: "task-result-digest-only",
@@ -2922,6 +5842,7 @@ function projectDynamicWorkflowObservability(state) {
2922
5842
  checkpointLineage,
2923
5843
  ),
2924
5844
  lineage: checkpointLineage,
5845
+ storeReadbacks: checkpointStoreReadbacks,
2925
5846
  },
2926
5847
  gaps,
2927
5848
  },
@@ -2930,7 +5851,135 @@ function projectDynamicWorkflowObservability(state) {
2930
5851
  );
2931
5852
  }
2932
5853
 
2933
- export function projectDynamicWorkflowRuntime(stateOrPath) {
5854
+ function projectCurrentWorkflowStoreReadbacks(state, deps = {}) {
5855
+ const artifactStore = deps.artifactStore || new ArtifactStore();
5856
+ const checkpointStore =
5857
+ deps.checkpointStore || new WorkspaceTransactionManager();
5858
+ const artifacts = {
5859
+ eligibleCalls: 0,
5860
+ verifiedCalls: 0,
5861
+ mismatchedCalls: 0,
5862
+ unavailableCalls: 0,
5863
+ lineage: [],
5864
+ };
5865
+ const checkpoints = {
5866
+ eligibleCalls: 0,
5867
+ verifiedCalls: 0,
5868
+ mismatchedCalls: 0,
5869
+ unavailableCalls: 0,
5870
+ lineage: [],
5871
+ };
5872
+
5873
+ for (const effect of state.effects) {
5874
+ for (const call of effect.calls || []) {
5875
+ if (validArtifactReadback(call.artifactReadback)) {
5876
+ artifacts.eligibleCalls += 1;
5877
+ const storedDigest = artifactReadbackDigest(call.artifactReadback);
5878
+ let currentDigest = null;
5879
+ let status = "unavailable";
5880
+ try {
5881
+ const current = artifactReadbackSettlement(
5882
+ { result: { published: call.artifactReadback.metadata } },
5883
+ artifactStore,
5884
+ );
5885
+ currentDigest = artifactReadbackDigest(current);
5886
+ status = currentDigest === storedDigest ? "verified" : "mismatch";
5887
+ } catch {
5888
+ status = "unavailable";
5889
+ }
5890
+ if (status === "verified") artifacts.verifiedCalls += 1;
5891
+ else if (status === "mismatch") artifacts.mismatchedCalls += 1;
5892
+ else artifacts.unavailableCalls += 1;
5893
+ artifacts.lineage.push({
5894
+ effectId: effect.id,
5895
+ callRecordId: call.id,
5896
+ artifactId: call.artifactReadback.metadata.id,
5897
+ storedReadbackDigest: storedDigest,
5898
+ currentReadbackDigest: currentDigest,
5899
+ status,
5900
+ });
5901
+ }
5902
+
5903
+ if (validCheckpointReadback(call.checkpointReadback)) {
5904
+ checkpoints.eligibleCalls += 1;
5905
+ const storedDigest = checkpointReadbackDigest(call.checkpointReadback);
5906
+ let currentDigest = null;
5907
+ let status = "unavailable";
5908
+ if (validCheckpointBinding(call.checkpointBinding)) {
5909
+ try {
5910
+ const current = checkpointReadbackFromPreparedBinding(
5911
+ call.checkpointBinding,
5912
+ checkpointStore,
5913
+ );
5914
+ if (current !== null) {
5915
+ currentDigest = checkpointReadbackDigest(current);
5916
+ status = currentDigest === storedDigest ? "verified" : "mismatch";
5917
+ }
5918
+ } catch {
5919
+ status = "unavailable";
5920
+ }
5921
+ }
5922
+ if (status === "verified") checkpoints.verifiedCalls += 1;
5923
+ else if (status === "mismatch") checkpoints.mismatchedCalls += 1;
5924
+ else checkpoints.unavailableCalls += 1;
5925
+ checkpoints.lineage.push({
5926
+ effectId: effect.id,
5927
+ callRecordId: call.id,
5928
+ transactionId: call.checkpointReadback.transactionId,
5929
+ checkpointId: call.checkpointReadback.checkpointId,
5930
+ storedReadbackDigest: storedDigest,
5931
+ currentReadbackDigest: currentDigest,
5932
+ status,
5933
+ });
5934
+ }
5935
+ }
5936
+ }
5937
+
5938
+ const gaps = [];
5939
+ if (artifacts.mismatchedCalls > 0) {
5940
+ gaps.push("artifact-store-current-readback-mismatch");
5941
+ }
5942
+ if (artifacts.unavailableCalls > 0) {
5943
+ gaps.push("artifact-store-current-readback-unavailable");
5944
+ }
5945
+ if (checkpoints.mismatchedCalls > 0) {
5946
+ gaps.push("checkpoint-store-current-readback-mismatch");
5947
+ }
5948
+ if (checkpoints.unavailableCalls > 0) {
5949
+ gaps.push("checkpoint-store-current-readback-unavailable");
5950
+ }
5951
+ const eligibleCalls = artifacts.eligibleCalls + checkpoints.eligibleCalls;
5952
+ const verifiedCalls = artifacts.verifiedCalls + checkpoints.verifiedCalls;
5953
+ return snapshotJson(
5954
+ {
5955
+ schema: "cc-dynamic-workflow-current-store-readback/v1",
5956
+ authority: "artifact-and-checkpoint-store-at-status-projection",
5957
+ verificationTiming: "runtime-status",
5958
+ complete: eligibleCalls > 0 && eligibleCalls === verifiedCalls,
5959
+ eligibleCalls,
5960
+ verifiedCalls,
5961
+ artifacts: {
5962
+ ...artifacts,
5963
+ lineageDigest: digest(
5964
+ "chainlesschain.dynamic-workflow.current-artifact-readback-lineage.v1\0",
5965
+ artifacts.lineage,
5966
+ ),
5967
+ },
5968
+ checkpoints: {
5969
+ ...checkpoints,
5970
+ lineageDigest: digest(
5971
+ "chainlesschain.dynamic-workflow.current-checkpoint-readback-lineage.v1\0",
5972
+ checkpoints.lineage,
5973
+ ),
5974
+ },
5975
+ gaps,
5976
+ },
5977
+ "dynamic workflow current store readback projection",
5978
+ 2 * 1024 * 1024,
5979
+ );
5980
+ }
5981
+
5982
+ export function projectDynamicWorkflowRuntime(stateOrPath, options = {}) {
2934
5983
  const state =
2935
5984
  typeof stateOrPath === "string"
2936
5985
  ? readDynamicWorkflowRuntimeState(stateOrPath)
@@ -2966,8 +6015,32 @@ export function projectDynamicWorkflowRuntime(stateOrPath) {
2966
6015
  }),
2967
6016
  ),
2968
6017
  ),
6018
+ inputRequestCount: runtimeInputRequests(state).length,
6019
+ answeredInputRequestCount: runtimeInputRequests(state).filter(
6020
+ (request) => request.status === "answered",
6021
+ ).length,
6022
+ pendingInputRequests: Object.freeze(
6023
+ runtimeInputRequests(state)
6024
+ .filter((request) => request.status === "pending")
6025
+ .map((request) =>
6026
+ Object.freeze({
6027
+ id: request.id,
6028
+ stepId: request.stepId,
6029
+ prompt: request.prompt,
6030
+ options: request.options
6031
+ ? Object.freeze([...request.options])
6032
+ : null,
6033
+ multiSelect: request.multiSelect,
6034
+ requestedAt: request.requestedAt,
6035
+ }),
6036
+ ),
6037
+ ),
2969
6038
  finalRecordStatus: state.finalRecord?.status || null,
2970
6039
  observability: projectDynamicWorkflowObservability(state),
6040
+ currentStoreReadbacks:
6041
+ options.currentStoreReadback === true
6042
+ ? projectCurrentWorkflowStoreReadbacks(state, options)
6043
+ : null,
2971
6044
  updatedAt: state.updatedAt,
2972
6045
  stateDigest: state.stateDigest,
2973
6046
  });