chainlesschain 0.164.0 → 0.165.2
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 +248 -2
- package/package.json +7 -4
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-lS1R0Cza.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-BmlgBPsj.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-CNKczv1S.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-BuZGJARQ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-Dm73Sl-O.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-BGMW7A5e.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-BQ6M54JR.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-DBzGP0bR.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-D1mLCoOV.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-BUfcN7p8.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-Du0Yg5Pi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-B2RXziHW.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-DWhekOrR.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-HQhrkon4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-izHNb4uz.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BDwLy41-.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-DFJhLa7K.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DYY9RG7-.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-rr7tJTtv.js} +1 -1
- package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-D2sfHRLi.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-q_eGdhha.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-Cufrh2Bz.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-B-kdv10f.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-CAwRTLZH.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-vx3daNDF.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Q6SwauyD.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-BJ1q4IQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-CSWjROgD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-DVF8Mnns.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-BQP7LOA4.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-BNlJCmNc.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-DLLg5WQ4.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-CR2skNAF.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-B17pgqMI.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-4UmV5cIL.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-DoMEXEcU.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-h9DMXFX4.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-Cihn-xX8.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BE6ZGa3R.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-CnwojoSI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-B2vpwmjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-C4z28R0-.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-BVlFkwhW.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-C28xs4lp.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-Dgl_rBjM.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-3QUIhrKI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-8Gh33OZ5.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-BNmg4tZi.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-vgMfeCf9.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CXoOa8eX.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-BhfTF0oS.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C746HEAj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-BSej8Eo0.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-f3gmnlG3.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-CyvybpkN.js} +1 -1
- package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-DwV2PeYn.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-11GrzjZy.js} +3 -3
- package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-BjKXkrAY.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-eWiFb9Yk.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CdCsMAtt.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-4Q2UTKQ5.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-v4rtYrRj.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-CRhsFvd_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-D16zonl7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-Xxozz0i_.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-CXJpPtCT.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-XRV0L1ZF.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-CYuO1i_w.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-BXqitWsu.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-BvPrDHIy.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-DhCrSTf5.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-DmsuZD6K.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-D_yrts5C.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-B01ihPrm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-DpUjKa-N.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-CL07yON6.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-CLMflnRr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DtUqe8h_.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-DPKrgVfy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTt2GtOm.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-D_VeUc1J.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-1kndiylj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-2eyAXE3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-4bna2_SR.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-8nz_fmlj.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-B9KvcS7R.js} +1 -1
- package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BAXfRKfG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BHpBMWCh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-BaZAbw9A.js} +1 -1
- package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-BepLZwgB.js} +1 -1
- package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-Bhudc_Ez.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-BlDNVgBt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtobShro.js → index-By5n8up9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-CHG-KcS4.js} +1 -1
- package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
- package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-CX81qSM4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-CauoBQkH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-CcO04eDn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CtF7EN49.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CzjkKUmR.js} +1 -1
- package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-D95OFht7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-DFIZ_1Sf.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-DG2MZQ3f.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-DJ8vXGOa.js} +1 -1
- package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-Dbbymxpj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DdkplL9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-DfGl8CqG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DfKBRdSS.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-DsJCDUJG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-Dwqmtk65.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-DxdTdBix.js} +1 -1
- package/src/assets/web-panel/assets/{index-C484EJiS.js → index-FzdwcSy2.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-GsoUQ2M7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-brCInrzq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-dVc-CXY4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-fk2Rjacw.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-l1BETrDb.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-m31Ka8hZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-ocf06EVQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-sDoMHTNA.js} +1 -1
- package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-CvyYJd7z.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-DYZkhUdz.js} +1 -1
- package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-slnJRVT1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-DVc0CKmy.js} +1 -1
- package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-BCXAtHCq.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DBsemz_K.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-BRtkJ-nv.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-Cebq7EZn.js} +1 -1
- package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-DT7qvId2.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-pQiITLkG.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-c-19BVKQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-CxGiMe5n.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-D9btVID5.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-CUYFsFEN.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-CXmn_35j.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/cowork.js +559 -62
- package/src/commands/plugin.js +819 -64
- package/src/commands/session-location.js +318 -7
- package/src/constants.js +2 -2
- package/src/data/changelog.json +28 -2
- package/src/gateways/ws/action-protocol.js +293 -19
- package/src/gateways/ws/message-dispatcher.js +30 -0
- package/src/gateways/ws/remote-session-protocol.js +542 -35
- package/src/gateways/ws/ws-approval-gate.js +5 -0
- package/src/gateways/ws/ws-server.js +462 -38
- package/src/harness/jsonl-session-store.js +638 -2
- package/src/harness/prompt-compressor.js +4 -0
- package/src/harness/remote-session-audit.js +1 -0
- package/src/harness/remote-session-crypto.js +750 -50
- package/src/harness/remote-session-registry.js +795 -27
- package/src/lib/auto-mode-config.js +98 -3
- package/src/lib/background-agent-keeper-client.js +29 -0
- package/src/lib/background-agent-keeper-protocol.js +158 -10
- package/src/lib/background-agent-supervisor.js +870 -48
- package/src/lib/background-session-transport.js +23 -6
- package/src/lib/background-turn-bootstrap-protocol.js +64 -0
- package/src/lib/cowork-task-runner.js +70 -0
- package/src/lib/cowork-workflow.js +887 -62
- package/src/lib/delivery-production-adapter.js +38 -2
- package/src/lib/doctor-checkup.js +34 -10
- package/src/lib/dynamic-workflow-draft.js +535 -0
- package/src/lib/dynamic-workflow-facade.js +1105 -17
- package/src/lib/dynamic-workflow-runtime.js +2974 -0
- package/src/lib/execution-location-contract.js +111 -7
- package/src/lib/execution-location-runtime.js +3 -1
- package/src/lib/execution-location-target.js +910 -0
- package/src/lib/llm-providers.js +1 -0
- package/src/lib/mcp-call-ledger-store.js +35 -0
- package/src/lib/mcp-call-ledger.js +30 -0
- package/src/lib/mcp-stdio-package-materialization.js +1 -1
- package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
- package/src/lib/plugin-runtime/install.js +4956 -657
- package/src/lib/plugin-runtime/manifest.js +3 -1
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
- package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
- package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
- package/src/lib/plugin-runtime/marketplace-network.js +317 -0
- package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
- package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
- package/src/lib/plugin-runtime/policy.js +21 -6
- package/src/lib/plugin-runtime/publisher-trust.js +332 -0
- package/src/lib/plugin-runtime/remote-source.js +323 -27
- package/src/lib/plugin-runtime/scopes.js +317 -41
- package/src/lib/plugin-runtime/transaction-journal.js +806 -0
- package/src/lib/public-ide-capability-manifest.js +1 -1
- package/src/lib/remote-approval-bridge.js +745 -108
- package/src/lib/remote-control.js +2 -0
- package/src/lib/remote-membership-coordinator.js +612 -46
- package/src/lib/remote-membership-host-store.js +59 -10
- package/src/lib/runtime-provenance-ledger.js +171 -46
- package/src/lib/shell-approval.js +169 -6
- package/src/lib/sub-agent-context.js +268 -2
- package/src/lib/task-model-selector.js +1 -1
- package/src/lib/with-file-lock.js +167 -42
- package/src/repl/agent-repl.js +70 -29
- package/src/repl/remote-approval.js +29 -7
- package/src/runtime/agent-core.js +1695 -194
- package/src/runtime/headless-runner.js +22 -5
- package/src/runtime/headless-stream.js +19 -3
- package/src/workers/background-agent-keeper.js +279 -58
- package/src/workers/background-agent-worker.js +7 -0
- package/src/workers/background-turn-bootstrap.js +29 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
- package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
- package/src/assets/web-panel/assets/SpeechSettings-D6UL3dA5.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
- package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
- package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { TextDecoder } from "node:util";
|
|
6
|
+
import {
|
|
7
|
+
EXECUTION_LOCATION_TARGET_ATTESTATION_SCHEMA,
|
|
8
|
+
createExecutionLocationTargetAttestation,
|
|
9
|
+
normalizeExecutionLocationBinding,
|
|
10
|
+
} from "./execution-location-contract.js";
|
|
11
|
+
import { containsSecret } from "./secret-scan.js";
|
|
12
|
+
import {
|
|
13
|
+
sameFileStatIdentity,
|
|
14
|
+
samePathHandleFileIdentity,
|
|
15
|
+
withTrustedFileParentSync,
|
|
16
|
+
} from "./secure-file-identity.js";
|
|
17
|
+
import { canonicalJson } from "./scheduler-kernel/contract.js";
|
|
18
|
+
import { executionBroker } from "./process-execution-broker/index.js";
|
|
19
|
+
|
|
20
|
+
export const EXECUTION_LOCATION_PROFILE_SCHEMA =
|
|
21
|
+
"cc-execution-location-profile/v1";
|
|
22
|
+
export { EXECUTION_LOCATION_TARGET_ATTESTATION_SCHEMA };
|
|
23
|
+
export const EXECUTION_LOCATION_TARGET_RESUME_SCHEMA =
|
|
24
|
+
"cc-execution-location-target-resume/v1";
|
|
25
|
+
|
|
26
|
+
const SESSION_LOCATION_AUTHORITY_SCHEMA =
|
|
27
|
+
"cc-session-execution-location-authority/v1";
|
|
28
|
+
const SESSION_LOCATION_HANDOFF_INSTALL_SCHEMA =
|
|
29
|
+
"chainlesschain.session-execution-location-handoff-install/v1";
|
|
30
|
+
const PROFILE_TARGETS = new Set(["wsl", "ssh", "container"]);
|
|
31
|
+
const SESSION_STORE_MODES = new Set(["replicated", "shared"]);
|
|
32
|
+
const SHA256_RE = /^sha256:[a-f0-9]{64}$/u;
|
|
33
|
+
const COMMIT_RE = /^[a-f0-9]{40,64}$/u;
|
|
34
|
+
const MAX_PROFILE_BYTES = 1024 * 1024;
|
|
35
|
+
const MAX_PROBE_BYTES = 1024 * 1024;
|
|
36
|
+
const MAX_REPLICA_BYTES = 64 * 1024 * 1024;
|
|
37
|
+
|
|
38
|
+
function digest(domain, value) {
|
|
39
|
+
return `sha256:${createHash("sha256")
|
|
40
|
+
.update(domain, "utf8")
|
|
41
|
+
.update(canonicalJson(value, "executionLocationTarget"), "utf8")
|
|
42
|
+
.digest("hex")}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function safeString(value, field, max = 4096) {
|
|
46
|
+
if (
|
|
47
|
+
typeof value !== "string" ||
|
|
48
|
+
value.length === 0 ||
|
|
49
|
+
value.length > max ||
|
|
50
|
+
[...value].some((character) => {
|
|
51
|
+
const code = character.codePointAt(0);
|
|
52
|
+
return code < 32 || code === 127;
|
|
53
|
+
})
|
|
54
|
+
) {
|
|
55
|
+
throw new TypeError(`${field} is invalid`);
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function safeName(value, field, max = 256) {
|
|
61
|
+
const normalized = safeString(value, field, max);
|
|
62
|
+
if (
|
|
63
|
+
normalized.startsWith("-") ||
|
|
64
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:@+-]*$/u.test(normalized)
|
|
65
|
+
) {
|
|
66
|
+
throw new TypeError(`${field} is invalid`);
|
|
67
|
+
}
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function safePath(value, field) {
|
|
72
|
+
const normalized = safeString(value, field);
|
|
73
|
+
if (normalized.startsWith("-") || normalized.includes("\0")) {
|
|
74
|
+
throw new TypeError(`${field} is invalid`);
|
|
75
|
+
}
|
|
76
|
+
return normalized;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function exactObject(value, fields, field) {
|
|
80
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
81
|
+
throw new TypeError(`${field} must be an object`);
|
|
82
|
+
}
|
|
83
|
+
const actual = Object.keys(value).sort();
|
|
84
|
+
const expected = [...fields].sort();
|
|
85
|
+
if (
|
|
86
|
+
actual.length !== expected.length ||
|
|
87
|
+
actual.some((entry, index) => entry !== expected[index])
|
|
88
|
+
) {
|
|
89
|
+
throw new TypeError(`${field} has an invalid schema`);
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeTools(value) {
|
|
95
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 64) {
|
|
96
|
+
throw new TypeError("profile.expected.tools is invalid");
|
|
97
|
+
}
|
|
98
|
+
const tools = value.map((entry) => safeName(entry, "expected tool", 128));
|
|
99
|
+
if (new Set(tools).size !== tools.length) {
|
|
100
|
+
throw new TypeError("profile.expected.tools contains duplicates");
|
|
101
|
+
}
|
|
102
|
+
return tools.sort();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizeSessionStore(value) {
|
|
106
|
+
if (value == null) return null;
|
|
107
|
+
const input = exactObject(
|
|
108
|
+
value,
|
|
109
|
+
["mode", "targetSessionId", "headHash", "eventCount"],
|
|
110
|
+
"profile.sessionStore",
|
|
111
|
+
);
|
|
112
|
+
const mode = safeName(input.mode, "profile.sessionStore.mode", 32);
|
|
113
|
+
const targetSessionId = safeName(
|
|
114
|
+
input.targetSessionId,
|
|
115
|
+
"profile.sessionStore.targetSessionId",
|
|
116
|
+
);
|
|
117
|
+
const headHash = safeName(
|
|
118
|
+
input.headHash,
|
|
119
|
+
"profile.sessionStore.headHash",
|
|
120
|
+
64,
|
|
121
|
+
).toLowerCase();
|
|
122
|
+
const eventCount = Number(input.eventCount);
|
|
123
|
+
if (
|
|
124
|
+
!SESSION_STORE_MODES.has(mode) ||
|
|
125
|
+
!/^[a-f0-9]{64}$/u.test(headHash) ||
|
|
126
|
+
!Number.isSafeInteger(eventCount) ||
|
|
127
|
+
eventCount < 1
|
|
128
|
+
) {
|
|
129
|
+
throw new TypeError("profile.sessionStore is invalid");
|
|
130
|
+
}
|
|
131
|
+
return Object.freeze({ mode, targetSessionId, headHash, eventCount });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function normalizeTransport(target, value) {
|
|
135
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
136
|
+
throw new TypeError("profile.transport must be an object");
|
|
137
|
+
}
|
|
138
|
+
if (target === "wsl") {
|
|
139
|
+
const input = exactObject(value, ["distro"], "profile.transport");
|
|
140
|
+
return Object.freeze({
|
|
141
|
+
distro: safeName(input.distro, "profile.transport.distro"),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (target === "container") {
|
|
145
|
+
const input = exactObject(value, ["container"], "profile.transport");
|
|
146
|
+
return Object.freeze({
|
|
147
|
+
container: safeName(input.container, "profile.transport.container"),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const allowed = new Set([
|
|
151
|
+
"host",
|
|
152
|
+
"user",
|
|
153
|
+
"port",
|
|
154
|
+
"identityFile",
|
|
155
|
+
"knownHostsFile",
|
|
156
|
+
"knownHostsDigest",
|
|
157
|
+
]);
|
|
158
|
+
if (
|
|
159
|
+
Object.keys(value).some((field) => !allowed.has(field)) ||
|
|
160
|
+
![
|
|
161
|
+
"host",
|
|
162
|
+
"user",
|
|
163
|
+
"port",
|
|
164
|
+
"identityFile",
|
|
165
|
+
"knownHostsFile",
|
|
166
|
+
"knownHostsDigest",
|
|
167
|
+
].every((field) => Object.hasOwn(value, field))
|
|
168
|
+
) {
|
|
169
|
+
throw new TypeError("profile.transport has an invalid schema");
|
|
170
|
+
}
|
|
171
|
+
const host = safeName(value.host, "profile.transport.host");
|
|
172
|
+
const user =
|
|
173
|
+
value.user == null ? null : safeName(value.user, "profile.transport.user");
|
|
174
|
+
const port = Number(value.port);
|
|
175
|
+
const identityFile =
|
|
176
|
+
value.identityFile == null
|
|
177
|
+
? null
|
|
178
|
+
: path.resolve(
|
|
179
|
+
safePath(value.identityFile, "profile.transport.identityFile"),
|
|
180
|
+
);
|
|
181
|
+
const knownHostsFile = path.resolve(
|
|
182
|
+
safePath(value.knownHostsFile, "profile.transport.knownHostsFile"),
|
|
183
|
+
);
|
|
184
|
+
const knownHostsDigest = safeString(
|
|
185
|
+
value.knownHostsDigest,
|
|
186
|
+
"profile.transport.knownHostsDigest",
|
|
187
|
+
80,
|
|
188
|
+
).toLowerCase();
|
|
189
|
+
if (
|
|
190
|
+
!Number.isSafeInteger(port) ||
|
|
191
|
+
port < 1 ||
|
|
192
|
+
port > 65535 ||
|
|
193
|
+
!SHA256_RE.test(knownHostsDigest)
|
|
194
|
+
) {
|
|
195
|
+
throw new TypeError("profile.transport SSH authority is invalid");
|
|
196
|
+
}
|
|
197
|
+
return Object.freeze({
|
|
198
|
+
host,
|
|
199
|
+
user,
|
|
200
|
+
port,
|
|
201
|
+
identityFile,
|
|
202
|
+
knownHostsFile,
|
|
203
|
+
knownHostsDigest,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function normalizeExecutionLocationProfile(value) {
|
|
208
|
+
const baseFields = [
|
|
209
|
+
"schema",
|
|
210
|
+
"id",
|
|
211
|
+
"target",
|
|
212
|
+
"evidenceId",
|
|
213
|
+
"cliCommand",
|
|
214
|
+
"cwd",
|
|
215
|
+
"transport",
|
|
216
|
+
"expected",
|
|
217
|
+
"sessionStore",
|
|
218
|
+
];
|
|
219
|
+
const suppliedDigest = value?.profileDigest;
|
|
220
|
+
const input = exactObject(
|
|
221
|
+
value,
|
|
222
|
+
suppliedDigest == null ? baseFields : [...baseFields, "profileDigest"],
|
|
223
|
+
"execution location profile",
|
|
224
|
+
);
|
|
225
|
+
if (input.schema !== EXECUTION_LOCATION_PROFILE_SCHEMA) {
|
|
226
|
+
throw new TypeError(
|
|
227
|
+
`execution location profile must use ${EXECUTION_LOCATION_PROFILE_SCHEMA}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
if (containsSecret(JSON.stringify(input))) {
|
|
231
|
+
throw new TypeError(
|
|
232
|
+
"execution location profile contains secret-shaped data",
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
const target = safeName(input.target, "profile.target", 32).toLowerCase();
|
|
236
|
+
if (!PROFILE_TARGETS.has(target)) {
|
|
237
|
+
throw new TypeError(
|
|
238
|
+
"profile.target is not supported by the target launcher",
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const expected = exactObject(
|
|
242
|
+
input.expected,
|
|
243
|
+
["platform", "arch", "cliVersion", "gitCommit", "tools"],
|
|
244
|
+
"profile.expected",
|
|
245
|
+
);
|
|
246
|
+
const gitCommit = safeName(
|
|
247
|
+
expected.gitCommit,
|
|
248
|
+
"profile.expected.gitCommit",
|
|
249
|
+
64,
|
|
250
|
+
).toLowerCase();
|
|
251
|
+
if (!COMMIT_RE.test(gitCommit)) {
|
|
252
|
+
throw new TypeError("profile.expected.gitCommit is invalid");
|
|
253
|
+
}
|
|
254
|
+
const profile = {
|
|
255
|
+
schema: EXECUTION_LOCATION_PROFILE_SCHEMA,
|
|
256
|
+
id: safeName(input.id, "profile.id"),
|
|
257
|
+
target,
|
|
258
|
+
evidenceId: safeName(input.evidenceId, "profile.evidenceId"),
|
|
259
|
+
cliCommand: safePath(input.cliCommand, "profile.cliCommand"),
|
|
260
|
+
cwd: safePath(input.cwd, "profile.cwd"),
|
|
261
|
+
transport: normalizeTransport(target, input.transport),
|
|
262
|
+
expected: Object.freeze({
|
|
263
|
+
platform: safeName(expected.platform, "profile.expected.platform", 64),
|
|
264
|
+
arch: safeName(expected.arch, "profile.expected.arch", 64),
|
|
265
|
+
cliVersion: safeName(
|
|
266
|
+
expected.cliVersion,
|
|
267
|
+
"profile.expected.cliVersion",
|
|
268
|
+
64,
|
|
269
|
+
),
|
|
270
|
+
gitCommit,
|
|
271
|
+
tools: Object.freeze(normalizeTools(expected.tools)),
|
|
272
|
+
}),
|
|
273
|
+
sessionStore: normalizeSessionStore(input.sessionStore),
|
|
274
|
+
};
|
|
275
|
+
const profileDigest = digest(
|
|
276
|
+
"chainlesschain.execution-location.profile.v1\0",
|
|
277
|
+
profile,
|
|
278
|
+
);
|
|
279
|
+
if (suppliedDigest != null && suppliedDigest !== profileDigest) {
|
|
280
|
+
throw new TypeError("execution location profile digest is invalid");
|
|
281
|
+
}
|
|
282
|
+
return Object.freeze({
|
|
283
|
+
...profile,
|
|
284
|
+
profileDigest,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function readBoundedRegularFile(filePath, maxBytes, label, runtimeFs = fs) {
|
|
289
|
+
return withTrustedFileParentSync(
|
|
290
|
+
runtimeFs,
|
|
291
|
+
path.resolve(filePath),
|
|
292
|
+
({ canonicalPath, parentDevice }) => {
|
|
293
|
+
const before = runtimeFs.lstatSync(canonicalPath, { bigint: true });
|
|
294
|
+
if (
|
|
295
|
+
before.isSymbolicLink() ||
|
|
296
|
+
!before.isFile() ||
|
|
297
|
+
Number(before.nlink) !== 1 ||
|
|
298
|
+
Number(before.size) <= 0 ||
|
|
299
|
+
Number(before.size) > maxBytes
|
|
300
|
+
) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`${label} must be a bounded, regular, single-link file`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
let descriptor;
|
|
306
|
+
try {
|
|
307
|
+
descriptor = runtimeFs.openSync(
|
|
308
|
+
canonicalPath,
|
|
309
|
+
runtimeFs.constants.O_RDONLY |
|
|
310
|
+
Number(runtimeFs.constants.O_NOFOLLOW || 0),
|
|
311
|
+
);
|
|
312
|
+
const opened = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
313
|
+
if (
|
|
314
|
+
!opened.isFile() ||
|
|
315
|
+
Number(opened.nlink) !== 1 ||
|
|
316
|
+
!samePathHandleFileIdentity(before, opened, parentDevice)
|
|
317
|
+
) {
|
|
318
|
+
throw new Error(`${label} identity changed while opening`);
|
|
319
|
+
}
|
|
320
|
+
const bytes = runtimeFs.readFileSync(descriptor);
|
|
321
|
+
const after = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
322
|
+
if (
|
|
323
|
+
bytes.length <= 0 ||
|
|
324
|
+
bytes.length > maxBytes ||
|
|
325
|
+
!sameFileStatIdentity(opened, after)
|
|
326
|
+
) {
|
|
327
|
+
throw new Error(`${label} changed while reading`);
|
|
328
|
+
}
|
|
329
|
+
return bytes;
|
|
330
|
+
} finally {
|
|
331
|
+
if (descriptor !== undefined) runtimeFs.closeSync(descriptor);
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function readExecutionLocationProfile(filePath, deps = {}) {
|
|
338
|
+
const bytes = readBoundedRegularFile(
|
|
339
|
+
filePath,
|
|
340
|
+
MAX_PROFILE_BYTES,
|
|
341
|
+
"execution location profile",
|
|
342
|
+
deps.fs || fs,
|
|
343
|
+
);
|
|
344
|
+
let parsed;
|
|
345
|
+
try {
|
|
346
|
+
parsed = JSON.parse(
|
|
347
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
348
|
+
);
|
|
349
|
+
} catch {
|
|
350
|
+
throw new Error("execution location profile is not strict UTF-8 JSON");
|
|
351
|
+
}
|
|
352
|
+
return normalizeExecutionLocationProfile(parsed);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function quotePosix(value) {
|
|
356
|
+
return `'${String(value).replace(/'/gu, `'"'"'`)}'`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function materializeKnownHostsAuthority(bytes, deps = {}) {
|
|
360
|
+
const runtimeFs = deps.fs || fs;
|
|
361
|
+
const root = runtimeFs.mkdtempSync(
|
|
362
|
+
path.join(deps.tmpdir || tmpdir(), "cc-target-known-hosts-"),
|
|
363
|
+
);
|
|
364
|
+
const authorityPath = path.join(root, "known_hosts");
|
|
365
|
+
let descriptor;
|
|
366
|
+
try {
|
|
367
|
+
descriptor = runtimeFs.openSync(
|
|
368
|
+
authorityPath,
|
|
369
|
+
runtimeFs.constants.O_CREAT |
|
|
370
|
+
runtimeFs.constants.O_EXCL |
|
|
371
|
+
runtimeFs.constants.O_WRONLY,
|
|
372
|
+
0o600,
|
|
373
|
+
);
|
|
374
|
+
runtimeFs.writeFileSync(descriptor, bytes);
|
|
375
|
+
runtimeFs.fsyncSync(descriptor);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
try {
|
|
378
|
+
runtimeFs.rmSync(root, { recursive: true, force: true });
|
|
379
|
+
} catch {
|
|
380
|
+
// Preserve the original materialization failure.
|
|
381
|
+
}
|
|
382
|
+
throw error;
|
|
383
|
+
} finally {
|
|
384
|
+
if (descriptor !== undefined) runtimeFs.closeSync(descriptor);
|
|
385
|
+
}
|
|
386
|
+
return Object.freeze({
|
|
387
|
+
path: authorityPath,
|
|
388
|
+
cleanup: () => runtimeFs.rmSync(root, { recursive: true, force: true }),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function targetInvocation(profile, cliArgs, deps = {}, options = {}) {
|
|
393
|
+
if (profile.target === "wsl") {
|
|
394
|
+
if ((deps.platform || process.platform) !== "win32") {
|
|
395
|
+
throw new Error("WSL target launch requires a Windows host");
|
|
396
|
+
}
|
|
397
|
+
return Object.freeze({
|
|
398
|
+
file: "wsl.exe",
|
|
399
|
+
args: Object.freeze([
|
|
400
|
+
"--distribution",
|
|
401
|
+
profile.transport.distro,
|
|
402
|
+
"--cd",
|
|
403
|
+
profile.cwd,
|
|
404
|
+
"--exec",
|
|
405
|
+
profile.cliCommand,
|
|
406
|
+
...cliArgs,
|
|
407
|
+
]),
|
|
408
|
+
cleanup: null,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (profile.target === "container") {
|
|
412
|
+
return Object.freeze({
|
|
413
|
+
file: "docker",
|
|
414
|
+
args: Object.freeze([
|
|
415
|
+
"exec",
|
|
416
|
+
...(options.input == null ? [] : ["-i"]),
|
|
417
|
+
"--workdir",
|
|
418
|
+
profile.cwd,
|
|
419
|
+
profile.transport.container,
|
|
420
|
+
profile.cliCommand,
|
|
421
|
+
...cliArgs,
|
|
422
|
+
]),
|
|
423
|
+
cleanup: null,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
const transport = profile.transport;
|
|
427
|
+
const knownHostsBytes = readBoundedRegularFile(
|
|
428
|
+
transport.knownHostsFile,
|
|
429
|
+
MAX_PROFILE_BYTES,
|
|
430
|
+
"SSH known-hosts authority",
|
|
431
|
+
deps.fs || fs,
|
|
432
|
+
);
|
|
433
|
+
const actualKnownHostsDigest = `sha256:${createHash("sha256")
|
|
434
|
+
.update(knownHostsBytes)
|
|
435
|
+
.digest("hex")}`;
|
|
436
|
+
if (actualKnownHostsDigest !== transport.knownHostsDigest) {
|
|
437
|
+
throw new Error("SSH known-hosts authority digest mismatch");
|
|
438
|
+
}
|
|
439
|
+
const knownHostsAuthority = materializeKnownHostsAuthority(
|
|
440
|
+
knownHostsBytes,
|
|
441
|
+
deps,
|
|
442
|
+
);
|
|
443
|
+
const args = [
|
|
444
|
+
"-o",
|
|
445
|
+
"BatchMode=yes",
|
|
446
|
+
"-o",
|
|
447
|
+
"StrictHostKeyChecking=yes",
|
|
448
|
+
"-o",
|
|
449
|
+
"NumberOfPasswordPrompts=0",
|
|
450
|
+
"-o",
|
|
451
|
+
"ConnectTimeout=10",
|
|
452
|
+
"-o",
|
|
453
|
+
`UserKnownHostsFile=${knownHostsAuthority.path}`,
|
|
454
|
+
"-p",
|
|
455
|
+
String(transport.port),
|
|
456
|
+
];
|
|
457
|
+
if (transport.identityFile) {
|
|
458
|
+
args.push("-o", "IdentitiesOnly=yes", "-i", transport.identityFile);
|
|
459
|
+
}
|
|
460
|
+
const target = transport.user
|
|
461
|
+
? `${transport.user}@${transport.host}`
|
|
462
|
+
: transport.host;
|
|
463
|
+
args.push(
|
|
464
|
+
target,
|
|
465
|
+
`cd ${quotePosix(profile.cwd)} && ${[profile.cliCommand, ...cliArgs]
|
|
466
|
+
.map(quotePosix)
|
|
467
|
+
.join(" ")}`,
|
|
468
|
+
);
|
|
469
|
+
return Object.freeze({
|
|
470
|
+
file: "ssh",
|
|
471
|
+
args: Object.freeze(args),
|
|
472
|
+
cleanup: knownHostsAuthority.cleanup,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function runTargetCommand(profile, cliArgs, deps = {}, options = {}) {
|
|
477
|
+
const invocation = targetInvocation(profile, cliArgs, deps, options);
|
|
478
|
+
const spawnSync =
|
|
479
|
+
deps.spawnSync ||
|
|
480
|
+
((file, args, spawnOptions) =>
|
|
481
|
+
executionBroker.spawnSync(file, args, spawnOptions));
|
|
482
|
+
try {
|
|
483
|
+
const result = spawnSync(invocation.file, invocation.args, {
|
|
484
|
+
origin: "execution-location:target",
|
|
485
|
+
scope: "execution-location",
|
|
486
|
+
policy: "allow",
|
|
487
|
+
shell: false,
|
|
488
|
+
encoding: "utf8",
|
|
489
|
+
timeout: options.interactive ? undefined : 30000,
|
|
490
|
+
maxBuffer: options.interactive ? undefined : MAX_PROBE_BYTES,
|
|
491
|
+
...(options.input == null ? {} : { input: options.input }),
|
|
492
|
+
stdio: options.interactive
|
|
493
|
+
? "inherit"
|
|
494
|
+
: [options.input == null ? "ignore" : "pipe", "pipe", "pipe"],
|
|
495
|
+
});
|
|
496
|
+
if (result?.error) throw result.error;
|
|
497
|
+
if (!result || result.status !== 0) {
|
|
498
|
+
const error = new Error(
|
|
499
|
+
`target command failed with status ${result?.status ?? "unknown"}`,
|
|
500
|
+
);
|
|
501
|
+
error.code = "CC_EXECUTION_LOCATION_TARGET_COMMAND_FAILED";
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
return options.interactive ? null : result.stdout;
|
|
505
|
+
} finally {
|
|
506
|
+
invocation.cleanup?.();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function parseTargetProjection(raw, label) {
|
|
511
|
+
const bytes = Buffer.byteLength(String(raw || ""), "utf8");
|
|
512
|
+
if (bytes <= 0 || bytes > MAX_PROBE_BYTES) {
|
|
513
|
+
throw new Error(`${label} output is empty or exceeds its byte limit`);
|
|
514
|
+
}
|
|
515
|
+
let projection;
|
|
516
|
+
try {
|
|
517
|
+
projection = JSON.parse(String(raw));
|
|
518
|
+
} catch {
|
|
519
|
+
throw new Error(`${label} output is not one exact JSON object`);
|
|
520
|
+
}
|
|
521
|
+
if (
|
|
522
|
+
!projection ||
|
|
523
|
+
typeof projection !== "object" ||
|
|
524
|
+
Array.isArray(projection) ||
|
|
525
|
+
containsSecret(JSON.stringify(projection))
|
|
526
|
+
) {
|
|
527
|
+
throw new Error(`${label} output is invalid or secret-bearing`);
|
|
528
|
+
}
|
|
529
|
+
return projection;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function validateProfileHandoff(profile, handoff) {
|
|
533
|
+
if (!handoff?.allowed) {
|
|
534
|
+
throw new Error("execution location handoff is not allowed");
|
|
535
|
+
}
|
|
536
|
+
if (
|
|
537
|
+
handoff.target?.location !== profile.target ||
|
|
538
|
+
handoff.target?.evidenceId !== profile.evidenceId
|
|
539
|
+
) {
|
|
540
|
+
throw new Error("execution location profile does not match handoff target");
|
|
541
|
+
}
|
|
542
|
+
if (
|
|
543
|
+
handoff.transfer?.git?.baseCommit !== profile.expected.gitCommit ||
|
|
544
|
+
handoff.target?.dataBoundary?.root !== profile.cwd
|
|
545
|
+
) {
|
|
546
|
+
throw new Error("execution location profile source or boundary drifted");
|
|
547
|
+
}
|
|
548
|
+
if (profile.sessionStore) {
|
|
549
|
+
if (
|
|
550
|
+
profile.sessionStore.targetSessionId !== handoff.session?.sessionId ||
|
|
551
|
+
profile.sessionStore.headHash !== handoff.session?.headHash ||
|
|
552
|
+
profile.sessionStore.eventCount !== handoff.session?.eventCount
|
|
553
|
+
) {
|
|
554
|
+
throw new Error("target session-store authority does not match handoff");
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function validateCurrentProjection(profile, rawProjection) {
|
|
560
|
+
if (
|
|
561
|
+
rawProjection.schema !== SESSION_LOCATION_AUTHORITY_SCHEMA ||
|
|
562
|
+
rawProjection.authority !== "current-process-observation"
|
|
563
|
+
) {
|
|
564
|
+
throw new Error("target current-location projection has invalid authority");
|
|
565
|
+
}
|
|
566
|
+
const binding = normalizeExecutionLocationBinding(rawProjection.binding);
|
|
567
|
+
const expected = profile.expected;
|
|
568
|
+
const missingTools = expected.tools.filter(
|
|
569
|
+
(tool) => !binding.runtime.tools.includes(tool),
|
|
570
|
+
);
|
|
571
|
+
if (
|
|
572
|
+
binding.observed !== true ||
|
|
573
|
+
binding.location !== profile.target ||
|
|
574
|
+
binding.source.cwd !== profile.cwd ||
|
|
575
|
+
binding.source.git.commit !== expected.gitCommit ||
|
|
576
|
+
binding.runtime.platform !== expected.platform ||
|
|
577
|
+
binding.runtime.arch !== expected.arch ||
|
|
578
|
+
binding.runtime.cliVersion !== expected.cliVersion ||
|
|
579
|
+
missingTools.length > 0
|
|
580
|
+
) {
|
|
581
|
+
throw new Error("target current-location facts do not match the profile");
|
|
582
|
+
}
|
|
583
|
+
return binding;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function attestExecutionLocationTarget(input = {}, deps = {}) {
|
|
587
|
+
const profile = normalizeExecutionLocationProfile(input.profile);
|
|
588
|
+
validateProfileHandoff(profile, input.handoff);
|
|
589
|
+
const raw = runTargetCommand(
|
|
590
|
+
profile,
|
|
591
|
+
["session", "location", "current", "--json"],
|
|
592
|
+
deps,
|
|
593
|
+
);
|
|
594
|
+
const current = parseTargetProjection(raw, "target current-location probe");
|
|
595
|
+
const binding = validateCurrentProjection(profile, current);
|
|
596
|
+
return createExecutionLocationTargetAttestation({
|
|
597
|
+
profileDigest: profile.profileDigest,
|
|
598
|
+
sourceSessionId: input.handoff.session.sessionId,
|
|
599
|
+
sourceHeadHash: input.handoff.session.headHash,
|
|
600
|
+
sourceEventCount: input.handoff.session.eventCount,
|
|
601
|
+
targetEvidenceId: input.handoff.target.evidenceId,
|
|
602
|
+
baseCommit: input.handoff.transfer.git.baseCommit,
|
|
603
|
+
binding,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function verifyTargetSessionStore(
|
|
608
|
+
profile,
|
|
609
|
+
handoff,
|
|
610
|
+
transfer,
|
|
611
|
+
attestation,
|
|
612
|
+
deps,
|
|
613
|
+
) {
|
|
614
|
+
if (!profile.sessionStore) {
|
|
615
|
+
throw new Error("target session-store authority is required for resume");
|
|
616
|
+
}
|
|
617
|
+
const raw = runTargetCommand(
|
|
618
|
+
profile,
|
|
619
|
+
[
|
|
620
|
+
"session",
|
|
621
|
+
"location",
|
|
622
|
+
"show",
|
|
623
|
+
profile.sessionStore.targetSessionId,
|
|
624
|
+
"--json",
|
|
625
|
+
],
|
|
626
|
+
deps,
|
|
627
|
+
);
|
|
628
|
+
const projection = parseTargetProjection(raw, "target session-store probe");
|
|
629
|
+
const commonMatches =
|
|
630
|
+
projection.schema === SESSION_LOCATION_AUTHORITY_SCHEMA &&
|
|
631
|
+
projection.sessionId === profile.sessionStore.targetSessionId &&
|
|
632
|
+
projection.sessionId === handoff.session.sessionId;
|
|
633
|
+
const replicatedMatches =
|
|
634
|
+
profile.sessionStore.mode === "replicated" &&
|
|
635
|
+
projection.authority === "verified-session-location-handoff" &&
|
|
636
|
+
projection.headHash === transfer.targetHeadHash &&
|
|
637
|
+
projection.eventCount === transfer.targetEventCount &&
|
|
638
|
+
projection.bindingEventHash === transfer.targetHeadHash &&
|
|
639
|
+
projection.bindingEventCount === transfer.targetEventCount &&
|
|
640
|
+
projection.locationHandoff?.handoffId === transfer.handoffId &&
|
|
641
|
+
projection.locationHandoff?.source?.sessionId ===
|
|
642
|
+
handoff.session.sessionId &&
|
|
643
|
+
projection.locationHandoff?.source?.headHash === handoff.session.headHash &&
|
|
644
|
+
projection.locationHandoff?.source?.eventCount ===
|
|
645
|
+
handoff.session.eventCount &&
|
|
646
|
+
projection.locationHandoff?.source?.transcriptDigest ===
|
|
647
|
+
transfer.transcriptDigest &&
|
|
648
|
+
projection.locationHandoff?.target?.profileDigest ===
|
|
649
|
+
profile.profileDigest &&
|
|
650
|
+
projection.locationHandoff?.target?.targetEvidenceId ===
|
|
651
|
+
profile.evidenceId &&
|
|
652
|
+
projection.locationHandoff?.target?.targetFactsDigest ===
|
|
653
|
+
attestation.targetFactsDigest &&
|
|
654
|
+
projection.locationHandoff?.target?.attestationDigest ===
|
|
655
|
+
transfer.attestationDigest;
|
|
656
|
+
const sharedMatches =
|
|
657
|
+
profile.sessionStore.mode === "shared" &&
|
|
658
|
+
projection.authority === "verified-session-start" &&
|
|
659
|
+
projection.headHash === profile.sessionStore.headHash &&
|
|
660
|
+
projection.eventCount === profile.sessionStore.eventCount &&
|
|
661
|
+
projection.headHash === handoff.session.headHash &&
|
|
662
|
+
projection.eventCount === handoff.session.eventCount;
|
|
663
|
+
if (!commonMatches || (!replicatedMatches && !sharedMatches)) {
|
|
664
|
+
throw new Error(
|
|
665
|
+
"target canonical session authority does not match handoff",
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
return Object.freeze({
|
|
669
|
+
mode: profile.sessionStore.mode,
|
|
670
|
+
sessionId: projection.sessionId,
|
|
671
|
+
headHash: projection.headHash,
|
|
672
|
+
eventCount: projection.eventCount,
|
|
673
|
+
authority: projection.authority,
|
|
674
|
+
handoffId: projection.locationHandoff?.handoffId ?? null,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function transferTargetSessionStore(
|
|
679
|
+
profile,
|
|
680
|
+
handoff,
|
|
681
|
+
attestation,
|
|
682
|
+
transcriptInput,
|
|
683
|
+
deps,
|
|
684
|
+
) {
|
|
685
|
+
if (!profile.sessionStore) {
|
|
686
|
+
throw new Error("target session-store authority is required for resume");
|
|
687
|
+
}
|
|
688
|
+
if (profile.sessionStore.mode === "shared") {
|
|
689
|
+
return Object.freeze({
|
|
690
|
+
mode: "shared",
|
|
691
|
+
performed: false,
|
|
692
|
+
installed: false,
|
|
693
|
+
transcriptDigest: null,
|
|
694
|
+
receiptDigest: null,
|
|
695
|
+
handoffId: null,
|
|
696
|
+
targetHeadHash: handoff.session.headHash,
|
|
697
|
+
targetEventCount: handoff.session.eventCount,
|
|
698
|
+
attestationDigest: null,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
const transcriptBytes = Buffer.isBuffer(transcriptInput)
|
|
702
|
+
? transcriptInput
|
|
703
|
+
: Buffer.from(transcriptInput || "");
|
|
704
|
+
if (transcriptBytes.length === 0) {
|
|
705
|
+
throw new Error("source transcript bytes are required for replication");
|
|
706
|
+
}
|
|
707
|
+
if (transcriptBytes.length > MAX_REPLICA_BYTES) {
|
|
708
|
+
throw new Error(
|
|
709
|
+
`source transcript exceeds ${MAX_REPLICA_BYTES} replica bytes`,
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
const transcriptDigest = `sha256:${createHash("sha256")
|
|
713
|
+
.update(transcriptBytes)
|
|
714
|
+
.digest("hex")}`;
|
|
715
|
+
const raw = runTargetCommand(
|
|
716
|
+
profile,
|
|
717
|
+
[
|
|
718
|
+
"session",
|
|
719
|
+
"location",
|
|
720
|
+
"prepare",
|
|
721
|
+
profile.sessionStore.targetSessionId,
|
|
722
|
+
"--expected-head-hash",
|
|
723
|
+
handoff.session.headHash,
|
|
724
|
+
"--expected-event-count",
|
|
725
|
+
String(handoff.session.eventCount),
|
|
726
|
+
"--expected-transcript-digest",
|
|
727
|
+
transcriptDigest,
|
|
728
|
+
"--expected-target-facts-digest",
|
|
729
|
+
attestation.targetFactsDigest,
|
|
730
|
+
"--profile-digest",
|
|
731
|
+
profile.profileDigest,
|
|
732
|
+
"--target-evidence-id",
|
|
733
|
+
profile.evidenceId,
|
|
734
|
+
"--attestation-digest",
|
|
735
|
+
attestation.attestationDigest,
|
|
736
|
+
"--json",
|
|
737
|
+
],
|
|
738
|
+
deps,
|
|
739
|
+
{ input: transcriptBytes },
|
|
740
|
+
);
|
|
741
|
+
const receipt = parseTargetProjection(
|
|
742
|
+
raw,
|
|
743
|
+
"target session location-handoff receipt",
|
|
744
|
+
);
|
|
745
|
+
exactObject(
|
|
746
|
+
receipt,
|
|
747
|
+
[
|
|
748
|
+
"schema",
|
|
749
|
+
"sessionId",
|
|
750
|
+
"sourceHeadHash",
|
|
751
|
+
"sourceEventCount",
|
|
752
|
+
"transcriptDigest",
|
|
753
|
+
"handoffId",
|
|
754
|
+
"targetHeadHash",
|
|
755
|
+
"targetEventCount",
|
|
756
|
+
"targetFactsDigest",
|
|
757
|
+
"profileDigest",
|
|
758
|
+
"targetEvidenceId",
|
|
759
|
+
"attestationDigest",
|
|
760
|
+
"replicaInstalled",
|
|
761
|
+
"handoffAppended",
|
|
762
|
+
"receiptDigest",
|
|
763
|
+
],
|
|
764
|
+
"target session location-handoff receipt",
|
|
765
|
+
);
|
|
766
|
+
const receiptMaterial = {
|
|
767
|
+
schema: SESSION_LOCATION_HANDOFF_INSTALL_SCHEMA,
|
|
768
|
+
sessionId: handoff.session.sessionId,
|
|
769
|
+
sourceHeadHash: handoff.session.headHash,
|
|
770
|
+
sourceEventCount: handoff.session.eventCount,
|
|
771
|
+
transcriptDigest,
|
|
772
|
+
handoffId: receipt.handoffId,
|
|
773
|
+
targetHeadHash: receipt.targetHeadHash,
|
|
774
|
+
targetEventCount: handoff.session.eventCount + 1,
|
|
775
|
+
targetFactsDigest: attestation.targetFactsDigest,
|
|
776
|
+
profileDigest: profile.profileDigest,
|
|
777
|
+
targetEvidenceId: profile.evidenceId,
|
|
778
|
+
attestationDigest: receipt.attestationDigest,
|
|
779
|
+
replicaInstalled: receipt.replicaInstalled,
|
|
780
|
+
handoffAppended: receipt.handoffAppended,
|
|
781
|
+
};
|
|
782
|
+
const expectedReceiptDigest = digest(
|
|
783
|
+
"chainlesschain.session-execution-location-handoff-install.v1\0",
|
|
784
|
+
receiptMaterial,
|
|
785
|
+
);
|
|
786
|
+
if (
|
|
787
|
+
receipt.schema !== receiptMaterial.schema ||
|
|
788
|
+
receipt.sessionId !== receiptMaterial.sessionId ||
|
|
789
|
+
receipt.sourceHeadHash !== receiptMaterial.sourceHeadHash ||
|
|
790
|
+
receipt.sourceEventCount !== receiptMaterial.sourceEventCount ||
|
|
791
|
+
receipt.transcriptDigest !== receiptMaterial.transcriptDigest ||
|
|
792
|
+
!SHA256_RE.test(receipt.handoffId || "") ||
|
|
793
|
+
!/^[a-f0-9]{64}$/u.test(receipt.targetHeadHash || "") ||
|
|
794
|
+
receipt.targetEventCount !== receiptMaterial.targetEventCount ||
|
|
795
|
+
receipt.targetFactsDigest !== receiptMaterial.targetFactsDigest ||
|
|
796
|
+
receipt.profileDigest !== receiptMaterial.profileDigest ||
|
|
797
|
+
receipt.targetEvidenceId !== receiptMaterial.targetEvidenceId ||
|
|
798
|
+
!SHA256_RE.test(receipt.attestationDigest || "") ||
|
|
799
|
+
(receipt.handoffAppended === true &&
|
|
800
|
+
receipt.attestationDigest !== attestation.attestationDigest) ||
|
|
801
|
+
typeof receipt.replicaInstalled !== "boolean" ||
|
|
802
|
+
typeof receipt.handoffAppended !== "boolean" ||
|
|
803
|
+
receipt.receiptDigest !== expectedReceiptDigest
|
|
804
|
+
) {
|
|
805
|
+
throw new Error(
|
|
806
|
+
"target session location-handoff receipt does not match source",
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
return Object.freeze({
|
|
810
|
+
mode: "replicated",
|
|
811
|
+
performed: true,
|
|
812
|
+
installed: receipt.replicaInstalled,
|
|
813
|
+
handoffAppended: receipt.handoffAppended,
|
|
814
|
+
transcriptDigest,
|
|
815
|
+
handoffId: receipt.handoffId,
|
|
816
|
+
targetHeadHash: receipt.targetHeadHash,
|
|
817
|
+
targetEventCount: receipt.targetEventCount,
|
|
818
|
+
attestationDigest: receipt.attestationDigest,
|
|
819
|
+
receiptDigest: receipt.receiptDigest,
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function verifySourceAuthorityBeforeResume(handoff, readSourceAuthority) {
|
|
824
|
+
if (typeof readSourceAuthority !== "function") {
|
|
825
|
+
throw new Error("source authority revalidation is required before resume");
|
|
826
|
+
}
|
|
827
|
+
const current = readSourceAuthority();
|
|
828
|
+
if (
|
|
829
|
+
current?.sessionId !== handoff.session?.sessionId ||
|
|
830
|
+
current?.headHash !== handoff.session?.headHash ||
|
|
831
|
+
current?.eventCount !== handoff.session?.eventCount
|
|
832
|
+
) {
|
|
833
|
+
throw new Error("source session authority changed before target resume");
|
|
834
|
+
}
|
|
835
|
+
return Object.freeze({
|
|
836
|
+
sessionId: current.sessionId,
|
|
837
|
+
headHash: current.headHash,
|
|
838
|
+
eventCount: current.eventCount,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
export function resumeExecutionLocationTarget(input = {}, deps = {}) {
|
|
843
|
+
const profile = normalizeExecutionLocationProfile(input.profile);
|
|
844
|
+
validateProfileHandoff(profile, input.handoff);
|
|
845
|
+
const attestation = attestExecutionLocationTarget(
|
|
846
|
+
{ profile, handoff: input.handoff },
|
|
847
|
+
deps,
|
|
848
|
+
);
|
|
849
|
+
const expectedTargetFactsDigest = safeString(
|
|
850
|
+
input.expectedTargetFactsDigest,
|
|
851
|
+
"expected target facts digest",
|
|
852
|
+
80,
|
|
853
|
+
).toLowerCase();
|
|
854
|
+
if (
|
|
855
|
+
!SHA256_RE.test(expectedTargetFactsDigest) ||
|
|
856
|
+
expectedTargetFactsDigest !== attestation.targetFactsDigest
|
|
857
|
+
) {
|
|
858
|
+
throw new Error("target facts digest changed before resume");
|
|
859
|
+
}
|
|
860
|
+
const transfer = transferTargetSessionStore(
|
|
861
|
+
profile,
|
|
862
|
+
input.handoff,
|
|
863
|
+
attestation,
|
|
864
|
+
input.transcriptBytes,
|
|
865
|
+
deps,
|
|
866
|
+
);
|
|
867
|
+
const sessionStore = verifyTargetSessionStore(
|
|
868
|
+
profile,
|
|
869
|
+
input.handoff,
|
|
870
|
+
transfer,
|
|
871
|
+
attestation,
|
|
872
|
+
deps,
|
|
873
|
+
);
|
|
874
|
+
const sourceAuthority = verifySourceAuthorityBeforeResume(
|
|
875
|
+
input.handoff,
|
|
876
|
+
input.readSourceAuthority,
|
|
877
|
+
);
|
|
878
|
+
runTargetCommand(
|
|
879
|
+
profile,
|
|
880
|
+
["session", "resume", sessionStore.sessionId],
|
|
881
|
+
deps,
|
|
882
|
+
{ interactive: true },
|
|
883
|
+
);
|
|
884
|
+
const material = {
|
|
885
|
+
schema: EXECUTION_LOCATION_TARGET_RESUME_SCHEMA,
|
|
886
|
+
profileDigest: profile.profileDigest,
|
|
887
|
+
attestationDigest: attestation.attestationDigest,
|
|
888
|
+
target: profile.target,
|
|
889
|
+
targetEvidenceId: profile.evidenceId,
|
|
890
|
+
sourceAuthority,
|
|
891
|
+
sessionStore: { ...sessionStore, transfer },
|
|
892
|
+
command: "session-resume",
|
|
893
|
+
exitStatus: 0,
|
|
894
|
+
continuity: "target-process-lifetime",
|
|
895
|
+
gaps: [
|
|
896
|
+
"disconnect-reconnect-not-durable",
|
|
897
|
+
"cross-host-concurrent-writer-fencing-not-durable",
|
|
898
|
+
...(profile.sessionStore.mode === "shared"
|
|
899
|
+
? ["shared-session-location-handoff-event-not-anchored"]
|
|
900
|
+
: []),
|
|
901
|
+
],
|
|
902
|
+
};
|
|
903
|
+
return Object.freeze({
|
|
904
|
+
...material,
|
|
905
|
+
receiptDigest: digest(
|
|
906
|
+
"chainlesschain.execution-location.target-resume.v1\0",
|
|
907
|
+
material,
|
|
908
|
+
),
|
|
909
|
+
});
|
|
910
|
+
}
|