chainlesschain 0.162.188 → 0.162.193
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 +61 -11
- package/bin/chainlesschain.js +7 -52
- package/completions/_cc +209 -0
- package/completions/cc.bash +18 -0
- package/completions/cc.fish +13 -0
- package/completions/cc.ps1 +226 -0
- package/package.json +10 -2
- package/src/assets/web-panel/assets/{AIOps-Brr1LVcE.js → AIOps-DBLkaTje.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DQU74DKG.js → ActionButton-BAy-Qebo.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CYp9178y.js → Analytics-Dfb1Mlau.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DW4SCYsW.js → AppLayout-BbFkN8m0.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CYlD_ed1.js → Artifacts-kM4-cEEH.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CWl1rZOi.js → Audit-BSQfYWf3.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BOR-LPsa.js → BackgroundAgents-CWDbI_fG.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DLpG7j1X.js → Backup-BxLPbq_Q.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-B35K9wdr.js → BaseInput-DuIbdrp6.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CwUJb9gB.js → Chat-NEmjGahU.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-1v6aMS1m.js → ChatBubbleRenderer-DfJEo-5_.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-pi3-tPNW.js → Checkbox-CaoeG3Ns.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-FyNF35eL.js → Codegen-BFf_g3xY.js} +1 -1
- package/src/assets/web-panel/assets/{Col-tGt_JDEr.js → Col-CNOd8mdQ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CC7LonQX.js → Community-BhpDqz-i.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BGXrHHjE.js → Compact-CQ42T7Z6.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DSW0TBZ8.js → Compliance-DI5PcLMN.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bhk7rbvE.js → Cowork-wYYZe0wR.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DxyiQ2NZ.js → Cron-Dlq03DD_.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B2ZkxHok.js → Crosschain-BzqS8PP3.js} +1 -1
- package/src/assets/web-panel/assets/{DID-yOJV99eJ.js → DID-It55Yem7.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CKsttz7D.js → Dashboard-9HkH6oxn.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CVFldXSL.js → Dropdown-00e2YWRB.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bufv638J.js → EmailListRenderer-By-ugRDL.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-4ncjoIjL.js → FamilyGuardDashboard-C396yu5m.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DsknEDoX.js → Federation-BME5sbZY.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cqe5jWXA.js → FormItemContext-B4TEZCr-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-NAJaFKxc.js → GenericCardRenderer-_9CX9ieY.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CszKQpnl.js → Git-BrS6WVSr.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BXGodXi6.js → Governance-BMdCWnKF.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CkoYuG2c.js → Inference-Bcp3vzcA.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DFlEr_T0.js → KnowledgeGraph-DlLuqGY5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CVaENddA.js → Logs-D-W_6x09.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-DElGPxCZ.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CDlLT0fw.js → Marketplace-Dzn2guvI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DdwOyszN.js → McpTools-uPict-KL.js} +6 -6
- package/src/assets/web-panel/assets/{Memory-DKQnkKuG.js → Memory-BCw43kOb.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DGseqlzJ.js → MobileBridge-DmV8Wv7F.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-COBbEOlj.js → MobileProjects-C-HaCmSM.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-O61Vwyli.js → Mtc-C6jxJ34Y.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Dw2SyasS.js → MtcAudit-Za8ExwhQ.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-hSu_l8kf.js → Multisig-KKDbuEgb.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CX1Epurr.js → NLProgramming-Dj97zbCk.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-R9xX8o8_.js → Notes-DBXvMraU.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-jtSNLCuK.js → NotificationSettings-DOxITLYl.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-7DVjZwMM.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Pu2WO-U3.js → Organization-D1i73mjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BjnqUyFp.js → Overflow-kW6Q3rvg.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-jjkvz44c.js → P2P-C-v_8p2v.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-tUbbY64W.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-D4yDpivd.js → Permissions-DkHxEZ58.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-0qsL6iH1.js → PersonalDataHub-DWuMPGrK.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-Cx7Ybrk2.js → Pipeline-30IYXWWm.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-PbfR_M_V.js → Privacy-DZNQ4RrH.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CyV6Gsub.js → ProjectInit-De2MCElf.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C7KZ2Wdk.js → ProjectSettings-5uHk9wsC.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DQDjO-yK.js → Projects-XjDfrFLb.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DyFVBBMX.js → Providers-Bgwj6gcE.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CzB1BC9_.js → QrScannerModal-gKql9umv.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Bk636mC1.js → QuickAsk-BWvmwkh7.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DoWxHIT3.js → Recommend-Uok4hZPd.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-oOVIv9wq.js → RemoteSession-DLJzTk9a.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-BucoReGG.js → Reputation-ZVwIPMSO.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DzmOdJU7.js → Row-CN04HxWR.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C1tK7KGU.js → RssFeed-BdSrEG4g.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CSnInOqk.js → Search-5PwVd0IR.js} +1 -1
- package/src/assets/web-panel/assets/{Security-dlejr15l.js → Security-DdBo4zjN.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BioPZuNh.js → Services-D3letU7M.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DWMibwpC.js → Skeleton--Jb83f1v.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BAAnx1B2.js → Skills-Bg-CulHX.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-jWNp4hNO.js → Sla-7QOa7S-X.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-y5kqKha-.js → SpeechSettings-DQwppjRw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B9GNsc3e.js → SyncSettings-dNPQGL-s.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BDBYKRen.js → Tasks-Ggs-Z48q.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DRiE5eaN.js → Templates-78TYHgRO.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BGlAvExF.js → Tenant-xHwwv9vO.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B8uzrbNY.js → Terminal-BJXSJfm4.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Df48iveY.js → TimelineRenderer-fLbjRhJw.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CcmHndN6.js → Tokens-oMSBSBmB.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DPkWxmQ2.js → Trigger-CfnY_wX_.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DTb5a5AV.js → Trust-AdyTgYKO.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CBmg5vch.js → UkeySign-C5K_81dS.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B0mZrz4O.js → VideoEditing-vcGVDqki.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Bxs7_mpd.js → Wallet-hhOc3hlY.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C8x3cMxU.js → WebAuthn-CVloC11s.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-DPrNznrP.js → WorkflowEditor-CYeE32R5.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D3bYWu7S.js → chat-CNROR5bN.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DY-PwMu4.js → colors-Cjx-cnwU.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-LDk10Kw6.js → compact-item-DOIfelvD.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C-2MZnFq.js → createContext-DfA7UZ8u.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-ChcCBGs7.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BQIBjcnB.js → hasIn-BMSCzcfM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQVb2dWp.js → index-5-HXgOX5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdXnYMCZ.js → index-AWg-0M1S.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx6D_dx2.js → index-B4iYzpkL.js} +1 -1
- package/src/assets/web-panel/assets/{index-_HRO_cIu.js → index-B5Udeb_6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlRYqt3S.js → index-B7gf77CJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrnRO9yE.js → index-BEMwBBHk.js} +1 -1
- package/src/assets/web-panel/assets/{index-VCh5zNJo.js → index-BM1y_9hM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRI3ngNg.js → index-BUonmKSE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3GNvExG.js → index-BbQ2eQ8H.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSvo_bp0.js → index-BemJ2u_E.js} +1 -1
- package/src/assets/web-panel/assets/{index-L6qixyLc.js → index-BolaOQxx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BI4KgcYC.js → index-BxOk9vkG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CY4KnaO9.js → index-By61ojUx.js} +1 -1
- package/src/assets/web-panel/assets/{index-L3smcoQX.js → index-C2K9SNVX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BX2b9Orc.js → index-C3L5gdiD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz4b6Z_7.js → index-C7xn0Nub.js} +1 -1
- package/src/assets/web-panel/assets/{index-COG61sRv.js → index-CAnvhwg6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7FC8cP2.js → index-CEKMcOyU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN-xdSWZ.js → index-CNC5onm4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY2FIrmz.js → index-CVL27kXC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiTvBAr-.js → index-CVm1_i3t.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhRt9cYJ.js → index-CdYBye8B.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5pgtZnY.js → index-ClNMrqEY.js} +1 -1
- package/src/assets/web-panel/assets/{index-7xKu7Fns.js → index-Cs6l7uRu.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4K1weKc.js → index-CshQbilz.js} +1 -1
- package/src/assets/web-panel/assets/{index-k7C_oDCO.js → index-Cyweh0Hy.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3Oq-DLL.js → index-DBpWXFed.js} +3 -3
- package/src/assets/web-panel/assets/{index-DYyLJPlF.js → index-DHtpHZAQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dh-tkpKQ.js → index-DLmn0BoK.js} +1 -1
- package/src/assets/web-panel/assets/{index-8JC9BJa1.js → index-DPAWBrMf.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dd2Km97U.js → index-DjEsxO5h.js} +1 -1
- package/src/assets/web-panel/assets/{index-KFZO_lcx.js → index-FBc_CJBd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTIZpPtG.js → index-Icdoqmzt.js} +1 -1
- package/src/assets/web-panel/assets/index-Q2IajL54.js +1 -0
- package/src/assets/web-panel/assets/index-bRkHgOMc.js +1 -0
- package/src/assets/web-panel/assets/{index-lmm9p-jm.js → index-dCd8cSUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-HasPt4-h.js → index-m2vG9gG_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaslYch6.js → index-mTpHoEzr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbR6ROOa.js → index-ti_lnTWl.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-zQPSHf2C.js → initDefaultProps-Bu8k7H1Y.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CKCStGVk.js → motion-s3iVK41k.js} +1 -1
- package/src/assets/web-panel/assets/{move-BufVDGYF.js → move-B6B-E5Jz.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DcaFxy5D.js → mtc-parser-BiYgTidA.js} +1 -1
- package/src/assets/web-panel/assets/{omit-EScpkGdo.js → omit-pU1RdUDA.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-72hG_qfT.js → pickAttrs-Cdoq_SeM.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CMFWiH20.js → placementArrow-DIuvAn2K.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DKQHS41p.js → responsiveObserve-8w16YR2D.js} +1 -1
- package/src/assets/web-panel/assets/{slide-QVAFHTIQ.js → slide-VZ01cZe3.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DIz38e2G.js → statusUtils-lh8YMN8Z.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-zK5cyLby.js → styleChecker-CmDyreec.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DhZngk62.js → useFlexGapSupport-hiJWMU5y.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BvV2cvkg.js → useFs-Thnu-Iaj.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CRmxb9l3.js → usePersonalDataHub-LBdOxjwY.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CgQIbobP.js → vnode-Cg6lFY_X.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D4XrrO0R.js → zoom-DVfsR1YO.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/cli-help.js +181 -0
- package/src/command-help-index.json +181 -0
- package/src/command-manifest.json +1107 -175
- package/src/command-surface-policy.js +197 -0
- package/src/commands/agent.js +98 -33
- package/src/commands/artifacts.js +400 -2
- package/src/commands/background-session.js +41 -3
- package/src/commands/checkpoint-managed.js +1059 -0
- package/src/commands/checkpoint-restore-recovery.js +1476 -0
- package/src/commands/checkpoint.js +950 -22
- package/src/commands/compact.js +101 -25
- package/src/commands/config.js +506 -20
- package/src/commands/doctor.js +4 -1
- package/src/commands/hub.js +1 -1
- package/src/commands/mcp.js +688 -24
- package/src/commands/pack.js +16 -2
- package/src/commands/plugin.js +20 -12
- package/src/commands/session-mcp-recovery.js +218 -0
- package/src/commands/session.js +470 -110
- package/src/commands/setup.js +36 -10
- package/src/commands/skill.js +181 -110
- package/src/commands/status.js +21 -5
- package/src/commands/team-distributed.js +3324 -0
- package/src/commands/team.js +1130 -74
- package/src/commands/update.js +68 -6
- package/src/constants.js +11 -0
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/action-protocol.js +10 -0
- package/src/gateways/ws/session-protocol.js +555 -27
- package/src/gateways/ws/ws-agent-handler.js +541 -14
- package/src/gateways/ws/ws-session-gateway.js +129 -22
- package/src/gateways/ws/ws-session-state.js +431 -0
- package/src/harness/background-task-manager.js +233 -36
- package/src/harness/jsonl-session-store.js +2551 -167
- package/src/harness/mcp-client.js +802 -61
- package/src/harness/prompt-compressor.js +227 -27
- package/src/harness/session-index.js +89 -38
- package/src/harness/session-list-index.js +254 -0
- package/src/harness/structured-handoff.js +410 -0
- package/src/lazy-dispatch.js +747 -84
- package/src/lib/__tests__/logger.test.js +50 -2
- package/src/lib/advisor-runtime.js +1047 -0
- package/src/lib/agent-core.js +2 -0
- package/src/lib/agent-sandbox.js +120 -26
- package/src/lib/agent-session-export.js +20 -0
- package/src/lib/agent-team/task-lease.js +271 -3
- package/src/lib/agent-team/team-adjudication.js +1501 -0
- package/src/lib/agent-team/team-budget.js +4 -1
- package/src/lib/agent-team/team-control-store.js +582 -0
- package/src/lib/agent-team/team-distributed-queue.js +5246 -0
- package/src/lib/agent-team/team-process-checkpoint.js +211 -0
- package/src/lib/agent-team/team-runner.js +543 -24
- package/src/lib/agent-team/team-worktree.js +1403 -95
- package/src/lib/artifact-store.js +131 -8
- package/src/lib/background-agent-supervisor.js +185 -13
- package/src/lib/background-interaction-journal.js +37 -9
- package/src/lib/background-launch-profile.js +1058 -0
- package/src/lib/checkpoint-restore-already-completed-controller.js +902 -0
- package/src/lib/checkpoint-restore-orchestrator.js +987 -0
- package/src/lib/checkpoint-restore-partial-rollback-controller.js +1953 -0
- package/src/lib/checkpoint-restore-recovery-controller.js +689 -0
- package/src/lib/checkpoint-restore-recovery.js +884 -0
- package/src/lib/checkpoint-restore-retention.js +126 -0
- package/src/lib/checkpoint-restore-saga.js +5079 -0
- package/src/lib/checkpoint-restore-session-recovery.js +1178 -0
- package/src/lib/checkpoint-restore-workspace-target-verifier.js +270 -0
- package/src/lib/checkpoint-store.js +1688 -97
- package/src/lib/checkpoint-timeline-authority.js +522 -0
- package/src/lib/checkpoint-timeline.js +642 -0
- package/src/lib/config-keys.js +18 -119
- package/src/lib/config-manager.js +446 -27
- package/src/lib/config-redaction.js +57 -0
- package/src/lib/config-schema.js +853 -0
- package/src/lib/cowork-mcp-tools.js +5 -0
- package/src/lib/cowork-task-runner.js +458 -63
- package/src/lib/delivery-coordinator.js +1234 -0
- package/src/lib/delivery-evidence.js +450 -0
- package/src/lib/doctor-checkup.js +199 -14
- package/src/lib/executable-path.js +34 -0
- package/src/lib/fatal-handler.js +16 -0
- package/src/lib/file-checkpoint.js +4909 -186
- package/src/lib/file-lines.js +125 -0
- package/src/lib/headless-config-command.js +16 -5
- package/src/lib/hook-runner.cjs +139 -38
- package/src/lib/hooks-v2-workspace-context.js +10 -0
- package/src/lib/impacted-gate-selector.js +424 -0
- package/src/lib/interaction-adapter.js +62 -0
- package/src/lib/jsonl-session-store.js +1 -35
- package/src/lib/logger.js +66 -14
- package/src/lib/managed-checkpoint-render.js +60 -0
- package/src/lib/managed-tool-checkpoint.js +194 -0
- package/src/lib/mcp-call-ledger-store.js +1069 -0
- package/src/lib/mcp-call-ledger.js +747 -0
- package/src/lib/mcp-client.js +5 -0
- package/src/lib/mcp-conflict-scheduler.js +468 -0
- package/src/lib/mcp-effect-contract.js +102 -0
- package/src/lib/mcp-headers-helper-trust.js +145 -0
- package/src/lib/mcp-headers-helper.js +616 -0
- package/src/lib/mcp-host-recovery-runtime.js +131 -0
- package/src/lib/mcp-ledger-recovery-admission.js +1065 -0
- package/src/lib/mcp-recovery-adjudication.js +443 -0
- package/src/lib/output-context.js +86 -0
- package/src/lib/packer/file-identity.js +170 -0
- package/src/lib/packer/index.js +3 -0
- package/src/lib/packer/native-update-state.js +277 -0
- package/src/lib/packer/pack-update-applier.js +1983 -136
- package/src/lib/packer/pack-update-checker.js +34 -0
- package/src/lib/packer/pack-update-downloader.js +875 -86
- package/src/lib/packer/pack-update-signature.js +86 -0
- package/src/lib/packer/pkg-config-generator.js +6 -0
- package/src/lib/paths.js +65 -10
- package/src/lib/plan-mode.js +831 -14
- package/src/lib/plan-persistence.js +540 -0
- package/src/lib/plugin-runtime/hooks.js +227 -69
- package/src/lib/plugin-runtime/plugin-options.js +189 -44
- package/src/lib/pr-automation-policy.js +146 -8
- package/src/lib/process-execution-broker/credential-agent.js +7 -0
- package/src/lib/process-execution-broker/index.js +491 -13
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
- package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
- package/src/lib/process-execution-broker/workspace-transaction.js +4459 -0
- package/src/lib/project-instructions.js +165 -29
- package/src/lib/project-mcp-trust.js +67 -0
- package/src/lib/public-ide-capability-manifest.js +272 -0
- package/src/lib/public-ide-capability-validation.js +351 -0
- package/src/lib/secret-store.js +267 -43
- package/src/lib/secure-file-identity.js +433 -0
- package/src/lib/secure-fs.js +1089 -0
- package/src/lib/security-audit.js +58 -0
- package/src/lib/service-manager.js +34 -13
- package/src/lib/session-budget-runtime.js +1452 -0
- package/src/lib/session-host-snapshot.js +388 -0
- package/src/lib/session-message-provenance.js +429 -0
- package/src/lib/session-projection.js +804 -0
- package/src/lib/session-resource-budget.js +1495 -0
- package/src/lib/settings-hooks.cjs +138 -22
- package/src/lib/settings-loader.cjs +25 -2
- package/src/lib/side-effect-ledger-store.js +45 -23
- package/src/lib/skill-execution-identity.js +217 -0
- package/src/lib/skill-loader.js +299 -18
- package/src/lib/sub-agent-context.js +236 -28
- package/src/lib/sub-agent-registry.js +5 -0
- package/src/lib/subagent-contract.js +94 -0
- package/src/lib/todo-manager.js +185 -18
- package/src/lib/todo-persistence.js +417 -0
- package/src/lib/turn-binding-store.js +41 -23
- package/src/lib/turn-binding.js +160 -2
- package/src/lib/with-file-lock.js +165 -24
- package/src/lib/ws-chat-handler.js +21 -0
- package/src/program-base.js +18 -0
- package/src/repl/advisor-command.js +103 -0
- package/src/repl/agent-repl.js +2175 -146
- package/src/repl/btw-command.js +93 -10
- package/src/repl/clipboard-image.js +119 -0
- package/src/repl/config-summary.js +64 -108
- package/src/repl/prompt-editor.js +233 -0
- package/src/repl/prompt-interactions.js +344 -0
- package/src/repl/prompt-stash.js +354 -0
- package/src/repl/prompt-suggestions.js +312 -0
- package/src/repl/repl-keybindings.js +227 -0
- package/src/repl/session-recap.js +217 -0
- package/src/repl/slash-command-registry.js +22 -0
- package/src/repl/terminal-layout.js +88 -0
- package/src/runtime/agent-core.js +1630 -447
- package/src/runtime/coding-agent-contract-shared.cjs +13 -1
- package/src/runtime/coding-agent-managed-tool-policy.cjs +7 -2
- package/src/runtime/diagnostics.js +32 -13
- package/src/runtime/headless-runner.js +310 -118
- package/src/runtime/headless-stream.js +356 -95
- package/src/runtime/mcp-config.js +148 -14
- package/src/runtime/policies/agent-policy.js +14 -0
- package/src/runtime/status-diagnostics-lite.js +137 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-7iWGwZ-r.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BU2Yqq5H.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-69ca_WeR.js +0 -7
- package/src/assets/web-panel/assets/devWarning-B4dXIJqD.js +0 -1
- package/src/assets/web-panel/assets/index-C-C7DaG6.js +0 -1
- package/src/assets/web-panel/assets/index-DkfWzWtd.js +0 -1
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared durable orchestration for checkpoint workspace restores.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator owns the workspace-lock and saga boundaries. Callers only
|
|
5
|
+
* provide an immutable restore plan, a locked revalidation callback, the
|
|
6
|
+
* engine adapter, and (for timeline restores) an optional session-authority
|
|
7
|
+
* adapter. All callbacks must be synchronous because the canonical workspace
|
|
8
|
+
* lock is synchronous and must span every durable boundary.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import {
|
|
14
|
+
CheckpointRestoreSagaStore,
|
|
15
|
+
computeCheckpointRestoreWorkspaceLockOwnerDigest,
|
|
16
|
+
} from "./checkpoint-restore-saga.js";
|
|
17
|
+
import { withWorkspaceLockSync as withCanonicalWorkspaceLockSync } from "./process-execution-broker/workspace-transaction.js";
|
|
18
|
+
|
|
19
|
+
const HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
20
|
+
const OPERATION_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/;
|
|
21
|
+
const RECOVERY_PHASES = new Set([
|
|
22
|
+
"mutation_started",
|
|
23
|
+
"workspace_applied",
|
|
24
|
+
"session_committed",
|
|
25
|
+
"recovery_required",
|
|
26
|
+
"recovery_started",
|
|
27
|
+
]);
|
|
28
|
+
const TERMINAL_PHASES = new Set(["completed", "aborted", "rolled_back"]);
|
|
29
|
+
|
|
30
|
+
export const CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES = Object.freeze({
|
|
31
|
+
ASYNC_UNSUPPORTED: "CHECKPOINT_RESTORE_ASYNC_UNSUPPORTED",
|
|
32
|
+
BOUNDARY_MISSING: "CHECKPOINT_RESTORE_BOUNDARY_MISSING",
|
|
33
|
+
INVALID_PLAN: "CHECKPOINT_RESTORE_PLAN_INVALID",
|
|
34
|
+
INVALID_RESULT: "CHECKPOINT_RESTORE_RESULT_INVALID",
|
|
35
|
+
SESSION_BOUNDARY_MISSING: "CHECKPOINT_RESTORE_SESSION_BOUNDARY_MISSING",
|
|
36
|
+
WORKSPACE_STALE: "CHECKPOINT_WORKSPACE_STALE",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function restoreError(code, message, details = {}) {
|
|
40
|
+
const error = new Error(message);
|
|
41
|
+
error.code = code;
|
|
42
|
+
Object.assign(error, details);
|
|
43
|
+
return error;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function asError(error) {
|
|
47
|
+
if (error && typeof error === "object") return error;
|
|
48
|
+
return new Error(String(error || "checkpoint restore failed"));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isThenable(value) {
|
|
52
|
+
return (
|
|
53
|
+
value !== null &&
|
|
54
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
55
|
+
typeof value.then === "function"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function requireSynchronous(value, boundary) {
|
|
60
|
+
if (isThenable(value)) {
|
|
61
|
+
throw restoreError(
|
|
62
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.ASYNC_UNSUPPORTED,
|
|
63
|
+
`${boundary} must be synchronous while the workspace lock is held`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function stableValue(value) {
|
|
70
|
+
if (value === undefined) return "null";
|
|
71
|
+
if (
|
|
72
|
+
value === null ||
|
|
73
|
+
typeof value === "string" ||
|
|
74
|
+
typeof value === "boolean"
|
|
75
|
+
) {
|
|
76
|
+
return JSON.stringify(value);
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
79
|
+
return JSON.stringify(value);
|
|
80
|
+
}
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
return `[${value.map((entry) => stableValue(entry)).join(",")}]`;
|
|
83
|
+
}
|
|
84
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
85
|
+
return `{${Object.keys(value)
|
|
86
|
+
.sort()
|
|
87
|
+
.map((key) => `${JSON.stringify(key)}:${stableValue(value[key])}`)
|
|
88
|
+
.join(",")}}`;
|
|
89
|
+
}
|
|
90
|
+
throw new TypeError("checkpoint restore value is not canonical JSON");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function computeCheckpointRestoreDigest(domain, value) {
|
|
94
|
+
if (typeof domain !== "string" || domain.length === 0) {
|
|
95
|
+
throw new TypeError("checkpoint restore digest domain is required");
|
|
96
|
+
}
|
|
97
|
+
return `sha256:${createHash("sha256")
|
|
98
|
+
.update(`${domain}\0${stableValue(value)}`)
|
|
99
|
+
.digest("hex")}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function boundedText(value, fallback, maximum = 2_048) {
|
|
103
|
+
let sanitized = "";
|
|
104
|
+
for (const character of String(value || fallback)) {
|
|
105
|
+
const code = character.charCodeAt(0);
|
|
106
|
+
sanitized += code < 32 || code === 127 ? " " : character;
|
|
107
|
+
}
|
|
108
|
+
const normalized = sanitized.trim();
|
|
109
|
+
return (normalized || fallback).slice(0, maximum);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function errorCode(error) {
|
|
113
|
+
return boundedText(error?.code, "CHECKPOINT_RESTORE_FAILED", 128);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function commitStateUnknown(error) {
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
let current = error;
|
|
119
|
+
for (let depth = 0; current && depth < 8; depth += 1) {
|
|
120
|
+
if (seen.has(current)) break;
|
|
121
|
+
seen.add(current);
|
|
122
|
+
if (/unknown/i.test(String(current.commitState || ""))) return true;
|
|
123
|
+
current =
|
|
124
|
+
current.transactionError ||
|
|
125
|
+
current.checkpointRestoreCause ||
|
|
126
|
+
current.cause ||
|
|
127
|
+
null;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function hasPendingSessionIntent(saga) {
|
|
133
|
+
if (!saga?.pending || !Array.isArray(saga.events)) return false;
|
|
134
|
+
return saga.events.some(
|
|
135
|
+
(event) =>
|
|
136
|
+
event?.phase === "intent_committed" &&
|
|
137
|
+
event?.evidence?.intentAuthority === "session",
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function pathKey(value, platform) {
|
|
142
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
143
|
+
const resolved = path.resolve(value);
|
|
144
|
+
return platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function rootsMatch(left, right, platform) {
|
|
148
|
+
const leftKey = pathKey(left, platform);
|
|
149
|
+
const rightKey = pathKey(right, platform);
|
|
150
|
+
return leftKey !== null && leftKey === rightKey;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function requireText(value, field, maximum = 1_024) {
|
|
154
|
+
if (
|
|
155
|
+
typeof value !== "string" ||
|
|
156
|
+
value.length === 0 ||
|
|
157
|
+
value.length > maximum ||
|
|
158
|
+
value.includes("\0")
|
|
159
|
+
) {
|
|
160
|
+
throw restoreError(
|
|
161
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
162
|
+
`checkpoint restore plan has an invalid ${field}`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function optionalText(value, field, maximum = 256) {
|
|
169
|
+
return value == null ? null : requireText(value, field, maximum);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function normalizePlan(input, platform) {
|
|
173
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
174
|
+
throw restoreError(
|
|
175
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
176
|
+
"checkpoint restore plan is required",
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (input.restoreKind !== "git" && input.restoreKind !== "copy") {
|
|
180
|
+
throw restoreError(
|
|
181
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
182
|
+
"checkpoint restore plan must identify the git or copy engine",
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (
|
|
186
|
+
input.restoreSurface !== "direct" &&
|
|
187
|
+
input.restoreSurface !== "timeline"
|
|
188
|
+
) {
|
|
189
|
+
throw restoreError(
|
|
190
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
191
|
+
"checkpoint restore plan must identify its direct or timeline surface",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const workspaceRoot = requireText(
|
|
195
|
+
input.workspaceRoot,
|
|
196
|
+
"workspaceRoot",
|
|
197
|
+
4_096,
|
|
198
|
+
);
|
|
199
|
+
if (!path.isAbsolute(workspaceRoot)) {
|
|
200
|
+
throw restoreError(
|
|
201
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
202
|
+
"checkpoint restore workspaceRoot must be absolute",
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const binding = input.workspaceBinding;
|
|
206
|
+
if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
|
|
207
|
+
throw restoreError(
|
|
208
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
209
|
+
"checkpoint restore plan requires a workspace binding",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (
|
|
213
|
+
binding.engine !== input.restoreKind ||
|
|
214
|
+
!rootsMatch(binding.workspaceRoot, workspaceRoot, platform)
|
|
215
|
+
) {
|
|
216
|
+
throw restoreError(
|
|
217
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
218
|
+
"checkpoint restore binding does not match its engine or workspace",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
for (const field of [
|
|
222
|
+
"scopeIdentity",
|
|
223
|
+
"prestateIdentity",
|
|
224
|
+
"writePlanIdentity",
|
|
225
|
+
"targetPoststateIdentity",
|
|
226
|
+
]) {
|
|
227
|
+
requireText(binding[field], `workspaceBinding.${field}`);
|
|
228
|
+
}
|
|
229
|
+
if (!Number.isSafeInteger(input.targetCount) || input.targetCount < 0) {
|
|
230
|
+
throw restoreError(
|
|
231
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
232
|
+
"checkpoint restore targetCount must be a non-negative safe integer",
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (!HASH_PATTERN.test(String(input.confirmationDigest || ""))) {
|
|
236
|
+
throw restoreError(
|
|
237
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
238
|
+
"checkpoint restore confirmationDigest is invalid",
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const sessionId = optionalText(input.sessionId, "sessionId");
|
|
242
|
+
const timelineEntryId = optionalText(
|
|
243
|
+
input.timelineEntryId,
|
|
244
|
+
"timelineEntryId",
|
|
245
|
+
);
|
|
246
|
+
if (input.restoreSurface === "direct" && (sessionId || timelineEntryId)) {
|
|
247
|
+
throw restoreError(
|
|
248
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
249
|
+
"direct checkpoint restore cannot claim session authority",
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
if (input.restoreSurface === "timeline" && !sessionId && !timelineEntryId) {
|
|
253
|
+
throw restoreError(
|
|
254
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
255
|
+
"timeline checkpoint restore requires a session or timeline entry",
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const normalized = {
|
|
260
|
+
...input,
|
|
261
|
+
restoreKind: input.restoreKind,
|
|
262
|
+
restoreSurface: input.restoreSurface,
|
|
263
|
+
checkpointId: requireText(input.checkpointId, "checkpointId", 256),
|
|
264
|
+
checkpointIdentity: requireText(
|
|
265
|
+
input.checkpointIdentity,
|
|
266
|
+
"checkpointIdentity",
|
|
267
|
+
),
|
|
268
|
+
checkpointNamespace: optionalText(
|
|
269
|
+
input.checkpointNamespace,
|
|
270
|
+
"checkpointNamespace",
|
|
271
|
+
),
|
|
272
|
+
workspaceRoot,
|
|
273
|
+
workspaceBinding: binding,
|
|
274
|
+
targetCount: input.targetCount,
|
|
275
|
+
confirmationDigest: input.confirmationDigest,
|
|
276
|
+
sessionId,
|
|
277
|
+
timelineEntryId,
|
|
278
|
+
};
|
|
279
|
+
normalized.authorityFingerprint = stableValue({
|
|
280
|
+
restoreKind: normalized.restoreKind,
|
|
281
|
+
restoreSurface: normalized.restoreSurface,
|
|
282
|
+
checkpointId: normalized.checkpointId,
|
|
283
|
+
checkpointIdentity: normalized.checkpointIdentity,
|
|
284
|
+
checkpointNamespace: normalized.checkpointNamespace,
|
|
285
|
+
workspaceRoot: pathKey(normalized.workspaceRoot, platform),
|
|
286
|
+
workspaceBinding: normalized.workspaceBinding,
|
|
287
|
+
targetCount: normalized.targetCount,
|
|
288
|
+
confirmationDigest: normalized.confirmationDigest,
|
|
289
|
+
sessionId: normalized.sessionId,
|
|
290
|
+
timelineEntryId: normalized.timelineEntryId,
|
|
291
|
+
});
|
|
292
|
+
return Object.freeze(normalized);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function stalePlanError(message) {
|
|
296
|
+
return restoreError(
|
|
297
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.WORKSPACE_STALE,
|
|
298
|
+
message,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function stateDigest(plan, state) {
|
|
303
|
+
return computeCheckpointRestoreDigest(`cc-checkpoint-restore-${state}-v1`, {
|
|
304
|
+
engine: plan.restoreKind,
|
|
305
|
+
scopeIdentity: plan.workspaceBinding.scopeIdentity,
|
|
306
|
+
stateIdentity:
|
|
307
|
+
state === "prestate"
|
|
308
|
+
? plan.workspaceBinding.prestateIdentity
|
|
309
|
+
: plan.workspaceBinding.targetPoststateIdentity,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function validateSagaSnapshot(snapshot, operationId) {
|
|
314
|
+
if (
|
|
315
|
+
!snapshot ||
|
|
316
|
+
typeof snapshot !== "object" ||
|
|
317
|
+
snapshot.operationId !== operationId ||
|
|
318
|
+
!Number.isSafeInteger(snapshot.seq) ||
|
|
319
|
+
snapshot.seq < 1 ||
|
|
320
|
+
!HASH_PATTERN.test(String(snapshot.headHash || "")) ||
|
|
321
|
+
typeof snapshot.phase !== "string"
|
|
322
|
+
) {
|
|
323
|
+
throw restoreError(
|
|
324
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_RESULT,
|
|
325
|
+
"checkpoint restore saga returned an invalid snapshot",
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
return snapshot;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function exactEvidenceMatches(left, right) {
|
|
332
|
+
try {
|
|
333
|
+
return stableValue(left) === stableValue(right);
|
|
334
|
+
} catch {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function attachDiagnostics(error, operationId, saga, recoveryRequired = null) {
|
|
340
|
+
const diagnosed = asError(error);
|
|
341
|
+
diagnosed.checkpointRestoreOperationId ||= operationId;
|
|
342
|
+
diagnosed.checkpointRestoreSagaPhase ||= saga?.phase || null;
|
|
343
|
+
diagnosed.checkpointRestoreSagaSeq ||= saga?.seq || null;
|
|
344
|
+
diagnosed.checkpointRestoreSagaHeadHash ||= saga?.headHash || null;
|
|
345
|
+
if (recoveryRequired !== null) {
|
|
346
|
+
diagnosed.checkpointRestoreRecoveryRequired = recoveryRequired;
|
|
347
|
+
}
|
|
348
|
+
return diagnosed;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function attachSafetyEvidence(error, evidence) {
|
|
352
|
+
const diagnosed = asError(error);
|
|
353
|
+
if (!evidence) return diagnosed;
|
|
354
|
+
diagnosed.safetyId ||= evidence.safetyId || null;
|
|
355
|
+
diagnosed.safetyIdentity ||= evidence.safetyIdentity || null;
|
|
356
|
+
diagnosed.safetyPlanIdentity ||= evidence.safetyPlanIdentity || null;
|
|
357
|
+
diagnosed.safetyCoverage ||= evidence.safetyCoverage || null;
|
|
358
|
+
return diagnosed;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Execute one checkpoint restore under a canonical workspace lock and durable
|
|
363
|
+
* saga. The optional withSessionAuthority callback is required for timeline
|
|
364
|
+
* restores and forbidden for direct restores.
|
|
365
|
+
*
|
|
366
|
+
* @param {object} options
|
|
367
|
+
* @param {object} options.plan immutable pre-lock authority plan
|
|
368
|
+
* @param {Function} options.revalidate sync callback returning the locked plan
|
|
369
|
+
* @param {Function} options.restore sync engine callback; it must use hooks
|
|
370
|
+
* @param {Function} [options.withSessionAuthority] sync timeline settlement
|
|
371
|
+
* @param {string} [options.operationId]
|
|
372
|
+
* @param {object} [options.dependencies]
|
|
373
|
+
* @returns {{operationId:string,result:*,sessionCommitDigest:string|null,
|
|
374
|
+
* saga:{phase:string,seq:number,headHash:string},warnings:string[]}}
|
|
375
|
+
*/
|
|
376
|
+
export function runCheckpointRestoreOperation(options = {}) {
|
|
377
|
+
const dependencies = options.dependencies || {};
|
|
378
|
+
const platform = dependencies.platform || process.platform;
|
|
379
|
+
const initialPlan = normalizePlan(options.plan, platform);
|
|
380
|
+
if (typeof options.revalidate !== "function") {
|
|
381
|
+
throw restoreError(
|
|
382
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
383
|
+
"checkpoint restore revalidate callback is required",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
if (typeof options.restore !== "function") {
|
|
387
|
+
throw restoreError(
|
|
388
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
389
|
+
"checkpoint restore engine callback is required",
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if (
|
|
393
|
+
initialPlan.restoreSurface === "timeline" &&
|
|
394
|
+
typeof options.withSessionAuthority !== "function"
|
|
395
|
+
) {
|
|
396
|
+
throw restoreError(
|
|
397
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
398
|
+
"timeline checkpoint restore requires withSessionAuthority",
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (
|
|
402
|
+
initialPlan.restoreSurface === "direct" &&
|
|
403
|
+
options.withSessionAuthority != null
|
|
404
|
+
) {
|
|
405
|
+
throw restoreError(
|
|
406
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
407
|
+
"direct checkpoint restore cannot use session authority",
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const operationId =
|
|
412
|
+
options.operationId ||
|
|
413
|
+
(
|
|
414
|
+
dependencies.createOperationId ||
|
|
415
|
+
(() => `checkpoint-restore-${randomUUID()}`)
|
|
416
|
+
)();
|
|
417
|
+
if (
|
|
418
|
+
typeof operationId !== "string" ||
|
|
419
|
+
!OPERATION_ID_PATTERN.test(operationId)
|
|
420
|
+
) {
|
|
421
|
+
throw restoreError(
|
|
422
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
423
|
+
"checkpoint restore operationId is invalid",
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
const actorPid = dependencies.actorPid ?? process.pid;
|
|
427
|
+
if (!Number.isSafeInteger(actorPid) || actorPid <= 0) {
|
|
428
|
+
throw restoreError(
|
|
429
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_PLAN,
|
|
430
|
+
"checkpoint restore actorPid is invalid",
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
const createSagaStore =
|
|
434
|
+
dependencies.createSagaStore ||
|
|
435
|
+
((storeOptions) => new CheckpointRestoreSagaStore(storeOptions));
|
|
436
|
+
const withWorkspaceLockSync =
|
|
437
|
+
dependencies.withWorkspaceLockSync || withCanonicalWorkspaceLockSync;
|
|
438
|
+
const workspaceLockOwnerDigest =
|
|
439
|
+
dependencies.computeWorkspaceLockOwnerDigest ||
|
|
440
|
+
computeCheckpointRestoreWorkspaceLockOwnerDigest;
|
|
441
|
+
|
|
442
|
+
let sagaStore = null;
|
|
443
|
+
let saga = null;
|
|
444
|
+
let latestSafetyEvidence = null;
|
|
445
|
+
let sessionCommitDigest = null;
|
|
446
|
+
let callbackEntered = false;
|
|
447
|
+
let deferredFailure = null;
|
|
448
|
+
|
|
449
|
+
const loadSaga = () => {
|
|
450
|
+
const loaded = requireSynchronous(
|
|
451
|
+
sagaStore.load(operationId),
|
|
452
|
+
"checkpoint restore saga load",
|
|
453
|
+
);
|
|
454
|
+
saga = validateSagaSnapshot(loaded, operationId);
|
|
455
|
+
return saga;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const advanceSaga = (phase, evidence) => {
|
|
459
|
+
const prior = saga || loadSaga();
|
|
460
|
+
try {
|
|
461
|
+
const advanced = requireSynchronous(
|
|
462
|
+
sagaStore.advance(operationId, {
|
|
463
|
+
expectedSeq: prior.seq,
|
|
464
|
+
expectedHash: prior.headHash,
|
|
465
|
+
phase,
|
|
466
|
+
evidence,
|
|
467
|
+
}),
|
|
468
|
+
"checkpoint restore saga advance",
|
|
469
|
+
);
|
|
470
|
+
saga = validateSagaSnapshot(advanced, operationId);
|
|
471
|
+
return saga;
|
|
472
|
+
} catch (caught) {
|
|
473
|
+
const error = asError(caught);
|
|
474
|
+
try {
|
|
475
|
+
const observed = loadSaga();
|
|
476
|
+
const latest = observed?.events?.at(-1);
|
|
477
|
+
if (
|
|
478
|
+
observed.seq === prior.seq + 1 &&
|
|
479
|
+
observed.phase === phase &&
|
|
480
|
+
latest?.prevHash === prior.headHash &&
|
|
481
|
+
exactEvidenceMatches(latest.evidence, evidence)
|
|
482
|
+
) {
|
|
483
|
+
return observed;
|
|
484
|
+
}
|
|
485
|
+
} catch (loadError) {
|
|
486
|
+
error.checkpointRestoreSagaLoadError = loadError;
|
|
487
|
+
}
|
|
488
|
+
throw attachDiagnostics(error, operationId, saga);
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const archiveTerminal = () => {
|
|
493
|
+
const archived = requireSynchronous(
|
|
494
|
+
sagaStore.archiveTerminal(operationId, {
|
|
495
|
+
expectedSeq: saga.seq,
|
|
496
|
+
expectedHash: saga.headHash,
|
|
497
|
+
}),
|
|
498
|
+
"checkpoint restore saga archive",
|
|
499
|
+
);
|
|
500
|
+
return archived;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const settleFailure = (caught, workspaceLease) => {
|
|
504
|
+
const operationError = attachSafetyEvidence(
|
|
505
|
+
asError(caught),
|
|
506
|
+
latestSafetyEvidence,
|
|
507
|
+
);
|
|
508
|
+
let sagaLoadFailed = false;
|
|
509
|
+
try {
|
|
510
|
+
loadSaga();
|
|
511
|
+
} catch (loadError) {
|
|
512
|
+
sagaLoadFailed = true;
|
|
513
|
+
operationError.checkpointRestoreSagaLoadError = loadError;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let recoveryRequired =
|
|
517
|
+
sagaLoadFailed ||
|
|
518
|
+
commitStateUnknown(operationError) ||
|
|
519
|
+
operationError.checkpointRestoreSessionIntentCommitted === true ||
|
|
520
|
+
operationError.checkpointRestoreSessionSettled === true ||
|
|
521
|
+
hasPendingSessionIntent(saga) ||
|
|
522
|
+
RECOVERY_PHASES.has(saga?.phase) ||
|
|
523
|
+
operationError.restorePhase === "workspace-mutation" ||
|
|
524
|
+
operationError.restorePhase === "workspace-applied";
|
|
525
|
+
|
|
526
|
+
if (
|
|
527
|
+
!recoveryRequired &&
|
|
528
|
+
saga?.pending &&
|
|
529
|
+
saga.phase !== "recovery_required" &&
|
|
530
|
+
saga.phase !== "recovery_started"
|
|
531
|
+
) {
|
|
532
|
+
try {
|
|
533
|
+
advanceSaga("aborted", {
|
|
534
|
+
reason: boundedText(
|
|
535
|
+
operationError.message,
|
|
536
|
+
"checkpoint restore stopped before workspace mutation",
|
|
537
|
+
),
|
|
538
|
+
});
|
|
539
|
+
} catch (abortError) {
|
|
540
|
+
recoveryRequired = true;
|
|
541
|
+
operationError.checkpointRestoreSagaError = abortError;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (recoveryRequired && !sagaLoadFailed) {
|
|
546
|
+
try {
|
|
547
|
+
if (
|
|
548
|
+
saga?.pending &&
|
|
549
|
+
saga.phase !== "recovery_required" &&
|
|
550
|
+
saga.phase !== "recovery_started"
|
|
551
|
+
) {
|
|
552
|
+
advanceSaga("recovery_required", {
|
|
553
|
+
reason: boundedText(
|
|
554
|
+
operationError.message,
|
|
555
|
+
"checkpoint restore outcome requires recovery",
|
|
556
|
+
),
|
|
557
|
+
errorCode: errorCode(operationError),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
} catch (recoveryError) {
|
|
561
|
+
operationError.checkpointRestoreSagaError = recoveryError;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
attachDiagnostics(operationError, operationId, saga, recoveryRequired);
|
|
566
|
+
if (!recoveryRequired && saga && TERMINAL_PHASES.has(saga.phase)) {
|
|
567
|
+
// When a workspace lease exists, the terminal saga must not move into
|
|
568
|
+
// archive retention until the lifetime lock has been released. Return a
|
|
569
|
+
// sentinel through the callback; the outer scope archives and rethrows
|
|
570
|
+
// only after withWorkspaceLockSync has completed its release boundary.
|
|
571
|
+
if (workspaceLease) {
|
|
572
|
+
deferredFailure = operationError;
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
archiveTerminal();
|
|
577
|
+
} catch (archiveError) {
|
|
578
|
+
operationError.checkpointRestoreSagaArchiveError = archiveError;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (recoveryRequired && workspaceLease) {
|
|
583
|
+
try {
|
|
584
|
+
workspaceLease.retainForRecovery(
|
|
585
|
+
boundedText(
|
|
586
|
+
`${errorCode(operationError)}: ${operationError.message || "restore outcome unknown"}`,
|
|
587
|
+
"checkpoint restore outcome unknown",
|
|
588
|
+
512,
|
|
589
|
+
),
|
|
590
|
+
);
|
|
591
|
+
operationError.workspaceLockRetained = true;
|
|
592
|
+
} catch (retentionCaught) {
|
|
593
|
+
const retentionError = asError(retentionCaught);
|
|
594
|
+
retentionError.checkpointRestoreCause = operationError;
|
|
595
|
+
retentionError.checkpointRestoreOperationId = operationId;
|
|
596
|
+
retentionError.checkpointRestoreSagaPhase = saga?.phase || null;
|
|
597
|
+
retentionError.checkpointRestoreSagaSeq = saga?.seq || null;
|
|
598
|
+
retentionError.checkpointRestoreSagaHeadHash = saga?.headHash || null;
|
|
599
|
+
retentionError.checkpointRestoreRecoveryRequired = true;
|
|
600
|
+
throw retentionError;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
throw operationError;
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
try {
|
|
607
|
+
sagaStore = requireSynchronous(
|
|
608
|
+
createSagaStore({ workspaceRoot: initialPlan.workspaceRoot }),
|
|
609
|
+
"checkpoint restore saga store creation",
|
|
610
|
+
);
|
|
611
|
+
if (
|
|
612
|
+
!sagaStore ||
|
|
613
|
+
typeof sagaStore.create !== "function" ||
|
|
614
|
+
typeof sagaStore.load !== "function" ||
|
|
615
|
+
typeof sagaStore.advance !== "function" ||
|
|
616
|
+
typeof sagaStore.archiveTerminal !== "function"
|
|
617
|
+
) {
|
|
618
|
+
throw restoreError(
|
|
619
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_RESULT,
|
|
620
|
+
"checkpoint restore saga store is invalid",
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
const createdEvidence = {
|
|
624
|
+
restoreKind: initialPlan.restoreKind,
|
|
625
|
+
restoreSurface: initialPlan.restoreSurface,
|
|
626
|
+
checkpointId: initialPlan.checkpointId,
|
|
627
|
+
checkpointIdentity: initialPlan.checkpointIdentity,
|
|
628
|
+
...(initialPlan.checkpointNamespace
|
|
629
|
+
? { checkpointNamespace: initialPlan.checkpointNamespace }
|
|
630
|
+
: {}),
|
|
631
|
+
...(initialPlan.sessionId ? { sessionId: initialPlan.sessionId } : {}),
|
|
632
|
+
...(initialPlan.timelineEntryId
|
|
633
|
+
? { timelineEntryId: initialPlan.timelineEntryId }
|
|
634
|
+
: {}),
|
|
635
|
+
workspaceBinding:
|
|
636
|
+
initialPlan.workspaceBinding.writePlanIdentity ||
|
|
637
|
+
initialPlan.confirmationDigest,
|
|
638
|
+
confirmationDigest: initialPlan.confirmationDigest,
|
|
639
|
+
actorPid,
|
|
640
|
+
};
|
|
641
|
+
saga = validateSagaSnapshot(
|
|
642
|
+
requireSynchronous(
|
|
643
|
+
sagaStore.create({ operationId, evidence: createdEvidence }),
|
|
644
|
+
"checkpoint restore saga creation",
|
|
645
|
+
),
|
|
646
|
+
operationId,
|
|
647
|
+
);
|
|
648
|
+
} catch (caught) {
|
|
649
|
+
throw attachDiagnostics(asError(caught), operationId, saga);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
let result;
|
|
653
|
+
let workspaceLease = null;
|
|
654
|
+
try {
|
|
655
|
+
const lockedResult = withWorkspaceLockSync(
|
|
656
|
+
{
|
|
657
|
+
workspaceRoot: initialPlan.workspaceRoot,
|
|
658
|
+
operationId,
|
|
659
|
+
purpose: "checkpoint-restore",
|
|
660
|
+
},
|
|
661
|
+
(lease) => {
|
|
662
|
+
callbackEntered = true;
|
|
663
|
+
workspaceLease = lease;
|
|
664
|
+
try {
|
|
665
|
+
if (
|
|
666
|
+
!lease ||
|
|
667
|
+
typeof lease.assertOwned !== "function" ||
|
|
668
|
+
typeof lease.retainForRecovery !== "function" ||
|
|
669
|
+
!lease.owner
|
|
670
|
+
) {
|
|
671
|
+
throw restoreError(
|
|
672
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.INVALID_RESULT,
|
|
673
|
+
"workspace lock returned an invalid lease",
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
lease.assertOwned();
|
|
677
|
+
advanceSaga("locked", {
|
|
678
|
+
workspaceLockOwner: lease.owner,
|
|
679
|
+
lockOwnerDigest: workspaceLockOwnerDigest(lease.owner),
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const lockedPlan = normalizePlan(
|
|
683
|
+
requireSynchronous(
|
|
684
|
+
options.revalidate({
|
|
685
|
+
operationId,
|
|
686
|
+
plan: initialPlan,
|
|
687
|
+
workspaceLease: lease,
|
|
688
|
+
}),
|
|
689
|
+
"checkpoint restore revalidation",
|
|
690
|
+
),
|
|
691
|
+
platform,
|
|
692
|
+
);
|
|
693
|
+
if (
|
|
694
|
+
lockedPlan.authorityFingerprint !== initialPlan.authorityFingerprint
|
|
695
|
+
) {
|
|
696
|
+
throw stalePlanError(
|
|
697
|
+
"workspace or checkpoint restore plan changed while waiting for the lock",
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
if (
|
|
701
|
+
!rootsMatch(
|
|
702
|
+
lockedPlan.workspaceRoot,
|
|
703
|
+
lease.canonicalWorkspaceRoot,
|
|
704
|
+
platform,
|
|
705
|
+
)
|
|
706
|
+
) {
|
|
707
|
+
throw stalePlanError(
|
|
708
|
+
"checkpoint restore scope changed while waiting for the lock",
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
advanceSaga("prepared", {
|
|
713
|
+
prestateDigest: stateDigest(lockedPlan, "prestate"),
|
|
714
|
+
targetCount: lockedPlan.targetCount,
|
|
715
|
+
workspaceBinding:
|
|
716
|
+
lockedPlan.workspaceBinding.writePlanIdentity ||
|
|
717
|
+
lockedPlan.confirmationDigest,
|
|
718
|
+
});
|
|
719
|
+
lease.assertOwned();
|
|
720
|
+
|
|
721
|
+
let intentCommitted = false;
|
|
722
|
+
let restoreInvoked = false;
|
|
723
|
+
|
|
724
|
+
const commitIntent = (intentCommitDigest) => {
|
|
725
|
+
lease.assertOwned();
|
|
726
|
+
if (intentCommitted) {
|
|
727
|
+
throw restoreError(
|
|
728
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.BOUNDARY_MISSING,
|
|
729
|
+
"checkpoint restore intent was committed more than once",
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
if (!HASH_PATTERN.test(String(intentCommitDigest || ""))) {
|
|
733
|
+
throw restoreError(
|
|
734
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.BOUNDARY_MISSING,
|
|
735
|
+
"checkpoint restore intent commit digest is invalid",
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
advanceSaga("intent_committed", {
|
|
739
|
+
intentAuthority:
|
|
740
|
+
lockedPlan.restoreSurface === "timeline"
|
|
741
|
+
? "session"
|
|
742
|
+
: "operation",
|
|
743
|
+
intentCommitDigest,
|
|
744
|
+
...(lockedPlan.restoreSurface === "timeline" &&
|
|
745
|
+
lockedPlan.sessionId
|
|
746
|
+
? { sessionId: lockedPlan.sessionId }
|
|
747
|
+
: {}),
|
|
748
|
+
...(lockedPlan.restoreSurface === "timeline" &&
|
|
749
|
+
lockedPlan.timelineEntryId
|
|
750
|
+
? { timelineEntryId: lockedPlan.timelineEntryId }
|
|
751
|
+
: {}),
|
|
752
|
+
});
|
|
753
|
+
intentCommitted = true;
|
|
754
|
+
lease.assertOwned();
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
const hooks = {
|
|
758
|
+
onSafetyReady(evidence = {}) {
|
|
759
|
+
lease.assertOwned();
|
|
760
|
+
if (lockedPlan.targetCount === 0) {
|
|
761
|
+
throw restoreError(
|
|
762
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.BOUNDARY_MISSING,
|
|
763
|
+
"zero-target checkpoint restore cannot create safety evidence",
|
|
764
|
+
{ restorePhase: "safety-ready" },
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
latestSafetyEvidence = {
|
|
768
|
+
safetyId: evidence.safetyId,
|
|
769
|
+
safetyIdentity: evidence.safetyIdentity,
|
|
770
|
+
safetyPlanIdentity: evidence.safetyPlanIdentity,
|
|
771
|
+
safetyCoverage: evidence.safetyCoverage,
|
|
772
|
+
};
|
|
773
|
+
advanceSaga("safety_ready", latestSafetyEvidence);
|
|
774
|
+
lease.assertOwned();
|
|
775
|
+
},
|
|
776
|
+
onMutationStarted(evidence = {}) {
|
|
777
|
+
lease.assertOwned();
|
|
778
|
+
if (
|
|
779
|
+
lockedPlan.targetCount === 0 ||
|
|
780
|
+
evidence.mutationCount !== lockedPlan.targetCount
|
|
781
|
+
) {
|
|
782
|
+
throw restoreError(
|
|
783
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.BOUNDARY_MISSING,
|
|
784
|
+
"checkpoint restore mutation count does not match its prepared plan",
|
|
785
|
+
{ restorePhase: "workspace-mutation" },
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
advanceSaga("mutation_started", {
|
|
789
|
+
targetCount: lockedPlan.targetCount,
|
|
790
|
+
});
|
|
791
|
+
lease.assertOwned();
|
|
792
|
+
},
|
|
793
|
+
onWorkspaceApplied(evidence = {}) {
|
|
794
|
+
lease.assertOwned();
|
|
795
|
+
if (
|
|
796
|
+
!Number.isSafeInteger(evidence.appliedCount) ||
|
|
797
|
+
evidence.appliedCount !== lockedPlan.targetCount
|
|
798
|
+
) {
|
|
799
|
+
throw restoreError(
|
|
800
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.BOUNDARY_MISSING,
|
|
801
|
+
"checkpoint restore applied count does not match its prepared plan",
|
|
802
|
+
{ restorePhase: "workspace-applied" },
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
advanceSaga("workspace_applied", {
|
|
806
|
+
appliedCount: evidence.appliedCount,
|
|
807
|
+
poststateDigest: stateDigest(lockedPlan, "poststate"),
|
|
808
|
+
});
|
|
809
|
+
lease.assertOwned();
|
|
810
|
+
},
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
const restoreWorkspace = () => {
|
|
814
|
+
lease.assertOwned();
|
|
815
|
+
if (!intentCommitted) {
|
|
816
|
+
throw restoreError(
|
|
817
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.BOUNDARY_MISSING,
|
|
818
|
+
"checkpoint restore cannot mutate before durable intent",
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
if (restoreInvoked) {
|
|
822
|
+
throw restoreError(
|
|
823
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.BOUNDARY_MISSING,
|
|
824
|
+
"checkpoint restore engine was invoked more than once",
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
restoreInvoked = true;
|
|
828
|
+
let restored;
|
|
829
|
+
try {
|
|
830
|
+
restored = requireSynchronous(
|
|
831
|
+
options.restore({
|
|
832
|
+
operationId,
|
|
833
|
+
plan: lockedPlan,
|
|
834
|
+
workspaceLease: lease,
|
|
835
|
+
expectedIdentity: lockedPlan.checkpointIdentity,
|
|
836
|
+
expectedWorkspaceBinding: lockedPlan.workspaceBinding,
|
|
837
|
+
targetCount: lockedPlan.targetCount,
|
|
838
|
+
hooks,
|
|
839
|
+
}),
|
|
840
|
+
"checkpoint restore engine",
|
|
841
|
+
);
|
|
842
|
+
} catch (caught) {
|
|
843
|
+
throw attachSafetyEvidence(asError(caught), latestSafetyEvidence);
|
|
844
|
+
}
|
|
845
|
+
if (saga?.phase !== "workspace_applied") {
|
|
846
|
+
throw restoreError(
|
|
847
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.BOUNDARY_MISSING,
|
|
848
|
+
"checkpoint restore engine omitted its durable workspace boundary",
|
|
849
|
+
{ restorePhase: "workspace-applied" },
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
lease.assertOwned();
|
|
853
|
+
return restored;
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
if (lockedPlan.restoreSurface === "timeline") {
|
|
857
|
+
const settled = requireSynchronous(
|
|
858
|
+
options.withSessionAuthority({
|
|
859
|
+
operationId,
|
|
860
|
+
plan: lockedPlan,
|
|
861
|
+
workspaceLease: lease,
|
|
862
|
+
commitIntent,
|
|
863
|
+
restoreWorkspace,
|
|
864
|
+
}),
|
|
865
|
+
"checkpoint restore session authority",
|
|
866
|
+
);
|
|
867
|
+
if (
|
|
868
|
+
!settled ||
|
|
869
|
+
typeof settled !== "object" ||
|
|
870
|
+
!Object.hasOwn(settled, "result") ||
|
|
871
|
+
!HASH_PATTERN.test(String(settled.sessionCommitDigest || ""))
|
|
872
|
+
) {
|
|
873
|
+
throw restoreError(
|
|
874
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.SESSION_BOUNDARY_MISSING,
|
|
875
|
+
"checkpoint restore session settlement is unavailable",
|
|
876
|
+
{ commitState: "unknown" },
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
// A successful adapter return proves that its session transaction
|
|
880
|
+
// settled. Any missing workspace boundary after that point is not
|
|
881
|
+
// a safe pre-mutation abort: a durable session intent may exist.
|
|
882
|
+
// Preserve the workspace lock so recovery can reconcile both
|
|
883
|
+
// authorities instead of silently archiving the operation.
|
|
884
|
+
if (saga?.phase !== "workspace_applied") {
|
|
885
|
+
const boundaryError = restoreError(
|
|
886
|
+
CHECKPOINT_RESTORE_ORCHESTRATOR_ERROR_CODES.BOUNDARY_MISSING,
|
|
887
|
+
"checkpoint restore session authority omitted workspace settlement",
|
|
888
|
+
);
|
|
889
|
+
boundaryError.checkpointRestoreSessionSettled = true;
|
|
890
|
+
throw boundaryError;
|
|
891
|
+
}
|
|
892
|
+
sessionCommitDigest = settled.sessionCommitDigest;
|
|
893
|
+
lease.assertOwned();
|
|
894
|
+
advanceSaga("session_committed", { sessionCommitDigest });
|
|
895
|
+
result = settled.result;
|
|
896
|
+
} else {
|
|
897
|
+
commitIntent(
|
|
898
|
+
computeCheckpointRestoreDigest(
|
|
899
|
+
"cc-checkpoint-restore-operation-intent-v1",
|
|
900
|
+
{
|
|
901
|
+
operationId,
|
|
902
|
+
restoreKind: lockedPlan.restoreKind,
|
|
903
|
+
checkpointId: lockedPlan.checkpointId,
|
|
904
|
+
checkpointIdentity: lockedPlan.checkpointIdentity,
|
|
905
|
+
checkpointNamespace: lockedPlan.checkpointNamespace,
|
|
906
|
+
workspaceBinding:
|
|
907
|
+
lockedPlan.workspaceBinding.writePlanIdentity,
|
|
908
|
+
confirmationDigest: lockedPlan.confirmationDigest,
|
|
909
|
+
targetCount: lockedPlan.targetCount,
|
|
910
|
+
},
|
|
911
|
+
),
|
|
912
|
+
);
|
|
913
|
+
result = restoreWorkspace();
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
lease.assertOwned();
|
|
917
|
+
advanceSaga("completed", {
|
|
918
|
+
resultDigest: computeCheckpointRestoreDigest(
|
|
919
|
+
"cc-checkpoint-restore-result-v1",
|
|
920
|
+
{
|
|
921
|
+
operationId,
|
|
922
|
+
restoreSurface: lockedPlan.restoreSurface,
|
|
923
|
+
sessionCommitDigest,
|
|
924
|
+
result,
|
|
925
|
+
},
|
|
926
|
+
),
|
|
927
|
+
});
|
|
928
|
+
lease.assertOwned();
|
|
929
|
+
return result;
|
|
930
|
+
} catch (caught) {
|
|
931
|
+
return settleFailure(caught, lease);
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
);
|
|
935
|
+
requireSynchronous(lockedResult, "checkpoint restore workspace lock");
|
|
936
|
+
if (deferredFailure) {
|
|
937
|
+
try {
|
|
938
|
+
archiveTerminal();
|
|
939
|
+
} catch (archiveError) {
|
|
940
|
+
deferredFailure.checkpointRestoreSagaArchiveError = archiveError;
|
|
941
|
+
}
|
|
942
|
+
throw deferredFailure;
|
|
943
|
+
}
|
|
944
|
+
} catch (caught) {
|
|
945
|
+
if (callbackEntered) {
|
|
946
|
+
const callbackError = attachDiagnostics(
|
|
947
|
+
asError(caught),
|
|
948
|
+
operationId,
|
|
949
|
+
saga,
|
|
950
|
+
);
|
|
951
|
+
if (deferredFailure && callbackError !== deferredFailure) {
|
|
952
|
+
callbackError.checkpointRestoreCause ||= deferredFailure;
|
|
953
|
+
callbackError.checkpointRestoreRecoveryRequired = true;
|
|
954
|
+
} else if (saga?.terminal && callbackError !== deferredFailure) {
|
|
955
|
+
// A terminal callback returned but the workspace lifetime boundary
|
|
956
|
+
// itself failed. The saga is authoritative, but its lock release is
|
|
957
|
+
// uncertain and must remain visible to recovery tooling.
|
|
958
|
+
callbackError.checkpointRestoreRecoveryRequired = true;
|
|
959
|
+
}
|
|
960
|
+
throw callbackError;
|
|
961
|
+
}
|
|
962
|
+
return settleFailure(caught, workspaceLease);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
let archiveWarning = null;
|
|
966
|
+
try {
|
|
967
|
+
archiveTerminal();
|
|
968
|
+
} catch (archiveError) {
|
|
969
|
+
archiveWarning = boundedText(
|
|
970
|
+
`restore completed, but its saga archive is pending: ${archiveError?.code || archiveError?.message || "archive failed"}`,
|
|
971
|
+
"restore completed, but its saga archive is pending",
|
|
972
|
+
512,
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return {
|
|
977
|
+
operationId,
|
|
978
|
+
result,
|
|
979
|
+
sessionCommitDigest,
|
|
980
|
+
saga: {
|
|
981
|
+
phase: saga.phase,
|
|
982
|
+
seq: saga.seq,
|
|
983
|
+
headHash: saga.headHash,
|
|
984
|
+
},
|
|
985
|
+
warnings: archiveWarning ? [archiveWarning] : [],
|
|
986
|
+
};
|
|
987
|
+
}
|