chainlesschain 0.163.2 → 0.163.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/scripts/command-lifecycle-report.mjs +330 -38
- package/src/assets/web-panel/assets/{AIOps-DnGsmsqJ.js → AIOps-DB5X7qqV.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CrO6fcQ7.js → ActionButton-DxHRtKLk.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DsBdOaP2.js → Analytics-DIH5QrxT.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DmQslKUB.js → AppLayout-uu0oxVz7.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BvyrT_QQ.js → Artifacts-BNSMiW9Y.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-prndoLiC.js → Audit-DcAzB4_x.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DOTFyA55.js → BackgroundAgents-ClSsvjIc.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-9zOnPp_8.js → Backup-BMciY0js.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C4PC6sAG.js → BaseInput-Hn3b57Sh.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BBAF-W9v.js → Chat-CoSlC53V.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CWhvvloO.js → ChatBubbleRenderer-CFevJ3So.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BI9iculd.js → Checkbox-B4L4Eu4w.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-3DP9BCKb.js → Codegen-C77FgRxF.js} +1 -1
- package/src/assets/web-panel/assets/{Col-df-2TnJX.js → Col-BrLtJLH3.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CHs8gG_A.js → Community-Be7pXXhA.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CDfF6q7V.js → Compact-DuGPecjo.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CthrDnO7.js → Compliance-CzS66mjP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-HjIqsWJw.js → Cowork-CEhjCoyK.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BKc196fb.js → Cron-D7ir8Vja.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CJnjdc8W.js → Crosschain-BcXmuLxf.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BHUy-8Q2.js → DID-Cto1dtBy.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-QDB7sKeY.js → Dashboard-B7KjgwOX.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DStcVK11.js → Dropdown-COMqxijg.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BwSUskMJ.js → EmailListRenderer-BZrya-nf.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DgF_kurT.js → FamilyGuardDashboard-AmVeMq0J.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-k8wvv-wN.js → Federation-kZU8c-27.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C11qoq2r.js → FormItemContext-sOV22v2-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BIIpr9kg.js → GenericCardRenderer-BUuLtGCz.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BlA1exTe.js → Git-K-ZxsLsH.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYa1ZYM4.js → Governance-D3OEnawI.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-6_kKVIt6.js → Inference-Bll0gRmP.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CB4AwxYy.js → KnowledgeGraph-DBXwjr53.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-xf7nHFAe.js → Logs-CEzPwEfP.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-Bf3DEqP6.js → MarkdownRenderer-CLmCbHx6.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-aPg8e4Jt.js → Marketplace-BYyRyOPz.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C-TNlVUf.js → McpTools-DVu8w_rM.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DMTPIpGa.js → Memory-tenuZ_hO.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BxyvveRq.js → MobileBridge-Bxgy4ozS.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DyPCrAyQ.js → MobileProjects-Dgx1D6sc.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BdhqNXp6.js → Mtc-CRfJ8r8j.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Cz-HFxzW.js → MtcAudit-BBJq4Aiz.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-CIUaFNsx.js → Multisig-B3ii9lai.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CWe6-J17.js → NLProgramming-FIi-Cubv.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-vYAKeviU.js → Notes-DIq8MPf1.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DQL9-2d7.js → NotificationSettings-BFOD9eGd.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DnXj1jBn.js → OrderTableRenderer--41u7vre.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B3spU8AN.js → Organization-B_0AWztW.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-DgEwo7vm.js → Overflow-B6I7TuXv.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-hujMRH8X.js → P2P-Cdf6qens.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C_qhdg9h.js → PdhVaultBrowser-B_0FcQJY.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-vp7LMcSf.js → Permissions-6nFBjsQp.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dxr5Hgnq.js → PersonalDataHub-nQYV7xpn.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DEPZqqH4.js → Pipeline-CBVod5jm.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DTpDiKPe.js → Privacy-D52jW1Nt.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-jDJsRWmF.js → ProjectInit-Bdx3BYhf.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Dhor5RWH.js → ProjectSettings-b7xr4DUa.js} +2 -2
- package/src/assets/web-panel/assets/Projects-DhfifCKx.js +1 -0
- package/src/assets/web-panel/assets/Providers-Dy_PgDMr.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-CjIw-wIb.js → QrScannerModal-FQKSmc3e.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DX6jcqoi.js → QuickAsk-CSTrhzcF.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DopXkhar.js → Recommend-CxMABOUh.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BLvqJE9c.js → RemoteSession-Cv2T8yRa.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DhRj2Me7.js → Reputation-CE1k4Vvu.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DQ53vuPq.js → Row-BNgNcAV_.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BslvwiBD.js → RssFeed-5ZiBZ3ph.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DJKBDNKZ.js → Search-mDOIF1UV.js} +1 -1
- package/src/assets/web-panel/assets/{Security-COhpxHER.js → Security-CtuCTC-e.js} +3 -3
- package/src/assets/web-panel/assets/{Services-dbulD6cI.js → Services-DxFO2Zg7.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-ciIVNfhC.js → Skeleton-CKmKkH9y.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-AiRTkTCk.js → Skills-Bmzl9qkk.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DDMcWYBc.js → Sla-9RqaWHOC.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BPxWbpXO.js → SpeechSettings-CGdzFIYw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BoEOXCyg.js → SyncSettings-CvNuCeBT.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-B_sLdcnw.js +1 -0
- package/src/assets/web-panel/assets/{Templates-UowY8Ixe.js → Templates-D9_YijdK.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DauSLQXN.js → Tenant-Cuc7BUSP.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DBj7Jneq.js → Terminal-6VYRnJgd.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-UMrPwoZC.js → TimelineRenderer-GtK8xd8Y.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-4ea96-bn.js → Tokens-BZ94Lt5W.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger--ISCqt4b.js → Trigger-DEcZgDaX.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-D4GVimRJ.js → Trust-CC3z9THe.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Ci-nXRQc.js → UkeySign-Cvtw8cV3.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Cjk7mKPw.js → VideoEditing-CL4e6Bn3.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BgOLOO3Z.js → Wallet-ycEemP27.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CCX0K29i.js → WebAuthn-DgZ4X_aw.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-Ch3zgIsE.js → WorkflowEditor-D0qMhSv7.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DKOU_2Vb.js → chat-DsAg01Zn.js} +1 -1
- package/src/assets/web-panel/assets/{colors-6OVo4u3C.js → colors-B0oqAhrQ.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BH78Hlsq.js → compact-item-CmX0Rabz.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CFRpSbbC.js → createContext-Be4cn1Ub.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CP2qzG6D.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DNQhgcVi.js → hasIn-D5v12kVq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkN3iTTp.js → index-0eWLkQKT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkNEMANR.js → index-301ogenv.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3RtTT4K.js → index-5LGY0JbG.js} +1 -1
- package/src/assets/web-panel/assets/{index-dDkoL4Pe.js → index-7VKDn-t1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHa4WyTZ.js → index-8WvYGnON.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaYqY35Z.js → index-B1YGjOWC.js} +1 -1
- package/src/assets/web-panel/assets/{index-bKm3TUDp.js → index-B3mUHuVc.js} +1 -1
- package/src/assets/web-panel/assets/{index--rq90rL9.js → index-BA4oulaP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaDgdJYj.js → index-BBREnn1V.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_Z-YoxX.js → index-BBvyHguy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkqHgG-b.js → index-BKmsS2jj.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrcLHN2F.js → index-BRIqLy1m.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaVE6E8y.js → index-BXGSbelg.js} +3 -3
- package/src/assets/web-panel/assets/{index-BYrnm5Ob.js → index-BaE0Ui07.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDSR7ecX.js → index-BoAIAZ5C.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNuCHHQT.js → index-BqltWojQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Eag8e_mA.js → index-CGiIyTZw.js} +1 -1
- package/src/assets/web-panel/assets/index-CKB0vbJU.js +1 -0
- package/src/assets/web-panel/assets/{index-BepBTpa8.js → index-CZ2wUEgr.js} +1 -1
- package/src/assets/web-panel/assets/{index-HNGGVF6o.js → index-CZpfzcuy.js} +1 -1
- package/src/assets/web-panel/assets/{index-AuBhLqOm.js → index-Ckrk3pIV.js} +1 -1
- package/src/assets/web-panel/assets/{index-7TC3S8SN.js → index-CqoCZ5vR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFPJ8yzF.js → index-CyWBir7N.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bb6kDrUx.js → index-CyyGTMKd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKN2noIF.js → index-D0BSkxYQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Du0rnksC.js → index-D5YVopyr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxRy5LPR.js → index-DKUBj36_.js} +1 -1
- package/src/assets/web-panel/assets/{index-sm7W8AXy.js → index-DM724NgU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BujsOZfc.js → index-DQRsQen0.js} +1 -1
- package/src/assets/web-panel/assets/index-DgoXmwqB.js +1 -0
- package/src/assets/web-panel/assets/{index-Y9MJv28e.js → index-DikJ0Qqe.js} +1 -1
- package/src/assets/web-panel/assets/{index-caye7AOE.js → index-Dn2dSdXx.js} +1 -1
- package/src/assets/web-panel/assets/{index-cdf7fd7d.js → index-DyUaFWEk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnnXQot_.js → index-Hfe_dEGx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvHGtNnF.js → index-_JjRLSD0.js} +1 -1
- package/src/assets/web-panel/assets/{index-nbfHl1oV.js → index-frSojpHj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cu3L4r5l.js → index-jK3ax6Z6.js} +1 -1
- package/src/assets/web-panel/assets/{index-D84id2Y4.js → index-qMuZ-NRR.js} +1 -1
- package/src/assets/web-panel/assets/{index-hPY0fFrd.js → index-q_fQ94SE.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BZ9w6jPL.js → initDefaultProps-D4VdhJ9_.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BnwUiOho.js → motion-CZJA0MQW.js} +1 -1
- package/src/assets/web-panel/assets/{move-ByUKJnNm.js → move-BqEs7Sb1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-V7yK1KEl.js → mtc-parser-BpHEtsu1.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BEXBBt4j.js → omit-BW94tY9W.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-qDm_46Gi.js → pickAttrs-BnBjPWP9.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BIbhEo_0.js → placementArrow-uUkjQ_hu.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-NbPbDgA_.js → responsiveObserve-B-klUqXK.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BnUWglZ0.js → slide-CvY1cNAs.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BX8_wZI5.js → statusUtils-Ix5sEDT2.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BV4OycQX.js → styleChecker-C8qcXOne.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Od_H5llY.js → useFlexGapSupport-KNF6Kj8f.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BWI-PyiX.js → useFs-DGJr345n.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BqMuhG3g.js → usePersonalDataHub-BboN99rI.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DNd23z6f.js → vnode-Bgt8Vgte.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Cjaxnm7l.js → zoom-BhJtErgt.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agenda.js +6 -5
- package/src/commands/agent.js +237 -75
- package/src/commands/agents.js +6 -0
- package/src/commands/artifacts.js +98 -0
- package/src/commands/background-session.js +11 -1
- package/src/commands/mcp.js +218 -31
- package/src/commands/review.js +221 -22
- package/src/commands/session-mcp-recovery.js +5 -5
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/ws-agent-handler.js +492 -14
- package/src/harness/jsonl-session-store.js +39 -1
- package/src/harness/mcp-client.js +414 -71
- package/src/harness/prompt-compressor.js +24 -0
- package/src/harness/provider-backed-compaction.js +205 -0
- package/src/harness/worktree-isolator.js +26 -3
- package/src/lib/agent-worktree.js +41 -2
- package/src/lib/app-preview.js +3 -0
- package/src/lib/background-agent-supervisor.js +790 -186
- package/src/lib/background-command-argv.js +318 -0
- package/src/lib/background-launch-profile.js +1 -0
- package/src/lib/background-phase-reporter.js +22 -2
- package/src/lib/background-worktree-policy.js +109 -0
- package/src/lib/command-lifecycle-report.js +1340 -145
- package/src/lib/cowork-mcp-tools.js +51 -6
- package/src/lib/cowork-task-runner.js +1 -0
- package/src/lib/delivery-coordinator.js +30 -5
- package/src/lib/delivery-production-adapter.js +1777 -0
- package/src/lib/delivery-production-runner.js +240 -0
- package/src/lib/durable-security-store.js +29 -17
- package/src/lib/execution-trace/trace-context.cjs +179 -0
- package/src/lib/execution-trace/trace-context.js +8 -175
- package/src/lib/mcp-call-ledger-store.js +58 -6
- package/src/lib/mcp-headers-helper.js +76 -8
- package/src/lib/mcp-host-recovery-runtime.js +2 -0
- package/src/lib/mcp-ledger-recovery-admission.js +39 -3
- package/src/lib/mcp-recovery-adjudication.js +256 -19
- package/src/lib/mcp-sandbox-policy.js +184 -0
- package/src/lib/mcp-stdio-capsule-builder-worker.cjs +495 -0
- package/src/lib/mcp-stdio-executable-identity.js +33 -2
- package/src/lib/mcp-stdio-execution-authority.js +163 -0
- package/src/lib/mcp-stdio-immutable-vfs-resolver.cjs +830 -0
- package/src/lib/mcp-stdio-package-materialization.js +721 -333
- package/src/lib/mcp-stdio-workspace-authority.js +252 -0
- package/src/lib/observability/index.js +2 -0
- package/src/lib/observability/otlp-exporter.js +2 -2
- package/src/lib/packer/pack-update-applier.js +8 -1
- package/src/lib/packer/pkg-config-generator.js +34 -0
- package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
- package/src/lib/process-execution-broker/index.js +213 -109
- package/src/lib/process-execution-broker/platform-sandbox.js +1024 -169
- package/src/lib/process-tree-termination.js +17 -1
- package/src/lib/scheduler-kernel/contract.js +315 -0
- package/src/lib/scheduler-kernel/store.js +1345 -0
- package/src/lib/session-host-lease.js +403 -30
- package/src/lib/session-persistence-failure.js +4 -1
- package/src/lib/skill-mcp.js +65 -4
- package/src/lib/sub-agent-context.js +8 -0
- package/src/lib/web-fetch.js +4 -4
- package/src/repl/agent-repl.js +285 -38
- package/src/runtime/agent-core.js +1044 -90
- package/src/runtime/agent-runtime.js +61 -8
- package/src/runtime/headless-runner.js +260 -166
- package/src/runtime/headless-stream.js +254 -25
- package/src/runtime/mcp-config.js +235 -28
- package/src/workers/background-agent-worker.js +492 -71
- package/src/assets/web-panel/assets/Projects-08xUDZGt.js +0 -1
- package/src/assets/web-panel/assets/Providers-BGs47s1a.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CEYFJwmr.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DEMblAIm.js +0 -1
- package/src/assets/web-panel/assets/index-BTvq-36D.js +0 -1
- package/src/assets/web-panel/assets/index-hUpvk6Jl.js +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { randomUUID as nodeRandomUUID } from "node:crypto";
|
|
2
1
|
import { appendAuthorityEventIfHead as storeAppendAuthorityEventIfHead } from "../harness/jsonl-session-store.js";
|
|
3
2
|
import {
|
|
4
3
|
MCP_CALL_RECOVERY_ADJUDICATION_EVENT,
|
|
5
4
|
MCP_CALL_RECOVERY_ADJUDICATION_SCHEMA_VERSION,
|
|
6
5
|
MCP_CALL_RECOVERY_AUTHORITY,
|
|
7
6
|
MCP_CALL_RECOVERY_CONFIRMATION,
|
|
7
|
+
MCP_CALL_RECOVERY_LEGACY_CONFIRMATION,
|
|
8
8
|
McpCallRecoveryDecision,
|
|
9
9
|
deriveMcpExactReplayDenies,
|
|
10
10
|
loadMcpLedgerRecovery,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
sha256PayloadDigest,
|
|
16
16
|
snapshotMcpJsonRpcInput,
|
|
17
17
|
} from "./mcp-call-ledger.js";
|
|
18
|
+
import { revokeSessionHostAuthority as storeRevokeSessionHostAuthority } from "./session-host-lease.js";
|
|
18
19
|
|
|
19
20
|
const PAYLOAD_DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
20
21
|
const RAW_AUTHORITY_HASH = /^[0-9a-f]{64}$/;
|
|
@@ -32,6 +33,30 @@ const PUBLIC_REPLAY_DENY_FIELDS = new Set([
|
|
|
32
33
|
"inputBytes",
|
|
33
34
|
"replayDigest",
|
|
34
35
|
]);
|
|
36
|
+
const PUBLIC_ADJUDICATION_FIELDS_V1 = new Set([
|
|
37
|
+
"schemaVersion",
|
|
38
|
+
"requestId",
|
|
39
|
+
"sessionId",
|
|
40
|
+
"ledgerId",
|
|
41
|
+
"decision",
|
|
42
|
+
"expectedHeadHash",
|
|
43
|
+
"expectedRecoveryDigest",
|
|
44
|
+
"authority",
|
|
45
|
+
"confirmation",
|
|
46
|
+
"reasonDigest",
|
|
47
|
+
]);
|
|
48
|
+
const PUBLIC_ADJUDICATION_FIELDS_V2 = new Set([
|
|
49
|
+
...PUBLIC_ADJUDICATION_FIELDS_V1,
|
|
50
|
+
"hostRevocation",
|
|
51
|
+
]);
|
|
52
|
+
const PUBLIC_HOST_REVOCATION_FIELDS = new Set([
|
|
53
|
+
"requestId",
|
|
54
|
+
"revocationEpoch",
|
|
55
|
+
"targetLeaseId",
|
|
56
|
+
"targetFencingToken",
|
|
57
|
+
"targetOwnerPid",
|
|
58
|
+
]);
|
|
59
|
+
const HOST_LEASE_ID = /^lease-[0-9a-f-]{36}$/;
|
|
35
60
|
|
|
36
61
|
export class McpRecoveryAdjudicationError extends Error {
|
|
37
62
|
constructor(code, message, options = {}) {
|
|
@@ -40,6 +65,9 @@ export class McpRecoveryAdjudicationError extends Error {
|
|
|
40
65
|
this.code = code;
|
|
41
66
|
this.sessionId = options.sessionId || null;
|
|
42
67
|
this.ledgerId = options.ledgerId || null;
|
|
68
|
+
this.hostRevocation = options.hostRevocation || null;
|
|
69
|
+
this.commitState = options.commitState || null;
|
|
70
|
+
this.outcomeUnknown = options.outcomeUnknown === true;
|
|
43
71
|
}
|
|
44
72
|
}
|
|
45
73
|
|
|
@@ -226,25 +254,70 @@ export function publicMcpRecoveryAuthority(sessionId, recovery) {
|
|
|
226
254
|
});
|
|
227
255
|
});
|
|
228
256
|
const adjudications = snapshot.adjudications.map((entry) => {
|
|
257
|
+
const legacy = entry?.schemaVersion === 1;
|
|
258
|
+
const fields = Object.keys(entry || {});
|
|
259
|
+
const allowedFields = legacy
|
|
260
|
+
? PUBLIC_ADJUDICATION_FIELDS_V1
|
|
261
|
+
: PUBLIC_ADJUDICATION_FIELDS_V2;
|
|
229
262
|
const requestId = canonicalIdentifier(entry?.requestId);
|
|
230
263
|
const ledgerId = canonicalLedgerId(entry?.ledgerId);
|
|
264
|
+
const confirmationIsValid =
|
|
265
|
+
(entry?.schemaVersion === 1 &&
|
|
266
|
+
entry?.confirmation === MCP_CALL_RECOVERY_LEGACY_CONFIRMATION) ||
|
|
267
|
+
(entry?.schemaVersion === MCP_CALL_RECOVERY_ADJUDICATION_SCHEMA_VERSION &&
|
|
268
|
+
entry?.confirmation === MCP_CALL_RECOVERY_CONFIRMATION);
|
|
269
|
+
const revocation = entry?.hostRevocation;
|
|
270
|
+
const revocationFields = Object.keys(revocation || {});
|
|
271
|
+
const targetIsNull = revocation?.targetLeaseId === null;
|
|
272
|
+
const revocationIsValid =
|
|
273
|
+
legacy ||
|
|
274
|
+
(revocationFields.length === PUBLIC_HOST_REVOCATION_FIELDS.size &&
|
|
275
|
+
revocationFields.every((field) =>
|
|
276
|
+
PUBLIC_HOST_REVOCATION_FIELDS.has(field),
|
|
277
|
+
) &&
|
|
278
|
+
revocation?.requestId === requestId &&
|
|
279
|
+
Number.isSafeInteger(revocation?.revocationEpoch) &&
|
|
280
|
+
revocation.revocationEpoch >= 1 &&
|
|
281
|
+
(targetIsNull
|
|
282
|
+
? revocation.targetFencingToken === null &&
|
|
283
|
+
revocation.targetOwnerPid === null
|
|
284
|
+
: HOST_LEASE_ID.test(String(revocation?.targetLeaseId || "")) &&
|
|
285
|
+
Number.isSafeInteger(revocation?.targetFencingToken) &&
|
|
286
|
+
revocation.targetFencingToken >= 1 &&
|
|
287
|
+
Number.isSafeInteger(revocation?.targetOwnerPid) &&
|
|
288
|
+
revocation.targetOwnerPid >= 1));
|
|
231
289
|
if (
|
|
290
|
+
fields.length !== allowedFields.size ||
|
|
291
|
+
fields.some((field) => !allowedFields.has(field)) ||
|
|
232
292
|
!requestId ||
|
|
233
293
|
!ledgerId ||
|
|
234
294
|
!Object.values(McpCallRecoveryDecision).includes(entry?.decision) ||
|
|
235
295
|
entry?.authority !== MCP_CALL_RECOVERY_AUTHORITY ||
|
|
236
|
-
|
|
296
|
+
!confirmationIsValid ||
|
|
297
|
+
!revocationIsValid ||
|
|
237
298
|
!PAYLOAD_DIGEST.test(String(entry?.reasonDigest || ""))
|
|
238
299
|
) {
|
|
239
300
|
return invalidProjection();
|
|
240
301
|
}
|
|
241
302
|
return Object.freeze({
|
|
303
|
+
schemaVersion: entry.schemaVersion,
|
|
242
304
|
requestId,
|
|
243
305
|
ledgerId,
|
|
244
306
|
decision: entry.decision,
|
|
245
307
|
authority: entry.authority,
|
|
246
308
|
confirmation: entry.confirmation,
|
|
247
309
|
reasonDigest: entry.reasonDigest,
|
|
310
|
+
...(legacy
|
|
311
|
+
? {}
|
|
312
|
+
: {
|
|
313
|
+
hostRevocation: Object.freeze({
|
|
314
|
+
requestId: revocation.requestId,
|
|
315
|
+
revocationEpoch: revocation.revocationEpoch,
|
|
316
|
+
targetLeaseId: revocation.targetLeaseId,
|
|
317
|
+
targetFencingToken: revocation.targetFencingToken,
|
|
318
|
+
targetOwnerPid: revocation.targetOwnerPid,
|
|
319
|
+
}),
|
|
320
|
+
}),
|
|
248
321
|
});
|
|
249
322
|
});
|
|
250
323
|
return Object.freeze({
|
|
@@ -286,14 +359,106 @@ export function buildMcpRecoveryAdjudicationChallenge({
|
|
|
286
359
|
);
|
|
287
360
|
}
|
|
288
361
|
return (
|
|
289
|
-
`HOST STOPPED; ADJUDICATE ${canonicalSessionId} ${canonicalId} ` +
|
|
362
|
+
`HOST STOPPED AND MCP DISPATCH DRAINED; REVOKE HOST AUTHORITY; ADJUDICATE ${canonicalSessionId} ${canonicalId} ` +
|
|
290
363
|
`${canonicalDecision} ${canonicalDigest}`
|
|
291
364
|
);
|
|
292
365
|
}
|
|
293
366
|
|
|
367
|
+
function deterministicAdjudicationRequestId({
|
|
368
|
+
sessionId,
|
|
369
|
+
ledgerId,
|
|
370
|
+
decision,
|
|
371
|
+
expectedHeadHash,
|
|
372
|
+
expectedRecoveryDigest,
|
|
373
|
+
reasonDigest,
|
|
374
|
+
}) {
|
|
375
|
+
const digest = sha256PayloadDigest({
|
|
376
|
+
schemaVersion: MCP_CALL_RECOVERY_ADJUDICATION_SCHEMA_VERSION,
|
|
377
|
+
sessionId,
|
|
378
|
+
ledgerId,
|
|
379
|
+
decision,
|
|
380
|
+
expectedHeadHash,
|
|
381
|
+
expectedRecoveryDigest,
|
|
382
|
+
reasonDigest,
|
|
383
|
+
});
|
|
384
|
+
return `mcp-recovery-${digest.slice("sha256:".length)}`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function canonicalHostRevocation(value, requestId, context) {
|
|
388
|
+
let snapshot;
|
|
389
|
+
try {
|
|
390
|
+
snapshot = snapshotMcpJsonRpcInput(value);
|
|
391
|
+
} catch (cause) {
|
|
392
|
+
throw new McpRecoveryAdjudicationError(
|
|
393
|
+
"CC_MCP_RECOVERY_HOST_REVOCATION_FAILED",
|
|
394
|
+
"MCP recovery host revocation returned malformed authority",
|
|
395
|
+
{ ...context, cause },
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
const targetIsNull = snapshot?.targetLeaseId === null;
|
|
399
|
+
if (
|
|
400
|
+
!snapshot ||
|
|
401
|
+
snapshot.requestId !== requestId ||
|
|
402
|
+
snapshot.reasonCode !== "mcp-recovery-adjudication" ||
|
|
403
|
+
!Number.isSafeInteger(snapshot.revocationEpoch) ||
|
|
404
|
+
snapshot.revocationEpoch < 1 ||
|
|
405
|
+
typeof snapshot.replayed !== "boolean" ||
|
|
406
|
+
(targetIsNull
|
|
407
|
+
? snapshot.targetFencingToken !== null || snapshot.targetOwnerPid !== null
|
|
408
|
+
: !HOST_LEASE_ID.test(String(snapshot.targetLeaseId || "")) ||
|
|
409
|
+
!Number.isSafeInteger(snapshot.targetFencingToken) ||
|
|
410
|
+
snapshot.targetFencingToken < 1 ||
|
|
411
|
+
!Number.isSafeInteger(snapshot.targetOwnerPid) ||
|
|
412
|
+
snapshot.targetOwnerPid < 1)
|
|
413
|
+
) {
|
|
414
|
+
throw new McpRecoveryAdjudicationError(
|
|
415
|
+
"CC_MCP_RECOVERY_HOST_REVOCATION_FAILED",
|
|
416
|
+
"MCP recovery host revocation returned malformed authority",
|
|
417
|
+
context,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return Object.freeze({
|
|
421
|
+
requestId: snapshot.requestId,
|
|
422
|
+
revocationEpoch: snapshot.revocationEpoch,
|
|
423
|
+
replayed: snapshot.replayed,
|
|
424
|
+
targetLeaseId: snapshot.targetLeaseId,
|
|
425
|
+
targetFencingToken: snapshot.targetFencingToken,
|
|
426
|
+
targetOwnerPid: snapshot.targetOwnerPid,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function persistedHostRevocation(hostRevocation) {
|
|
431
|
+
return Object.freeze({
|
|
432
|
+
requestId: hostRevocation.requestId,
|
|
433
|
+
revocationEpoch: hostRevocation.revocationEpoch,
|
|
434
|
+
targetLeaseId: hostRevocation.targetLeaseId,
|
|
435
|
+
targetFencingToken: hostRevocation.targetFencingToken,
|
|
436
|
+
targetOwnerPid: hostRevocation.targetOwnerPid,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function exactAdjudicationReadback(sessionId, data, dependencies) {
|
|
441
|
+
try {
|
|
442
|
+
const recovery = readMcpRecoveryAuthority(sessionId, dependencies);
|
|
443
|
+
const expected = sha256PayloadDigest(data);
|
|
444
|
+
const match = recovery.adjudications.find(
|
|
445
|
+
(entry) =>
|
|
446
|
+
entry?.requestId === data.requestId &&
|
|
447
|
+
sha256PayloadDigest(entry) === expected,
|
|
448
|
+
);
|
|
449
|
+
if (!match || !RAW_AUTHORITY_HASH.test(String(recovery.headHash || ""))) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
return Object.freeze({ hash: recovery.headHash, recovery });
|
|
453
|
+
} catch {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
294
458
|
/**
|
|
295
|
-
*
|
|
296
|
-
* CAS. A stale comparison is
|
|
459
|
+
* Durably revoke current host authority, then append exactly one human
|
|
460
|
+
* adjudication event under a verified-head CAS. A stale comparison is never
|
|
461
|
+
* retried; the capability-reducing revocation remains durable on append loss.
|
|
297
462
|
*/
|
|
298
463
|
export async function adjudicateMcpRecovery(request, dependencies = {}) {
|
|
299
464
|
let safeRequest;
|
|
@@ -364,9 +529,16 @@ export async function adjudicateMcpRecovery(request, dependencies = {}) {
|
|
|
364
529
|
}
|
|
365
530
|
|
|
366
531
|
const reasonDigest = sha256PayloadDigest(reason);
|
|
367
|
-
const randomUUID = dependencies.randomUUID || nodeRandomUUID;
|
|
368
532
|
const requestId = canonicalIdentifier(
|
|
369
|
-
safeRequest.requestId ||
|
|
533
|
+
safeRequest.requestId ||
|
|
534
|
+
deterministicAdjudicationRequestId({
|
|
535
|
+
sessionId,
|
|
536
|
+
ledgerId,
|
|
537
|
+
decision,
|
|
538
|
+
expectedHeadHash: recovery.headHash,
|
|
539
|
+
expectedRecoveryDigest: recovery.recoveryDigest,
|
|
540
|
+
reasonDigest,
|
|
541
|
+
}),
|
|
370
542
|
);
|
|
371
543
|
if (!requestId) {
|
|
372
544
|
throw new McpRecoveryAdjudicationError(
|
|
@@ -375,6 +547,39 @@ export async function adjudicateMcpRecovery(request, dependencies = {}) {
|
|
|
375
547
|
context,
|
|
376
548
|
);
|
|
377
549
|
}
|
|
550
|
+
const revokeSessionHostAuthority =
|
|
551
|
+
dependencies.revokeSessionHostAuthority || storeRevokeSessionHostAuthority;
|
|
552
|
+
let hostRevocation;
|
|
553
|
+
try {
|
|
554
|
+
hostRevocation = canonicalHostRevocation(
|
|
555
|
+
await revokeSessionHostAuthority(sessionId, {
|
|
556
|
+
requestId,
|
|
557
|
+
reasonCode: "mcp-recovery-adjudication",
|
|
558
|
+
}),
|
|
559
|
+
requestId,
|
|
560
|
+
context,
|
|
561
|
+
);
|
|
562
|
+
} catch (cause) {
|
|
563
|
+
if (cause instanceof McpRecoveryAdjudicationError) throw cause;
|
|
564
|
+
const commitState = cause?.commitState || "unknown";
|
|
565
|
+
if (commitState !== "not-committed") {
|
|
566
|
+
throw new McpRecoveryAdjudicationError(
|
|
567
|
+
"CC_MCP_RECOVERY_HOST_REVOCATION_OUTCOME_UNKNOWN",
|
|
568
|
+
"MCP recovery host revocation outcome is unknown; do not retry or adjudicate until exact authority readback succeeds",
|
|
569
|
+
{
|
|
570
|
+
...context,
|
|
571
|
+
cause,
|
|
572
|
+
commitState,
|
|
573
|
+
outcomeUnknown: true,
|
|
574
|
+
},
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
throw new McpRecoveryAdjudicationError(
|
|
578
|
+
"CC_MCP_RECOVERY_HOST_REVOCATION_FAILED",
|
|
579
|
+
"MCP recovery host authority could not be durably revoked; no adjudication was appended",
|
|
580
|
+
{ ...context, cause, commitState },
|
|
581
|
+
);
|
|
582
|
+
}
|
|
378
583
|
const data = Object.freeze({
|
|
379
584
|
schemaVersion: MCP_CALL_RECOVERY_ADJUDICATION_SCHEMA_VERSION,
|
|
380
585
|
requestId,
|
|
@@ -386,6 +591,7 @@ export async function adjudicateMcpRecovery(request, dependencies = {}) {
|
|
|
386
591
|
authority: MCP_CALL_RECOVERY_AUTHORITY,
|
|
387
592
|
confirmation: MCP_CALL_RECOVERY_CONFIRMATION,
|
|
388
593
|
reasonDigest,
|
|
594
|
+
hostRevocation: persistedHostRevocation(hostRevocation),
|
|
389
595
|
});
|
|
390
596
|
const appendAuthorityEventIfHead =
|
|
391
597
|
dependencies.appendAuthorityEventIfHead || storeAppendAuthorityEventIfHead;
|
|
@@ -403,22 +609,52 @@ export async function adjudicateMcpRecovery(request, dependencies = {}) {
|
|
|
403
609
|
if (cause?.code === "SESSION_REVISION_STALE") {
|
|
404
610
|
throw new McpRecoveryAdjudicationError(
|
|
405
611
|
"CC_MCP_RECOVERY_ADJUDICATION_STALE",
|
|
406
|
-
"MCP recovery authority changed; run show again before adjudicating",
|
|
407
|
-
{ ...context, cause },
|
|
612
|
+
"MCP recovery authority changed after host revocation; run show again before adjudicating",
|
|
613
|
+
{ ...context, cause, hostRevocation },
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
const commitState = cause?.commitState || "unknown";
|
|
617
|
+
if (commitState !== "not-committed") {
|
|
618
|
+
const readback = exactAdjudicationReadback(sessionId, data, dependencies);
|
|
619
|
+
if (readback) {
|
|
620
|
+
appended = readback;
|
|
621
|
+
} else {
|
|
622
|
+
throw new McpRecoveryAdjudicationError(
|
|
623
|
+
"CC_MCP_RECOVERY_ADJUDICATION_OUTCOME_UNKNOWN",
|
|
624
|
+
"MCP recovery adjudication persistence outcome is unknown; host authority remains revoked and exact transcript reconciliation is required before retry",
|
|
625
|
+
{
|
|
626
|
+
...context,
|
|
627
|
+
cause,
|
|
628
|
+
hostRevocation,
|
|
629
|
+
commitState,
|
|
630
|
+
outcomeUnknown: true,
|
|
631
|
+
},
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
throw new McpRecoveryAdjudicationError(
|
|
636
|
+
"CC_MCP_RECOVERY_ADJUDICATION_PERSIST_FAILED",
|
|
637
|
+
"MCP recovery adjudication was not persisted; host authority remains revoked",
|
|
638
|
+
{ ...context, cause, hostRevocation, commitState },
|
|
408
639
|
);
|
|
409
640
|
}
|
|
410
|
-
throw new McpRecoveryAdjudicationError(
|
|
411
|
-
"CC_MCP_RECOVERY_ADJUDICATION_PERSIST_FAILED",
|
|
412
|
-
"MCP recovery adjudication was not persisted",
|
|
413
|
-
{ ...context, cause },
|
|
414
|
-
);
|
|
415
641
|
}
|
|
416
642
|
if (!appended || !RAW_AUTHORITY_HASH.test(String(appended.hash || ""))) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
643
|
+
const readback = exactAdjudicationReadback(sessionId, data, dependencies);
|
|
644
|
+
if (readback) {
|
|
645
|
+
appended = readback;
|
|
646
|
+
} else {
|
|
647
|
+
throw new McpRecoveryAdjudicationError(
|
|
648
|
+
"CC_MCP_RECOVERY_ADJUDICATION_OUTCOME_UNKNOWN",
|
|
649
|
+
"MCP recovery adjudication returned no durable authority hash; host authority remains revoked and exact transcript reconciliation is required",
|
|
650
|
+
{
|
|
651
|
+
...context,
|
|
652
|
+
hostRevocation,
|
|
653
|
+
commitState: appended?.commitState || "unknown",
|
|
654
|
+
outcomeUnknown: true,
|
|
655
|
+
},
|
|
656
|
+
);
|
|
657
|
+
}
|
|
422
658
|
}
|
|
423
659
|
|
|
424
660
|
return Object.freeze({
|
|
@@ -437,6 +673,7 @@ export async function adjudicateMcpRecovery(request, dependencies = {}) {
|
|
|
437
673
|
: [],
|
|
438
674
|
),
|
|
439
675
|
replayDenied: decision === McpCallRecoveryDecision.CONFIRMED_APPLIED,
|
|
676
|
+
hostRevocation,
|
|
440
677
|
runtimeReloadRequired: true,
|
|
441
678
|
remediation: "restart_or_resume_before_mcp_calls",
|
|
442
679
|
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { isProxy } from "node:util/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Declarative sandbox boundaries an external MCP source may require.
|
|
5
|
+
*
|
|
6
|
+
* Internal broker guarantees (for example process-tree and code-snapshot) are
|
|
7
|
+
* deliberately not part of this source-controlled contract. A trusted host
|
|
8
|
+
* may add its own non-removable requirements after this value is normalized.
|
|
9
|
+
*/
|
|
10
|
+
export const MCP_SANDBOX_BOUNDARIES = Object.freeze(["filesystem", "network"]);
|
|
11
|
+
|
|
12
|
+
export const MCP_SANDBOX_POLICY_INVALID_CODE = "CC_MCP_SANDBOX_POLICY_INVALID";
|
|
13
|
+
export const MCP_STDIO_CWD_INVALID_CODE = "CC_MCP_STDIO_SANDBOX_CWD_INVALID";
|
|
14
|
+
|
|
15
|
+
const SUPPORTED_BOUNDARIES = new Set(MCP_SANDBOX_BOUNDARIES);
|
|
16
|
+
const MAX_BOUNDARY_DECLARATIONS = 32;
|
|
17
|
+
|
|
18
|
+
function invalidPolicy(message) {
|
|
19
|
+
const error = new TypeError(message);
|
|
20
|
+
error.code = MCP_SANDBOX_POLICY_INVALID_CODE;
|
|
21
|
+
return error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function invalidCwd(message) {
|
|
25
|
+
const error = new TypeError(message);
|
|
26
|
+
error.code = MCP_STDIO_CWD_INVALID_CODE;
|
|
27
|
+
return error;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read an optional stdio working directory without invoking accessors or Proxy
|
|
32
|
+
* traps. Absence means "retain/default" while null and the empty string are an
|
|
33
|
+
* explicit clear. Every other value must be a NUL-free string.
|
|
34
|
+
*/
|
|
35
|
+
export function readMcpStdioCwd(owner, options = {}) {
|
|
36
|
+
const label =
|
|
37
|
+
typeof options.label === "string" && options.label.length > 0
|
|
38
|
+
? options.label
|
|
39
|
+
: "MCP stdio config";
|
|
40
|
+
if (!owner || typeof owner !== "object" || isProxy(owner)) {
|
|
41
|
+
throw invalidCwd(`${label} must be a non-Proxy object`);
|
|
42
|
+
}
|
|
43
|
+
const descriptor = Object.getOwnPropertyDescriptor(owner, "cwd");
|
|
44
|
+
if (!descriptor) return { present: false, cwd: null };
|
|
45
|
+
if (!("value" in descriptor)) {
|
|
46
|
+
throw invalidCwd(`${label}.cwd must be an own data property`);
|
|
47
|
+
}
|
|
48
|
+
const value = descriptor.value;
|
|
49
|
+
if (value == null || value === "") return { present: true, cwd: null };
|
|
50
|
+
if (typeof value !== "string" || value.includes("\0")) {
|
|
51
|
+
throw invalidCwd(`${label}.cwd must be a NUL-free path string`);
|
|
52
|
+
}
|
|
53
|
+
return { present: true, cwd: value };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ownDataDescriptor(value, key, label) {
|
|
57
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
58
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
59
|
+
throw invalidPolicy(`${label} must be an own data property`);
|
|
60
|
+
}
|
|
61
|
+
return descriptor;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeRequiredBoundaries(value, label) {
|
|
65
|
+
if (!Array.isArray(value) || isProxy(value)) {
|
|
66
|
+
throw invalidPolicy(`${label} must be a non-Proxy array`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const lengthDescriptor = ownDataDescriptor(
|
|
70
|
+
value,
|
|
71
|
+
"length",
|
|
72
|
+
`${label}.length`,
|
|
73
|
+
);
|
|
74
|
+
const length = lengthDescriptor.value;
|
|
75
|
+
if (length > MAX_BOUNDARY_DECLARATIONS) {
|
|
76
|
+
throw invalidPolicy(
|
|
77
|
+
`${label} exceeds ${MAX_BOUNDARY_DECLARATIONS} boundary declarations`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const allowedKeys = new Set(["length"]);
|
|
81
|
+
const requiredBoundaries = new Set();
|
|
82
|
+
|
|
83
|
+
for (let index = 0; index < length; index += 1) {
|
|
84
|
+
const key = String(index);
|
|
85
|
+
allowedKeys.add(key);
|
|
86
|
+
const descriptor = ownDataDescriptor(value, key, `${label}[${index}]`);
|
|
87
|
+
const boundary = descriptor.value;
|
|
88
|
+
if (typeof boundary !== "string" || !SUPPORTED_BOUNDARIES.has(boundary)) {
|
|
89
|
+
throw invalidPolicy(`${label} contains an unsupported boundary`);
|
|
90
|
+
}
|
|
91
|
+
requiredBoundaries.add(boundary);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
95
|
+
if (typeof key !== "string" || !allowedKeys.has(key)) {
|
|
96
|
+
throw invalidPolicy(
|
|
97
|
+
`${label} contains unsupported property: ${String(key)}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return Object.freeze([...requiredBoundaries].sort());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Normalize an MCP source's small declarative sandbox contract.
|
|
107
|
+
*
|
|
108
|
+
* The input is treated as untrusted data: Proxy objects, inherited/accessor
|
|
109
|
+
* fields, non-plain objects, and unknown fields are rejected without invoking
|
|
110
|
+
* user-controlled code. `null` and an empty policy retain the legacy meaning
|
|
111
|
+
* of "no source-declared requirements".
|
|
112
|
+
*
|
|
113
|
+
* @param {unknown} value
|
|
114
|
+
* @param {{label?: string}} [options]
|
|
115
|
+
* @returns {{requiredBoundaries: readonly string[]}|null}
|
|
116
|
+
*/
|
|
117
|
+
export function normalizeMcpSandboxPolicy(value, options = {}) {
|
|
118
|
+
const label =
|
|
119
|
+
typeof options.label === "string" && options.label.length > 0
|
|
120
|
+
? options.label
|
|
121
|
+
: "sandboxPolicy";
|
|
122
|
+
|
|
123
|
+
if (value === undefined || value === null) return null;
|
|
124
|
+
if (typeof value !== "object" || Array.isArray(value) || isProxy(value)) {
|
|
125
|
+
throw invalidPolicy(`${label} must be a non-Proxy plain object`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const prototype = Object.getPrototypeOf(value);
|
|
129
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
130
|
+
throw invalidPolicy(`${label} must be a plain object`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const keys = Reflect.ownKeys(value);
|
|
134
|
+
for (const key of keys) {
|
|
135
|
+
if (key !== "requiredBoundaries") {
|
|
136
|
+
throw invalidPolicy(
|
|
137
|
+
`${label} contains unsupported field: ${String(key)}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!keys.includes("requiredBoundaries")) return null;
|
|
143
|
+
const descriptor = ownDataDescriptor(
|
|
144
|
+
value,
|
|
145
|
+
"requiredBoundaries",
|
|
146
|
+
`${label}.requiredBoundaries`,
|
|
147
|
+
);
|
|
148
|
+
const requiredBoundaries = normalizeRequiredBoundaries(
|
|
149
|
+
descriptor.value,
|
|
150
|
+
`${label}.requiredBoundaries`,
|
|
151
|
+
);
|
|
152
|
+
if (requiredBoundaries.length === 0) return null;
|
|
153
|
+
|
|
154
|
+
return Object.freeze({ requiredBoundaries });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Add the host-owned isolation floor for an exact-package stdio capsule.
|
|
159
|
+
*
|
|
160
|
+
* Source configuration is normalized as untrusted data first and can only add
|
|
161
|
+
* requirements to this floor. The returned policy is a new deeply-frozen
|
|
162
|
+
* runtime value, so neither the source object nor later launch preparation can
|
|
163
|
+
* remove a host requirement. Callers must only attach this policy after the
|
|
164
|
+
* trusted executable-identity preparer has issued a capsule execution
|
|
165
|
+
* contract; legacy stdio sources intentionally retain their existing policy.
|
|
166
|
+
* There is deliberately no source-configured escape hatch: future workspace
|
|
167
|
+
* or egress access must be a separate host-issued, authority-bound capability.
|
|
168
|
+
*
|
|
169
|
+
* @param {unknown} value
|
|
170
|
+
* @param {{label?: string}} [options]
|
|
171
|
+
* @returns {{requiredBoundaries: readonly string[]}}
|
|
172
|
+
*/
|
|
173
|
+
export function enforceMcpStdioCapsuleHostSandboxPolicy(value, options = {}) {
|
|
174
|
+
const sourcePolicy = normalizeMcpSandboxPolicy(value, options);
|
|
175
|
+
const requiredBoundaries = Object.freeze(
|
|
176
|
+
[
|
|
177
|
+
...new Set([
|
|
178
|
+
...MCP_SANDBOX_BOUNDARIES,
|
|
179
|
+
...(sourcePolicy?.requiredBoundaries || []),
|
|
180
|
+
]),
|
|
181
|
+
].sort(),
|
|
182
|
+
);
|
|
183
|
+
return Object.freeze({ requiredBoundaries });
|
|
184
|
+
}
|