chainlesschain 0.165.6 → 0.165.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/assets/web-panel/assets/{AIOps-vfoDb6HZ.js → AIOps-B3gWmXjo.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Cm5f7p93.js → ActionButton-G9pdT-0e.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-yRuvgWxa.js → Analytics-Ca30J7m2.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BROsC4h1.js → AppLayout-BtVTm8PU.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CimHApb1.js → Artifacts-BhViNa6u.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DI8kr8zI.js → Audit-DhKq-FnY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DSLzKy-n.js → BackgroundAgents-BL0SVbnE.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DkH_n2r1.js → Backup-BWkblnFi.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CKSrUoRA.js → BaseInput-ClaRNrF8.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-ClwfMXUS.js → Chat-sZE2Z77o.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CjMaqfL7.js → ChatBubbleRenderer-BAfKOnRe.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BYTehcQG.js → Checkbox-Cvhay5WM.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B8AMcp6J.js → Codegen-BpejL-4N.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BNSogPo3.js → Col-g2Ki6g_1.js} +1 -1
- package/src/assets/web-panel/assets/{Community-JxPvGb8O.js → Community-D2xKbNo9.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DF7bFAgE.js → Compact-DbL1Pds3.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Bvhqnwrq.js → Compliance-DGtZCV4c.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BDS0CJvM.js → Cowork-4kQpoNsI.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DqvRfxoQ.js → Cron-BxTYXXAN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CDcIvnqS.js → Crosschain-k0J0LT2N.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D7jaUxU3.js → DID-BaqhoPgN.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-B5A18wEV.js → Dashboard-T4RVPIM1.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-_6IVkGag.js → Dropdown-DszOToWq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C9PeiKCG.js → EmailListRenderer-C9KJU7HF.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DXjuEHrX.js → FamilyGuardDashboard-Cmr0JnsA.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-B4IAR9zv.js → Federation-BI-_2xZX.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CxQeSNx1.js → FormItemContext-DsnDEPsl.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B8GS-Y3N.js → GenericCardRenderer-BLncAKKK.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BS8SouUX.js → Git-BPQhGXDn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Dq9aYb5Q.js → Governance-CyElbUQW.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BrCEe9AJ.js → Inference-CcSntBQD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DybOpiY2.js → KnowledgeGraph-p6e7HZ2r.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DBpzbRfx.js → Logs-CNEtr-wz.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-Bj9xI0YH.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Cc7CCSCu.js → Marketplace-Dd2notAJ.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DFaIhOfu.js → McpTools-BBsN2CSW.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-KCqlp9ol.js → Memory-Dg2uTvwx.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Bys84ySN.js → MobileBridge-r1YMRiJV.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-B-6qU-je.js → MobileProjects-C-89kSm4.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BgY-fvCq.js → Mtc-CrPZcVgf.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-B8pCmqQJ.js → MtcAudit-sxi7hqJb.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BbOmYutA.js → Multisig-CAgOMfF1.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BocLQz_M.js → NLProgramming-DafaPts3.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CSwbxEop.js → Notes-CJM-xUW_.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CqFNSYG3.js → NotificationSettings-L0ZjfAh_.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C6gMfAk7.js → OrderTableRenderer-BB1CvCK0.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-QrDXVdMJ.js → Organization-DeLtgAD8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cxntotjt.js → Overflow-CKC2b10z.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CIdBOwNq.js → P2P-eyf2rzY1.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CT5aP8rD.js → PdhVaultBrowser-5upNIOW8.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DH7OM8XU.js → Permissions-B8p3W6JK.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CnJqbWce.js → PersonalDataHub-BnRMxTq3.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-5C3HG1kH.js → Pipeline-DjCNI1li.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B4yIp7xK.js → Privacy-u6gXLbWT.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BMvt-2M0.js → ProjectInit-BZAw4tkM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CvSTEMRG.js → ProjectSettings-B1Tr72q7.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BKBJxEek.js → Projects-DO7HFuBQ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BdwUqFWN.js → Providers-BjCztyhj.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DtnCKlTm.js → QrScannerModal-ZWyCUbR1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-sTbgwyNr.js → QuickAsk-DhDjeW4G.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Bzr14gSb.js → Recommend-R_CN2MuU.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C_TF5IaJ.js → RemoteSession-uxFcujeu.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-GCAM4H2K.js → Reputation-DX8D7rks.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Dir0r6OQ.js → Row-BYahQILD.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CpGnlecX.js → RssFeed-CUSQ2tm-.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DMMamC6u.js → Search-CnN_S8Cs.js} +1 -1
- package/src/assets/web-panel/assets/{Security-yqay5yG-.js → Security-DcZlh-O4.js} +4 -4
- package/src/assets/web-panel/assets/{Services-C5i2wh9G.js → Services-ZhBOjzIX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-vUqk35yj.js → Skeleton-D5Qnq_lI.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DzcSkhot.js → Skills-BC9cZ3Be.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CB6qbYNr.js → Sla-DauJ3E8_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DbNeZ_fh.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D-RrsxTJ.js → SyncSettings-DUvJbSct.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DFxdOJiq.js → Tasks-BXa-5noQ.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-SKAob_Aq.js → Templates-JxaiUt_3.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BPfsfHQ-.js → Tenant-BW4ERQN8.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-8iw7FpUf.js → Terminal-DltUbdtv.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C4Gjj5Su.js → TimelineRenderer-CMoZz0Tc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-COYhN18e.js → Tokens-D_VEV60K.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B-JzXcf6.js → Trigger-CCXTCs0R.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BJ5bg0nI.js → Trust-lkGRIPcG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BG_ByRMf.js → UkeySign-BfGeQ9HX.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B3kOnSiU.js → VideoEditing-BEC7pYvc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CcoGFnK7.js → Wallet-BZdG24o0.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DdT8vRC_.js → WebAuthn-yoEddedp.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-CZu3M9N8.js → WorkflowEditor-BXZEprOW.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DxJt3Qba.js → chat-gg0bzZal.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DdYfwJaB.js → colors-DjRVRuin.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BG3SUzwj.js → compact-item-su-dieAc.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-By51z2-1.js → createContext-Ci3S8oKF.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CxbFX9uH.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-B0apZ3KJ.js → hasIn-D8F10heH.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE76D-kA.js → index-11wtYQiF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkvcEo4p.js → index-1IgxgALJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bos5_aMV.js → index-1agckWSq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfWYsB9t.js → index-5JqhujnR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAhHirVH.js → index-6Tniqk1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYINoRLK.js → index-8Z-8BIlN.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5rLiEo0.js → index-BCI5v0yB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZ_0qm4g.js → index-BDJntYmJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWEuZWCK.js → index-BGzixOGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLIbGOLX.js → index-BMMOVoLD.js} +1 -1
- package/src/assets/web-panel/assets/{index-bP_VlDb4.js → index-BTFpdBtt.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3rWuPMW.js → index-Ba_HyPnE.js} +1 -1
- package/src/assets/web-panel/assets/{index-Da_WBdLX.js → index-BbF1IHof.js} +1 -1
- package/src/assets/web-panel/assets/{index-RD6O-0C6.js → index-BgT9su_e.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpjZhgNd.js → index-BzPB6kGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Q2SCX-MI.js → index-C6ygzplr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIrJfqO4.js → index-CC3ZDjzY.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBPf3TpM.js → index-CEmXRhAE.js} +1 -1
- package/src/assets/web-panel/assets/index-CJGOlduY.js +1 -0
- package/src/assets/web-panel/assets/{index-Cp6e-hGw.js → index-CMPqd5Cv.js} +1 -1
- package/src/assets/web-panel/assets/{index-kf57LJ93.js → index-COWd8uGx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CW93tZDv.js → index-CPIYSp4H.js} +1 -1
- package/src/assets/web-panel/assets/{index-BewKYrX9.js → index-CR0h6seT.js} +1 -1
- package/src/assets/web-panel/assets/{index-p1_8SbI1.js → index-CUmDhC4j.js} +1 -1
- package/src/assets/web-panel/assets/index-CkBCN5yH.js +1 -0
- package/src/assets/web-panel/assets/{index-ZxtP8jG7.js → index-CusgZPtc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWF8_mkx.js → index-DBAETXft.js} +1 -1
- package/src/assets/web-panel/assets/{index-BP94CrBV.js → index-DIJ2qFOC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNIxXMmN.js → index-Dbq4NMET.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzmQElZy.js → index-DkMEF5VT.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZFbps9po.js → index-DkfQR3hM.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dz49VOyQ.js → index-DnLucOn9.js} +1 -1
- package/src/assets/web-panel/assets/{index-oxRrmFO9.js → index-DugB_aKa.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bissefox.js → index-DyLUarhZ.js} +3 -3
- package/src/assets/web-panel/assets/{index-DaIz0NAW.js → index-T6_6ygJZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-SQqOlR3d.js → index-Tu8eWqh-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCn8IcxV.js → index-W7XUgdVL.js} +1 -1
- package/src/assets/web-panel/assets/{index-3jWkIu5j.js → index-dHZinfko.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1C9i79A.js → index-jyNgp5FI.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BWJktcg8.js → initDefaultProps-Cv4p8e4w.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BEXBzUFV.js → motion-B8EwmMOw.js} +1 -1
- package/src/assets/web-panel/assets/{move-BUKwn2sx.js → move-B8R9F8TK.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-FTI7btqQ.js → mtc-parser-DoFYz5Bp.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CsLtSPH3.js → omit-CHWYLBjI.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DWZxsT_S.js → pickAttrs-CXuPDO77.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Bcfs43CX.js → placementArrow-BaAtUUon.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-a9bcC1p-.js → responsiveObserve-DCc4zcDD.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BDTzgOXN.js → slide-B_lx89oZ.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Iq1j_HUg.js → statusUtils-DE8DPmR0.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DAZEYxeh.js → styleChecker-1ktXOsL_.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DVkjyDrh.js → useFlexGapSupport-xH89-wjh.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-wXTpNTHt.js → useFs-DscvfW5p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CtQM_aZr.js → usePersonalDataHub-RdMKQta7.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-hjEoG85v.js → vnode-Z3wTni2T.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BHVpyKg_.js → zoom-B0iChCsm.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/agent.js +91 -60
- package/src/commands/git.js +39 -0
- package/src/commands/plugin.js +17 -2
- package/src/commands/session-group.js +131 -0
- package/src/commands/session-location.js +48 -0
- package/src/commands/session.js +11 -0
- package/src/commands/sync-providers.js +4 -1
- package/src/commands/update.js +1 -0
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/ws-agent-handler.js +20 -12
- package/src/harness/jsonl-session-store.js +279 -37
- package/src/harness/mcp-client.js +701 -91
- package/src/harness/session-index.js +15 -4
- package/src/lib/agent-worktree.js +8 -1
- package/src/lib/auto-mode-safety-classifier.js +3 -17
- package/src/lib/background-agent-keeper-protocol.js +26 -6
- package/src/lib/background-agent-supervisor.js +17 -1
- package/src/lib/browser-evidence.js +1405 -0
- package/src/lib/chrome-connector.js +465 -79
- package/src/lib/claude-project-auto-memory.js +481 -0
- package/src/lib/claude-project-storage-layout.js +104 -0
- package/src/lib/cloud/cloud-client.js +40 -0
- package/src/lib/execution-location-local-supervisor.mjs +226 -0
- package/src/lib/execution-location-runner-lifecycle.js +991 -0
- package/src/lib/execution-location-target-preflight.js +333 -0
- package/src/lib/execution-location-target.js +783 -13
- package/src/lib/file-lines.js +6 -1
- package/src/lib/gateway-client-resilience.js +571 -0
- package/src/lib/gitlab-mr-worktree.js +176 -0
- package/src/lib/hooks-v2-producers.js +119 -15
- package/src/lib/hooks-v2-workspace-context.js +44 -1
- package/src/lib/host-resource-budget.js +295 -0
- package/src/lib/ide-context.js +15 -2
- package/src/lib/llm-config-defaults.js +43 -0
- package/src/lib/mcp-lifecycle-authority.js +887 -0
- package/src/lib/mcp-oauth.js +101 -20
- package/src/lib/mcp-tls.js +103 -3
- package/src/lib/paths.js +260 -32
- package/src/lib/permission-request.js +48 -0
- package/src/lib/plugin-runtime/bin.js +3 -1
- package/src/lib/plugin-runtime/hooks.js +3 -1
- package/src/lib/plugin-runtime/install.js +117 -1
- package/src/lib/plugin-runtime/lsp.js +4 -4
- package/src/lib/plugin-runtime/marketplace-archive-source.js +40 -5
- package/src/lib/plugin-runtime/marketplace-catalog.js +13 -2
- package/src/lib/plugin-runtime/marketplace-command-descriptor.js +343 -0
- package/src/lib/plugin-runtime/marketplace-command-source.js +580 -0
- package/src/lib/plugin-runtime/marketplace-source-adapter.js +33 -8
- package/src/lib/plugin-runtime/mcp.js +3 -1
- package/src/lib/plugin-runtime/monitors.js +3 -1
- package/src/lib/plugin-runtime/remote-source.js +151 -6
- package/src/lib/plugin-runtime/settings.js +3 -1
- package/src/lib/plugin-runtime/trust.js +274 -13
- package/src/lib/process-execution-broker/credential-agent.js +210 -41
- package/src/lib/process-execution-broker/index.js +15 -1
- package/src/lib/process-execution-broker/linux-cgroup-v2.js +391 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +50 -28
- package/src/lib/project-mcp-trust.js +165 -10
- package/src/lib/resume-session.js +9 -6
- package/src/lib/session-core-singletons.js +26 -11
- package/src/lib/session-host-lease.js +52 -16
- package/src/lib/session-lifecycle-scope.js +153 -0
- package/src/lib/session-message-fabric.js +168 -21
- package/src/lib/session-projection.js +244 -4
- package/src/lib/session-store-guard.js +3 -1
- package/src/lib/session-workbench-store.js +466 -0
- package/src/lib/spellcheck.js +211 -0
- package/src/lib/sub-agent-context.js +9 -9
- package/src/lib/sync-cli-db.js +485 -129
- package/src/lib/sync-credentials.js +31 -20
- package/src/lib/web-fetch.js +112 -40
- package/src/lib/web-search.js +127 -61
- package/src/lib/workspace-trust.js +793 -0
- package/src/repl/agent-repl.js +124 -5
- package/src/repl/permission-prompt.js +10 -12
- package/src/runtime/agent-core.js +311 -18
- package/src/runtime/bootstrap.js +37 -13
- package/src/runtime/coding-agent-contract-shared.cjs +11 -4
- package/src/runtime/headless-runner.js +234 -36
- package/src/runtime/headless-stream.js +181 -6
- package/src/runtime/mcp-config.js +298 -8
- package/src/runtime/policies/agent-policy.js +8 -0
- package/src/runtime/status-diagnostics-lite.js +9 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-CFyt8iUd.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-BHUI7cox.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DorRZcI9.js +0 -1
- package/src/assets/web-panel/assets/index-BETiL0a3.js +0 -1
- package/src/assets/web-panel/assets/index-BhAVHuW8.js +0 -1
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { TextDecoder } from "node:util";
|
|
6
7
|
import {
|
|
7
8
|
EXECUTION_LOCATION_TARGET_ATTESTATION_SCHEMA,
|
|
@@ -16,6 +17,9 @@ import {
|
|
|
16
17
|
} from "./secure-file-identity.js";
|
|
17
18
|
import { canonicalJson } from "./scheduler-kernel/contract.js";
|
|
18
19
|
import { executionBroker } from "./process-execution-broker/index.js";
|
|
20
|
+
import { assertExecutionLocationRunnerLeaseAuthority } from "./execution-location-runner-lifecycle.js";
|
|
21
|
+
import { ensurePrivateDirectory, repairPrivatePaths } from "./secure-fs.js";
|
|
22
|
+
import { getSessionAntiRollbackDirectory } from "./session-anti-rollback-anchor.js";
|
|
19
23
|
import {
|
|
20
24
|
MAX_EXECUTION_LOCATION_RESULT_BUNDLE_BYTES,
|
|
21
25
|
normalizeExecutionLocationResultBundle,
|
|
@@ -24,6 +28,8 @@ import {
|
|
|
24
28
|
|
|
25
29
|
export const EXECUTION_LOCATION_PROFILE_SCHEMA =
|
|
26
30
|
"cc-execution-location-profile/v1";
|
|
31
|
+
export const EXECUTION_LOCATION_PROFILE_SCHEMA_V2 =
|
|
32
|
+
"cc-execution-location-profile/v2";
|
|
27
33
|
export { EXECUTION_LOCATION_TARGET_ATTESTATION_SCHEMA };
|
|
28
34
|
export const EXECUTION_LOCATION_TARGET_RESUME_SCHEMA =
|
|
29
35
|
"cc-execution-location-target-resume/v1";
|
|
@@ -36,13 +42,26 @@ const SESSION_LOCATION_AUTHORITY_SCHEMA =
|
|
|
36
42
|
"cc-session-execution-location-authority/v1";
|
|
37
43
|
const SESSION_LOCATION_HANDOFF_INSTALL_SCHEMA =
|
|
38
44
|
"chainlesschain.session-execution-location-handoff-install/v1";
|
|
39
|
-
const PROFILE_TARGETS = new Set(["wsl", "ssh", "container"]);
|
|
45
|
+
const PROFILE_TARGETS = new Set(["local", "wsl", "ssh", "container"]);
|
|
46
|
+
const RUNNER_LIFECYCLE_STATES = new Set([
|
|
47
|
+
"accepting",
|
|
48
|
+
"draining",
|
|
49
|
+
"parked",
|
|
50
|
+
"reclaiming",
|
|
51
|
+
]);
|
|
40
52
|
const SESSION_STORE_MODES = new Set(["replicated", "shared"]);
|
|
41
53
|
const SHA256_RE = /^sha256:[a-f0-9]{64}$/u;
|
|
42
54
|
const COMMIT_RE = /^[a-f0-9]{40,64}$/u;
|
|
43
55
|
const MAX_PROFILE_BYTES = 1024 * 1024;
|
|
44
56
|
const MAX_PROBE_BYTES = 1024 * 1024;
|
|
45
57
|
const MAX_REPLICA_BYTES = 64 * 1024 * 1024;
|
|
58
|
+
const DEFAULT_TARGET_COMMAND_TIMEOUT_MS = 30_000;
|
|
59
|
+
const MAX_TARGET_COMMAND_TIMEOUT_MS = 120_000;
|
|
60
|
+
const REPLICA_TRANSFER_TIMEOUT_MS = MAX_TARGET_COMMAND_TIMEOUT_MS;
|
|
61
|
+
const MAX_PROXY_AUTHORITY_AGE_MS = 5 * 60 * 1000;
|
|
62
|
+
const LOCAL_TARGET_SUPERVISOR = fileURLToPath(
|
|
63
|
+
new URL("./execution-location-local-supervisor.mjs", import.meta.url),
|
|
64
|
+
);
|
|
46
65
|
const RESULT_MEDIA_TYPE_RE =
|
|
47
66
|
/^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}\/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$/u;
|
|
48
67
|
|
|
@@ -87,6 +106,26 @@ function safePath(value, field) {
|
|
|
87
106
|
return normalized;
|
|
88
107
|
}
|
|
89
108
|
|
|
109
|
+
function safeInteger(value, field, minimum, maximum) {
|
|
110
|
+
const normalized = Number(value);
|
|
111
|
+
if (
|
|
112
|
+
!Number.isSafeInteger(normalized) ||
|
|
113
|
+
normalized < minimum ||
|
|
114
|
+
normalized > maximum
|
|
115
|
+
) {
|
|
116
|
+
throw new TypeError(`${field} is invalid`);
|
|
117
|
+
}
|
|
118
|
+
return normalized;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function safeTimestamp(value, field) {
|
|
122
|
+
const normalized = safeString(value, field, 64);
|
|
123
|
+
if (!Number.isFinite(Date.parse(normalized))) {
|
|
124
|
+
throw new TypeError(`${field} is invalid`);
|
|
125
|
+
}
|
|
126
|
+
return normalized;
|
|
127
|
+
}
|
|
128
|
+
|
|
90
129
|
function exactObject(value, fields, field) {
|
|
91
130
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
92
131
|
throw new TypeError(`${field} must be an object`);
|
|
@@ -142,10 +181,169 @@ function normalizeSessionStore(value) {
|
|
|
142
181
|
return Object.freeze({ mode, targetSessionId, headHash, eventCount });
|
|
143
182
|
}
|
|
144
183
|
|
|
184
|
+
function normalizeRunnerLifecycle(value, profileCwd) {
|
|
185
|
+
const input = exactObject(
|
|
186
|
+
value,
|
|
187
|
+
[
|
|
188
|
+
"runnerId",
|
|
189
|
+
"authorityFile",
|
|
190
|
+
"state",
|
|
191
|
+
"generation",
|
|
192
|
+
"lease",
|
|
193
|
+
"proxyAuthority",
|
|
194
|
+
"baseDir",
|
|
195
|
+
"resources",
|
|
196
|
+
"postSessionHook",
|
|
197
|
+
],
|
|
198
|
+
"profile.lifecycle",
|
|
199
|
+
);
|
|
200
|
+
const state = safeName(input.state, "profile.lifecycle.state", 32);
|
|
201
|
+
if (!RUNNER_LIFECYCLE_STATES.has(state)) {
|
|
202
|
+
throw new TypeError("profile.lifecycle.state is invalid");
|
|
203
|
+
}
|
|
204
|
+
if (state === "parked" || state === "reclaiming") {
|
|
205
|
+
throw new TypeError("profile lifecycle is not accepting target work");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const generation = safeInteger(
|
|
209
|
+
input.generation,
|
|
210
|
+
"profile.lifecycle.generation",
|
|
211
|
+
1,
|
|
212
|
+
Number.MAX_SAFE_INTEGER,
|
|
213
|
+
);
|
|
214
|
+
const lease = exactObject(
|
|
215
|
+
input.lease,
|
|
216
|
+
["id", "generation", "expiresAt"],
|
|
217
|
+
"profile.lifecycle.lease",
|
|
218
|
+
);
|
|
219
|
+
const leaseGeneration = safeInteger(
|
|
220
|
+
lease.generation,
|
|
221
|
+
"profile.lifecycle.lease.generation",
|
|
222
|
+
1,
|
|
223
|
+
generation,
|
|
224
|
+
);
|
|
225
|
+
const leaseExpiresAt = safeTimestamp(
|
|
226
|
+
lease.expiresAt,
|
|
227
|
+
"profile.lifecycle.lease.expiresAt",
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const proxyAuthority = exactObject(
|
|
231
|
+
input.proxyAuthority,
|
|
232
|
+
["id", "revision", "issuedAt", "expiresAt"],
|
|
233
|
+
"profile.lifecycle.proxyAuthority",
|
|
234
|
+
);
|
|
235
|
+
const proxyIssuedAt = safeTimestamp(
|
|
236
|
+
proxyAuthority.issuedAt,
|
|
237
|
+
"profile.lifecycle.proxyAuthority.issuedAt",
|
|
238
|
+
);
|
|
239
|
+
const proxyExpiresAt = safeTimestamp(
|
|
240
|
+
proxyAuthority.expiresAt,
|
|
241
|
+
"profile.lifecycle.proxyAuthority.expiresAt",
|
|
242
|
+
);
|
|
243
|
+
if (Date.parse(proxyExpiresAt) <= Date.parse(proxyIssuedAt)) {
|
|
244
|
+
throw new TypeError("profile lifecycle proxy authority is invalid");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const baseDir = exactObject(
|
|
248
|
+
input.baseDir,
|
|
249
|
+
["path", "writableRequired"],
|
|
250
|
+
"profile.lifecycle.baseDir",
|
|
251
|
+
);
|
|
252
|
+
const baseDirPath = safePath(baseDir.path, "profile.lifecycle.baseDir.path");
|
|
253
|
+
if (baseDirPath !== profileCwd || baseDir.writableRequired !== true) {
|
|
254
|
+
throw new TypeError("profile lifecycle base directory is invalid");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const resources = exactObject(
|
|
258
|
+
input.resources,
|
|
259
|
+
["cpuSeconds", "memoryBytes"],
|
|
260
|
+
"profile.lifecycle.resources",
|
|
261
|
+
);
|
|
262
|
+
const memoryBytes = safeInteger(
|
|
263
|
+
resources.memoryBytes,
|
|
264
|
+
"profile.lifecycle.resources.memoryBytes",
|
|
265
|
+
64 * 1024 * 1024,
|
|
266
|
+
64 * 1024 * 1024 * 1024,
|
|
267
|
+
);
|
|
268
|
+
const cpuSeconds = safeInteger(
|
|
269
|
+
resources.cpuSeconds,
|
|
270
|
+
"profile.lifecycle.resources.cpuSeconds",
|
|
271
|
+
1,
|
|
272
|
+
24 * 60 * 60,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const postSessionHook = exactObject(
|
|
276
|
+
input.postSessionHook,
|
|
277
|
+
["digest", "generation"],
|
|
278
|
+
"profile.lifecycle.postSessionHook",
|
|
279
|
+
);
|
|
280
|
+
const hookDigest = safeString(
|
|
281
|
+
postSessionHook.digest,
|
|
282
|
+
"profile.lifecycle.postSessionHook.digest",
|
|
283
|
+
80,
|
|
284
|
+
).toLowerCase();
|
|
285
|
+
if (!SHA256_RE.test(hookDigest)) {
|
|
286
|
+
throw new TypeError("profile lifecycle post-session hook is invalid");
|
|
287
|
+
}
|
|
288
|
+
const hookGeneration = safeInteger(
|
|
289
|
+
postSessionHook.generation,
|
|
290
|
+
"profile.lifecycle.postSessionHook.generation",
|
|
291
|
+
1,
|
|
292
|
+
generation,
|
|
293
|
+
);
|
|
294
|
+
if (hookGeneration !== leaseGeneration) {
|
|
295
|
+
throw new TypeError("profile lifecycle post-session hook fence is stale");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return Object.freeze({
|
|
299
|
+
runnerId: safeName(input.runnerId, "profile.lifecycle.runnerId"),
|
|
300
|
+
authorityFile: path.resolve(
|
|
301
|
+
safePath(input.authorityFile, "profile.lifecycle.authorityFile"),
|
|
302
|
+
),
|
|
303
|
+
state,
|
|
304
|
+
generation,
|
|
305
|
+
lease: Object.freeze({
|
|
306
|
+
id: safeName(lease.id, "profile.lifecycle.lease.id"),
|
|
307
|
+
generation: leaseGeneration,
|
|
308
|
+
expiresAt: leaseExpiresAt,
|
|
309
|
+
}),
|
|
310
|
+
proxyAuthority: Object.freeze({
|
|
311
|
+
id: safeName(proxyAuthority.id, "profile.lifecycle.proxyAuthority.id"),
|
|
312
|
+
revision: safeInteger(
|
|
313
|
+
proxyAuthority.revision,
|
|
314
|
+
"profile.lifecycle.proxyAuthority.revision",
|
|
315
|
+
1,
|
|
316
|
+
Number.MAX_SAFE_INTEGER,
|
|
317
|
+
),
|
|
318
|
+
issuedAt: proxyIssuedAt,
|
|
319
|
+
expiresAt: proxyExpiresAt,
|
|
320
|
+
}),
|
|
321
|
+
baseDir: Object.freeze({ path: baseDirPath, writableRequired: true }),
|
|
322
|
+
resources: Object.freeze({ cpuSeconds, memoryBytes }),
|
|
323
|
+
postSessionHook: Object.freeze({
|
|
324
|
+
digest: hookDigest,
|
|
325
|
+
generation: hookGeneration,
|
|
326
|
+
}),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
145
330
|
function normalizeTransport(target, value) {
|
|
146
331
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
147
332
|
throw new TypeError("profile.transport must be an object");
|
|
148
333
|
}
|
|
334
|
+
if (target === "local") {
|
|
335
|
+
const input = exactObject(
|
|
336
|
+
value,
|
|
337
|
+
["home", "securityHome"],
|
|
338
|
+
"profile.transport",
|
|
339
|
+
);
|
|
340
|
+
return Object.freeze({
|
|
341
|
+
home: path.resolve(safePath(input.home, "profile.transport.home")),
|
|
342
|
+
securityHome: path.resolve(
|
|
343
|
+
safePath(input.securityHome, "profile.transport.securityHome"),
|
|
344
|
+
),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
149
347
|
if (target === "wsl") {
|
|
150
348
|
const input = exactObject(value, ["distro"], "profile.transport");
|
|
151
349
|
return Object.freeze({
|
|
@@ -228,14 +426,24 @@ export function normalizeExecutionLocationProfile(value) {
|
|
|
228
426
|
"sessionStore",
|
|
229
427
|
];
|
|
230
428
|
const suppliedDigest = value?.profileDigest;
|
|
429
|
+
const isV2 = value?.schema === EXECUTION_LOCATION_PROFILE_SCHEMA_V2;
|
|
231
430
|
const input = exactObject(
|
|
232
431
|
value,
|
|
233
|
-
suppliedDigest == null
|
|
432
|
+
suppliedDigest == null
|
|
433
|
+
? isV2
|
|
434
|
+
? [...baseFields, "lifecycle"]
|
|
435
|
+
: baseFields
|
|
436
|
+
: isV2
|
|
437
|
+
? [...baseFields, "lifecycle", "profileDigest"]
|
|
438
|
+
: [...baseFields, "profileDigest"],
|
|
234
439
|
"execution location profile",
|
|
235
440
|
);
|
|
236
|
-
if (
|
|
441
|
+
if (
|
|
442
|
+
input.schema !== EXECUTION_LOCATION_PROFILE_SCHEMA &&
|
|
443
|
+
input.schema !== EXECUTION_LOCATION_PROFILE_SCHEMA_V2
|
|
444
|
+
) {
|
|
237
445
|
throw new TypeError(
|
|
238
|
-
`execution location profile must use ${EXECUTION_LOCATION_PROFILE_SCHEMA}`,
|
|
446
|
+
`execution location profile must use ${EXECUTION_LOCATION_PROFILE_SCHEMA} or ${EXECUTION_LOCATION_PROFILE_SCHEMA_V2}`,
|
|
239
447
|
);
|
|
240
448
|
}
|
|
241
449
|
if (containsSecret(JSON.stringify(input))) {
|
|
@@ -262,13 +470,14 @@ export function normalizeExecutionLocationProfile(value) {
|
|
|
262
470
|
if (!COMMIT_RE.test(gitCommit)) {
|
|
263
471
|
throw new TypeError("profile.expected.gitCommit is invalid");
|
|
264
472
|
}
|
|
473
|
+
const cwd = safePath(input.cwd, "profile.cwd");
|
|
265
474
|
const profile = {
|
|
266
|
-
schema:
|
|
475
|
+
schema: input.schema,
|
|
267
476
|
id: safeName(input.id, "profile.id"),
|
|
268
477
|
target,
|
|
269
478
|
evidenceId: safeName(input.evidenceId, "profile.evidenceId"),
|
|
270
479
|
cliCommand: safePath(input.cliCommand, "profile.cliCommand"),
|
|
271
|
-
cwd
|
|
480
|
+
cwd,
|
|
272
481
|
transport: normalizeTransport(target, input.transport),
|
|
273
482
|
expected: Object.freeze({
|
|
274
483
|
platform: safeName(expected.platform, "profile.expected.platform", 64),
|
|
@@ -282,9 +491,14 @@ export function normalizeExecutionLocationProfile(value) {
|
|
|
282
491
|
tools: Object.freeze(normalizeTools(expected.tools)),
|
|
283
492
|
}),
|
|
284
493
|
sessionStore: normalizeSessionStore(input.sessionStore),
|
|
494
|
+
...(isV2
|
|
495
|
+
? { lifecycle: normalizeRunnerLifecycle(input.lifecycle, cwd) }
|
|
496
|
+
: {}),
|
|
285
497
|
};
|
|
286
498
|
const profileDigest = digest(
|
|
287
|
-
|
|
499
|
+
isV2
|
|
500
|
+
? "chainlesschain.execution-location.profile.v2\0"
|
|
501
|
+
: "chainlesschain.execution-location.profile.v1\0",
|
|
288
502
|
profile,
|
|
289
503
|
);
|
|
290
504
|
if (suppliedDigest != null && suppliedDigest !== profileDigest) {
|
|
@@ -367,6 +581,163 @@ function quotePosix(value) {
|
|
|
367
581
|
return `'${String(value).replace(/'/gu, `'"'"'`)}'`;
|
|
368
582
|
}
|
|
369
583
|
|
|
584
|
+
function targetLifecycleEnvironment(profile, deps = {}) {
|
|
585
|
+
if (!profile.lifecycle) return Object.freeze([]);
|
|
586
|
+
const now = Number((deps.now || Date.now)());
|
|
587
|
+
const lifecycle = profile.lifecycle;
|
|
588
|
+
(
|
|
589
|
+
deps.assertRunnerLifecycleAuthority ||
|
|
590
|
+
assertExecutionLocationRunnerLeaseAuthority
|
|
591
|
+
)(lifecycle, profile.target, { now: () => now });
|
|
592
|
+
const leaseExpiry = Date.parse(lifecycle.lease.expiresAt);
|
|
593
|
+
const proxyIssuedAt = Date.parse(lifecycle.proxyAuthority.issuedAt);
|
|
594
|
+
const proxyExpiry = Date.parse(lifecycle.proxyAuthority.expiresAt);
|
|
595
|
+
if (
|
|
596
|
+
!Number.isFinite(now) ||
|
|
597
|
+
leaseExpiry <= now ||
|
|
598
|
+
proxyIssuedAt > now + 30_000 ||
|
|
599
|
+
now - proxyIssuedAt > MAX_PROXY_AUTHORITY_AGE_MS ||
|
|
600
|
+
proxyExpiry <= now
|
|
601
|
+
) {
|
|
602
|
+
throw new Error("execution location lease or proxy authority is stale");
|
|
603
|
+
}
|
|
604
|
+
const values = {
|
|
605
|
+
CC_EXECUTION_LOCATION_BASE_DIR: lifecycle.baseDir.path,
|
|
606
|
+
CC_EXECUTION_LOCATION_CPU_SECONDS: String(lifecycle.resources.cpuSeconds),
|
|
607
|
+
CC_EXECUTION_LOCATION_GENERATION: String(lifecycle.generation),
|
|
608
|
+
CC_EXECUTION_LOCATION_LEASE_GENERATION: String(lifecycle.lease.generation),
|
|
609
|
+
CC_EXECUTION_LOCATION_LEASE_ID: lifecycle.lease.id,
|
|
610
|
+
CC_EXECUTION_LOCATION_LEASE_EXPIRES_AT: lifecycle.lease.expiresAt,
|
|
611
|
+
CC_EXECUTION_LOCATION_MEMORY_BYTES: String(lifecycle.resources.memoryBytes),
|
|
612
|
+
CC_EXECUTION_LOCATION_POST_SESSION_HOOK_DIGEST:
|
|
613
|
+
lifecycle.postSessionHook.digest,
|
|
614
|
+
CC_EXECUTION_LOCATION_POST_SESSION_HOOK_GENERATION: String(
|
|
615
|
+
lifecycle.postSessionHook.generation,
|
|
616
|
+
),
|
|
617
|
+
CC_EXECUTION_LOCATION_PROXY_AUTHORITY_ID: lifecycle.proxyAuthority.id,
|
|
618
|
+
CC_EXECUTION_LOCATION_PROXY_EXPIRES_AT: lifecycle.proxyAuthority.expiresAt,
|
|
619
|
+
CC_EXECUTION_LOCATION_PROXY_ISSUED_AT: lifecycle.proxyAuthority.issuedAt,
|
|
620
|
+
CC_EXECUTION_LOCATION_PROXY_REVISION: String(
|
|
621
|
+
lifecycle.proxyAuthority.revision,
|
|
622
|
+
),
|
|
623
|
+
CC_EXECUTION_LOCATION_RUNNER_ID: lifecycle.runnerId,
|
|
624
|
+
CC_EXECUTION_LOCATION_STATE: lifecycle.state,
|
|
625
|
+
};
|
|
626
|
+
return Object.freeze(
|
|
627
|
+
Object.entries(values)
|
|
628
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
629
|
+
.map(([key, value]) => `${key}=${value}`),
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function localTargetEnvironment(profile, lifecycleEnvironment) {
|
|
634
|
+
const inherited = {};
|
|
635
|
+
for (const key of [
|
|
636
|
+
"ComSpec",
|
|
637
|
+
"FORCE_COLOR",
|
|
638
|
+
"LANG",
|
|
639
|
+
"LC_ALL",
|
|
640
|
+
"NO_COLOR",
|
|
641
|
+
"PATH",
|
|
642
|
+
"PATHEXT",
|
|
643
|
+
"SystemRoot",
|
|
644
|
+
"TEMP",
|
|
645
|
+
"TMP",
|
|
646
|
+
"TMPDIR",
|
|
647
|
+
"WINDIR",
|
|
648
|
+
]) {
|
|
649
|
+
if (typeof process.env[key] === "string") inherited[key] = process.env[key];
|
|
650
|
+
}
|
|
651
|
+
return Object.freeze({
|
|
652
|
+
...inherited,
|
|
653
|
+
APPDATA: path.join(profile.transport.home, "AppData", "Roaming"),
|
|
654
|
+
// `home` is the target account's home directory, not the application
|
|
655
|
+
// state directory. Pointing CHAINLESSCHAIN_HOME at it makes the
|
|
656
|
+
// owner-only guard (correctly) refuse to repair a broad user home on
|
|
657
|
+
// Windows. Keep all CLI state beneath a dedicated child directory.
|
|
658
|
+
CHAINLESSCHAIN_HOME: path.join(profile.transport.home, ".chainlesschain"),
|
|
659
|
+
CHAINLESSCHAIN_SECURITY_ANCHOR_HOME: profile.transport.securityHome,
|
|
660
|
+
FORCE_COLOR: "0",
|
|
661
|
+
HOME: profile.transport.home,
|
|
662
|
+
LOCALAPPDATA: path.join(profile.transport.home, "AppData", "Local"),
|
|
663
|
+
NO_COLOR: "1",
|
|
664
|
+
USERPROFILE: profile.transport.home,
|
|
665
|
+
...Object.fromEntries(
|
|
666
|
+
lifecycleEnvironment.map((entry) => {
|
|
667
|
+
const separator = entry.indexOf("=");
|
|
668
|
+
return [entry.slice(0, separator), entry.slice(separator + 1)];
|
|
669
|
+
}),
|
|
670
|
+
),
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function isLocalSessionPrepare(cliArgs) {
|
|
675
|
+
return (
|
|
676
|
+
cliArgs[0] === "session" &&
|
|
677
|
+
cliArgs[1] === "location" &&
|
|
678
|
+
cliArgs[2] === "prepare" &&
|
|
679
|
+
typeof cliArgs[3] === "string"
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Materialize the exact durable directories required by a Local replica
|
|
685
|
+
* handoff before launching its restricted Windows target process. The target
|
|
686
|
+
* is deliberately allowed to write only within these target-owned roots, but
|
|
687
|
+
* starting one PowerShell ACL repair per directory under a restricted token
|
|
688
|
+
* can consume the entire bounded target command window. Create and repair
|
|
689
|
+
* the fixed tree in one host-side batch, then let the target verify and use
|
|
690
|
+
* the already-private paths.
|
|
691
|
+
*
|
|
692
|
+
* @param {Object} profile
|
|
693
|
+
* @param {string[]} cliArgs
|
|
694
|
+
* @param {Object} deps
|
|
695
|
+
*/
|
|
696
|
+
export function prepareLocalTargetState(profile, cliArgs, deps = {}) {
|
|
697
|
+
if (!isLocalSessionPrepare(cliArgs)) return null;
|
|
698
|
+
const sessionId = safeName(
|
|
699
|
+
cliArgs[3],
|
|
700
|
+
"Local target replica session id",
|
|
701
|
+
1024,
|
|
702
|
+
);
|
|
703
|
+
const stateHome = path.join(profile.transport.home, ".chainlesschain");
|
|
704
|
+
const antiRollbackDirectory = getSessionAntiRollbackDirectory({
|
|
705
|
+
homeDir: stateHome,
|
|
706
|
+
anchorBase: profile.transport.securityHome,
|
|
707
|
+
});
|
|
708
|
+
const sessionDigest = createHash("sha256").update(sessionId).digest("hex");
|
|
709
|
+
const directories = [
|
|
710
|
+
stateHome,
|
|
711
|
+
path.join(stateHome, "sessions"),
|
|
712
|
+
profile.transport.securityHome,
|
|
713
|
+
path.join(profile.transport.securityHome, "sessions-v1"),
|
|
714
|
+
antiRollbackDirectory,
|
|
715
|
+
path.join(antiRollbackDirectory, "namespace"),
|
|
716
|
+
path.join(antiRollbackDirectory, "records"),
|
|
717
|
+
path.join(antiRollbackDirectory, "records", sessionDigest.slice(0, 2)),
|
|
718
|
+
];
|
|
719
|
+
const uniqueDirectories = [...new Set(directories)];
|
|
720
|
+
const ensure = deps.ensurePrivateDirectory || ensurePrivateDirectory;
|
|
721
|
+
const repair = deps.repairPrivatePaths || repairPrivatePaths;
|
|
722
|
+
const platform = deps.platform || process.platform;
|
|
723
|
+
for (const directory of uniqueDirectories) {
|
|
724
|
+
ensure(
|
|
725
|
+
directory,
|
|
726
|
+
platform === "win32"
|
|
727
|
+
? { applyWindowsAcl: false, failIfUnavailable: true }
|
|
728
|
+
: { failIfUnavailable: true },
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
if (platform === "win32") {
|
|
732
|
+
repair(uniqueDirectories, { platform: "win32" });
|
|
733
|
+
}
|
|
734
|
+
return Object.freeze({
|
|
735
|
+
stateHome,
|
|
736
|
+
antiRollbackDirectory,
|
|
737
|
+
directories: Object.freeze(uniqueDirectories),
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
370
741
|
function materializeKnownHostsAuthority(bytes, deps = {}) {
|
|
371
742
|
const runtimeFs = deps.fs || fs;
|
|
372
743
|
const root = runtimeFs.mkdtempSync(
|
|
@@ -400,7 +771,103 @@ function materializeKnownHostsAuthority(bytes, deps = {}) {
|
|
|
400
771
|
});
|
|
401
772
|
}
|
|
402
773
|
|
|
774
|
+
/**
|
|
775
|
+
* A brokered Local target cannot reliably receive a source-side stdin pipe on
|
|
776
|
+
* Windows: the restricted-token helper owns the process boundary. Stage the
|
|
777
|
+
* bounded replica beneath the already owner-only target state tree instead of
|
|
778
|
+
* using the source process temporary directory, which the target is not
|
|
779
|
+
* authorized to read.
|
|
780
|
+
*/
|
|
781
|
+
function materializeLocalTargetInput(profile, input, deps = {}) {
|
|
782
|
+
const bytes = Buffer.isBuffer(input) ? input : Buffer.from(input || "");
|
|
783
|
+
if (bytes.length === 0 || bytes.length > MAX_REPLICA_BYTES) {
|
|
784
|
+
throw new Error("local target input is empty or exceeds its byte limit");
|
|
785
|
+
}
|
|
786
|
+
const runtimeFs = deps.fs || fs;
|
|
787
|
+
const root = runtimeFs.mkdtempSync(
|
|
788
|
+
path.join(profile.transport.home, ".chainlesschain", "target-input-"),
|
|
789
|
+
);
|
|
790
|
+
const inputPath = path.join(root, "stdin.bin");
|
|
791
|
+
let descriptor;
|
|
792
|
+
try {
|
|
793
|
+
descriptor = runtimeFs.openSync(
|
|
794
|
+
inputPath,
|
|
795
|
+
runtimeFs.constants.O_CREAT |
|
|
796
|
+
runtimeFs.constants.O_EXCL |
|
|
797
|
+
runtimeFs.constants.O_WRONLY,
|
|
798
|
+
0o600,
|
|
799
|
+
);
|
|
800
|
+
runtimeFs.writeFileSync(descriptor, bytes);
|
|
801
|
+
runtimeFs.fsyncSync(descriptor);
|
|
802
|
+
} catch (error) {
|
|
803
|
+
try {
|
|
804
|
+
runtimeFs.rmSync(root, { recursive: true, force: true });
|
|
805
|
+
} catch {
|
|
806
|
+
// Preserve the original materialization failure.
|
|
807
|
+
}
|
|
808
|
+
throw error;
|
|
809
|
+
} finally {
|
|
810
|
+
if (descriptor !== undefined) runtimeFs.closeSync(descriptor);
|
|
811
|
+
}
|
|
812
|
+
return Object.freeze({
|
|
813
|
+
path: inputPath,
|
|
814
|
+
cleanup: () => runtimeFs.rmSync(root, { recursive: true, force: true }),
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
403
818
|
function targetInvocation(profile, cliArgs, deps = {}, options = {}) {
|
|
819
|
+
const lifecycleEnvironment = targetLifecycleEnvironment(profile, deps);
|
|
820
|
+
if (profile.target === "local") {
|
|
821
|
+
if (!path.isAbsolute(profile.cliCommand)) {
|
|
822
|
+
throw new Error("Local target CLI entry must be an absolute path");
|
|
823
|
+
}
|
|
824
|
+
if (!profile.lifecycle) {
|
|
825
|
+
throw new Error("Local target launch requires a lifecycle profile");
|
|
826
|
+
}
|
|
827
|
+
if (isLocalSessionPrepare(cliArgs)) {
|
|
828
|
+
// Injected spawnSync calls model a target transport in unit tests. They
|
|
829
|
+
// must not mutate a caller's host filesystem merely to exercise argv
|
|
830
|
+
// validation; production always takes the authenticated bootstrap path.
|
|
831
|
+
const bootstrap =
|
|
832
|
+
deps.prepareLocalTargetState ||
|
|
833
|
+
(deps.spawnSync ? null : prepareLocalTargetState);
|
|
834
|
+
bootstrap?.(profile, cliArgs, deps);
|
|
835
|
+
}
|
|
836
|
+
if (options.input != null && !isLocalSessionPrepare(cliArgs)) {
|
|
837
|
+
throw new Error(
|
|
838
|
+
"local target input is only supported for session prepare",
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
const inputAuthority =
|
|
842
|
+
options.input == null
|
|
843
|
+
? null
|
|
844
|
+
: materializeLocalTargetInput(profile, options.input, deps);
|
|
845
|
+
return Object.freeze({
|
|
846
|
+
file: deps.nodeCommand || process.execPath,
|
|
847
|
+
args: Object.freeze([
|
|
848
|
+
LOCAL_TARGET_SUPERVISOR,
|
|
849
|
+
"--cwd",
|
|
850
|
+
profile.cwd,
|
|
851
|
+
"--cpu-seconds",
|
|
852
|
+
String(profile.lifecycle.resources.cpuSeconds),
|
|
853
|
+
"--memory-bytes",
|
|
854
|
+
String(profile.lifecycle.resources.memoryBytes),
|
|
855
|
+
"--entry",
|
|
856
|
+
profile.cliCommand,
|
|
857
|
+
...(inputAuthority === null
|
|
858
|
+
? []
|
|
859
|
+
: ["--stdin-file", inputAuthority.path]),
|
|
860
|
+
"--",
|
|
861
|
+
...cliArgs,
|
|
862
|
+
]),
|
|
863
|
+
spawnOptions: Object.freeze({
|
|
864
|
+
cwd: profile.cwd,
|
|
865
|
+
env: localTargetEnvironment(profile, lifecycleEnvironment),
|
|
866
|
+
}),
|
|
867
|
+
inputHandled: inputAuthority !== null,
|
|
868
|
+
cleanup: inputAuthority?.cleanup || null,
|
|
869
|
+
});
|
|
870
|
+
}
|
|
404
871
|
if (profile.target === "wsl") {
|
|
405
872
|
if ((deps.platform || process.platform) !== "win32") {
|
|
406
873
|
throw new Error("WSL target launch requires a Windows host");
|
|
@@ -413,6 +880,9 @@ function targetInvocation(profile, cliArgs, deps = {}, options = {}) {
|
|
|
413
880
|
"--cd",
|
|
414
881
|
profile.cwd,
|
|
415
882
|
"--exec",
|
|
883
|
+
...(lifecycleEnvironment.length > 0
|
|
884
|
+
? ["env", ...lifecycleEnvironment]
|
|
885
|
+
: []),
|
|
416
886
|
profile.cliCommand,
|
|
417
887
|
...cliArgs,
|
|
418
888
|
]),
|
|
@@ -427,6 +897,7 @@ function targetInvocation(profile, cliArgs, deps = {}, options = {}) {
|
|
|
427
897
|
...(options.input == null ? [] : ["-i"]),
|
|
428
898
|
"--workdir",
|
|
429
899
|
profile.cwd,
|
|
900
|
+
...lifecycleEnvironment.flatMap((entry) => ["--env", entry]),
|
|
430
901
|
profile.transport.container,
|
|
431
902
|
profile.cliCommand,
|
|
432
903
|
...cliArgs,
|
|
@@ -473,7 +944,13 @@ function targetInvocation(profile, cliArgs, deps = {}, options = {}) {
|
|
|
473
944
|
: transport.host;
|
|
474
945
|
args.push(
|
|
475
946
|
target,
|
|
476
|
-
`cd ${quotePosix(profile.cwd)} && ${[
|
|
947
|
+
`cd ${quotePosix(profile.cwd)} && ${[
|
|
948
|
+
...(lifecycleEnvironment.length > 0
|
|
949
|
+
? ["env", ...lifecycleEnvironment]
|
|
950
|
+
: []),
|
|
951
|
+
profile.cliCommand,
|
|
952
|
+
...cliArgs,
|
|
953
|
+
]
|
|
477
954
|
.map(quotePosix)
|
|
478
955
|
.join(" ")}`,
|
|
479
956
|
);
|
|
@@ -499,18 +976,37 @@ function runTargetCommand(profile, cliArgs, deps = {}, options = {}) {
|
|
|
499
976
|
) {
|
|
500
977
|
throw new Error("target command output boundary is invalid");
|
|
501
978
|
}
|
|
979
|
+
const timeoutMs = Number(
|
|
980
|
+
options.timeoutMs ?? DEFAULT_TARGET_COMMAND_TIMEOUT_MS,
|
|
981
|
+
);
|
|
982
|
+
if (
|
|
983
|
+
!Number.isSafeInteger(timeoutMs) ||
|
|
984
|
+
timeoutMs < 1 ||
|
|
985
|
+
timeoutMs > MAX_TARGET_COMMAND_TIMEOUT_MS
|
|
986
|
+
) {
|
|
987
|
+
throw new Error("target command timeout boundary is invalid");
|
|
988
|
+
}
|
|
502
989
|
const result = spawnSync(invocation.file, invocation.args, {
|
|
503
990
|
origin: "execution-location:target",
|
|
504
991
|
scope: "execution-location",
|
|
505
992
|
policy: "allow",
|
|
506
993
|
shell: false,
|
|
507
994
|
encoding: "utf8",
|
|
508
|
-
timeout: options.interactive ? undefined :
|
|
995
|
+
timeout: options.interactive ? undefined : timeoutMs,
|
|
509
996
|
maxBuffer: options.interactive ? undefined : maxBuffer,
|
|
510
|
-
...(options.input == null
|
|
997
|
+
...(options.input == null || invocation.inputHandled
|
|
998
|
+
? {}
|
|
999
|
+
: { input: options.input }),
|
|
511
1000
|
stdio: options.interactive
|
|
512
1001
|
? "inherit"
|
|
513
|
-
: [
|
|
1002
|
+
: [
|
|
1003
|
+
options.input == null || invocation.inputHandled
|
|
1004
|
+
? "ignore"
|
|
1005
|
+
: "pipe",
|
|
1006
|
+
"pipe",
|
|
1007
|
+
"pipe",
|
|
1008
|
+
],
|
|
1009
|
+
...(invocation.spawnOptions || {}),
|
|
514
1010
|
});
|
|
515
1011
|
if (result?.error) throw result.error;
|
|
516
1012
|
if (!result || result.status !== 0) {
|
|
@@ -548,6 +1044,266 @@ function parseTargetProjection(raw, label) {
|
|
|
548
1044
|
return projection;
|
|
549
1045
|
}
|
|
550
1046
|
|
|
1047
|
+
function preflightExecutionLocationTarget(profile, deps = {}) {
|
|
1048
|
+
if (!profile.lifecycle) return null;
|
|
1049
|
+
const raw = runTargetCommand(
|
|
1050
|
+
profile,
|
|
1051
|
+
["session", "location", "target-preflight", "--json"],
|
|
1052
|
+
deps,
|
|
1053
|
+
);
|
|
1054
|
+
const receipt = exactObject(
|
|
1055
|
+
parseTargetProjection(raw, "target lifecycle preflight"),
|
|
1056
|
+
[
|
|
1057
|
+
"schema",
|
|
1058
|
+
"runnerId",
|
|
1059
|
+
"state",
|
|
1060
|
+
"generation",
|
|
1061
|
+
"lease",
|
|
1062
|
+
"proxyAuthority",
|
|
1063
|
+
"baseDir",
|
|
1064
|
+
"resources",
|
|
1065
|
+
"postSessionHook",
|
|
1066
|
+
"secretTransferCount",
|
|
1067
|
+
"receiptDigest",
|
|
1068
|
+
],
|
|
1069
|
+
"target lifecycle preflight",
|
|
1070
|
+
);
|
|
1071
|
+
const lifecycle = profile.lifecycle;
|
|
1072
|
+
const reportedEnforcement = receipt.resources?.enforcement;
|
|
1073
|
+
const expectedEnforcement =
|
|
1074
|
+
profile.target === "local"
|
|
1075
|
+
? "target-supervisor"
|
|
1076
|
+
: reportedEnforcement === "posix-rlimit" ||
|
|
1077
|
+
reportedEnforcement === "target-supervisor"
|
|
1078
|
+
? reportedEnforcement
|
|
1079
|
+
: "posix-rlimit";
|
|
1080
|
+
const material = {
|
|
1081
|
+
schema: "cc-execution-location-target-preflight/v1",
|
|
1082
|
+
runnerId: lifecycle.runnerId,
|
|
1083
|
+
state: lifecycle.state,
|
|
1084
|
+
generation: lifecycle.generation,
|
|
1085
|
+
lease: {
|
|
1086
|
+
id: lifecycle.lease.id,
|
|
1087
|
+
generation: lifecycle.lease.generation,
|
|
1088
|
+
expiresAt: lifecycle.lease.expiresAt,
|
|
1089
|
+
},
|
|
1090
|
+
proxyAuthority: {
|
|
1091
|
+
id: lifecycle.proxyAuthority.id,
|
|
1092
|
+
revision: lifecycle.proxyAuthority.revision,
|
|
1093
|
+
issuedAt: lifecycle.proxyAuthority.issuedAt,
|
|
1094
|
+
expiresAt: lifecycle.proxyAuthority.expiresAt,
|
|
1095
|
+
},
|
|
1096
|
+
baseDir: {
|
|
1097
|
+
digest: digest(
|
|
1098
|
+
"chainlesschain.execution-location.base-dir.v1\0",
|
|
1099
|
+
lifecycle.baseDir.path,
|
|
1100
|
+
),
|
|
1101
|
+
writable: true,
|
|
1102
|
+
},
|
|
1103
|
+
resources: {
|
|
1104
|
+
cpuSeconds: lifecycle.resources.cpuSeconds,
|
|
1105
|
+
memoryBytes: lifecycle.resources.memoryBytes,
|
|
1106
|
+
observedCpuSeconds: Number(receipt.resources?.observedCpuSeconds),
|
|
1107
|
+
observedMemoryBytes: Number(receipt.resources?.observedMemoryBytes),
|
|
1108
|
+
targetEnforced: true,
|
|
1109
|
+
enforcement: expectedEnforcement,
|
|
1110
|
+
},
|
|
1111
|
+
postSessionHook: {
|
|
1112
|
+
digest: lifecycle.postSessionHook.digest,
|
|
1113
|
+
generation: lifecycle.postSessionHook.generation,
|
|
1114
|
+
},
|
|
1115
|
+
secretTransferCount: 0,
|
|
1116
|
+
};
|
|
1117
|
+
const expectedReceiptDigest = digest(
|
|
1118
|
+
"chainlesschain.execution-location.target-preflight.v1\0",
|
|
1119
|
+
material,
|
|
1120
|
+
);
|
|
1121
|
+
if (
|
|
1122
|
+
canonicalJson(receipt, "targetLifecyclePreflight") !==
|
|
1123
|
+
canonicalJson(
|
|
1124
|
+
{ ...material, receiptDigest: expectedReceiptDigest },
|
|
1125
|
+
"expectedTargetLifecyclePreflight",
|
|
1126
|
+
) ||
|
|
1127
|
+
material.resources.observedCpuSeconds < 1 ||
|
|
1128
|
+
material.resources.observedCpuSeconds > lifecycle.resources.cpuSeconds ||
|
|
1129
|
+
material.resources.observedMemoryBytes < 1 ||
|
|
1130
|
+
material.resources.observedMemoryBytes > lifecycle.resources.memoryBytes
|
|
1131
|
+
) {
|
|
1132
|
+
throw new Error("target lifecycle preflight does not match the profile");
|
|
1133
|
+
}
|
|
1134
|
+
return Object.freeze(receipt);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
export function probeExecutionLocationTargetPreflight(input = {}, deps = {}) {
|
|
1138
|
+
const profile = normalizeExecutionLocationProfile(input.profile);
|
|
1139
|
+
if (!profile.lifecycle) {
|
|
1140
|
+
throw new TypeError("target preflight requires a v2 lifecycle profile");
|
|
1141
|
+
}
|
|
1142
|
+
return preflightExecutionLocationTarget(profile, deps);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
export function probeExecutionLocationTargetResourceLimit(
|
|
1146
|
+
input = {},
|
|
1147
|
+
deps = {},
|
|
1148
|
+
) {
|
|
1149
|
+
const profile = normalizeExecutionLocationProfile(input.profile);
|
|
1150
|
+
if (!profile.lifecycle) {
|
|
1151
|
+
throw new TypeError(
|
|
1152
|
+
"resource enforcement probe requires a v2 lifecycle profile",
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
const kind = input.kind;
|
|
1156
|
+
if (kind !== "cpu" && kind !== "memory") {
|
|
1157
|
+
throw new TypeError(
|
|
1158
|
+
"resource enforcement probe kind must be cpu or memory",
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
const preflight = preflightExecutionLocationTarget(profile, deps);
|
|
1162
|
+
const invocation = targetInvocation(
|
|
1163
|
+
profile,
|
|
1164
|
+
["session", "location", "resource-probe", kind],
|
|
1165
|
+
deps,
|
|
1166
|
+
);
|
|
1167
|
+
const spawnSync =
|
|
1168
|
+
deps.spawnSync ||
|
|
1169
|
+
((file, args, spawnOptions) =>
|
|
1170
|
+
executionBroker.spawnSync(file, args, spawnOptions));
|
|
1171
|
+
let result;
|
|
1172
|
+
try {
|
|
1173
|
+
result = spawnSync(invocation.file, invocation.args, {
|
|
1174
|
+
origin: "execution-location:target-resource-probe",
|
|
1175
|
+
scope: "execution-location",
|
|
1176
|
+
policy: "allow",
|
|
1177
|
+
shell: false,
|
|
1178
|
+
encoding: "utf8",
|
|
1179
|
+
timeout: Math.min(
|
|
1180
|
+
60_000,
|
|
1181
|
+
(profile.lifecycle.resources.cpuSeconds + 30) * 1000,
|
|
1182
|
+
),
|
|
1183
|
+
maxBuffer: MAX_PROBE_BYTES,
|
|
1184
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1185
|
+
...(invocation.spawnOptions || {}),
|
|
1186
|
+
});
|
|
1187
|
+
} finally {
|
|
1188
|
+
invocation.cleanup?.();
|
|
1189
|
+
}
|
|
1190
|
+
if (result?.error) throw result.error;
|
|
1191
|
+
const expectedMarker = `CC_EXECUTION_LOCATION_RESOURCE_PROBE_ARMED:${kind}`;
|
|
1192
|
+
const status = Number(result?.status);
|
|
1193
|
+
const signal = result?.signal == null ? null : String(result.signal);
|
|
1194
|
+
const signalTerminated = new Set(["SIGABRT", "SIGKILL", "SIGXCPU"]).has(
|
|
1195
|
+
signal,
|
|
1196
|
+
);
|
|
1197
|
+
const exitTerminated = [134, 137, 152].includes(status);
|
|
1198
|
+
if (
|
|
1199
|
+
!result ||
|
|
1200
|
+
String(result.stdout || "").trim() !== expectedMarker ||
|
|
1201
|
+
(!signalTerminated && !exitTerminated)
|
|
1202
|
+
) {
|
|
1203
|
+
throw new Error("target workload did not terminate at its resource limit");
|
|
1204
|
+
}
|
|
1205
|
+
const material = {
|
|
1206
|
+
schema: "cc-execution-location-target-resource-enforcement/v1",
|
|
1207
|
+
target: profile.target,
|
|
1208
|
+
kind,
|
|
1209
|
+
profileDigest: profile.profileDigest,
|
|
1210
|
+
preflightReceiptDigest: preflight.receiptDigest,
|
|
1211
|
+
runnerId: profile.lifecycle.runnerId,
|
|
1212
|
+
runnerGeneration: profile.lifecycle.generation,
|
|
1213
|
+
leaseId: profile.lifecycle.lease.id,
|
|
1214
|
+
leaseGeneration: profile.lifecycle.lease.generation,
|
|
1215
|
+
limit:
|
|
1216
|
+
kind === "cpu"
|
|
1217
|
+
? profile.lifecycle.resources.cpuSeconds
|
|
1218
|
+
: profile.lifecycle.resources.memoryBytes,
|
|
1219
|
+
enforcementScope: "target-workload",
|
|
1220
|
+
termination: signalTerminated
|
|
1221
|
+
? { kind: "signal", value: signal }
|
|
1222
|
+
: { kind: "exit-status", value: status },
|
|
1223
|
+
secretTransferCount: 0,
|
|
1224
|
+
};
|
|
1225
|
+
return Object.freeze({
|
|
1226
|
+
...material,
|
|
1227
|
+
receiptDigest: digest(
|
|
1228
|
+
"chainlesschain.execution-location.target-resource-enforcement.v1\0",
|
|
1229
|
+
material,
|
|
1230
|
+
),
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
export function probeExecutionLocationTargetSigtermDrain(
|
|
1235
|
+
input = {},
|
|
1236
|
+
deps = {},
|
|
1237
|
+
) {
|
|
1238
|
+
const profile = normalizeExecutionLocationProfile(input.profile);
|
|
1239
|
+
if (!profile.lifecycle || profile.lifecycle.state !== "accepting") {
|
|
1240
|
+
throw new TypeError("SIGTERM drain probe requires an accepting v2 profile");
|
|
1241
|
+
}
|
|
1242
|
+
const preflight = preflightExecutionLocationTarget(profile, deps);
|
|
1243
|
+
const raw = runTargetCommand(
|
|
1244
|
+
profile,
|
|
1245
|
+
["session", "location", "sigterm-drain-probe", "--json"],
|
|
1246
|
+
deps,
|
|
1247
|
+
);
|
|
1248
|
+
const receipt = exactObject(
|
|
1249
|
+
parseTargetProjection(raw, "target SIGTERM drain probe"),
|
|
1250
|
+
[
|
|
1251
|
+
"schema",
|
|
1252
|
+
"runnerId",
|
|
1253
|
+
"signal",
|
|
1254
|
+
"before",
|
|
1255
|
+
"after",
|
|
1256
|
+
"lease",
|
|
1257
|
+
"preflightReceiptDigest",
|
|
1258
|
+
"signalDeliveryCount",
|
|
1259
|
+
"postSignalLeaseAcceptanceCount",
|
|
1260
|
+
"secretTransferCount",
|
|
1261
|
+
"receiptDigest",
|
|
1262
|
+
],
|
|
1263
|
+
"target SIGTERM drain probe",
|
|
1264
|
+
);
|
|
1265
|
+
const lifecycle = profile.lifecycle;
|
|
1266
|
+
const material = {
|
|
1267
|
+
schema: "cc-execution-location-target-sigterm-drain/v1",
|
|
1268
|
+
runnerId: lifecycle.runnerId,
|
|
1269
|
+
signal: "SIGTERM",
|
|
1270
|
+
before: {
|
|
1271
|
+
state: "accepting",
|
|
1272
|
+
generation: lifecycle.generation,
|
|
1273
|
+
accepting: true,
|
|
1274
|
+
},
|
|
1275
|
+
after: {
|
|
1276
|
+
state: "draining",
|
|
1277
|
+
generation: lifecycle.generation + 1,
|
|
1278
|
+
accepting: false,
|
|
1279
|
+
},
|
|
1280
|
+
lease: {
|
|
1281
|
+
id: lifecycle.lease.id,
|
|
1282
|
+
generation: lifecycle.lease.generation,
|
|
1283
|
+
expiresAt: lifecycle.lease.expiresAt,
|
|
1284
|
+
continued: true,
|
|
1285
|
+
},
|
|
1286
|
+
preflightReceiptDigest: preflight.receiptDigest,
|
|
1287
|
+
signalDeliveryCount: 1,
|
|
1288
|
+
postSignalLeaseAcceptanceCount: 0,
|
|
1289
|
+
secretTransferCount: 0,
|
|
1290
|
+
};
|
|
1291
|
+
const expected = {
|
|
1292
|
+
...material,
|
|
1293
|
+
receiptDigest: digest(
|
|
1294
|
+
"chainlesschain.execution-location.target-sigterm-drain.v1\0",
|
|
1295
|
+
material,
|
|
1296
|
+
),
|
|
1297
|
+
};
|
|
1298
|
+
if (
|
|
1299
|
+
canonicalJson(receipt, "targetSigtermDrain") !==
|
|
1300
|
+
canonicalJson(expected, "expectedTargetSigtermDrain")
|
|
1301
|
+
) {
|
|
1302
|
+
throw new Error("target SIGTERM drain receipt does not match the profile");
|
|
1303
|
+
}
|
|
1304
|
+
return Object.freeze(receipt);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
551
1307
|
function validateProfileHandoff(profile, handoff) {
|
|
552
1308
|
if (!handoff?.allowed) {
|
|
553
1309
|
throw new Error("execution location handoff is not allowed");
|
|
@@ -605,6 +1361,7 @@ function validateCurrentProjection(profile, rawProjection) {
|
|
|
605
1361
|
export function attestExecutionLocationTarget(input = {}, deps = {}) {
|
|
606
1362
|
const profile = normalizeExecutionLocationProfile(input.profile);
|
|
607
1363
|
validateProfileHandoff(profile, input.handoff);
|
|
1364
|
+
const lifecyclePreflight = preflightExecutionLocationTarget(profile, deps);
|
|
608
1365
|
const raw = runTargetCommand(
|
|
609
1366
|
profile,
|
|
610
1367
|
["session", "location", "current", "--json"],
|
|
@@ -612,7 +1369,7 @@ export function attestExecutionLocationTarget(input = {}, deps = {}) {
|
|
|
612
1369
|
);
|
|
613
1370
|
const current = parseTargetProjection(raw, "target current-location probe");
|
|
614
1371
|
const binding = validateCurrentProjection(profile, current);
|
|
615
|
-
|
|
1372
|
+
const attestation = createExecutionLocationTargetAttestation({
|
|
616
1373
|
profileDigest: profile.profileDigest,
|
|
617
1374
|
sourceSessionId: input.handoff.session.sessionId,
|
|
618
1375
|
sourceHeadHash: input.handoff.session.headHash,
|
|
@@ -621,6 +1378,19 @@ export function attestExecutionLocationTarget(input = {}, deps = {}) {
|
|
|
621
1378
|
baseCommit: input.handoff.transfer.git.baseCommit,
|
|
622
1379
|
binding,
|
|
623
1380
|
});
|
|
1381
|
+
if (!lifecyclePreflight) return attestation;
|
|
1382
|
+
return Object.freeze({
|
|
1383
|
+
...attestation,
|
|
1384
|
+
lifecyclePreflight,
|
|
1385
|
+
lifecycleAttestationDigest: digest(
|
|
1386
|
+
"chainlesschain.execution-location.lifecycle-attestation.v1\0",
|
|
1387
|
+
{
|
|
1388
|
+
attestationDigest: attestation.attestationDigest,
|
|
1389
|
+
preflightReceiptDigest: lifecyclePreflight.receiptDigest,
|
|
1390
|
+
profileDigest: profile.profileDigest,
|
|
1391
|
+
},
|
|
1392
|
+
),
|
|
1393
|
+
});
|
|
624
1394
|
}
|
|
625
1395
|
|
|
626
1396
|
function verifyTargetSessionStore(
|
|
@@ -755,7 +1525,7 @@ function transferTargetSessionStore(
|
|
|
755
1525
|
"--json",
|
|
756
1526
|
],
|
|
757
1527
|
deps,
|
|
758
|
-
{ input: transcriptBytes },
|
|
1528
|
+
{ input: transcriptBytes, timeoutMs: REPLICA_TRANSFER_TIMEOUT_MS },
|
|
759
1529
|
);
|
|
760
1530
|
const receipt = parseTargetProjection(
|
|
761
1531
|
raw,
|