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
|
@@ -17,6 +17,7 @@ import fs, {
|
|
|
17
17
|
lstatSync,
|
|
18
18
|
readSync,
|
|
19
19
|
writeSync,
|
|
20
|
+
writeFileSync,
|
|
20
21
|
ftruncateSync,
|
|
21
22
|
fsyncSync,
|
|
22
23
|
} from "node:fs";
|
|
@@ -70,7 +71,12 @@ import {
|
|
|
70
71
|
import { createSessionPersistenceFailure } from "../lib/session-persistence-failure.js";
|
|
71
72
|
import { withSessionHostWriteAuthority } from "../lib/session-host-lease.js";
|
|
72
73
|
import { createSessionTranscriptStructureProjection } from "../lib/session-transcript-structure.js";
|
|
73
|
-
import {
|
|
74
|
+
import {
|
|
75
|
+
computeExecutionLocationTargetFactsDigest,
|
|
76
|
+
createExecutionLocationTargetAttestation,
|
|
77
|
+
normalizeExecutionLocationBinding,
|
|
78
|
+
} from "../lib/execution-location-contract.js";
|
|
79
|
+
import { canonicalJson } from "../lib/scheduler-kernel/contract.js";
|
|
74
80
|
import {
|
|
75
81
|
listSessionAntiRollbackIds,
|
|
76
82
|
publishSessionAntiRollbackAnchor,
|
|
@@ -104,6 +110,10 @@ export const _sessionScaleFaultHooks = Object.seal({
|
|
|
104
110
|
beforeForkDirectoryFsync: null,
|
|
105
111
|
afterForkDirectoryFsync: null,
|
|
106
112
|
afterForkMeta: null,
|
|
113
|
+
afterReplicaPublish: null,
|
|
114
|
+
beforeReplicaDirectoryFsync: null,
|
|
115
|
+
afterReplicaDirectoryFsync: null,
|
|
116
|
+
afterLocationHandoffAppend: null,
|
|
107
117
|
beforeDeleteDirectoryFsync: null,
|
|
108
118
|
afterDeleteDirectoryFsync: null,
|
|
109
119
|
afterAntiRollbackPublish: null,
|
|
@@ -116,6 +126,15 @@ export const WS_TURN_REQUEST_ID_MAX_BYTES = 128;
|
|
|
116
126
|
export const CANONICAL_JSONL_RECORD_MAX_BYTES = DEFAULT_MAX_FILE_LINE_BYTES;
|
|
117
127
|
export const CANONICAL_JSONL_RECORD_TOO_LARGE_CODE =
|
|
118
128
|
"CC_SESSION_JSONL_RECORD_TOO_LARGE";
|
|
129
|
+
export const SESSION_REPLICA_INSTALL_SCHEMA =
|
|
130
|
+
"chainlesschain.session-replica-install/v1";
|
|
131
|
+
export const SESSION_EXECUTION_LOCATION_HANDOFF_EVENT =
|
|
132
|
+
"execution_location_handoff";
|
|
133
|
+
export const SESSION_EXECUTION_LOCATION_HANDOFF_SCHEMA =
|
|
134
|
+
"chainlesschain.session-execution-location-handoff/v1";
|
|
135
|
+
export const SESSION_EXECUTION_LOCATION_HANDOFF_INSTALL_SCHEMA =
|
|
136
|
+
"chainlesschain.session-execution-location-handoff-install/v1";
|
|
137
|
+
export const MAX_SESSION_REPLICA_BYTES = 64 * 1024 * 1024;
|
|
119
138
|
const WS_TURN_CONTENT_MAX_BYTES = 4 * 1024 * 1024;
|
|
120
139
|
const WS_TURN_REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@-]*$/;
|
|
121
140
|
const WS_TURN_INPUT_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
@@ -2625,20 +2644,39 @@ export function startSession(sessionId, meta = {}) {
|
|
|
2625
2644
|
return id;
|
|
2626
2645
|
}
|
|
2627
2646
|
|
|
2628
|
-
/** Return the
|
|
2647
|
+
/** Return the latest canonical execution-location authority for a session. */
|
|
2629
2648
|
export function getVerifiedSessionExecutionLocationAuthority(sessionId) {
|
|
2630
2649
|
return readVerifiedProjection(sessionId, () => {
|
|
2631
2650
|
let sessionStartSeen = false;
|
|
2632
2651
|
let hasExecutionLocation = false;
|
|
2633
2652
|
let executionLocation = null;
|
|
2653
|
+
let eventCount = 0;
|
|
2654
|
+
let bindingEventHash = null;
|
|
2655
|
+
let bindingEventCount = null;
|
|
2656
|
+
let locationHandoff = null;
|
|
2634
2657
|
return {
|
|
2635
2658
|
accept(event) {
|
|
2659
|
+
eventCount += 1;
|
|
2636
2660
|
if (event?.type === "session_start" && !sessionStartSeen) {
|
|
2637
2661
|
sessionStartSeen = true;
|
|
2638
2662
|
if (Object.hasOwn(event.data || {}, "executionLocation")) {
|
|
2639
2663
|
hasExecutionLocation = true;
|
|
2640
2664
|
executionLocation = event.data.executionLocation;
|
|
2665
|
+
bindingEventHash = event.hash;
|
|
2666
|
+
bindingEventCount = eventCount;
|
|
2641
2667
|
}
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
if (event?.type === SESSION_EXECUTION_LOCATION_HANDOFF_EVENT) {
|
|
2671
|
+
locationHandoff = projectSessionExecutionLocationHandoff(
|
|
2672
|
+
sessionId,
|
|
2673
|
+
event,
|
|
2674
|
+
eventCount,
|
|
2675
|
+
);
|
|
2676
|
+
hasExecutionLocation = true;
|
|
2677
|
+
executionLocation = locationHandoff.target.binding;
|
|
2678
|
+
bindingEventHash = event.hash;
|
|
2679
|
+
bindingEventCount = eventCount;
|
|
2642
2680
|
}
|
|
2643
2681
|
},
|
|
2644
2682
|
finish(authority) {
|
|
@@ -2659,6 +2697,13 @@ export function getVerifiedSessionExecutionLocationAuthority(sessionId) {
|
|
|
2659
2697
|
sessionId,
|
|
2660
2698
|
headHash: authority.headHash,
|
|
2661
2699
|
eventCount: authority.eventCount,
|
|
2700
|
+
authority:
|
|
2701
|
+
locationHandoff === null
|
|
2702
|
+
? "verified-session-start"
|
|
2703
|
+
: "verified-session-location-handoff",
|
|
2704
|
+
bindingEventHash,
|
|
2705
|
+
bindingEventCount,
|
|
2706
|
+
locationHandoff,
|
|
2662
2707
|
binding: normalizeExecutionLocationBinding(executionLocation),
|
|
2663
2708
|
});
|
|
2664
2709
|
},
|
|
@@ -3435,6 +3480,597 @@ export function readVerifiedTranscriptBytes(sessionId) {
|
|
|
3435
3480
|
);
|
|
3436
3481
|
}
|
|
3437
3482
|
|
|
3483
|
+
function normalizeSessionReplicaExpectation(expected) {
|
|
3484
|
+
const headHash = String(expected?.headHash || "").toLowerCase();
|
|
3485
|
+
const eventCount = Number(expected?.eventCount);
|
|
3486
|
+
const transcriptDigest = String(
|
|
3487
|
+
expected?.transcriptDigest || "",
|
|
3488
|
+
).toLowerCase();
|
|
3489
|
+
if (
|
|
3490
|
+
!/^[a-f0-9]{64}$/u.test(headHash) ||
|
|
3491
|
+
!Number.isSafeInteger(eventCount) ||
|
|
3492
|
+
eventCount < 1 ||
|
|
3493
|
+
!/^sha256:[a-f0-9]{64}$/u.test(transcriptDigest)
|
|
3494
|
+
) {
|
|
3495
|
+
throw new TypeError("session replica expectation is invalid");
|
|
3496
|
+
}
|
|
3497
|
+
return Object.freeze({ headHash, eventCount, transcriptDigest });
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
function verifySessionReplicaCandidate(sessionId, filePath, expected) {
|
|
3501
|
+
return withTranscriptFileParent(
|
|
3502
|
+
filePath,
|
|
3503
|
+
({ canonicalPath, parentDevice }) => {
|
|
3504
|
+
let descriptor = null;
|
|
3505
|
+
try {
|
|
3506
|
+
descriptor = openSync(
|
|
3507
|
+
canonicalPath,
|
|
3508
|
+
fsConstants.O_RDONLY | Number(fsConstants.O_NOFOLLOW ?? 0),
|
|
3509
|
+
);
|
|
3510
|
+
const beforeStats = fstatSync(descriptor, { bigint: true });
|
|
3511
|
+
const publishedBeforeStats = readPhysicalTranscriptStats(canonicalPath);
|
|
3512
|
+
const before = physicalTranscriptStateFromStats(beforeStats);
|
|
3513
|
+
const publishedBefore =
|
|
3514
|
+
physicalTranscriptStateFromStats(publishedBeforeStats);
|
|
3515
|
+
const descriptorMatchesPublished =
|
|
3516
|
+
parentDevice === null
|
|
3517
|
+
? samePhysicalTranscriptState(before, publishedBefore)
|
|
3518
|
+
: samePathHandleStableFileIdentity(
|
|
3519
|
+
publishedBeforeStats,
|
|
3520
|
+
beforeStats,
|
|
3521
|
+
parentDevice,
|
|
3522
|
+
) && samePhysicalTranscriptContentState(before, publishedBefore);
|
|
3523
|
+
if (
|
|
3524
|
+
!beforeStats.isFile() ||
|
|
3525
|
+
Number(beforeStats.nlink) !== 1 ||
|
|
3526
|
+
!descriptorMatchesPublished
|
|
3527
|
+
) {
|
|
3528
|
+
throw new Error(
|
|
3529
|
+
"session replica candidate must be a stable, single-link file",
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
if (
|
|
3533
|
+
Number(before.size) <= 0 ||
|
|
3534
|
+
Number(before.size) > MAX_SESSION_REPLICA_BYTES
|
|
3535
|
+
) {
|
|
3536
|
+
throw new Error(
|
|
3537
|
+
"session replica candidate exceeds its byte boundary",
|
|
3538
|
+
);
|
|
3539
|
+
}
|
|
3540
|
+
const structure = createSessionTranscriptStructureProjection(sessionId);
|
|
3541
|
+
const verification = verifyTranscriptFile(canonicalPath, {
|
|
3542
|
+
onVerifiedEvent(event) {
|
|
3543
|
+
structure.accept(event);
|
|
3544
|
+
},
|
|
3545
|
+
});
|
|
3546
|
+
if (
|
|
3547
|
+
verification.status !== TRANSCRIPT_CHAIN_STATUS.VERIFIED ||
|
|
3548
|
+
verification.malformedLines > 0 ||
|
|
3549
|
+
verification.truncatedTail ||
|
|
3550
|
+
verification.lastHash !== expected.headHash ||
|
|
3551
|
+
verification.chainedEvents !== expected.eventCount
|
|
3552
|
+
) {
|
|
3553
|
+
throw unverifiedTranscriptError(sessionId, verification);
|
|
3554
|
+
}
|
|
3555
|
+
structure.finish({ assertValid: true });
|
|
3556
|
+
const bytes = readFileSync(descriptor);
|
|
3557
|
+
const afterStats = fstatSync(descriptor, { bigint: true });
|
|
3558
|
+
const publishedAfterStats = readPhysicalTranscriptStats(canonicalPath);
|
|
3559
|
+
const after = physicalTranscriptStateFromStats(afterStats);
|
|
3560
|
+
const publishedAfter =
|
|
3561
|
+
physicalTranscriptStateFromStats(publishedAfterStats);
|
|
3562
|
+
const descriptorStillPublished =
|
|
3563
|
+
parentDevice === null
|
|
3564
|
+
? samePhysicalTranscriptState(after, publishedAfter)
|
|
3565
|
+
: samePathHandleStableFileIdentity(
|
|
3566
|
+
publishedAfterStats,
|
|
3567
|
+
afterStats,
|
|
3568
|
+
parentDevice,
|
|
3569
|
+
) && samePhysicalTranscriptContentState(after, publishedAfter);
|
|
3570
|
+
if (
|
|
3571
|
+
Number(afterStats.nlink) !== 1 ||
|
|
3572
|
+
!samePhysicalTranscriptState(before, after) ||
|
|
3573
|
+
!descriptorStillPublished
|
|
3574
|
+
) {
|
|
3575
|
+
throw transcriptIdentityError(
|
|
3576
|
+
"unknown",
|
|
3577
|
+
"the replica candidate changed while it was verified",
|
|
3578
|
+
);
|
|
3579
|
+
}
|
|
3580
|
+
const transcriptDigest = `sha256:${createHash("sha256")
|
|
3581
|
+
.update(bytes)
|
|
3582
|
+
.digest("hex")}`;
|
|
3583
|
+
if (transcriptDigest !== expected.transcriptDigest) {
|
|
3584
|
+
throw new Error("session replica transcript digest mismatch");
|
|
3585
|
+
}
|
|
3586
|
+
return Object.freeze({ verification, transcriptDigest });
|
|
3587
|
+
} finally {
|
|
3588
|
+
if (descriptor !== null) closeSync(descriptor);
|
|
3589
|
+
}
|
|
3590
|
+
},
|
|
3591
|
+
);
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
function sessionReplicaReceipt(sessionId, expected, installed) {
|
|
3595
|
+
const material = {
|
|
3596
|
+
schema: SESSION_REPLICA_INSTALL_SCHEMA,
|
|
3597
|
+
sessionId,
|
|
3598
|
+
headHash: expected.headHash,
|
|
3599
|
+
eventCount: expected.eventCount,
|
|
3600
|
+
transcriptDigest: expected.transcriptDigest,
|
|
3601
|
+
installed,
|
|
3602
|
+
};
|
|
3603
|
+
return Object.freeze({
|
|
3604
|
+
...material,
|
|
3605
|
+
receiptDigest: `sha256:${createHash("sha256")
|
|
3606
|
+
.update("chainlesschain.session-replica-install.v1\0", "utf8")
|
|
3607
|
+
.update(JSON.stringify(material), "utf8")
|
|
3608
|
+
.digest("hex")}`,
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
function sessionLocationDigest(domain, value) {
|
|
3613
|
+
return `sha256:${createHash("sha256")
|
|
3614
|
+
.update(domain, "utf8")
|
|
3615
|
+
.update(canonicalJson(value, "sessionExecutionLocation"), "utf8")
|
|
3616
|
+
.digest("hex")}`;
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
function exactRecord(value, fields, label) {
|
|
3620
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3621
|
+
throw new TypeError(`${label} must be an object`);
|
|
3622
|
+
}
|
|
3623
|
+
const actual = Object.keys(value).sort();
|
|
3624
|
+
const expected = [...fields].sort();
|
|
3625
|
+
if (
|
|
3626
|
+
actual.length !== expected.length ||
|
|
3627
|
+
actual.some((entry, index) => entry !== expected[index])
|
|
3628
|
+
) {
|
|
3629
|
+
throw new TypeError(`${label} has an invalid schema`);
|
|
3630
|
+
}
|
|
3631
|
+
return value;
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
function boundedAuthorityString(value, field, max = 256) {
|
|
3635
|
+
if (
|
|
3636
|
+
typeof value !== "string" ||
|
|
3637
|
+
value.length === 0 ||
|
|
3638
|
+
value.length > max ||
|
|
3639
|
+
[...value].some((character) => {
|
|
3640
|
+
const code = character.codePointAt(0);
|
|
3641
|
+
return code < 32 || code === 127;
|
|
3642
|
+
})
|
|
3643
|
+
) {
|
|
3644
|
+
throw new TypeError(`${field} is invalid`);
|
|
3645
|
+
}
|
|
3646
|
+
return value;
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
function normalizeSessionLocationHandoffTarget(value) {
|
|
3650
|
+
const input = exactRecord(
|
|
3651
|
+
value,
|
|
3652
|
+
[
|
|
3653
|
+
"profileDigest",
|
|
3654
|
+
"targetEvidenceId",
|
|
3655
|
+
"targetFactsDigest",
|
|
3656
|
+
"attestationDigest",
|
|
3657
|
+
"binding",
|
|
3658
|
+
],
|
|
3659
|
+
"session execution-location handoff target",
|
|
3660
|
+
);
|
|
3661
|
+
const profileDigest = boundedAuthorityString(
|
|
3662
|
+
input.profileDigest,
|
|
3663
|
+
"profileDigest",
|
|
3664
|
+
80,
|
|
3665
|
+
).toLowerCase();
|
|
3666
|
+
const targetEvidenceId = boundedAuthorityString(
|
|
3667
|
+
input.targetEvidenceId,
|
|
3668
|
+
"targetEvidenceId",
|
|
3669
|
+
);
|
|
3670
|
+
const targetFactsDigest = boundedAuthorityString(
|
|
3671
|
+
input.targetFactsDigest,
|
|
3672
|
+
"targetFactsDigest",
|
|
3673
|
+
80,
|
|
3674
|
+
).toLowerCase();
|
|
3675
|
+
const attestationDigest = boundedAuthorityString(
|
|
3676
|
+
input.attestationDigest,
|
|
3677
|
+
"attestationDigest",
|
|
3678
|
+
80,
|
|
3679
|
+
).toLowerCase();
|
|
3680
|
+
const binding = normalizeExecutionLocationBinding(input.binding);
|
|
3681
|
+
if (
|
|
3682
|
+
!/^sha256:[a-f0-9]{64}$/u.test(profileDigest) ||
|
|
3683
|
+
!/^sha256:[a-f0-9]{64}$/u.test(targetFactsDigest) ||
|
|
3684
|
+
!/^sha256:[a-f0-9]{64}$/u.test(attestationDigest) ||
|
|
3685
|
+
computeExecutionLocationTargetFactsDigest(binding) !== targetFactsDigest ||
|
|
3686
|
+
binding.observed !== true
|
|
3687
|
+
) {
|
|
3688
|
+
throw new TypeError("session execution-location handoff target is invalid");
|
|
3689
|
+
}
|
|
3690
|
+
return Object.freeze({
|
|
3691
|
+
profileDigest,
|
|
3692
|
+
targetEvidenceId,
|
|
3693
|
+
targetFactsDigest,
|
|
3694
|
+
attestationDigest,
|
|
3695
|
+
binding,
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3699
|
+
function createSessionExecutionLocationHandoffData(
|
|
3700
|
+
sessionId,
|
|
3701
|
+
expectedInput,
|
|
3702
|
+
targetInput,
|
|
3703
|
+
) {
|
|
3704
|
+
const expected = normalizeSessionReplicaExpectation(expectedInput);
|
|
3705
|
+
const target = normalizeSessionLocationHandoffTarget(targetInput);
|
|
3706
|
+
const source = Object.freeze({
|
|
3707
|
+
sessionId,
|
|
3708
|
+
headHash: expected.headHash,
|
|
3709
|
+
eventCount: expected.eventCount,
|
|
3710
|
+
transcriptDigest: expected.transcriptDigest,
|
|
3711
|
+
});
|
|
3712
|
+
const attestation = createExecutionLocationTargetAttestation({
|
|
3713
|
+
profileDigest: target.profileDigest,
|
|
3714
|
+
sourceSessionId: source.sessionId,
|
|
3715
|
+
sourceHeadHash: source.headHash,
|
|
3716
|
+
sourceEventCount: source.eventCount,
|
|
3717
|
+
targetEvidenceId: target.targetEvidenceId,
|
|
3718
|
+
baseCommit: target.binding.source.git.commit,
|
|
3719
|
+
binding: target.binding,
|
|
3720
|
+
});
|
|
3721
|
+
if (target.attestationDigest !== attestation.attestationDigest) {
|
|
3722
|
+
throw new TypeError(
|
|
3723
|
+
"session execution-location handoff attestation digest is invalid",
|
|
3724
|
+
);
|
|
3725
|
+
}
|
|
3726
|
+
const identity = {
|
|
3727
|
+
source,
|
|
3728
|
+
target: {
|
|
3729
|
+
profileDigest: target.profileDigest,
|
|
3730
|
+
targetEvidenceId: target.targetEvidenceId,
|
|
3731
|
+
targetFactsDigest: target.targetFactsDigest,
|
|
3732
|
+
},
|
|
3733
|
+
};
|
|
3734
|
+
return Object.freeze({
|
|
3735
|
+
schema: SESSION_EXECUTION_LOCATION_HANDOFF_SCHEMA,
|
|
3736
|
+
handoffId: sessionLocationDigest(
|
|
3737
|
+
"chainlesschain.session-execution-location-handoff-id.v1\0",
|
|
3738
|
+
identity,
|
|
3739
|
+
),
|
|
3740
|
+
source,
|
|
3741
|
+
target,
|
|
3742
|
+
});
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
function projectSessionExecutionLocationHandoff(sessionId, event, eventCount) {
|
|
3746
|
+
const data = exactRecord(
|
|
3747
|
+
event?.data,
|
|
3748
|
+
["schema", "handoffId", "source", "target"],
|
|
3749
|
+
"session execution-location handoff",
|
|
3750
|
+
);
|
|
3751
|
+
if (data.schema !== SESSION_EXECUTION_LOCATION_HANDOFF_SCHEMA) {
|
|
3752
|
+
throw new TypeError("session execution-location handoff schema is invalid");
|
|
3753
|
+
}
|
|
3754
|
+
const source = exactRecord(
|
|
3755
|
+
data.source,
|
|
3756
|
+
["sessionId", "headHash", "eventCount", "transcriptDigest"],
|
|
3757
|
+
"session execution-location handoff source",
|
|
3758
|
+
);
|
|
3759
|
+
const expected = normalizeSessionReplicaExpectation(source);
|
|
3760
|
+
if (
|
|
3761
|
+
source.sessionId !== sessionId ||
|
|
3762
|
+
expected.headHash !== event?.prevHash ||
|
|
3763
|
+
expected.eventCount !== eventCount - 1
|
|
3764
|
+
) {
|
|
3765
|
+
throw new TypeError(
|
|
3766
|
+
"session execution-location handoff predecessor is invalid",
|
|
3767
|
+
);
|
|
3768
|
+
}
|
|
3769
|
+
const normalized = createSessionExecutionLocationHandoffData(
|
|
3770
|
+
sessionId,
|
|
3771
|
+
expected,
|
|
3772
|
+
data.target,
|
|
3773
|
+
);
|
|
3774
|
+
if (data.handoffId !== normalized.handoffId) {
|
|
3775
|
+
throw new TypeError("session execution-location handoff id is invalid");
|
|
3776
|
+
}
|
|
3777
|
+
return Object.freeze({
|
|
3778
|
+
...normalized,
|
|
3779
|
+
eventHash: event.hash,
|
|
3780
|
+
eventCount,
|
|
3781
|
+
at: new Date(event.timestamp).toISOString(),
|
|
3782
|
+
});
|
|
3783
|
+
}
|
|
3784
|
+
|
|
3785
|
+
function sessionLocationHandoffInstallReceipt(
|
|
3786
|
+
sessionId,
|
|
3787
|
+
handoff,
|
|
3788
|
+
targetHeadHash,
|
|
3789
|
+
{ replicaInstalled, handoffAppended },
|
|
3790
|
+
) {
|
|
3791
|
+
const material = {
|
|
3792
|
+
schema: SESSION_EXECUTION_LOCATION_HANDOFF_INSTALL_SCHEMA,
|
|
3793
|
+
sessionId,
|
|
3794
|
+
sourceHeadHash: handoff.source.headHash,
|
|
3795
|
+
sourceEventCount: handoff.source.eventCount,
|
|
3796
|
+
transcriptDigest: handoff.source.transcriptDigest,
|
|
3797
|
+
handoffId: handoff.handoffId,
|
|
3798
|
+
targetHeadHash,
|
|
3799
|
+
targetEventCount: handoff.source.eventCount + 1,
|
|
3800
|
+
targetFactsDigest: handoff.target.targetFactsDigest,
|
|
3801
|
+
profileDigest: handoff.target.profileDigest,
|
|
3802
|
+
targetEvidenceId: handoff.target.targetEvidenceId,
|
|
3803
|
+
attestationDigest: handoff.target.attestationDigest,
|
|
3804
|
+
replicaInstalled,
|
|
3805
|
+
handoffAppended,
|
|
3806
|
+
};
|
|
3807
|
+
return Object.freeze({
|
|
3808
|
+
...material,
|
|
3809
|
+
receiptDigest: sessionLocationDigest(
|
|
3810
|
+
"chainlesschain.session-execution-location-handoff-install.v1\0",
|
|
3811
|
+
material,
|
|
3812
|
+
),
|
|
3813
|
+
});
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
/**
|
|
3817
|
+
* Install one exact, source-verified transcript revision into an empty target
|
|
3818
|
+
* session store. The target validates hash chain, transcript structure, exact
|
|
3819
|
+
* head/count, and the byte digest before an atomic same-directory publish.
|
|
3820
|
+
* Existing canonical state is never replaced: an exact existing revision is
|
|
3821
|
+
* an idempotent success, while every divergent/tombstoned state fails closed.
|
|
3822
|
+
*/
|
|
3823
|
+
export function installSessionReplica(sessionId, input, expectedInput) {
|
|
3824
|
+
if (isUnsafeSessionId(sessionId)) {
|
|
3825
|
+
throw new Error(`unsafe session id: ${String(sessionId).slice(0, 60)}`);
|
|
3826
|
+
}
|
|
3827
|
+
const expected = normalizeSessionReplicaExpectation(expectedInput);
|
|
3828
|
+
const bytes = Buffer.isBuffer(input) ? input : Buffer.from(input || "");
|
|
3829
|
+
if (bytes.length <= 0 || bytes.length > MAX_SESSION_REPLICA_BYTES) {
|
|
3830
|
+
throw new Error(
|
|
3831
|
+
`session replica must be 1..${MAX_SESSION_REPLICA_BYTES} bytes`,
|
|
3832
|
+
);
|
|
3833
|
+
}
|
|
3834
|
+
try {
|
|
3835
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
3836
|
+
} catch {
|
|
3837
|
+
throw new Error("session replica is not strict UTF-8 JSONL");
|
|
3838
|
+
}
|
|
3839
|
+
const suppliedDigest = `sha256:${createHash("sha256")
|
|
3840
|
+
.update(bytes)
|
|
3841
|
+
.digest("hex")}`;
|
|
3842
|
+
if (suppliedDigest !== expected.transcriptDigest) {
|
|
3843
|
+
throw new Error("session replica transcript digest mismatch");
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
const filePath = sessionPath(sessionId);
|
|
3847
|
+
const sessionsDir = getSessionsDir();
|
|
3848
|
+
const pendingPath = join(
|
|
3849
|
+
sessionsDir,
|
|
3850
|
+
`.${basename(filePath)}.replica.pending`,
|
|
3851
|
+
);
|
|
3852
|
+
return withSessionHostWriterLock(
|
|
3853
|
+
sessionId,
|
|
3854
|
+
filePath,
|
|
3855
|
+
() => {
|
|
3856
|
+
const presence = getSessionPresence(sessionId);
|
|
3857
|
+
if (presence === SESSION_PRESENCE.PRESENT) {
|
|
3858
|
+
const current = verifySessionReplicaCandidate(
|
|
3859
|
+
sessionId,
|
|
3860
|
+
filePath,
|
|
3861
|
+
expected,
|
|
3862
|
+
);
|
|
3863
|
+
let meta = readSessionMeta(sessionsDir, sessionId);
|
|
3864
|
+
if (meta === null) {
|
|
3865
|
+
fsyncParentDirectory(
|
|
3866
|
+
filePath,
|
|
3867
|
+
Object.freeze({
|
|
3868
|
+
sessionId,
|
|
3869
|
+
filePath,
|
|
3870
|
+
operation: "replica-recovery",
|
|
3871
|
+
}),
|
|
3872
|
+
"beforeReplicaDirectoryFsync",
|
|
3873
|
+
"afterReplicaDirectoryFsync",
|
|
3874
|
+
);
|
|
3875
|
+
meta = rebuildSessionMetaUnlocked(sessionsDir, sessionId, filePath);
|
|
3876
|
+
}
|
|
3877
|
+
assertVerifiedTranscriptAnchor(
|
|
3878
|
+
sessionId,
|
|
3879
|
+
current.verification,
|
|
3880
|
+
meta.transcript,
|
|
3881
|
+
);
|
|
3882
|
+
return sessionReplicaReceipt(sessionId, expected, false);
|
|
3883
|
+
}
|
|
3884
|
+
if (presence !== SESSION_PRESENCE.ABSENT) {
|
|
3885
|
+
throw new Error(
|
|
3886
|
+
`target session store is not empty for replica ${sessionId} (${presence})`,
|
|
3887
|
+
);
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
if (existsSync(pendingPath)) {
|
|
3891
|
+
let reusable = false;
|
|
3892
|
+
try {
|
|
3893
|
+
const pendingStats = lstatSync(pendingPath);
|
|
3894
|
+
reusable =
|
|
3895
|
+
!pendingStats.isSymbolicLink() &&
|
|
3896
|
+
pendingStats.isFile() &&
|
|
3897
|
+
Number(pendingStats.nlink) === 1;
|
|
3898
|
+
if (reusable) {
|
|
3899
|
+
verifySessionReplicaCandidate(sessionId, pendingPath, expected);
|
|
3900
|
+
}
|
|
3901
|
+
} catch {
|
|
3902
|
+
reusable = false;
|
|
3903
|
+
}
|
|
3904
|
+
if (!reusable) {
|
|
3905
|
+
const pendingStats = lstatSync(pendingPath);
|
|
3906
|
+
if (
|
|
3907
|
+
pendingStats.isSymbolicLink() ||
|
|
3908
|
+
!pendingStats.isFile() ||
|
|
3909
|
+
Number(pendingStats.nlink) !== 1
|
|
3910
|
+
) {
|
|
3911
|
+
throw new Error("unsafe session replica pending path");
|
|
3912
|
+
}
|
|
3913
|
+
rmSync(pendingPath, { force: true });
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
if (!existsSync(pendingPath)) {
|
|
3918
|
+
let descriptor = null;
|
|
3919
|
+
try {
|
|
3920
|
+
descriptor = openSync(
|
|
3921
|
+
pendingPath,
|
|
3922
|
+
fsConstants.O_CREAT |
|
|
3923
|
+
fsConstants.O_EXCL |
|
|
3924
|
+
fsConstants.O_WRONLY |
|
|
3925
|
+
Number(fsConstants.O_NOFOLLOW ?? 0),
|
|
3926
|
+
0o600,
|
|
3927
|
+
);
|
|
3928
|
+
writeFileSync(descriptor, bytes);
|
|
3929
|
+
fsyncSync(descriptor);
|
|
3930
|
+
} finally {
|
|
3931
|
+
if (descriptor !== null) closeSync(descriptor);
|
|
3932
|
+
}
|
|
3933
|
+
ensurePrivateFile(pendingPath);
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3936
|
+
const candidate = verifySessionReplicaCandidate(
|
|
3937
|
+
sessionId,
|
|
3938
|
+
pendingPath,
|
|
3939
|
+
expected,
|
|
3940
|
+
);
|
|
3941
|
+
if (existsSync(filePath)) {
|
|
3942
|
+
throw new Error(`target session appeared during replica install`);
|
|
3943
|
+
}
|
|
3944
|
+
renameSync(pendingPath, filePath);
|
|
3945
|
+
ensurePrivateFile(filePath);
|
|
3946
|
+
runSessionScaleFaultHook(
|
|
3947
|
+
"afterReplicaPublish",
|
|
3948
|
+
Object.freeze({
|
|
3949
|
+
sessionId,
|
|
3950
|
+
filePath,
|
|
3951
|
+
operation: "replica-publish",
|
|
3952
|
+
}),
|
|
3953
|
+
);
|
|
3954
|
+
fsyncParentDirectory(
|
|
3955
|
+
filePath,
|
|
3956
|
+
Object.freeze({
|
|
3957
|
+
sessionId,
|
|
3958
|
+
filePath,
|
|
3959
|
+
operation: "replica-publish",
|
|
3960
|
+
}),
|
|
3961
|
+
"beforeReplicaDirectoryFsync",
|
|
3962
|
+
"afterReplicaDirectoryFsync",
|
|
3963
|
+
);
|
|
3964
|
+
const meta = rebuildSessionMetaUnlocked(sessionsDir, sessionId, filePath);
|
|
3965
|
+
assertVerifiedTranscriptAnchor(
|
|
3966
|
+
sessionId,
|
|
3967
|
+
candidate.verification,
|
|
3968
|
+
meta.transcript,
|
|
3969
|
+
);
|
|
3970
|
+
return sessionReplicaReceipt(sessionId, expected, true);
|
|
3971
|
+
},
|
|
3972
|
+
{
|
|
3973
|
+
failIfUnavailable: true,
|
|
3974
|
+
timeoutMs: 30_000,
|
|
3975
|
+
retryMs: 1,
|
|
3976
|
+
maxRetryMs: 8,
|
|
3977
|
+
retryJitterMs: 4,
|
|
3978
|
+
yieldAfterReleaseMs: 2,
|
|
3979
|
+
},
|
|
3980
|
+
);
|
|
3981
|
+
}
|
|
3982
|
+
|
|
3983
|
+
/**
|
|
3984
|
+
* Install an exact replica and append one CAS-bound location successor. A
|
|
3985
|
+
* response-loss retry recognizes only that exact one-event successor; a
|
|
3986
|
+
* session that advanced after handoff is never silently resumed again.
|
|
3987
|
+
*/
|
|
3988
|
+
export function installSessionReplicaWithLocationHandoff(
|
|
3989
|
+
sessionId,
|
|
3990
|
+
input,
|
|
3991
|
+
expectedInput,
|
|
3992
|
+
targetInput,
|
|
3993
|
+
) {
|
|
3994
|
+
if (isUnsafeSessionId(sessionId)) {
|
|
3995
|
+
throw new Error(`unsafe session id: ${String(sessionId).slice(0, 60)}`);
|
|
3996
|
+
}
|
|
3997
|
+
const expected = normalizeSessionReplicaExpectation(expectedInput);
|
|
3998
|
+
const handoff = createSessionExecutionLocationHandoffData(
|
|
3999
|
+
sessionId,
|
|
4000
|
+
expected,
|
|
4001
|
+
targetInput,
|
|
4002
|
+
);
|
|
4003
|
+
|
|
4004
|
+
if (getSessionPresence(sessionId) === SESSION_PRESENCE.PRESENT) {
|
|
4005
|
+
const current = getVerifiedSessionExecutionLocationAuthority(sessionId);
|
|
4006
|
+
if (current.locationHandoff !== null) {
|
|
4007
|
+
if (current.locationHandoff.handoffId !== handoff.handoffId) {
|
|
4008
|
+
throw new Error(
|
|
4009
|
+
"target session has a different execution-location handoff",
|
|
4010
|
+
);
|
|
4011
|
+
}
|
|
4012
|
+
if (
|
|
4013
|
+
current.bindingEventHash !== current.headHash ||
|
|
4014
|
+
current.bindingEventCount !== current.eventCount ||
|
|
4015
|
+
current.eventCount !== expected.eventCount + 1
|
|
4016
|
+
) {
|
|
4017
|
+
throw new Error("target session advanced after location handoff");
|
|
4018
|
+
}
|
|
4019
|
+
return sessionLocationHandoffInstallReceipt(
|
|
4020
|
+
sessionId,
|
|
4021
|
+
current.locationHandoff,
|
|
4022
|
+
current.headHash,
|
|
4023
|
+
{ replicaInstalled: false, handoffAppended: false },
|
|
4024
|
+
);
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
const replica = installSessionReplica(sessionId, input, expected);
|
|
4029
|
+
const before = getVerifiedSessionExecutionLocationAuthority(sessionId);
|
|
4030
|
+
if (
|
|
4031
|
+
before.locationHandoff !== null ||
|
|
4032
|
+
before.headHash !== expected.headHash ||
|
|
4033
|
+
before.eventCount !== expected.eventCount
|
|
4034
|
+
) {
|
|
4035
|
+
throw new Error(
|
|
4036
|
+
"target session is not the exact source revision before location handoff",
|
|
4037
|
+
);
|
|
4038
|
+
}
|
|
4039
|
+
const appended = appendAuthorityEventIfHead(
|
|
4040
|
+
sessionId,
|
|
4041
|
+
SESSION_EXECUTION_LOCATION_HANDOFF_EVENT,
|
|
4042
|
+
handoff,
|
|
4043
|
+
expected.headHash,
|
|
4044
|
+
);
|
|
4045
|
+
runSessionScaleFaultHook(
|
|
4046
|
+
"afterLocationHandoffAppend",
|
|
4047
|
+
Object.freeze({
|
|
4048
|
+
sessionId,
|
|
4049
|
+
handoffId: handoff.handoffId,
|
|
4050
|
+
sourceHeadHash: expected.headHash,
|
|
4051
|
+
targetHeadHash: appended.hash,
|
|
4052
|
+
}),
|
|
4053
|
+
);
|
|
4054
|
+
const after = getVerifiedSessionExecutionLocationAuthority(sessionId);
|
|
4055
|
+
if (
|
|
4056
|
+
after.authority !== "verified-session-location-handoff" ||
|
|
4057
|
+
after.locationHandoff?.handoffId !== handoff.handoffId ||
|
|
4058
|
+
after.bindingEventHash !== appended.hash ||
|
|
4059
|
+
after.headHash !== appended.hash ||
|
|
4060
|
+
after.eventCount !== expected.eventCount + 1
|
|
4061
|
+
) {
|
|
4062
|
+
throw new Error(
|
|
4063
|
+
"target execution-location handoff settlement is not canonical",
|
|
4064
|
+
);
|
|
4065
|
+
}
|
|
4066
|
+
return sessionLocationHandoffInstallReceipt(
|
|
4067
|
+
sessionId,
|
|
4068
|
+
after.locationHandoff,
|
|
4069
|
+
appended.hash,
|
|
4070
|
+
{ replicaInstalled: replica.installed, handoffAppended: true },
|
|
4071
|
+
);
|
|
4072
|
+
}
|
|
4073
|
+
|
|
3438
4074
|
function readVerifiedTranscriptBytesAtPath(sessionId, filePath, parentDevice) {
|
|
3439
4075
|
let flags = fsConstants.O_RDONLY;
|
|
3440
4076
|
if (typeof fsConstants.O_NOFOLLOW === "number") {
|
|
@@ -677,6 +677,10 @@ export class PromptCompressor {
|
|
|
677
677
|
// A durable-ledger write failure is authoritative too. Falling back here
|
|
678
678
|
// would let the host continue after losing the model-call boundary.
|
|
679
679
|
if (error?.runtimeLedgerPersistence === true) throw error;
|
|
680
|
+
// A workflow-bound query may already have reached the provider. An
|
|
681
|
+
// extractive fallback would make the outer durable effect look completed
|
|
682
|
+
// and authorize a replay of that physically unknown request.
|
|
683
|
+
if (error?.workflowEffectOutcomeUnknown === true) throw error;
|
|
680
684
|
summaryCallId = safeSummaryCallId(
|
|
681
685
|
error?.compactionCallId ?? error?.callId,
|
|
682
686
|
);
|