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