chainlesschain 0.166.48 → 0.166.58
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 +22 -0
- package/package.json +2 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-BLGEffkh.js → AIOps-DoyI29Aw.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Cz1z2VGD.js → ActionButton-eZKcEoxc.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C3j9cf6q.js → Analytics-B2C2e9A7.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BR9ERuCA.js → AppLayout-DkZJiu4X.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CozJCm4y.js → Artifacts-8PD3BXVa.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DC5g9Ra2.js → Audit-Cv6sRlFu.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-B1zrQ0dW.js → BackgroundAgents-D-V84Qpm.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-ChVnfjpm.js → Backup-DYJQcxj1.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-8_vLQsLf.js → BaseInput-KL2IeywH.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Dct9bm6L.js → Chat-Bt9wy_Yn.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BvoSsCKh.js → ChatBubbleRenderer-ChB6fH-W.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DZQIsCIC.js → Checkbox-Dm-nfdnd.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CdzHxmnq.js → Codegen-CYi3yDzv.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DdAN8LeW.js → Col-C7sQywfG.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CNgjT9MA.js → Community-DjxuqAM4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BtQiX6G-.js → Compact-CD_4lbxr.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-OGX5ma3j.js → Compliance-BtDpNJrS.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CgugQnGM.js → Cowork-l19ANH6Q.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Bpg7cPFD.js → Cron-Br-rBoEK.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-D1hsuswf.js → Crosschain-60UVvKET.js} +1 -1
- package/src/assets/web-panel/assets/{DID-miDRsMBw.js → DID-B9dqar51.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CGcl8htP.js → Dashboard-B39hhdHb.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BFaxTdVE.js → Dropdown-Bm5rCfvF.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BQdGLZIO.js → EmailListRenderer-BI9HANDl.js} +1 -1
- package/src/assets/web-panel/assets/EvolutionSettings-Cfqn_yCq.js +1 -0
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-c1WVC_--.js → FamilyGuardDashboard-9Y4_zyhT.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DyAcFrQY.js → Federation-vnbbmMdF.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CfwpfD2B.js → FormItemContext-BEKNtNF9.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DkJcbBpS.js → GenericCardRenderer-BmWr16IL.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DgYIrPJh.js → Git-CUUwAXdn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BF1tYPJ6.js → Governance-Bw8XZmH9.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BFkwRIrU.js → Inference-CNrml6dn.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DpCoqhBa.js → KnowledgeGraph-BhxOwpW_.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DU2qAJmD.js → Logs-BV8WViUG.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CO1qbiOY.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Dz9NUrfo.js → Marketplace-ztcu4mg4.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-B67I3d5v.js → McpTools-D-wzRO7z.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-crkElJSy.js → Memory-ChdjWC1R.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BO20VbdX.js → MobileBridge-B1QHzIjE.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Ba-3gZZd.js → MobileProjects-DmaRtaMw.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BAHZM084.js → Mtc-DRQMr4WP.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-CL3DzajL.js → MtcAudit-Cwo0on-u.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BSWWzvWH.js → Multisig-QplgySFt.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-fnxqXM_3.js → NLProgramming-BWkMyuAy.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-qUZPIQXT.js → Notes-BI_pSEM0.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-x9Xrixqu.js → NotificationSettings-DiuFo6Xe.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BtMtQUt7.js → OrderTableRenderer-DZzQhujZ.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-C946Hzna.js → Organization-DqEdqWB0.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-BYf8KIts.js → Overflow-CO7_2YLN.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cbqf5dnv.js → P2P-BN5RiNst.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-CapfG4bl.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-CZBw45EW.js → Permissions-B24c5fWW.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BKwUAt43.js → PersonalDataHub-D7Mb3_vh.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DmFD2QW4.js → Pipeline-BsfxlbOQ.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-KpuwWvt5.js → Privacy-Bq7c7IYy.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bt8UheX6.js → ProjectInit-Clse2DPl.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-2ZeAn9CP.js → ProjectSettings-CHAEdjri.js} +2 -2
- package/src/assets/web-panel/assets/Projects-C1UHvBEK.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DcEAnw6L.js → Providers-DQyH3PXU.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-qF7liYG9.js → QrScannerModal-t2Z-N49o.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-qiESlVBJ.js → QuickAsk-BacR7PlI.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Bk-cQyi9.js → Recommend-D-NAy0CQ.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-CwrekErS.js → RemoteSession-CjI3GeVQ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Bmw_UQtW.js → Reputation-HoQ3i2oW.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CPdqZIUX.js → Row-CY2jczWa.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D_W-xxvS.js → RssFeed-Bd3o20IY.js} +2 -2
- package/src/assets/web-panel/assets/{Search-Hu0oDFj_.js → Search-C88KjgTv.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DeRxUwmq.js → Security-yfmfvujK.js} +3 -3
- package/src/assets/web-panel/assets/{Services-CsNmHFkf.js → Services-DLPBWoxT.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-gv_Vz0tN.js → Skeleton-DzDjvVQi.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Dgg-_rCB.js → Skills-DqfrJIyP.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Bpu-bsEJ.js → Sla-BXhsGzkm.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CrhleHuF.js → SpeechSettings-jxSl2JDo.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-JHjUN6I9.js → SyncSettings-BX2xKtI3.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DKRWKkl9.js → Tasks-P9MmRYt4.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-leiWntLq.js → Templates-CTpj9f62.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant--1bYulvn.js → Tenant-nnQuhdPg.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C0y6FvSS.js → Terminal-cJ0vbuTe.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CQK4PVv4.js → TimelineRenderer-DN08D0SD.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DbIvF-DV.js → Tokens-C8BWWBUD.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CkXJ2vuq.js → Trigger-DhD-LUoe.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B02WXJPw.js → Trust-D0nFyc_Z.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-VPMkfzmw.js → UkeySign-B59G57LP.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C5ezUnah.js → VideoEditing-BpYACxVC.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CYfvYRrY.js → Wallet-DlZ3rKGD.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DtKl-kpr.js → WebAuthn-De4dNyig.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DACITF-u.js → WorkflowEditor-C2Tewnhp.js} +1 -1
- package/src/assets/web-panel/assets/{chat-qnygfnLi.js → chat-CDBn4JLE.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D4mISLBD.js → colors-BNawl0d_.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BBPfxYGL.js → compact-item-n65WDhze.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Byx4ndHp.js → createContext-DAKgOdIy.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-sBSSW127.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CGjLkkn3.js → hasIn-DhxFG46W.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWhI8G14.js → index--76HBS1k.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dmfe0L2Z.js → index-0Y-ncsVy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzeBBAl5.js → index-5oJjovnA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFMWjuav.js → index-BAogaHa9.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8UpadHC.js → index-BBFjMjmp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmKYaaGP.js → index-BDTrf8WH.js} +1 -1
- package/src/assets/web-panel/assets/index-BIJTDSLY.js +1 -0
- package/src/assets/web-panel/assets/{index-CD5Btq6p.js → index-BQFAuU1c.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVUJjQAT.js → index-BRyzRluv.js} +1 -1
- package/src/assets/web-panel/assets/{index-O09jXbir.js → index-BZI8xwwt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMj8qmpX.js → index-BfXdTvku.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_g9mA1N.js → index-BudfMkBy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN7_TSgU.js → index-By-zGgUp.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ne8UGllV.js → index-ByDs4cW4.js} +3 -3
- package/src/assets/web-panel/assets/{index-CZjNamSU.js → index-BzONk4rW.js} +1 -1
- package/src/assets/web-panel/assets/{index-xzRKTy0R.js → index-CE6OcLof.js} +1 -1
- package/src/assets/web-panel/assets/{index-J5JkStvY.js → index-CSOgoyqx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BklGH3YI.js → index-CW_OUdP8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmHE-lY_.js → index-CZJrAg98.js} +1 -1
- package/src/assets/web-panel/assets/index-CrBauOkp.js +1 -0
- package/src/assets/web-panel/assets/{index-lKJWAmd4.js → index-Cv7LqHwv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dzni_5Qw.js → index-D34AXxN9.js} +1 -1
- package/src/assets/web-panel/assets/{index-c_tBkIlU.js → index-D6sWUgN6.js} +1 -1
- package/src/assets/web-panel/assets/{index-Y7el_3WS.js → index-DCujBaFg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxQF1wCU.js → index-DFkamspD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsVuQsY1.js → index-DLFRz0_7.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByA1NyFP.js → index-DV6iHrUO.js} +1 -1
- package/src/assets/web-panel/assets/{index-DboTeuPF.js → index-D_-6qQf0.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9tcHs46.js → index-DeTlqus4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DH6iC5b2.js → index-DkW0HGr-.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9jH-h4X.js → index-DleKcclQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cpg3AWRy.js → index-Ds290C06.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_fHPZ8E.js → index-KT94nMha.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3abGsSW.js → index-KZkt4e9N.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZXhKAk9.js → index-L1QnA0gW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfgpKd64.js → index-PlOBwfR1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CoA8WsVr.js → index-gG1WJBpa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNe2lCk6.js → index-hA9VUXGr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CydHMjwc.js → index-pzEYIGNA.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-XuhfEIhB.js → initDefaultProps-B3bCivrt.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Cbxh6AxO.js → motion-CF5pGsxz.js} +1 -1
- package/src/assets/web-panel/assets/{move-C7IEnFly.js → move-CaOFtFLU.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-lLADx4LL.js → mtc-parser-Z4-50Y1c.js} +1 -1
- package/src/assets/web-panel/assets/{omit-mIbic_k-.js → omit-AK4WqVTT.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-_xIYEqu3.js → pickAttrs-Dbye0px4.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-ClB-E0ny.js → placementArrow-DCmr-Dks.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-D36Bg2ut.js → responsiveObserve-CJ3Zm_-H.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CUuO08Bl.js → slide-BuSk5-ue.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BKahwLbv.js → statusUtils-8u_lKSFJ.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-2pT-T8vy.js → styleChecker-sy6_m42F.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DyH5g7Rg.js → useFlexGapSupport-Di5EfYGo.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CTOauB3X.js → useFs-BTfX_Ri1.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-B02JYIMe.js → usePersonalDataHub-kqurO26X.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Do22wQrc.js → vnode-DRkH4mPV.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BfnL7rit.js → zoom-ON_-UuD_.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +5 -0
- package/src/commands/config.js +19 -10
- package/src/commands/context.js +31 -11
- package/src/commands/eval.js +39 -4
- package/src/commands/evolution-deployment.js +70 -0
- package/src/data/changelog.json +91 -0
- package/src/gateways/ws/evolution-deployment-topic-handlers.js +20 -0
- package/src/harness/prompt-compressor.js +12 -4
- package/src/lib/diagnostic-excerpt.js +51 -0
- package/src/lib/diagnostic-workflow.js +10 -0
- package/src/lib/eval/evidence.js +19 -10
- package/src/lib/eval/outcomes.js +373 -0
- package/src/lib/eval/runner.js +6 -0
- package/src/lib/evolution/evolution-deployment-config.js +16 -1
- package/src/lib/evolution/evolution-deployment-profile.js +102 -58
- package/src/lib/evolution/evolution-test-deployment.js +386 -0
- package/src/lib/git-tool-input.js +37 -0
- package/src/lib/llm-config-defaults.js +32 -9
- package/src/lib/model-context-window.js +3 -2
- package/src/lib/read-file-loop-guard.js +54 -0
- package/src/lib/remote-read-loop-guard.js +12 -14
- package/src/lib/runtime-usage-ledger.js +5 -0
- package/src/lib/session-message-fabric.js +74 -37
- package/src/lib/shell-output.js +30 -0
- package/src/lib/task-progress-tracker.js +88 -0
- package/src/repl/agent-repl.js +19 -4
- package/src/runtime/agent-core.js +108 -22
- package/src/runtime/headless-runner.js +4 -0
- package/src/runtime/headless-stream.js +34 -1
- package/src/assets/web-panel/assets/EvolutionSettings-bMvgBk6r.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-QWv7UJLq.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-rmxp0cuF.js +0 -7
- package/src/assets/web-panel/assets/Projects-zBuet8Zs.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DBy6gsuI.js +0 -1
- package/src/assets/web-panel/assets/index-BfEDXk_E.js +0 -1
- package/src/assets/web-panel/assets/index-CrxUwNM8.js +0 -1
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
readFileSync,
|
|
5
|
-
renameSync,
|
|
6
|
-
unlinkSync,
|
|
7
|
-
writeFileSync,
|
|
8
|
-
} from "node:fs";
|
|
3
|
+
import { readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
9
4
|
import { dirname, isAbsolute, join } from "node:path";
|
|
10
5
|
import { resolveConfigDataRoot } from "../paths.js";
|
|
11
6
|
import {
|
|
@@ -16,7 +11,7 @@ import {
|
|
|
16
11
|
import { withFileLock } from "../with-file-lock.js";
|
|
17
12
|
|
|
18
13
|
export const EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA =
|
|
19
|
-
"chainlesschain.evolution-deployment-profile/
|
|
14
|
+
"chainlesschain.evolution-deployment-profile/v5";
|
|
20
15
|
|
|
21
16
|
const LEGACY_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA =
|
|
22
17
|
"chainlesschain.evolution-deployment-profile/v1";
|
|
@@ -24,6 +19,8 @@ const PREVIOUS_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA =
|
|
|
24
19
|
"chainlesschain.evolution-deployment-profile/v2";
|
|
25
20
|
const PRIOR_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA =
|
|
26
21
|
"chainlesschain.evolution-deployment-profile/v3";
|
|
22
|
+
const LAST_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA =
|
|
23
|
+
"chainlesschain.evolution-deployment-profile/v4";
|
|
27
24
|
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
28
25
|
const MAX_TRUST_ROOT_REVISION_FLOORS = 64;
|
|
29
26
|
|
|
@@ -47,6 +44,8 @@ function normalizeProfile(value) {
|
|
|
47
44
|
PREVIOUS_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
48
45
|
PRIOR_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
49
46
|
].includes(value.schema);
|
|
47
|
+
const beforeDeploymentMode =
|
|
48
|
+
legacy || value.schema === LAST_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA;
|
|
50
49
|
const expected =
|
|
51
50
|
value.schema === LEGACY_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
52
51
|
? ["descriptorPath", "enabled", "schema", "trustRootPath"]
|
|
@@ -58,31 +57,44 @@ function normalizeProfile(value) {
|
|
|
58
57
|
"schema",
|
|
59
58
|
"trustRootPath",
|
|
60
59
|
]
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
60
|
+
: value.schema === PRIOR_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
61
|
+
? [
|
|
62
|
+
"activeTrustRootDigest",
|
|
63
|
+
"descriptorPath",
|
|
64
|
+
"enabled",
|
|
65
|
+
"revisionFloors",
|
|
66
|
+
"schema",
|
|
67
|
+
"trustRootPath",
|
|
68
|
+
]
|
|
69
|
+
: value.schema === LAST_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
70
|
+
? [
|
|
71
|
+
"activeTrustRootDigest",
|
|
72
|
+
"descriptorPath",
|
|
73
|
+
"enabled",
|
|
74
|
+
"revisionFloors",
|
|
75
|
+
"revokedDescriptorRevisions",
|
|
76
|
+
"schema",
|
|
77
|
+
"trustRootPath",
|
|
78
|
+
]
|
|
79
|
+
: [
|
|
80
|
+
"activeTrustRootDigest",
|
|
81
|
+
"deploymentMode",
|
|
82
|
+
"descriptorPath",
|
|
83
|
+
"enabled",
|
|
84
|
+
"revisionFloors",
|
|
85
|
+
"revokedDescriptorRevisions",
|
|
86
|
+
"schema",
|
|
87
|
+
"testPrivateKeyPath",
|
|
88
|
+
"trustRootPath",
|
|
89
|
+
];
|
|
79
90
|
if (JSON.stringify(keys) !== JSON.stringify(expected.sort()))
|
|
80
91
|
throw new TypeError("evolution deployment profile has unexpected fields");
|
|
81
92
|
if (
|
|
82
93
|
value.schema !== EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA &&
|
|
83
94
|
value.schema !== LEGACY_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA &&
|
|
84
95
|
value.schema !== PREVIOUS_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA &&
|
|
85
|
-
value.schema !== PRIOR_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
96
|
+
value.schema !== PRIOR_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA &&
|
|
97
|
+
value.schema !== LAST_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
86
98
|
)
|
|
87
99
|
throw new TypeError("evolution deployment profile schema is invalid");
|
|
88
100
|
if (typeof value.enabled !== "boolean")
|
|
@@ -103,10 +115,30 @@ function normalizeProfile(value) {
|
|
|
103
115
|
const activeTrustRootDigest = legacy
|
|
104
116
|
? null
|
|
105
117
|
: normalizeTrustRootDigest(value.activeTrustRootDigest);
|
|
106
|
-
const revokedDescriptorRevisions =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
const revokedDescriptorRevisions = [
|
|
119
|
+
EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
120
|
+
LAST_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
121
|
+
].includes(value.schema)
|
|
122
|
+
? normalizeRevokedDescriptorRevisions(value.revokedDescriptorRevisions)
|
|
123
|
+
: {};
|
|
124
|
+
const deploymentMode = beforeDeploymentMode
|
|
125
|
+
? "managed"
|
|
126
|
+
: value.deploymentMode;
|
|
127
|
+
if (!["managed", "test"].includes(deploymentMode))
|
|
128
|
+
throw new TypeError("evolution deployment profile mode is invalid");
|
|
129
|
+
const testPrivateKeyPath = beforeDeploymentMode
|
|
130
|
+
? null
|
|
131
|
+
: value.testPrivateKeyPath;
|
|
132
|
+
if (
|
|
133
|
+
(deploymentMode === "test" &&
|
|
134
|
+
(typeof testPrivateKeyPath !== "string" ||
|
|
135
|
+
!isAbsolute(testPrivateKeyPath))) ||
|
|
136
|
+
(deploymentMode === "managed" && testPrivateKeyPath !== null)
|
|
137
|
+
) {
|
|
138
|
+
throw new TypeError(
|
|
139
|
+
"evolution deployment profile test private key path is invalid",
|
|
140
|
+
);
|
|
141
|
+
}
|
|
110
142
|
return Object.freeze({
|
|
111
143
|
schema: EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
112
144
|
enabled: value.enabled,
|
|
@@ -115,6 +147,8 @@ function normalizeProfile(value) {
|
|
|
115
147
|
revisionFloors,
|
|
116
148
|
activeTrustRootDigest,
|
|
117
149
|
revokedDescriptorRevisions,
|
|
150
|
+
deploymentMode,
|
|
151
|
+
testPrivateKeyPath,
|
|
118
152
|
});
|
|
119
153
|
}
|
|
120
154
|
|
|
@@ -156,12 +190,16 @@ function normalizeTrustRootDigest(value) {
|
|
|
156
190
|
|
|
157
191
|
function normalizeRevisionFloors(value) {
|
|
158
192
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
159
|
-
throw new TypeError(
|
|
193
|
+
throw new TypeError(
|
|
194
|
+
"evolution deployment profile revisionFloors is invalid",
|
|
195
|
+
);
|
|
160
196
|
const entries = Object.entries(value).sort(([left], [right]) =>
|
|
161
197
|
left.localeCompare(right),
|
|
162
198
|
);
|
|
163
199
|
if (entries.length > MAX_TRUST_ROOT_REVISION_FLOORS)
|
|
164
|
-
throw new TypeError(
|
|
200
|
+
throw new TypeError(
|
|
201
|
+
"evolution deployment profile revisionFloors is too large",
|
|
202
|
+
);
|
|
165
203
|
for (const [trustRootDigest, revision] of entries) {
|
|
166
204
|
if (!DIGEST.test(trustRootDigest))
|
|
167
205
|
throw new TypeError(
|
|
@@ -228,6 +266,8 @@ function profileFromInput(input) {
|
|
|
228
266
|
revisionFloors: input?.revisionFloors || {},
|
|
229
267
|
activeTrustRootDigest: input?.activeTrustRootDigest,
|
|
230
268
|
revokedDescriptorRevisions: input?.revokedDescriptorRevisions || {},
|
|
269
|
+
deploymentMode: input?.deploymentMode || "managed",
|
|
270
|
+
testPrivateKeyPath: input?.testPrivateKeyPath || null,
|
|
231
271
|
});
|
|
232
272
|
}
|
|
233
273
|
|
|
@@ -251,29 +291,34 @@ export async function readEvolutionDeploymentProfile(options = {}) {
|
|
|
251
291
|
|
|
252
292
|
export async function writeEvolutionDeploymentProfile(input, options = {}) {
|
|
253
293
|
const proposed = profileFromInput(input);
|
|
254
|
-
return updateEvolutionDeploymentProfile(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
Object.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
trustRootDigest
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
294
|
+
return updateEvolutionDeploymentProfile(
|
|
295
|
+
(current) => ({
|
|
296
|
+
...proposed,
|
|
297
|
+
// This lower-level writer cannot authenticate a descriptor, so it never
|
|
298
|
+
// lowers or erases a floor established by configureEvolutionDeployment().
|
|
299
|
+
revisionFloors: Object.fromEntries(
|
|
300
|
+
Object.keys({
|
|
301
|
+
...(current?.revisionFloors || {}),
|
|
302
|
+
...proposed.revisionFloors,
|
|
303
|
+
}).map((trustRootDigest) => [
|
|
304
|
+
trustRootDigest,
|
|
305
|
+
Math.max(
|
|
306
|
+
current?.revisionFloors?.[trustRootDigest] || 0,
|
|
307
|
+
proposed.revisionFloors[trustRootDigest] || 0,
|
|
308
|
+
),
|
|
309
|
+
]),
|
|
310
|
+
),
|
|
311
|
+
activeTrustRootDigest:
|
|
312
|
+
proposed.activeTrustRootDigest || current?.activeTrustRootDigest,
|
|
313
|
+
revokedDescriptorRevisions: {
|
|
314
|
+
...(current?.revokedDescriptorRevisions || {}),
|
|
315
|
+
...(proposed.revokedDescriptorRevisions || {}),
|
|
316
|
+
},
|
|
317
|
+
deploymentMode: proposed.deploymentMode,
|
|
318
|
+
testPrivateKeyPath: proposed.testPrivateKeyPath,
|
|
319
|
+
}),
|
|
320
|
+
options,
|
|
321
|
+
);
|
|
277
322
|
}
|
|
278
323
|
|
|
279
324
|
/**
|
|
@@ -335,9 +380,8 @@ export function assertEvolutionDeploymentDescriptorNotRevoked(
|
|
|
335
380
|
profile,
|
|
336
381
|
descriptor,
|
|
337
382
|
) {
|
|
338
|
-
const revoked =
|
|
339
|
-
descriptor?.trustRootDigest
|
|
340
|
-
];
|
|
383
|
+
const revoked =
|
|
384
|
+
profile?.revokedDescriptorRevisions?.[descriptor?.trustRootDigest];
|
|
341
385
|
if (!revoked?.includes(descriptor?.revision)) return;
|
|
342
386
|
const error = new Error(
|
|
343
387
|
`evolution deployment descriptor revision ${descriptor.revision} is revoked`,
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPrivateKey,
|
|
3
|
+
createPublicKey,
|
|
4
|
+
generateKeyPairSync,
|
|
5
|
+
randomUUID,
|
|
6
|
+
sign as signBytes,
|
|
7
|
+
} from "node:crypto";
|
|
8
|
+
import {
|
|
9
|
+
existsSync,
|
|
10
|
+
mkdirSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
renameSync,
|
|
13
|
+
unlinkSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
} from "node:fs";
|
|
16
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
17
|
+
import { configureEvolutionDeployment } from "./evolution-deployment-config.js";
|
|
18
|
+
import {
|
|
19
|
+
EVOLUTION_DEPLOYMENT_DESCRIPTOR_SCHEMA,
|
|
20
|
+
computeEvolutionDeploymentDigest,
|
|
21
|
+
serializeEvolutionDeploymentDescriptorPayload,
|
|
22
|
+
verifyEvolutionDeployment,
|
|
23
|
+
} from "./evolution-deployment-loader.js";
|
|
24
|
+
import {
|
|
25
|
+
getEvolutionDeploymentProfilePath,
|
|
26
|
+
readEvolutionDeploymentProfile,
|
|
27
|
+
} from "./evolution-deployment-profile.js";
|
|
28
|
+
import {
|
|
29
|
+
EVOLUTION_DEPLOYMENT_ROOT_ROTATION_SCHEMA,
|
|
30
|
+
serializeEvolutionDeploymentRootRotationPayload,
|
|
31
|
+
} from "./evolution-deployment-root-rotation.js";
|
|
32
|
+
import {
|
|
33
|
+
PRIVATE_FILE_MODE,
|
|
34
|
+
ensurePrivateDirectory,
|
|
35
|
+
ensurePrivateFile,
|
|
36
|
+
} from "../secure-fs.js";
|
|
37
|
+
|
|
38
|
+
export const EVOLUTION_TEST_DEPLOYMENT_COMMANDS = Object.freeze([
|
|
39
|
+
"agent",
|
|
40
|
+
"ask",
|
|
41
|
+
"chat",
|
|
42
|
+
"compact",
|
|
43
|
+
"complete",
|
|
44
|
+
"cowork",
|
|
45
|
+
"desktop",
|
|
46
|
+
"evolution",
|
|
47
|
+
"hub",
|
|
48
|
+
"learning",
|
|
49
|
+
"marketplace",
|
|
50
|
+
"orchestrate",
|
|
51
|
+
"serve",
|
|
52
|
+
"skill",
|
|
53
|
+
"stream",
|
|
54
|
+
"ui",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const TEST_PRIVATE_KEY_FILE = "test-signing-private.pem";
|
|
58
|
+
const TEST_TRUST_ROOT_FILE = "test-signing-public.pem";
|
|
59
|
+
const TEST_DESCRIPTOR_FILE = "test-deployment.json";
|
|
60
|
+
const TEST_README_FILE = "README-TEST-ONLY.txt";
|
|
61
|
+
const MAX_MODULE_BYTES = 4 * 1024 * 1024;
|
|
62
|
+
|
|
63
|
+
function writePrivateAtomic(filePath, value) {
|
|
64
|
+
ensurePrivateDirectory(dirname(filePath));
|
|
65
|
+
const temporaryPath = join(
|
|
66
|
+
dirname(filePath),
|
|
67
|
+
`.${filePath.split(/[\\/]/u).at(-1)}.${randomUUID()}.tmp`,
|
|
68
|
+
);
|
|
69
|
+
try {
|
|
70
|
+
writeFileSync(temporaryPath, value, {
|
|
71
|
+
flag: "wx",
|
|
72
|
+
mode: PRIVATE_FILE_MODE,
|
|
73
|
+
});
|
|
74
|
+
ensurePrivateFile(temporaryPath);
|
|
75
|
+
renameSync(temporaryPath, filePath);
|
|
76
|
+
ensurePrivateFile(filePath);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
try {
|
|
79
|
+
unlinkSync(temporaryPath);
|
|
80
|
+
} catch {
|
|
81
|
+
// The temporary file was not created or has already been renamed.
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function assertNoEnvironmentOverride(env) {
|
|
88
|
+
if (
|
|
89
|
+
env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_DESCRIPTOR ||
|
|
90
|
+
env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_TRUST_ROOT
|
|
91
|
+
) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"test deployment setup requires the Evolution deployment environment overrides to be unset",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function normalizeCommands(commands) {
|
|
99
|
+
const value =
|
|
100
|
+
commands === undefined
|
|
101
|
+
? [...EVOLUTION_TEST_DEPLOYMENT_COMMANDS]
|
|
102
|
+
: typeof commands === "string"
|
|
103
|
+
? commands
|
|
104
|
+
.split(",")
|
|
105
|
+
.map((command) => command.trim())
|
|
106
|
+
.filter(Boolean)
|
|
107
|
+
: Array.isArray(commands)
|
|
108
|
+
? [...commands]
|
|
109
|
+
: null;
|
|
110
|
+
if (!value || value.length === 0 || new Set(value).size !== value.length)
|
|
111
|
+
throw new TypeError(
|
|
112
|
+
"test deployment commands must be a non-empty unique list",
|
|
113
|
+
);
|
|
114
|
+
return value.sort();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readRevision(descriptorPath) {
|
|
118
|
+
if (!existsSync(descriptorPath)) return 0;
|
|
119
|
+
try {
|
|
120
|
+
const descriptor = JSON.parse(readFileSync(descriptorPath, "utf8"));
|
|
121
|
+
if (!Number.isSafeInteger(descriptor.revision) || descriptor.revision < 1)
|
|
122
|
+
throw new Error("revision is invalid");
|
|
123
|
+
return descriptor.revision;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
"existing test deployment descriptor cannot be refreshed",
|
|
127
|
+
error instanceof Error ? { cause: error } : undefined,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function assertSigningPair(privateKeyBytes, trustRootBytes) {
|
|
133
|
+
let privateKey;
|
|
134
|
+
let actualPublic;
|
|
135
|
+
let expectedPublic;
|
|
136
|
+
try {
|
|
137
|
+
privateKey = createPrivateKey(privateKeyBytes);
|
|
138
|
+
actualPublic = createPublicKey(privateKey).export({
|
|
139
|
+
type: "spki",
|
|
140
|
+
format: "der",
|
|
141
|
+
});
|
|
142
|
+
expectedPublic = createPublicKey(trustRootBytes).export({
|
|
143
|
+
type: "spki",
|
|
144
|
+
format: "der",
|
|
145
|
+
});
|
|
146
|
+
} catch {
|
|
147
|
+
throw new Error("test deployment signing key pair is invalid");
|
|
148
|
+
}
|
|
149
|
+
if (!Buffer.from(actualPublic).equals(Buffer.from(expectedPublic)))
|
|
150
|
+
throw new Error(
|
|
151
|
+
"test deployment signing key does not match its trust root",
|
|
152
|
+
);
|
|
153
|
+
return privateKey;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function claimFreshTestRoot(profilePath) {
|
|
157
|
+
const parent = dirname(profilePath);
|
|
158
|
+
const preferred = join(parent, "test-deployment");
|
|
159
|
+
ensurePrivateDirectory(parent);
|
|
160
|
+
for (const candidate of [
|
|
161
|
+
preferred,
|
|
162
|
+
join(parent, `test-deployment-${randomUUID()}`),
|
|
163
|
+
]) {
|
|
164
|
+
try {
|
|
165
|
+
mkdirSync(candidate, { mode: 0o700 });
|
|
166
|
+
ensurePrivateDirectory(candidate);
|
|
167
|
+
return candidate;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error?.code !== "EEXIST") throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
throw new Error("could not allocate a private test deployment directory");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function testReadme({ modulePath, descriptorPath, trustRootPath }) {
|
|
176
|
+
return `ChainlessChain Skill evolution development environment / 仅限测试开发
|
|
177
|
+
|
|
178
|
+
This directory contains an automatically generated Ed25519 TEST key. It is not
|
|
179
|
+
a production certificate, HSM/KMS identity, reviewer identity, or production
|
|
180
|
+
approval. Automatic active promotion remains HOLD.
|
|
181
|
+
|
|
182
|
+
本目录包含自动生成的 Ed25519 测试密钥,只用于正式部署前联调。它不属于生产证书、
|
|
183
|
+
HSM/KMS 身份或审核身份,也不会解除人工审核和 automatic active promotion HOLD。
|
|
184
|
+
|
|
185
|
+
Deployment host: ${modulePath}
|
|
186
|
+
Signed descriptor: ${descriptorPath}
|
|
187
|
+
Test trust root: ${trustRootPath}
|
|
188
|
+
|
|
189
|
+
Re-run "cc evolution deployment init-test --module <absolute-path>" after the
|
|
190
|
+
host module changes. When the managed descriptor and trust root are available,
|
|
191
|
+
run "cc evolution deployment replace-test --descriptor <path> --trust-root <path>".
|
|
192
|
+
|
|
193
|
+
部署宿主模块变更后请重新执行 init-test 刷新摘要和签名。正式 descriptor 与 trust
|
|
194
|
+
root 到位后执行 replace-test;CLI 会用测试根签发一次根轮换证明并切换到正式配置。
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Generate or refresh a local-only Ed25519 deployment wrapper for an existing
|
|
200
|
+
* development host. The ordinary descriptor verifier and profile writer remain
|
|
201
|
+
* the only activation path; this helper never bypasses deployment admission.
|
|
202
|
+
*/
|
|
203
|
+
export async function initializeEvolutionTestDeployment(
|
|
204
|
+
{ modulePath, commands, enabled = true },
|
|
205
|
+
options = {},
|
|
206
|
+
) {
|
|
207
|
+
const env = options.env || process.env;
|
|
208
|
+
const resolveRealPath =
|
|
209
|
+
options.resolveRealPath ||
|
|
210
|
+
(async (value) => {
|
|
211
|
+
const { realpath } = await import("node:fs/promises");
|
|
212
|
+
return realpath(value);
|
|
213
|
+
});
|
|
214
|
+
assertNoEnvironmentOverride(env);
|
|
215
|
+
if (typeof modulePath !== "string" || !modulePath.trim())
|
|
216
|
+
throw new TypeError(
|
|
217
|
+
"an absolute test deployment host module path is required",
|
|
218
|
+
);
|
|
219
|
+
const requestedModulePath = modulePath.trim();
|
|
220
|
+
if (!isAbsolute(requestedModulePath))
|
|
221
|
+
throw new TypeError("test deployment host module path must be absolute");
|
|
222
|
+
const realModulePath = await resolveRealPath(requestedModulePath);
|
|
223
|
+
const moduleBytes = readFileSync(realModulePath);
|
|
224
|
+
if (moduleBytes.byteLength === 0 || moduleBytes.byteLength > MAX_MODULE_BYTES)
|
|
225
|
+
throw new Error("test deployment host module size is invalid");
|
|
226
|
+
|
|
227
|
+
const saved = await readEvolutionDeploymentProfile(options);
|
|
228
|
+
if (saved.error) throw new Error(saved.error);
|
|
229
|
+
if (saved.profile && saved.profile.deploymentMode !== "test") {
|
|
230
|
+
const error = new Error(
|
|
231
|
+
"a managed Evolution deployment profile already exists; test setup will not replace it",
|
|
232
|
+
);
|
|
233
|
+
error.code = "EVOLUTION_TEST_DEPLOYMENT_MANAGED_PROFILE_EXISTS";
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const requestedTestRoot = saved.profile
|
|
238
|
+
? dirname(saved.profile.descriptorPath)
|
|
239
|
+
: claimFreshTestRoot(
|
|
240
|
+
options.filePath || getEvolutionDeploymentProfilePath(options),
|
|
241
|
+
);
|
|
242
|
+
ensurePrivateDirectory(requestedTestRoot);
|
|
243
|
+
// macOS exposes /var through the /private/var real path. Persist and compare
|
|
244
|
+
// the physical path so a refresh does not mistake those aliases for two
|
|
245
|
+
// different credential directories.
|
|
246
|
+
const testRoot = await resolveRealPath(requestedTestRoot);
|
|
247
|
+
const privateKeyPath = join(testRoot, TEST_PRIVATE_KEY_FILE);
|
|
248
|
+
const trustRootPath = join(testRoot, TEST_TRUST_ROOT_FILE);
|
|
249
|
+
const descriptorPath = join(testRoot, TEST_DESCRIPTOR_FILE);
|
|
250
|
+
let privateKeyBytes;
|
|
251
|
+
let trustRootBytes;
|
|
252
|
+
let generated = false;
|
|
253
|
+
if (saved.profile) {
|
|
254
|
+
const [savedDescriptorPath, savedTrustRootPath, savedPrivateKeyPath] =
|
|
255
|
+
await Promise.all([
|
|
256
|
+
resolveRealPath(saved.profile.descriptorPath),
|
|
257
|
+
resolveRealPath(saved.profile.trustRootPath),
|
|
258
|
+
resolveRealPath(saved.profile.testPrivateKeyPath),
|
|
259
|
+
]);
|
|
260
|
+
if (
|
|
261
|
+
resolve(savedDescriptorPath) !== resolve(descriptorPath) ||
|
|
262
|
+
resolve(savedTrustRootPath) !== resolve(trustRootPath) ||
|
|
263
|
+
resolve(savedPrivateKeyPath) !== resolve(privateKeyPath)
|
|
264
|
+
) {
|
|
265
|
+
throw new Error("saved test deployment paths are inconsistent");
|
|
266
|
+
}
|
|
267
|
+
ensurePrivateFile(privateKeyPath);
|
|
268
|
+
ensurePrivateFile(trustRootPath);
|
|
269
|
+
privateKeyBytes = readFileSync(privateKeyPath);
|
|
270
|
+
trustRootBytes = readFileSync(trustRootPath);
|
|
271
|
+
} else {
|
|
272
|
+
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
273
|
+
privateKeyBytes = privateKey.export({ type: "pkcs8", format: "pem" });
|
|
274
|
+
trustRootBytes = publicKey.export({ type: "spki", format: "pem" });
|
|
275
|
+
writePrivateAtomic(privateKeyPath, privateKeyBytes);
|
|
276
|
+
writePrivateAtomic(trustRootPath, trustRootBytes);
|
|
277
|
+
generated = true;
|
|
278
|
+
}
|
|
279
|
+
const privateKey = assertSigningPair(privateKeyBytes, trustRootBytes);
|
|
280
|
+
const descriptor = {
|
|
281
|
+
schema: EVOLUTION_DEPLOYMENT_DESCRIPTOR_SCHEMA,
|
|
282
|
+
revision: readRevision(descriptorPath) + 1,
|
|
283
|
+
modulePath: realModulePath,
|
|
284
|
+
moduleDigest: computeEvolutionDeploymentDigest(moduleBytes),
|
|
285
|
+
trustRootDigest: computeEvolutionDeploymentDigest(trustRootBytes),
|
|
286
|
+
commands: normalizeCommands(commands),
|
|
287
|
+
};
|
|
288
|
+
descriptor.signature = signBytes(
|
|
289
|
+
null,
|
|
290
|
+
Buffer.from(serializeEvolutionDeploymentDescriptorPayload(descriptor)),
|
|
291
|
+
privateKey,
|
|
292
|
+
).toString("base64");
|
|
293
|
+
writePrivateAtomic(
|
|
294
|
+
descriptorPath,
|
|
295
|
+
`${JSON.stringify(descriptor, null, 2)}\n`,
|
|
296
|
+
);
|
|
297
|
+
writePrivateAtomic(
|
|
298
|
+
join(testRoot, TEST_README_FILE),
|
|
299
|
+
testReadme({ modulePath: realModulePath, descriptorPath, trustRootPath }),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const status = await configureEvolutionDeployment(
|
|
303
|
+
{
|
|
304
|
+
descriptorPath,
|
|
305
|
+
trustRootPath,
|
|
306
|
+
enabled,
|
|
307
|
+
deploymentMode: "test",
|
|
308
|
+
testPrivateKeyPath: privateKeyPath,
|
|
309
|
+
},
|
|
310
|
+
options,
|
|
311
|
+
);
|
|
312
|
+
return Object.freeze({
|
|
313
|
+
...status,
|
|
314
|
+
generated,
|
|
315
|
+
testRootPath: testRoot,
|
|
316
|
+
warning:
|
|
317
|
+
"TEST ONLY: local development credentials; automatic active promotion remains HOLD",
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Replace an active generated test root with an operator-signed deployment. */
|
|
322
|
+
export async function replaceEvolutionTestDeployment(
|
|
323
|
+
{ descriptorPath, trustRootPath },
|
|
324
|
+
options = {},
|
|
325
|
+
) {
|
|
326
|
+
const env = options.env || process.env;
|
|
327
|
+
assertNoEnvironmentOverride(env);
|
|
328
|
+
const saved = await readEvolutionDeploymentProfile(options);
|
|
329
|
+
if (saved.error) throw new Error(saved.error);
|
|
330
|
+
if (saved.profile?.deploymentMode !== "test") {
|
|
331
|
+
const error = new Error(
|
|
332
|
+
"the saved Evolution deployment is not a generated test deployment",
|
|
333
|
+
);
|
|
334
|
+
error.code = "EVOLUTION_TEST_DEPLOYMENT_NOT_ACTIVE";
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
const destination = await verifyEvolutionDeployment(
|
|
338
|
+
{ descriptorPath, trustRootPath },
|
|
339
|
+
options,
|
|
340
|
+
);
|
|
341
|
+
ensurePrivateFile(saved.profile.testPrivateKeyPath);
|
|
342
|
+
ensurePrivateFile(saved.profile.trustRootPath);
|
|
343
|
+
const privateKeyBytes = readFileSync(saved.profile.testPrivateKeyPath);
|
|
344
|
+
const fromTrustRootBytes = readFileSync(saved.profile.trustRootPath);
|
|
345
|
+
const privateKey = assertSigningPair(privateKeyBytes, fromTrustRootBytes);
|
|
346
|
+
if (
|
|
347
|
+
computeEvolutionDeploymentDigest(fromTrustRootBytes) !==
|
|
348
|
+
saved.profile.activeTrustRootDigest
|
|
349
|
+
) {
|
|
350
|
+
throw new Error(
|
|
351
|
+
"generated test trust root does not match the active profile",
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
const rotation = {
|
|
355
|
+
schema: EVOLUTION_DEPLOYMENT_ROOT_ROTATION_SCHEMA,
|
|
356
|
+
fromTrustRootDigest: saved.profile.activeTrustRootDigest,
|
|
357
|
+
toTrustRootDigest: destination.descriptor.trustRootDigest,
|
|
358
|
+
minimumDescriptorRevision: destination.descriptor.revision,
|
|
359
|
+
};
|
|
360
|
+
rotation.signature = signBytes(
|
|
361
|
+
null,
|
|
362
|
+
Buffer.from(serializeEvolutionDeploymentRootRotationPayload(rotation)),
|
|
363
|
+
privateKey,
|
|
364
|
+
).toString("base64");
|
|
365
|
+
const rotationPath = join(
|
|
366
|
+
dirname(saved.profile.descriptorPath),
|
|
367
|
+
"managed-root-rotation.json",
|
|
368
|
+
);
|
|
369
|
+
writePrivateAtomic(rotationPath, `${JSON.stringify(rotation, null, 2)}\n`);
|
|
370
|
+
const status = await configureEvolutionDeployment(
|
|
371
|
+
{
|
|
372
|
+
descriptorPath,
|
|
373
|
+
trustRootPath,
|
|
374
|
+
rootRotationPath: rotationPath,
|
|
375
|
+
enabled: true,
|
|
376
|
+
deploymentMode: "managed",
|
|
377
|
+
testPrivateKeyPath: null,
|
|
378
|
+
},
|
|
379
|
+
options,
|
|
380
|
+
);
|
|
381
|
+
return Object.freeze({
|
|
382
|
+
...status,
|
|
383
|
+
replacedTestDeployment: true,
|
|
384
|
+
rootRotationPath: rotationPath,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The git tool accepts argv text, not a shell program. Diagnose shell syntax
|
|
2
|
+
// before spawning git while preserving literal operators in quoted arguments.
|
|
3
|
+
export function gitToolInputError(command) {
|
|
4
|
+
let quote = null;
|
|
5
|
+
for (let i = 0; i < command.length; i++) {
|
|
6
|
+
const char = command[i];
|
|
7
|
+
if (char === "\\" && quote === '"') {
|
|
8
|
+
i++;
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (quote) {
|
|
12
|
+
if (char === quote) quote = null;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (char === "'" || char === '"') {
|
|
16
|
+
quote = char;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (/[|&;<>\r\n`]/.test(char) || (char === "$" && command[i + 1] === "(")) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
code: "CC_GIT_SHELL_SYNTAX",
|
|
23
|
+
error:
|
|
24
|
+
"The git tool accepts Git arguments only; shell operators are not executed.",
|
|
25
|
+
hint: 'Submit one Git command without pipes, redirects or &&. For example: "merge-base --is-ancestor main HEAD" (inspect exitCode), "log -n 5 --oneline", or "tag --list v-npm-* --sort=-v:refname". Do not reroute to a shell or execute a partial command automatically.',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (quote)
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
code: "CC_GIT_UNCLOSED_QUOTE",
|
|
33
|
+
error:
|
|
34
|
+
"Unclosed quote in Git arguments. Close the quoted argument and retry.",
|
|
35
|
+
};
|
|
36
|
+
return null;
|
|
37
|
+
}
|