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,1178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verified session reconciliation for checkpoint restores.
|
|
3
|
+
*
|
|
4
|
+
* The read path injects `readVerifiedProjection`, performs one forward fold,
|
|
5
|
+
* and retains only a constant-size projection for the requested operationId.
|
|
6
|
+
* It never calls readMessages(), findLatestEvent(), or a compatibility
|
|
7
|
+
* all-events reader. A separate helper may append one strictly validated
|
|
8
|
+
* rollback resolution through a caller-owned synchronous authority
|
|
9
|
+
* transaction; this module does not acquire or reorder locks itself.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import {
|
|
14
|
+
CHECKPOINT_RESTORE_RECOVERY_RESOLUTION_EVENT,
|
|
15
|
+
CHECKPOINT_TIMELINE_AUDIT_EVENT,
|
|
16
|
+
CHECKPOINT_TIMELINE_INTENT_EVENT,
|
|
17
|
+
} from "./checkpoint-timeline-authority.js";
|
|
18
|
+
import { TURN_BINDING_TIMELINE_EVENT } from "./turn-binding-store.js";
|
|
19
|
+
|
|
20
|
+
export const CHECKPOINT_RESTORE_SESSION_RECOVERY_SCHEMA =
|
|
21
|
+
"chainlesschain.checkpoint-restore-session-recovery";
|
|
22
|
+
export const CHECKPOINT_RESTORE_SESSION_RECOVERY_VERSION = 1;
|
|
23
|
+
export const CHECKPOINT_RESTORE_RECOVERY_RESOLUTION_SCHEMA =
|
|
24
|
+
"chainlesschain.checkpoint-restore-recovery-resolution";
|
|
25
|
+
export const CHECKPOINT_RESTORE_RECOVERY_RESOLUTION_VERSION = 1;
|
|
26
|
+
export const CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ACTION =
|
|
27
|
+
"rollback-partial-mutation";
|
|
28
|
+
export const CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_OUTCOME = "rolled_back";
|
|
29
|
+
export const CHECKPOINT_RESTORE_ROLLBACK_CONVERSATION_DISPOSITION =
|
|
30
|
+
"not-committed";
|
|
31
|
+
|
|
32
|
+
export const CHECKPOINT_RESTORE_SESSION_RECONCILIATION = Object.freeze({
|
|
33
|
+
NO_SESSION_DIRECT: "no-session/direct",
|
|
34
|
+
CLEAN_ABORT: "clean-abort",
|
|
35
|
+
CODE_SETTLEMENT_RESUMABLE: "code-settlement-resumable",
|
|
36
|
+
BOTH_SETTLEMENT_RESUMABLE: "both-settlement-resumable",
|
|
37
|
+
ALREADY_COMPLETED: "already-completed",
|
|
38
|
+
ROLLED_BACK: "rolled-back",
|
|
39
|
+
CONFLICT_UNKNOWN: "conflict/unknown",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES = Object.freeze({
|
|
43
|
+
INVALID_ARGUMENT: "CHECKPOINT_RESTORE_SESSION_RESOLUTION_INVALID_ARGUMENT",
|
|
44
|
+
HEAD_CONFLICT: "CHECKPOINT_RESTORE_SESSION_RESOLUTION_HEAD_CONFLICT",
|
|
45
|
+
APPEND_UNVERIFIED: "CHECKPOINT_RESTORE_SESSION_RESOLUTION_APPEND_UNVERIFIED",
|
|
46
|
+
ASYNC_UNSUPPORTED: "CHECKPOINT_RESTORE_SESSION_RESOLUTION_ASYNC_UNSUPPORTED",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export class CheckpointRestoreSessionResolutionError extends Error {
|
|
50
|
+
constructor(code, message, details = {}, cause = null) {
|
|
51
|
+
super(message, cause ? { cause } : undefined);
|
|
52
|
+
this.name = "CheckpointRestoreSessionResolutionError";
|
|
53
|
+
this.code = code;
|
|
54
|
+
Object.assign(this, details);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
59
|
+
const RAW_EVENT_HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
60
|
+
const OPERATION_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/;
|
|
61
|
+
const ACTIONS = new Set(["restore-code", "restore-both"]);
|
|
62
|
+
const AUDIT_STATUSES = new Set(["completed", "failed"]);
|
|
63
|
+
const FAILED_WORKSPACE_STATES = new Set(["unchanged", "unknown"]);
|
|
64
|
+
const MAX_TEXT = 4_096;
|
|
65
|
+
const RESOLUTION_INPUT_FIELDS = Object.freeze([
|
|
66
|
+
"operationId",
|
|
67
|
+
"recoveryRequestId",
|
|
68
|
+
"action",
|
|
69
|
+
"recoveryAction",
|
|
70
|
+
"outcome",
|
|
71
|
+
"intentEventHash",
|
|
72
|
+
"intentCommitDigest",
|
|
73
|
+
"failedAuditEventHash",
|
|
74
|
+
"conversationDisposition",
|
|
75
|
+
"checkpointId",
|
|
76
|
+
"checkpointIdentity",
|
|
77
|
+
"workspaceScopeIdentity",
|
|
78
|
+
"workspaceWritePlanIdentity",
|
|
79
|
+
"safetyId",
|
|
80
|
+
"safetyIdentity",
|
|
81
|
+
"safetyPlanIdentity",
|
|
82
|
+
"rollbackPlanIdentity",
|
|
83
|
+
"rollbackStateDigest",
|
|
84
|
+
"sagaWorkspaceRolledBackHash",
|
|
85
|
+
]);
|
|
86
|
+
const RESOLUTION_DATA_FIELDS = Object.freeze([
|
|
87
|
+
"schema",
|
|
88
|
+
"version",
|
|
89
|
+
...RESOLUTION_INPUT_FIELDS,
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
const REQUIRED_EVIDENCE = Object.freeze({
|
|
93
|
+
[CHECKPOINT_RESTORE_SESSION_RECONCILIATION.NO_SESSION_DIRECT]: Object.freeze([
|
|
94
|
+
"exact_saga_head",
|
|
95
|
+
"exact_workspace_owner_or_absence",
|
|
96
|
+
"verified_workspace_state",
|
|
97
|
+
]),
|
|
98
|
+
[CHECKPOINT_RESTORE_SESSION_RECONCILIATION.CLEAN_ABORT]: Object.freeze([
|
|
99
|
+
"exact_saga_head",
|
|
100
|
+
"exact_workspace_owner_or_absence",
|
|
101
|
+
"verified_transcript_head_cas",
|
|
102
|
+
"operation_events_still_absent",
|
|
103
|
+
]),
|
|
104
|
+
[CHECKPOINT_RESTORE_SESSION_RECONCILIATION.CODE_SETTLEMENT_RESUMABLE]:
|
|
105
|
+
Object.freeze([
|
|
106
|
+
"exact_saga_head",
|
|
107
|
+
"exact_workspace_owner",
|
|
108
|
+
"verified_workspace_target_state",
|
|
109
|
+
"verified_intent_commit_digest",
|
|
110
|
+
"verified_transcript_head_cas",
|
|
111
|
+
"durable_recovery_resolution_event",
|
|
112
|
+
"verified_recovery_settlement_hash",
|
|
113
|
+
]),
|
|
114
|
+
[CHECKPOINT_RESTORE_SESSION_RECONCILIATION.BOTH_SETTLEMENT_RESUMABLE]:
|
|
115
|
+
Object.freeze([
|
|
116
|
+
"exact_saga_head",
|
|
117
|
+
"exact_workspace_owner",
|
|
118
|
+
"verified_workspace_target_state",
|
|
119
|
+
"verified_intent_commit_digest",
|
|
120
|
+
"verified_transcript_head_cas",
|
|
121
|
+
"historical_conversation_and_binding_plan",
|
|
122
|
+
"exact_conversation_commit_or_reconciliation",
|
|
123
|
+
"durable_recovery_resolution_event",
|
|
124
|
+
"verified_recovery_settlement_hash",
|
|
125
|
+
]),
|
|
126
|
+
[CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ALREADY_COMPLETED]: Object.freeze([
|
|
127
|
+
"exact_saga_head",
|
|
128
|
+
"exact_workspace_owner",
|
|
129
|
+
"verified_workspace_target_state",
|
|
130
|
+
"verified_completed_audit_hash",
|
|
131
|
+
"verified_session_commit_digest",
|
|
132
|
+
]),
|
|
133
|
+
[CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ROLLED_BACK]: Object.freeze([
|
|
134
|
+
"exact_saga_head",
|
|
135
|
+
"exact_workspace_owner",
|
|
136
|
+
"verified_workspace_safety_state",
|
|
137
|
+
"verified_intent_commit_digest",
|
|
138
|
+
"verified_recovery_resolution_tail",
|
|
139
|
+
"verified_session_rollback_commit_digest",
|
|
140
|
+
]),
|
|
141
|
+
[CHECKPOINT_RESTORE_SESSION_RECONCILIATION.CONFLICT_UNKNOWN]: Object.freeze([
|
|
142
|
+
"manual_adjudication",
|
|
143
|
+
"verified_transcript_repair_or_authority_restoration",
|
|
144
|
+
]),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
function isPlainObject(value) {
|
|
148
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const prototype = Object.getPrototypeOf(value);
|
|
152
|
+
return prototype === Object.prototype || prototype === null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function exactKeys(value, fields) {
|
|
156
|
+
if (!isPlainObject(value)) return false;
|
|
157
|
+
const actual = Object.keys(value).sort();
|
|
158
|
+
const expected = [...fields].sort();
|
|
159
|
+
return (
|
|
160
|
+
actual.length === expected.length &&
|
|
161
|
+
actual.every((field, index) => field === expected[index])
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function resolutionError(code, message, details = {}, cause = null) {
|
|
166
|
+
return new CheckpointRestoreSessionResolutionError(
|
|
167
|
+
code,
|
|
168
|
+
message,
|
|
169
|
+
details,
|
|
170
|
+
cause,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function boundedText(value, maximum = MAX_TEXT) {
|
|
175
|
+
if (
|
|
176
|
+
typeof value !== "string" ||
|
|
177
|
+
value.length === 0 ||
|
|
178
|
+
value.length > maximum
|
|
179
|
+
) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
183
|
+
const code = value.charCodeAt(index);
|
|
184
|
+
if (code < 32 || code === 127) return false;
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function boundedTrimmedText(value, maximum = MAX_TEXT) {
|
|
190
|
+
return boundedText(value, maximum) && value.trim() === value;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function appendOutcomeUnknown(cause) {
|
|
194
|
+
let originalCode = null;
|
|
195
|
+
try {
|
|
196
|
+
originalCode =
|
|
197
|
+
typeof cause?.code === "string" && cause.code.length > 0
|
|
198
|
+
? cause.code
|
|
199
|
+
: null;
|
|
200
|
+
} catch {
|
|
201
|
+
// A hostile error value must not weaken the conservative outcome.
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (
|
|
205
|
+
originalCode &&
|
|
206
|
+
cause !== null &&
|
|
207
|
+
(typeof cause === "object" || typeof cause === "function")
|
|
208
|
+
) {
|
|
209
|
+
try {
|
|
210
|
+
cause.commitState = "unknown";
|
|
211
|
+
if (cause.commitState === "unknown") return cause;
|
|
212
|
+
} catch {
|
|
213
|
+
// Fall through to an immutable wrapper when the original is frozen.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return resolutionError(
|
|
218
|
+
originalCode ||
|
|
219
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.APPEND_UNVERIFIED,
|
|
220
|
+
"Session rollback resolution append outcome is unknown",
|
|
221
|
+
{
|
|
222
|
+
commitState: "unknown",
|
|
223
|
+
...(originalCode
|
|
224
|
+
? {
|
|
225
|
+
resolutionErrorCode:
|
|
226
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.APPEND_UNVERIFIED,
|
|
227
|
+
}
|
|
228
|
+
: {}),
|
|
229
|
+
},
|
|
230
|
+
cause,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function immutable(value) {
|
|
235
|
+
if (Array.isArray(value)) {
|
|
236
|
+
return Object.freeze(value.map((entry) => immutable(entry)));
|
|
237
|
+
}
|
|
238
|
+
if (isPlainObject(value)) {
|
|
239
|
+
const result = {};
|
|
240
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
241
|
+
result[key] = immutable(entry);
|
|
242
|
+
}
|
|
243
|
+
return Object.freeze(result);
|
|
244
|
+
}
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function canonicalJson(value) {
|
|
249
|
+
if (value === null) return "null";
|
|
250
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
251
|
+
return JSON.stringify(value);
|
|
252
|
+
}
|
|
253
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
254
|
+
return JSON.stringify(value);
|
|
255
|
+
}
|
|
256
|
+
if (Array.isArray(value)) {
|
|
257
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
|
|
258
|
+
}
|
|
259
|
+
if (isPlainObject(value)) {
|
|
260
|
+
return `{${Object.keys(value)
|
|
261
|
+
.sort()
|
|
262
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
263
|
+
.join(",")}}`;
|
|
264
|
+
}
|
|
265
|
+
throw new TypeError("checkpoint restore session evidence is not canonical");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function digest(domain, value) {
|
|
269
|
+
return `sha256:${createHash("sha256")
|
|
270
|
+
.update(`${domain}\0${canonicalJson(value)}`)
|
|
271
|
+
.digest("hex")}`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function intentCommitDigest(eventHash) {
|
|
275
|
+
return digest("cc-checkpoint-restore-intent-commit-v1", eventHash);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function sessionCommitDigest(eventHash) {
|
|
279
|
+
return digest("cc-checkpoint-restore-session-commit-v1", eventHash);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function computeCheckpointRestoreSessionRollbackCommitDigest(eventHash) {
|
|
283
|
+
if (!RAW_EVENT_HASH_PATTERN.test(String(eventHash || ""))) {
|
|
284
|
+
throw resolutionError(
|
|
285
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.INVALID_ARGUMENT,
|
|
286
|
+
"Session rollback commit requires a verified transcript event hash",
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return digest("cc-checkpoint-restore-session-rollback-commit-v1", eventHash);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function addIssue(state, issue) {
|
|
293
|
+
if (!state.issueSet.has(issue)) {
|
|
294
|
+
state.issueSet.add(issue);
|
|
295
|
+
state.issues.push(issue);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function validateOptions(options, dependencies) {
|
|
300
|
+
if (!isPlainObject(options)) {
|
|
301
|
+
throw new TypeError("checkpoint restore session recovery options required");
|
|
302
|
+
}
|
|
303
|
+
if (
|
|
304
|
+
typeof options.operationId !== "string" ||
|
|
305
|
+
!OPERATION_ID_PATTERN.test(options.operationId)
|
|
306
|
+
) {
|
|
307
|
+
throw new TypeError("operationId must be a safe checkpoint restore id");
|
|
308
|
+
}
|
|
309
|
+
if (!new Set(["direct", "timeline"]).has(options.restoreSurface)) {
|
|
310
|
+
throw new TypeError("restoreSurface must be direct or timeline");
|
|
311
|
+
}
|
|
312
|
+
if (
|
|
313
|
+
options.restoreSurface === "timeline" &&
|
|
314
|
+
!boundedText(options.sessionId, 256)
|
|
315
|
+
) {
|
|
316
|
+
throw new TypeError("timeline recovery requires a bounded sessionId");
|
|
317
|
+
}
|
|
318
|
+
if (options.restoreSurface === "direct" && options.sessionId != null) {
|
|
319
|
+
throw new TypeError("direct recovery cannot claim session authority");
|
|
320
|
+
}
|
|
321
|
+
if (options.expectedAction != null && !ACTIONS.has(options.expectedAction)) {
|
|
322
|
+
throw new TypeError("expectedAction is not a recoverable timeline action");
|
|
323
|
+
}
|
|
324
|
+
for (const field of [
|
|
325
|
+
"expectedIntentCommitDigest",
|
|
326
|
+
"expectedSessionCommitDigest",
|
|
327
|
+
"expectedSessionRollbackCommitDigest",
|
|
328
|
+
]) {
|
|
329
|
+
if (options[field] != null && !DIGEST_PATTERN.test(options[field])) {
|
|
330
|
+
throw new TypeError(`${field} must be a SHA-256 digest`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (
|
|
334
|
+
options.expectedSessionCommitDigest != null &&
|
|
335
|
+
options.expectedSessionRollbackCommitDigest != null
|
|
336
|
+
) {
|
|
337
|
+
throw new TypeError(
|
|
338
|
+
"completed and rolled-back session commit expectations are exclusive",
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
for (const field of [
|
|
342
|
+
"expectedTimelineEntryId",
|
|
343
|
+
"expectedCheckpointId",
|
|
344
|
+
"expectedCheckpointIdentity",
|
|
345
|
+
"expectedConfirmationDigest",
|
|
346
|
+
]) {
|
|
347
|
+
if (options[field] != null && !boundedText(options[field])) {
|
|
348
|
+
throw new TypeError(`${field} must be bounded text`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (
|
|
352
|
+
options.expectedConfirmationDigest != null &&
|
|
353
|
+
!DIGEST_PATTERN.test(options.expectedConfirmationDigest)
|
|
354
|
+
) {
|
|
355
|
+
throw new TypeError("expectedConfirmationDigest must be a SHA-256 digest");
|
|
356
|
+
}
|
|
357
|
+
const reader = dependencies?.readVerifiedProjection;
|
|
358
|
+
if (options.restoreSurface === "timeline" && typeof reader !== "function") {
|
|
359
|
+
throw new TypeError("readVerifiedProjection dependency is required");
|
|
360
|
+
}
|
|
361
|
+
if (
|
|
362
|
+
options.projectionOptions != null &&
|
|
363
|
+
!isPlainObject(options.projectionOptions)
|
|
364
|
+
) {
|
|
365
|
+
throw new TypeError("projectionOptions must be a plain object");
|
|
366
|
+
}
|
|
367
|
+
return reader;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function validStateIdentity(value) {
|
|
371
|
+
return (
|
|
372
|
+
DIGEST_PATTERN.test(String(value || "")) ||
|
|
373
|
+
/^git-tree:[a-f0-9]{40,64}$/u.test(String(value || ""))
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function validateResolutionData(value) {
|
|
378
|
+
if (
|
|
379
|
+
!exactKeys(value, RESOLUTION_DATA_FIELDS) ||
|
|
380
|
+
value.schema !== CHECKPOINT_RESTORE_RECOVERY_RESOLUTION_SCHEMA ||
|
|
381
|
+
value.version !== CHECKPOINT_RESTORE_RECOVERY_RESOLUTION_VERSION ||
|
|
382
|
+
!OPERATION_ID_PATTERN.test(String(value.operationId || "")) ||
|
|
383
|
+
!boundedTrimmedText(value.recoveryRequestId, 256) ||
|
|
384
|
+
!ACTIONS.has(value.action) ||
|
|
385
|
+
value.recoveryAction !== CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_ACTION ||
|
|
386
|
+
value.outcome !== CHECKPOINT_RESTORE_PARTIAL_ROLLBACK_OUTCOME ||
|
|
387
|
+
!RAW_EVENT_HASH_PATTERN.test(String(value.intentEventHash || "")) ||
|
|
388
|
+
!DIGEST_PATTERN.test(String(value.intentCommitDigest || "")) ||
|
|
389
|
+
value.intentCommitDigest !== intentCommitDigest(value.intentEventHash) ||
|
|
390
|
+
!(
|
|
391
|
+
value.failedAuditEventHash === null ||
|
|
392
|
+
RAW_EVENT_HASH_PATTERN.test(String(value.failedAuditEventHash || ""))
|
|
393
|
+
) ||
|
|
394
|
+
value.conversationDisposition !==
|
|
395
|
+
CHECKPOINT_RESTORE_ROLLBACK_CONVERSATION_DISPOSITION ||
|
|
396
|
+
!boundedText(value.checkpointId, 256) ||
|
|
397
|
+
!boundedText(value.checkpointIdentity, 1_024) ||
|
|
398
|
+
!DIGEST_PATTERN.test(String(value.workspaceScopeIdentity || "")) ||
|
|
399
|
+
!DIGEST_PATTERN.test(String(value.workspaceWritePlanIdentity || "")) ||
|
|
400
|
+
!boundedText(value.safetyId, 256) ||
|
|
401
|
+
!boundedText(value.safetyIdentity, 1_024) ||
|
|
402
|
+
!DIGEST_PATTERN.test(String(value.safetyPlanIdentity || "")) ||
|
|
403
|
+
!DIGEST_PATTERN.test(String(value.rollbackPlanIdentity || "")) ||
|
|
404
|
+
!DIGEST_PATTERN.test(String(value.rollbackStateDigest || "")) ||
|
|
405
|
+
!DIGEST_PATTERN.test(String(value.sagaWorkspaceRolledBackHash || ""))
|
|
406
|
+
) {
|
|
407
|
+
throw resolutionError(
|
|
408
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.INVALID_ARGUMENT,
|
|
409
|
+
"Checkpoint restore recovery resolution authority is invalid",
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
return immutable(value);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function buildCheckpointRestoreRecoveryResolution(input = {}) {
|
|
416
|
+
if (!exactKeys(input, RESOLUTION_INPUT_FIELDS)) {
|
|
417
|
+
throw resolutionError(
|
|
418
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.INVALID_ARGUMENT,
|
|
419
|
+
"Checkpoint restore recovery resolution fields are invalid",
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
return validateResolutionData({
|
|
423
|
+
schema: CHECKPOINT_RESTORE_RECOVERY_RESOLUTION_SCHEMA,
|
|
424
|
+
version: CHECKPOINT_RESTORE_RECOVERY_RESOLUTION_VERSION,
|
|
425
|
+
...input,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function sameResolutionData(left, right) {
|
|
430
|
+
try {
|
|
431
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
432
|
+
} catch {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Append one rollback settlement through an already-open synchronous session
|
|
439
|
+
* authority transaction. The caller owns transaction lock ordering.
|
|
440
|
+
*/
|
|
441
|
+
export function appendCheckpointRestoreRecoveryResolution(
|
|
442
|
+
transaction,
|
|
443
|
+
input,
|
|
444
|
+
{ expectedHeadHash } = {},
|
|
445
|
+
) {
|
|
446
|
+
const resolution = buildCheckpointRestoreRecoveryResolution(input);
|
|
447
|
+
if (
|
|
448
|
+
!transaction ||
|
|
449
|
+
typeof transaction.currentHeadHash !== "function" ||
|
|
450
|
+
typeof transaction.appendAuthorityEvent !== "function" ||
|
|
451
|
+
!RAW_EVENT_HASH_PATTERN.test(String(expectedHeadHash || ""))
|
|
452
|
+
) {
|
|
453
|
+
throw resolutionError(
|
|
454
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.INVALID_ARGUMENT,
|
|
455
|
+
"A synchronous session transaction and exact head are required",
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
const currentHeadHash = transaction.currentHeadHash();
|
|
459
|
+
if (
|
|
460
|
+
currentHeadHash &&
|
|
461
|
+
typeof currentHeadHash === "object" &&
|
|
462
|
+
typeof currentHeadHash.then === "function"
|
|
463
|
+
) {
|
|
464
|
+
throw resolutionError(
|
|
465
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.ASYNC_UNSUPPORTED,
|
|
466
|
+
"Session transaction head verification must be synchronous",
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
if (currentHeadHash !== expectedHeadHash) {
|
|
470
|
+
throw resolutionError(
|
|
471
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.HEAD_CONFLICT,
|
|
472
|
+
"Session transaction head changed before rollback settlement",
|
|
473
|
+
{ expectedHeadHash, actualHeadHash: currentHeadHash ?? null },
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
let appended;
|
|
478
|
+
try {
|
|
479
|
+
appended = transaction.appendAuthorityEvent(
|
|
480
|
+
CHECKPOINT_RESTORE_RECOVERY_RESOLUTION_EVENT,
|
|
481
|
+
resolution,
|
|
482
|
+
);
|
|
483
|
+
} catch (cause) {
|
|
484
|
+
throw appendOutcomeUnknown(cause);
|
|
485
|
+
}
|
|
486
|
+
if (
|
|
487
|
+
appended &&
|
|
488
|
+
typeof appended === "object" &&
|
|
489
|
+
typeof appended.then === "function"
|
|
490
|
+
) {
|
|
491
|
+
throw resolutionError(
|
|
492
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.ASYNC_UNSUPPORTED,
|
|
493
|
+
"Session rollback resolution append must be synchronous",
|
|
494
|
+
{ commitState: "unknown" },
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
const event = appended?.event;
|
|
498
|
+
const eventHash = appended?.hash;
|
|
499
|
+
let settledHeadHash;
|
|
500
|
+
try {
|
|
501
|
+
settledHeadHash = transaction.currentHeadHash();
|
|
502
|
+
} catch (cause) {
|
|
503
|
+
throw resolutionError(
|
|
504
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.APPEND_UNVERIFIED,
|
|
505
|
+
"Session rollback resolution append could not be verified",
|
|
506
|
+
{ commitState: "unknown" },
|
|
507
|
+
cause,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
if (
|
|
511
|
+
(settledHeadHash &&
|
|
512
|
+
typeof settledHeadHash === "object" &&
|
|
513
|
+
typeof settledHeadHash.then === "function") ||
|
|
514
|
+
!RAW_EVENT_HASH_PATTERN.test(String(eventHash || "")) ||
|
|
515
|
+
settledHeadHash !== eventHash ||
|
|
516
|
+
!isPlainObject(event) ||
|
|
517
|
+
event.type !== CHECKPOINT_RESTORE_RECOVERY_RESOLUTION_EVENT ||
|
|
518
|
+
event.hash !== eventHash ||
|
|
519
|
+
event.prevHash !== expectedHeadHash ||
|
|
520
|
+
!sameResolutionData(event.data, resolution)
|
|
521
|
+
) {
|
|
522
|
+
throw resolutionError(
|
|
523
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.APPEND_UNVERIFIED,
|
|
524
|
+
"Session rollback resolution append did not return exact authority",
|
|
525
|
+
{ commitState: "unknown" },
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
return immutable({
|
|
529
|
+
eventHash,
|
|
530
|
+
prevHash: expectedHeadHash,
|
|
531
|
+
sessionRollbackCommitDigest:
|
|
532
|
+
computeCheckpointRestoreSessionRollbackCommitDigest(eventHash),
|
|
533
|
+
resolution,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Resolve an unknown append response from one verified read projection. A
|
|
539
|
+
* mismatch returns null and must never authorize another blind append.
|
|
540
|
+
*/
|
|
541
|
+
export function reconcileCheckpointRestoreRecoveryResolutionProjection(
|
|
542
|
+
projection,
|
|
543
|
+
input,
|
|
544
|
+
options = {},
|
|
545
|
+
) {
|
|
546
|
+
const expected = buildCheckpointRestoreRecoveryResolution(input);
|
|
547
|
+
const expectedHeadHash = options?.expectedHeadHash;
|
|
548
|
+
const expectedSessionId = options?.expectedSessionId;
|
|
549
|
+
if (
|
|
550
|
+
!isPlainObject(options) ||
|
|
551
|
+
!RAW_EVENT_HASH_PATTERN.test(String(expectedHeadHash || "")) ||
|
|
552
|
+
!boundedText(expectedSessionId, 256)
|
|
553
|
+
) {
|
|
554
|
+
throw resolutionError(
|
|
555
|
+
CHECKPOINT_RESTORE_SESSION_RESOLUTION_ERROR_CODES.INVALID_ARGUMENT,
|
|
556
|
+
"Resolution reconciliation requires the exact session and pre-append head",
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
const candidate = projection?.resolution;
|
|
560
|
+
const eventHash = candidate?.eventHash;
|
|
561
|
+
const expectedCommitDigest = RAW_EVENT_HASH_PATTERN.test(
|
|
562
|
+
String(eventHash || ""),
|
|
563
|
+
)
|
|
564
|
+
? computeCheckpointRestoreSessionRollbackCommitDigest(eventHash)
|
|
565
|
+
: null;
|
|
566
|
+
if (
|
|
567
|
+
!isPlainObject(projection) ||
|
|
568
|
+
projection.schema !== CHECKPOINT_RESTORE_SESSION_RECOVERY_SCHEMA ||
|
|
569
|
+
projection.version !== CHECKPOINT_RESTORE_SESSION_RECOVERY_VERSION ||
|
|
570
|
+
projection.restoreSurface !== "timeline" ||
|
|
571
|
+
projection.sessionId !== expectedSessionId ||
|
|
572
|
+
projection.operationId !== expected.operationId ||
|
|
573
|
+
projection.classification !==
|
|
574
|
+
CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ROLLED_BACK ||
|
|
575
|
+
projection.failClosed !== false ||
|
|
576
|
+
!Array.isArray(projection.issues) ||
|
|
577
|
+
projection.issues.length !== 0 ||
|
|
578
|
+
projection.conversationCommit !== null ||
|
|
579
|
+
projection.audit?.completed !== null ||
|
|
580
|
+
(projection.audit?.failed?.eventHash ?? null) !==
|
|
581
|
+
expected.failedAuditEventHash ||
|
|
582
|
+
projection.intent?.eventHash !== expected.intentEventHash ||
|
|
583
|
+
projection.intent?.intentCommitDigest !== expected.intentCommitDigest ||
|
|
584
|
+
!Number.isSafeInteger(candidate?.index) ||
|
|
585
|
+
candidate.index < 1 ||
|
|
586
|
+
candidate.prevHash !== expectedHeadHash ||
|
|
587
|
+
projection.transcript?.operationTailHash !== eventHash ||
|
|
588
|
+
candidate.sessionRollbackCommitDigest !== expectedCommitDigest ||
|
|
589
|
+
!sameResolutionData(candidate.data, expected)
|
|
590
|
+
) {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
return immutable({
|
|
594
|
+
eventHash,
|
|
595
|
+
prevHash: expectedHeadHash,
|
|
596
|
+
sessionRollbackCommitDigest: expectedCommitDigest,
|
|
597
|
+
resolution: expected,
|
|
598
|
+
reconciledFromError: true,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const INTENT_AUTHORITY_FIELDS = Object.freeze([
|
|
603
|
+
"revision",
|
|
604
|
+
"action",
|
|
605
|
+
"turnId",
|
|
606
|
+
"checkpointId",
|
|
607
|
+
"checkpointIdentity",
|
|
608
|
+
"workspaceDir",
|
|
609
|
+
"workspaceScopeIdentity",
|
|
610
|
+
"workspacePrestateIdentity",
|
|
611
|
+
"workspaceWritePlanIdentity",
|
|
612
|
+
"workspaceTargetPoststateIdentity",
|
|
613
|
+
"confirmationDigest",
|
|
614
|
+
]);
|
|
615
|
+
|
|
616
|
+
function projectIntent(event, index, state) {
|
|
617
|
+
const data = event.data;
|
|
618
|
+
if (
|
|
619
|
+
!isPlainObject(data) ||
|
|
620
|
+
data.operationId !== state.operationId ||
|
|
621
|
+
!boundedText(data.revision, 1_024) ||
|
|
622
|
+
!ACTIONS.has(data.action) ||
|
|
623
|
+
!boundedText(data.turnId, 256) ||
|
|
624
|
+
!boundedText(data.checkpointId, 256) ||
|
|
625
|
+
!boundedText(data.checkpointIdentity, 1_024) ||
|
|
626
|
+
!boundedText(data.workspaceDir) ||
|
|
627
|
+
!DIGEST_PATTERN.test(String(data.workspaceScopeIdentity || "")) ||
|
|
628
|
+
!validStateIdentity(data.workspacePrestateIdentity) ||
|
|
629
|
+
!DIGEST_PATTERN.test(String(data.workspaceWritePlanIdentity || "")) ||
|
|
630
|
+
!validStateIdentity(data.workspaceTargetPoststateIdentity) ||
|
|
631
|
+
!DIGEST_PATTERN.test(String(data.confirmationDigest || ""))
|
|
632
|
+
) {
|
|
633
|
+
addIssue(state, "intent_shape_invalid");
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
const authority = {};
|
|
637
|
+
for (const field of INTENT_AUTHORITY_FIELDS) authority[field] = data[field];
|
|
638
|
+
return Object.freeze({
|
|
639
|
+
index,
|
|
640
|
+
eventHash: event.hash,
|
|
641
|
+
prevHash: event.prevHash ?? null,
|
|
642
|
+
intentCommitDigest: intentCommitDigest(event.hash),
|
|
643
|
+
authority: Object.freeze(authority),
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function projectConversationCommit(event, index, state) {
|
|
648
|
+
const data = event.data;
|
|
649
|
+
if (
|
|
650
|
+
!isPlainObject(data) ||
|
|
651
|
+
data.operationId !== state.operationId ||
|
|
652
|
+
!ACTIONS.has(data.action) ||
|
|
653
|
+
!boundedText(data.sourceRevision, 1_024) ||
|
|
654
|
+
!boundedText(data.turnId, 256) ||
|
|
655
|
+
!Array.isArray(data.messages) ||
|
|
656
|
+
!isPlainObject(data.binding) ||
|
|
657
|
+
!Array.isArray(data.binding.turns)
|
|
658
|
+
) {
|
|
659
|
+
addIssue(state, "conversation_commit_shape_invalid");
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
return Object.freeze({
|
|
663
|
+
index,
|
|
664
|
+
eventHash: event.hash,
|
|
665
|
+
prevHash: event.prevHash ?? null,
|
|
666
|
+
action: data.action,
|
|
667
|
+
sourceRevision: data.sourceRevision,
|
|
668
|
+
turnId: data.turnId,
|
|
669
|
+
messageCount: data.messages.length,
|
|
670
|
+
bindingTurnCount: data.binding.turns.length,
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function projectAudit(event, index, state) {
|
|
675
|
+
const data = event.data;
|
|
676
|
+
if (
|
|
677
|
+
!isPlainObject(data) ||
|
|
678
|
+
data.operationId !== state.operationId ||
|
|
679
|
+
!AUDIT_STATUSES.has(data.status) ||
|
|
680
|
+
!boundedText(data.revision, 1_024) ||
|
|
681
|
+
!ACTIONS.has(data.action) ||
|
|
682
|
+
!boundedText(data.turnId, 256) ||
|
|
683
|
+
!boundedText(data.checkpointId, 256) ||
|
|
684
|
+
!boundedText(data.checkpointIdentity, 1_024) ||
|
|
685
|
+
!boundedText(data.workspaceDir) ||
|
|
686
|
+
!DIGEST_PATTERN.test(String(data.workspaceScopeIdentity || "")) ||
|
|
687
|
+
!validStateIdentity(data.workspacePrestateIdentity) ||
|
|
688
|
+
!DIGEST_PATTERN.test(String(data.workspaceWritePlanIdentity || "")) ||
|
|
689
|
+
!validStateIdentity(data.workspaceTargetPoststateIdentity) ||
|
|
690
|
+
!DIGEST_PATTERN.test(String(data.confirmationDigest || "")) ||
|
|
691
|
+
(data.status === "failed" && !boundedText(data.failureCode, 128)) ||
|
|
692
|
+
(data.status === "failed" &&
|
|
693
|
+
!FAILED_WORKSPACE_STATES.has(data.workspaceState)) ||
|
|
694
|
+
(data.status === "completed" &&
|
|
695
|
+
(data.failureCode != null || data.workspaceState != null))
|
|
696
|
+
) {
|
|
697
|
+
addIssue(state, "audit_shape_invalid");
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
const authority = {};
|
|
701
|
+
for (const field of INTENT_AUTHORITY_FIELDS) authority[field] = data[field];
|
|
702
|
+
return Object.freeze({
|
|
703
|
+
index,
|
|
704
|
+
eventHash: event.hash,
|
|
705
|
+
prevHash: event.prevHash ?? null,
|
|
706
|
+
status: data.status,
|
|
707
|
+
failureCode: data.status === "failed" ? data.failureCode : null,
|
|
708
|
+
workspaceState:
|
|
709
|
+
typeof data.workspaceState === "string" ? data.workspaceState : null,
|
|
710
|
+
conversationSideEffectPresent: data.branchSessionId != null,
|
|
711
|
+
authority: Object.freeze(authority),
|
|
712
|
+
sessionCommitDigest:
|
|
713
|
+
data.status === "completed" ? sessionCommitDigest(event.hash) : null,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function projectResolution(event, index, state) {
|
|
718
|
+
let data;
|
|
719
|
+
try {
|
|
720
|
+
data = validateResolutionData(event.data);
|
|
721
|
+
} catch {
|
|
722
|
+
addIssue(state, "resolution_shape_invalid");
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
if (!RAW_EVENT_HASH_PATTERN.test(String(event.hash || ""))) {
|
|
726
|
+
addIssue(state, "resolution_event_hash_invalid");
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
const intent = state.intent;
|
|
730
|
+
if (!intent) {
|
|
731
|
+
addIssue(state, "resolution_before_intent");
|
|
732
|
+
} else if (
|
|
733
|
+
data.intentEventHash !== intent.eventHash ||
|
|
734
|
+
data.intentCommitDigest !== intent.intentCommitDigest ||
|
|
735
|
+
data.action !== intent.authority.action ||
|
|
736
|
+
data.checkpointId !== intent.authority.checkpointId ||
|
|
737
|
+
data.checkpointIdentity !== intent.authority.checkpointIdentity ||
|
|
738
|
+
data.workspaceScopeIdentity !== intent.authority.workspaceScopeIdentity ||
|
|
739
|
+
data.workspaceWritePlanIdentity !==
|
|
740
|
+
intent.authority.workspaceWritePlanIdentity
|
|
741
|
+
) {
|
|
742
|
+
addIssue(state, "resolution_intent_mismatch");
|
|
743
|
+
}
|
|
744
|
+
if (data.failedAuditEventHash !== (state.failedAudit?.eventHash ?? null)) {
|
|
745
|
+
addIssue(state, "resolution_failed_audit_mismatch");
|
|
746
|
+
}
|
|
747
|
+
return Object.freeze({
|
|
748
|
+
index,
|
|
749
|
+
eventHash: event.hash,
|
|
750
|
+
prevHash: event.prevHash ?? null,
|
|
751
|
+
sessionRollbackCommitDigest:
|
|
752
|
+
computeCheckpointRestoreSessionRollbackCommitDigest(event.hash),
|
|
753
|
+
data,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function sameAuthority(left, right) {
|
|
758
|
+
if (!left || !right) return false;
|
|
759
|
+
return INTENT_AUTHORITY_FIELDS.every((field) => left[field] === right[field]);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function validateExpectedIntent(state, options) {
|
|
763
|
+
const intent = state.intent;
|
|
764
|
+
if (!intent) {
|
|
765
|
+
if (options.expectedIntentCommitDigest) {
|
|
766
|
+
addIssue(state, "expected_intent_missing");
|
|
767
|
+
}
|
|
768
|
+
if (options.expectedSessionCommitDigest) {
|
|
769
|
+
addIssue(state, "expected_session_commit_missing");
|
|
770
|
+
}
|
|
771
|
+
if (options.expectedSessionRollbackCommitDigest) {
|
|
772
|
+
addIssue(state, "expected_session_rollback_commit_missing");
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (
|
|
777
|
+
options.expectedIntentCommitDigest &&
|
|
778
|
+
intent.intentCommitDigest !== options.expectedIntentCommitDigest
|
|
779
|
+
) {
|
|
780
|
+
addIssue(state, "intent_commit_digest_mismatch");
|
|
781
|
+
}
|
|
782
|
+
for (const [field, actual] of [
|
|
783
|
+
["expectedAction", intent.authority.action],
|
|
784
|
+
["expectedTimelineEntryId", intent.authority.turnId],
|
|
785
|
+
["expectedCheckpointId", intent.authority.checkpointId],
|
|
786
|
+
["expectedCheckpointIdentity", intent.authority.checkpointIdentity],
|
|
787
|
+
["expectedConfirmationDigest", intent.authority.confirmationDigest],
|
|
788
|
+
]) {
|
|
789
|
+
if (options[field] != null && options[field] !== actual) {
|
|
790
|
+
addIssue(
|
|
791
|
+
state,
|
|
792
|
+
`${field.replace(/^expected/u, "").toLowerCase()}_mismatch`,
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const completed = state.completedAudit;
|
|
797
|
+
if (
|
|
798
|
+
options.expectedSessionCommitDigest &&
|
|
799
|
+
(!completed ||
|
|
800
|
+
completed.sessionCommitDigest !== options.expectedSessionCommitDigest)
|
|
801
|
+
) {
|
|
802
|
+
addIssue(state, "session_commit_digest_mismatch");
|
|
803
|
+
}
|
|
804
|
+
if (
|
|
805
|
+
options.expectedSessionRollbackCommitDigest &&
|
|
806
|
+
(!state.resolution ||
|
|
807
|
+
state.resolution.sessionRollbackCommitDigest !==
|
|
808
|
+
options.expectedSessionRollbackCommitDigest)
|
|
809
|
+
) {
|
|
810
|
+
addIssue(state, "session_rollback_commit_digest_mismatch");
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function finishState(state, authority, options) {
|
|
815
|
+
if (
|
|
816
|
+
!authority ||
|
|
817
|
+
typeof authority !== "object" ||
|
|
818
|
+
!Number.isSafeInteger(authority.eventCount) ||
|
|
819
|
+
authority.eventCount < 0 ||
|
|
820
|
+
!(
|
|
821
|
+
authority.headHash === null ||
|
|
822
|
+
(typeof authority.headHash === "string" &&
|
|
823
|
+
RAW_EVENT_HASH_PATTERN.test(authority.headHash))
|
|
824
|
+
)
|
|
825
|
+
) {
|
|
826
|
+
addIssue(state, "transcript_authority_invalid");
|
|
827
|
+
} else {
|
|
828
|
+
if (authority.eventCount !== state.eventCount) {
|
|
829
|
+
addIssue(state, "transcript_event_count_mismatch");
|
|
830
|
+
}
|
|
831
|
+
if ((authority.headHash ?? null) !== (state.lastEventHash ?? null)) {
|
|
832
|
+
addIssue(state, "transcript_head_mismatch");
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (state.intent) {
|
|
837
|
+
if (
|
|
838
|
+
state.conversationCommit &&
|
|
839
|
+
(state.intent.authority.revision !==
|
|
840
|
+
state.conversationCommit.sourceRevision ||
|
|
841
|
+
state.intent.authority.action !== state.conversationCommit.action ||
|
|
842
|
+
state.intent.authority.turnId !== state.conversationCommit.turnId)
|
|
843
|
+
) {
|
|
844
|
+
addIssue(state, "conversation_commit_intent_mismatch");
|
|
845
|
+
}
|
|
846
|
+
for (const audit of [state.completedAudit, state.failedAudit]) {
|
|
847
|
+
if (audit && !sameAuthority(state.intent.authority, audit.authority)) {
|
|
848
|
+
addIssue(state, "audit_intent_mismatch");
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (
|
|
852
|
+
state.intent.authority.action === "restore-code" &&
|
|
853
|
+
state.conversationCommit
|
|
854
|
+
) {
|
|
855
|
+
addIssue(state, "code_restore_has_conversation_commit");
|
|
856
|
+
}
|
|
857
|
+
if (
|
|
858
|
+
state.completedAudit &&
|
|
859
|
+
state.intent.authority.action === "restore-both" &&
|
|
860
|
+
!state.conversationCommit
|
|
861
|
+
) {
|
|
862
|
+
addIssue(state, "completed_both_missing_conversation_commit");
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (state.resolution) {
|
|
866
|
+
if (state.conversationCommit) {
|
|
867
|
+
addIssue(state, "rollback_resolution_has_conversation_commit");
|
|
868
|
+
}
|
|
869
|
+
if (state.failedAudit?.conversationSideEffectPresent) {
|
|
870
|
+
addIssue(state, "rollback_resolution_has_conversation_side_effect");
|
|
871
|
+
}
|
|
872
|
+
if (state.completedAudit) {
|
|
873
|
+
addIssue(state, "rollback_resolution_has_completed_audit");
|
|
874
|
+
}
|
|
875
|
+
if (
|
|
876
|
+
state.resolution.data.failedAuditEventHash !==
|
|
877
|
+
(state.failedAudit?.eventHash ?? null)
|
|
878
|
+
) {
|
|
879
|
+
addIssue(state, "resolution_failed_audit_mismatch");
|
|
880
|
+
}
|
|
881
|
+
if (state.operationTailHash !== state.resolution.eventHash) {
|
|
882
|
+
addIssue(state, "resolution_not_operation_tail");
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (state.completedAudit && state.failedAudit) {
|
|
886
|
+
addIssue(state, "conflicting_audit_outcomes");
|
|
887
|
+
}
|
|
888
|
+
validateExpectedIntent(state, options);
|
|
889
|
+
|
|
890
|
+
let classification;
|
|
891
|
+
if (state.issues.length > 0) {
|
|
892
|
+
classification = CHECKPOINT_RESTORE_SESSION_RECONCILIATION.CONFLICT_UNKNOWN;
|
|
893
|
+
} else if (!state.intent) {
|
|
894
|
+
classification = CHECKPOINT_RESTORE_SESSION_RECONCILIATION.CLEAN_ABORT;
|
|
895
|
+
} else if (state.resolution) {
|
|
896
|
+
classification = CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ROLLED_BACK;
|
|
897
|
+
} else if (state.completedAudit) {
|
|
898
|
+
classification =
|
|
899
|
+
CHECKPOINT_RESTORE_SESSION_RECONCILIATION.ALREADY_COMPLETED;
|
|
900
|
+
} else if (state.intent.authority.action === "restore-code") {
|
|
901
|
+
classification =
|
|
902
|
+
CHECKPOINT_RESTORE_SESSION_RECONCILIATION.CODE_SETTLEMENT_RESUMABLE;
|
|
903
|
+
} else {
|
|
904
|
+
classification =
|
|
905
|
+
CHECKPOINT_RESTORE_SESSION_RECONCILIATION.BOTH_SETTLEMENT_RESUMABLE;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const operationTailIndex = state.operationTailIndex;
|
|
909
|
+
return immutable({
|
|
910
|
+
schema: CHECKPOINT_RESTORE_SESSION_RECOVERY_SCHEMA,
|
|
911
|
+
version: CHECKPOINT_RESTORE_SESSION_RECOVERY_VERSION,
|
|
912
|
+
operationId: state.operationId,
|
|
913
|
+
sessionId: options.sessionId,
|
|
914
|
+
restoreSurface: "timeline",
|
|
915
|
+
classification,
|
|
916
|
+
failClosed: classification === "conflict/unknown",
|
|
917
|
+
safeToMutate: false,
|
|
918
|
+
issues: state.issues,
|
|
919
|
+
transcript: {
|
|
920
|
+
headHash: authority?.headHash ?? null,
|
|
921
|
+
eventCount: Number.isSafeInteger(authority?.eventCount)
|
|
922
|
+
? authority.eventCount
|
|
923
|
+
: null,
|
|
924
|
+
operationEventCount: state.operationEventCount,
|
|
925
|
+
operationTailHash: state.operationTailHash,
|
|
926
|
+
eventsAfterOperationTail:
|
|
927
|
+
operationTailIndex == null ||
|
|
928
|
+
!Number.isSafeInteger(authority?.eventCount)
|
|
929
|
+
? null
|
|
930
|
+
: authority.eventCount - operationTailIndex,
|
|
931
|
+
},
|
|
932
|
+
intent: state.intent,
|
|
933
|
+
conversationCommit: state.conversationCommit,
|
|
934
|
+
audit: {
|
|
935
|
+
completed: state.completedAudit,
|
|
936
|
+
failed: state.failedAudit,
|
|
937
|
+
},
|
|
938
|
+
resolution: state.resolution,
|
|
939
|
+
requiredEvidence: REQUIRED_EVIDENCE[classification],
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function createFold(operationId, options) {
|
|
944
|
+
const state = {
|
|
945
|
+
operationId,
|
|
946
|
+
issueSet: new Set(),
|
|
947
|
+
issues: [],
|
|
948
|
+
eventCount: 0,
|
|
949
|
+
lastEventHash: null,
|
|
950
|
+
operationEventCount: 0,
|
|
951
|
+
operationTailHash: null,
|
|
952
|
+
operationTailIndex: null,
|
|
953
|
+
intent: null,
|
|
954
|
+
conversationCommit: null,
|
|
955
|
+
completedAudit: null,
|
|
956
|
+
failedAudit: null,
|
|
957
|
+
resolution: null,
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
return {
|
|
961
|
+
accept(event) {
|
|
962
|
+
state.eventCount += 1;
|
|
963
|
+
const index = state.eventCount;
|
|
964
|
+
if (
|
|
965
|
+
!isPlainObject(event) ||
|
|
966
|
+
typeof event.type !== "string" ||
|
|
967
|
+
!RAW_EVENT_HASH_PATTERN.test(String(event.hash || "")) ||
|
|
968
|
+
(event.prevHash ?? null) !== state.lastEventHash
|
|
969
|
+
) {
|
|
970
|
+
addIssue(state, "verified_event_chain_invalid");
|
|
971
|
+
}
|
|
972
|
+
state.lastEventHash = typeof event?.hash === "string" ? event.hash : null;
|
|
973
|
+
if (
|
|
974
|
+
!isPlainObject(event?.data) ||
|
|
975
|
+
event.data.operationId !== operationId
|
|
976
|
+
) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
state.operationEventCount += 1;
|
|
981
|
+
state.operationTailHash = event.hash || null;
|
|
982
|
+
state.operationTailIndex = index;
|
|
983
|
+
|
|
984
|
+
if (event.type === CHECKPOINT_TIMELINE_INTENT_EVENT) {
|
|
985
|
+
if (
|
|
986
|
+
state.intent ||
|
|
987
|
+
state.conversationCommit ||
|
|
988
|
+
state.completedAudit ||
|
|
989
|
+
state.failedAudit ||
|
|
990
|
+
state.resolution
|
|
991
|
+
) {
|
|
992
|
+
addIssue(state, "duplicate_or_out_of_order_intent");
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
state.intent = projectIntent(event, index, state);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (event.type === TURN_BINDING_TIMELINE_EVENT) {
|
|
999
|
+
if (!state.intent) addIssue(state, "conversation_commit_before_intent");
|
|
1000
|
+
if (state.conversationCommit) {
|
|
1001
|
+
addIssue(state, "duplicate_conversation_commit");
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
if (state.completedAudit || state.failedAudit) {
|
|
1005
|
+
addIssue(state, "conversation_commit_after_audit");
|
|
1006
|
+
}
|
|
1007
|
+
if (state.resolution) {
|
|
1008
|
+
addIssue(state, "conversation_commit_after_resolution");
|
|
1009
|
+
}
|
|
1010
|
+
state.conversationCommit = projectConversationCommit(
|
|
1011
|
+
event,
|
|
1012
|
+
index,
|
|
1013
|
+
state,
|
|
1014
|
+
);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
if (event.type === CHECKPOINT_TIMELINE_AUDIT_EVENT) {
|
|
1018
|
+
if (!state.intent) addIssue(state, "audit_before_intent");
|
|
1019
|
+
if (state.resolution) addIssue(state, "audit_after_resolution");
|
|
1020
|
+
const audit = projectAudit(event, index, state);
|
|
1021
|
+
if (!audit) return;
|
|
1022
|
+
if (audit.status === "completed") {
|
|
1023
|
+
if (state.completedAudit) {
|
|
1024
|
+
addIssue(state, "duplicate_completed_audit");
|
|
1025
|
+
} else {
|
|
1026
|
+
state.completedAudit = audit;
|
|
1027
|
+
}
|
|
1028
|
+
} else if (state.failedAudit) {
|
|
1029
|
+
addIssue(state, "duplicate_failed_audit");
|
|
1030
|
+
} else {
|
|
1031
|
+
state.failedAudit = audit;
|
|
1032
|
+
}
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
if (event.type === CHECKPOINT_RESTORE_RECOVERY_RESOLUTION_EVENT) {
|
|
1036
|
+
if (state.resolution) {
|
|
1037
|
+
addIssue(state, "duplicate_recovery_resolution");
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (!state.intent) addIssue(state, "resolution_before_intent");
|
|
1041
|
+
if (state.conversationCommit) {
|
|
1042
|
+
addIssue(state, "resolution_after_conversation_commit");
|
|
1043
|
+
}
|
|
1044
|
+
if (state.completedAudit) {
|
|
1045
|
+
addIssue(state, "resolution_after_completed_audit");
|
|
1046
|
+
}
|
|
1047
|
+
state.resolution = projectResolution(event, index, state);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
addIssue(state, "unknown_operation_event_type");
|
|
1051
|
+
},
|
|
1052
|
+
finish(authority) {
|
|
1053
|
+
return finishState(state, authority, options);
|
|
1054
|
+
},
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function directProjection(options) {
|
|
1059
|
+
const classification =
|
|
1060
|
+
CHECKPOINT_RESTORE_SESSION_RECONCILIATION.NO_SESSION_DIRECT;
|
|
1061
|
+
return immutable({
|
|
1062
|
+
schema: CHECKPOINT_RESTORE_SESSION_RECOVERY_SCHEMA,
|
|
1063
|
+
version: CHECKPOINT_RESTORE_SESSION_RECOVERY_VERSION,
|
|
1064
|
+
operationId: options.operationId,
|
|
1065
|
+
sessionId: null,
|
|
1066
|
+
restoreSurface: "direct",
|
|
1067
|
+
classification,
|
|
1068
|
+
failClosed: false,
|
|
1069
|
+
safeToMutate: false,
|
|
1070
|
+
issues: [],
|
|
1071
|
+
transcript: null,
|
|
1072
|
+
intent: null,
|
|
1073
|
+
conversationCommit: null,
|
|
1074
|
+
audit: { completed: null, failed: null },
|
|
1075
|
+
resolution: null,
|
|
1076
|
+
requiredEvidence: REQUIRED_EVIDENCE[classification],
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function unavailableProjection(options, error) {
|
|
1081
|
+
const classification =
|
|
1082
|
+
CHECKPOINT_RESTORE_SESSION_RECONCILIATION.CONFLICT_UNKNOWN;
|
|
1083
|
+
return immutable({
|
|
1084
|
+
schema: CHECKPOINT_RESTORE_SESSION_RECOVERY_SCHEMA,
|
|
1085
|
+
version: CHECKPOINT_RESTORE_SESSION_RECOVERY_VERSION,
|
|
1086
|
+
operationId: options.operationId,
|
|
1087
|
+
sessionId: options.sessionId,
|
|
1088
|
+
restoreSurface: "timeline",
|
|
1089
|
+
classification,
|
|
1090
|
+
failClosed: true,
|
|
1091
|
+
safeToMutate: false,
|
|
1092
|
+
issues: ["verified_projection_unavailable"],
|
|
1093
|
+
errorCode:
|
|
1094
|
+
typeof error?.code === "string" && boundedText(error.code, 128)
|
|
1095
|
+
? error.code
|
|
1096
|
+
: "SESSION_RECOVERY_PROJECTION_FAILED",
|
|
1097
|
+
transcript: null,
|
|
1098
|
+
intent: null,
|
|
1099
|
+
conversationCommit: null,
|
|
1100
|
+
audit: { completed: null, failed: null },
|
|
1101
|
+
resolution: null,
|
|
1102
|
+
requiredEvidence: REQUIRED_EVIDENCE[classification],
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
export function readCheckpointRestoreSessionRecovery(
|
|
1107
|
+
options = {},
|
|
1108
|
+
dependencies = {},
|
|
1109
|
+
) {
|
|
1110
|
+
const readVerifiedProjection = validateOptions(options, dependencies);
|
|
1111
|
+
if (options.restoreSurface === "direct") return directProjection(options);
|
|
1112
|
+
try {
|
|
1113
|
+
let projectionCreated = false;
|
|
1114
|
+
let finishedProjection;
|
|
1115
|
+
const returnedProjection = readVerifiedProjection(
|
|
1116
|
+
options.sessionId,
|
|
1117
|
+
() => {
|
|
1118
|
+
if (projectionCreated) {
|
|
1119
|
+
const error = new Error("verified projection factory was reused");
|
|
1120
|
+
error.code = "CHECKPOINT_RESTORE_SESSION_PROJECTION_INVALID";
|
|
1121
|
+
throw error;
|
|
1122
|
+
}
|
|
1123
|
+
projectionCreated = true;
|
|
1124
|
+
const fold = createFold(options.operationId, options);
|
|
1125
|
+
let finished = false;
|
|
1126
|
+
return Object.freeze({
|
|
1127
|
+
accept(event) {
|
|
1128
|
+
if (finished) {
|
|
1129
|
+
const error = new Error(
|
|
1130
|
+
"verified projection accepted an event after finish",
|
|
1131
|
+
);
|
|
1132
|
+
error.code = "CHECKPOINT_RESTORE_SESSION_PROJECTION_INVALID";
|
|
1133
|
+
throw error;
|
|
1134
|
+
}
|
|
1135
|
+
fold.accept(event);
|
|
1136
|
+
},
|
|
1137
|
+
finish(authority) {
|
|
1138
|
+
if (finished) {
|
|
1139
|
+
const error = new Error("verified projection finished twice");
|
|
1140
|
+
error.code = "CHECKPOINT_RESTORE_SESSION_PROJECTION_INVALID";
|
|
1141
|
+
throw error;
|
|
1142
|
+
}
|
|
1143
|
+
finished = true;
|
|
1144
|
+
finishedProjection = fold.finish(authority);
|
|
1145
|
+
return finishedProjection;
|
|
1146
|
+
},
|
|
1147
|
+
});
|
|
1148
|
+
},
|
|
1149
|
+
options.projectionOptions || {},
|
|
1150
|
+
);
|
|
1151
|
+
if (
|
|
1152
|
+
!projectionCreated ||
|
|
1153
|
+
finishedProjection === undefined ||
|
|
1154
|
+
returnedProjection !== finishedProjection
|
|
1155
|
+
) {
|
|
1156
|
+
const error = new Error("verified projection reader bypassed its fold");
|
|
1157
|
+
error.code = "CHECKPOINT_RESTORE_SESSION_PROJECTION_INVALID";
|
|
1158
|
+
throw error;
|
|
1159
|
+
}
|
|
1160
|
+
return returnedProjection;
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
return unavailableProjection(options, error);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
export function createCheckpointRestoreSessionRecoveryReader(dependencies) {
|
|
1167
|
+
if (
|
|
1168
|
+
!isPlainObject(dependencies) ||
|
|
1169
|
+
typeof dependencies.readVerifiedProjection !== "function"
|
|
1170
|
+
) {
|
|
1171
|
+
throw new TypeError("readVerifiedProjection dependency is required");
|
|
1172
|
+
}
|
|
1173
|
+
return Object.freeze({
|
|
1174
|
+
read(options) {
|
|
1175
|
+
return readCheckpointRestoreSessionRecovery(options, dependencies);
|
|
1176
|
+
},
|
|
1177
|
+
});
|
|
1178
|
+
}
|