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