chainlesschain 0.166.44 → 0.166.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-HiHMDVK3.js → AIOps-CrMhYNeH.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-H2RKuS8V.js → ActionButton-DLY5blZ3.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CBPFBB61.js → Analytics-C0NhKQB0.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Da-3Laze.js → AppLayout-DLyepgMq.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-DiRQ9yzD.js → Artifacts-Depk01Sa.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-C9LtWEBh.js → Audit-jKe6NQ3J.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-4rpi7fVg.js → BackgroundAgents-CG4GKT9s.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B8VQmHys.js → Backup-DYWCkzwM.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-iCVpF2DK.js → BaseInput-xm5vodmJ.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BrQaXBwC.js → Chat-B20_PNd3.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-HIyU-9c0.js → ChatBubbleRenderer-D4d-eh0F.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-CsV__0_R.js → Checkbox-CfgwkV_r.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BvFeCIAE.js → Codegen-DbnoWI44.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CagxV1O8.js → Col-CfzzhgGw.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DAWr4ahG.js → Community-BU7mGM8p.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Ba49Tdc2.js → Compact-DpYim0an.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Brk-MSO4.js → Compliance-BNJcO6ra.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DeKH1v0o.js → Cowork-CecZ-pWj.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-AHB-T-VL.js → Cron-BG_C1fe3.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Zbcrtimh.js → Crosschain-BAeILQLL.js} +1 -1
- package/src/assets/web-panel/assets/{DID-ByFa1MWG.js → DID-CYqOEkXi.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-9jKovyuH.js → Dashboard-BYU8wmL1.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DY8TaXi5.js → Dropdown-DYlJeTTa.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-rcaSveQ8.js → EmailListRenderer-C0M6py3K.js} +1 -1
- package/src/assets/web-panel/assets/EvolutionSettings-_QnjPFZJ.js +1 -0
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-5ao8smvA.js → FamilyGuardDashboard-Du90iDch.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-RTpNXXHT.js → Federation-DkFp_hzB.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DWZvRMWv.js → FormItemContext-BGgtGTfG.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CWi8SGvx.js → GenericCardRenderer-BIDsSCei.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CCLgDIqs.js → Git--d3Te93J.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DUuyKrQl.js → Governance-CZwD0vmO.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-RkFcM2A0.js → Inference-DiDcSFEW.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-C-QTauZr.js → KnowledgeGraph-Bi0hSfzg.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B-7Dz5Ui.js → Logs-C8NHSc0K.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-CgiHBe0A.js → MarkdownRenderer-DfDuADqF.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CK1kSFDt.js → Marketplace-D6kD0bNf.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BcaVwoY6.js → McpTools-mTSamCr5.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-QEI4xRwF.js → Memory-YQBXfLjU.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BqD-R0Ek.js → MobileBridge-D2awuupp.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CwFoXf1M.js → MobileProjects-Bl7lZc_-.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CTZ6qICG.js → Mtc-CBqjLK2Q.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Cn7y2BzO.js → MtcAudit-C4poRInZ.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BCjsdGiN.js → Multisig-C_J0zXYZ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CupTVNtA.js → NLProgramming-Ci43sWOU.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-C8kO3mGt.js → Notes-DN7A-GFZ.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cn-MJ8sQ.js → NotificationSettings-D5jNm71V.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-C9UsPOG8.js +1 -0
- package/src/assets/web-panel/assets/{Organization-G_RjOL7M.js → Organization-BapYMJrP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BvX644ya.js → Overflow-BwxwjhFw.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-yWmLgYgQ.js → P2P-TObXXOu3.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-UwJvMbFO.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DtU8fhcZ.js → Permissions-Damnxv-O.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B17Vyk-x.js → PersonalDataHub-d_7jlBb-.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-BZUsTyhP.js → Pipeline-DA87VjLw.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BswdhlPL.js → Privacy-DzO_UvWn.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-sJxSlY0C.js → ProjectInit-CwqECDhs.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BnbpdOke.js → ProjectSettings-CT5YdIl6.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DUjauf-6.js → Projects-D3KdZPCv.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DhF5I-UR.js → Providers-BjBIS8kk.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-B3PeCAGo.js → QrScannerModal-mMFqiKkI.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Cpaxrabg.js → QuickAsk-BwGi9ZU1.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DqXFF66-.js → Recommend-BY65SR5s.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DtDQ76Ox.js → RemoteSession-BGsVH_XU.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BgLLKwu2.js → Reputation-DpPbxSgI.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BuMLhd8M.js → Row-B5YBYoVz.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BizxUzBA.js → RssFeed-DvM5jaoh.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CYNrJAQY.js → Search-6RX39EPO.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CIC02soH.js → Security-Beal-7cM.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DD3WxXxw.js → Services-D6rjVLe-.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C-d9Mbos.js → Skeleton-Dur7R7vz.js} +1 -1
- package/src/assets/web-panel/assets/Skills-D1QkGwMk.js +1 -0
- package/src/assets/web-panel/assets/{Sla-DgrqUhns.js → Sla-3ObrX8Es.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CDXFwOHX.js → SpeechSettings-DG_KAiH1.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BBio_v8M.js → SyncSettings-BaHq0SJ2.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-BDpAkBAB.js +1 -0
- package/src/assets/web-panel/assets/{Templates-RAPdM85o.js → Templates-tYdoRd-c.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-2jI745PC.js → Tenant-yGS5zIQ1.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-ZCtYBMAe.js → Terminal-B3QbmVFa.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DfI36Yg6.js → TimelineRenderer-B_7Pr5dS.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-NQ1VaO0W.js → Tokens-DPvpDa53.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DwFh5AMe.js → Trigger-DcSAD0GZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Dr0v45uH.js → Trust-BBzEq-uy.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-i7Q1Bdqr.js → UkeySign-Dssav7-5.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CXSkk4gl.js → VideoEditing-CPOZf4yi.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-8_cAjou4.js → Wallet-BzBVRXEq.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-jn_3Gma7.js → WebAuthn-DdSGaCCh.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-BmvodVLl.js → WorkflowEditor-CKjBFoSs.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DGi3bmwL.js → chat-B9aMGLg0.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D8OSTOU0.js → colors-GFCMctSV.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DfNygJU_.js → compact-item-BqSDFAYJ.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-B0lT0dbj.js → createContext-BwUm3Ffp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-LxtRBsCm.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-Xtzg_EDR.js → hasIn-DN1kMJOT.js} +1 -1
- package/src/assets/web-panel/assets/index-36hxBH-o.js +1 -0
- package/src/assets/web-panel/assets/{index-De9jsBiC.js → index-7W1e9Vi4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZKM2nm2.js → index-B4MfxM5W.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFkgqRqx.js → index-B4dI_csl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVOyO-ZV.js → index-B7Yu5wwV.js} +1 -1
- package/src/assets/web-panel/assets/{index-svr-L6NS.js → index-B9Pe-EyB.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cp-yGWti.js → index-BBMW8bQO.js} +1 -1
- package/src/assets/web-panel/assets/{index-DEH5ms1R.js → index-BCnbC2z7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVfE2S0f.js → index-BDL2MwxN.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4-hl9Rw.js → index-BHUxz4dy.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2bgL5vL.js → index-BL_0ndQc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbTx5wo1.js → index-BPGlcDvs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFRkiqtr.js → index-BeycSy7n.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBegBTDb.js → index-BpiR6uzH.js} +1 -1
- package/src/assets/web-panel/assets/{index-4s8GZJ73.js → index-BwssFWI1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBR-_Pkv.js → index-By9GbJhu.js} +1 -1
- package/src/assets/web-panel/assets/index-C0b78zWZ.js +1 -0
- package/src/assets/web-panel/assets/{index-DXNWsfbw.js → index-CEMl-Qte.js} +1 -1
- package/src/assets/web-panel/assets/{index-Byto86PA.js → index-CH2wK5dx.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx_EAQEh.js → index-CRikhGgx.js} +1 -1
- package/src/assets/web-panel/assets/{index-ConAvib0.js → index-Ci0zIpSX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CShl59Um.js → index-Cli0VREv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpsIkWwQ.js → index-CmEHzOsw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhYM_Rzp.js → index-Cwih67e8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJ6nxb18.js → index-D8jcp9Rj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWpRgQvO.js → index-DAK28GY8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhS9-r8Z.js → index-DI90Wo_P.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhhS0xnV.js → index-DO-IKERp.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-B7DDjX.js → index-DaFr7XB1.js} +1 -1
- package/src/assets/web-panel/assets/{index-kdyFlPZw.js → index-DbDhKroq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyVOaMS7.js → index-DnJt9DaX.js} +1 -1
- package/src/assets/web-panel/assets/{index-EV-o-rTH.js → index-DvvJWwT8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bujix_OR.js → index-PJt1Zrx2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLRMkKeD.js → index-PKwZkQDC.js} +1 -1
- package/src/assets/web-panel/assets/{index-hDo9-qDw.js → index-THsr_UUJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0kDqFPU.js → index-bwb4cg_D.js} +1 -1
- package/src/assets/web-panel/assets/{index-CIbYQLD-.js → index-rCTtYCGu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVnowoxR.js → index-tMF_deZb.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3ittRaJ.js → index-y7cfh9rP.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CPsOp_T6.js → initDefaultProps-B6e5CZ5p.js} +1 -1
- package/src/assets/web-panel/assets/{motion-8Jakob3_.js → motion-riMM1KfJ.js} +1 -1
- package/src/assets/web-panel/assets/{move-DAogsEBs.js → move-C1NSDMjc.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CkA9zZB_.js → mtc-parser-Bibuny9B.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DMHNkosQ.js → omit-Cmf6FoRn.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-ByIvo_lk.js → pickAttrs-BSsYBUCY.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CafMUHzz.js → placementArrow-gSB7-GAS.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CTehELUQ.js → responsiveObserve-qaqttvKa.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BQISQ1KV.js → slide-RX2gxl3k.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Drm4jUoK.js → statusUtils-DEJmkhqw.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DGniLRqn.js → styleChecker-CIWmJ78O.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DYz_z9wn.js → useFlexGapSupport-D-UPSQBE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DRAPQMjC.js → useFs-9QFtqADc.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CpgGLb8q.js → usePersonalDataHub-DM6kyift.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-p6925VWJ.js → vnode-C-q6FNSJ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-ClpO8REK.js → zoom-D4WJY2m8.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/agent.js +19 -2
- package/src/commands/ask.js +13 -8
- package/src/commands/chat.js +8 -5
- package/src/commands/cowork.js +18 -9
- package/src/commands/eval.js +100 -45
- package/src/commands/evolution-deployment.js +58 -0
- package/src/commands/hub.js +13 -7
- package/src/commands/setup.js +31 -1
- package/src/data/changelog.json +5 -5
- package/src/gateways/ws/personal-data-hub-protocol.js +12 -5
- package/src/lazy-dispatch.js +42 -6
- package/src/lib/agent-router.js +25 -23
- package/src/lib/chat-intent-service.js +3 -13
- package/src/lib/context-memory-kernel/provider-context.js +19 -3
- package/src/lib/cowork-adapter.js +9 -4
- package/src/lib/doctor-checkup.js +42 -0
- package/src/lib/eval/evidence.js +203 -0
- package/src/lib/eval/runner.js +16 -2
- package/src/lib/eval/trend.js +4 -3
- package/src/lib/evolution/evolution-deployment-config.js +217 -10
- package/src/lib/evolution/evolution-deployment-descriptor-revocations.js +131 -0
- package/src/lib/evolution/evolution-deployment-loader.js +23 -2
- package/src/lib/evolution/evolution-deployment-profile.js +281 -37
- package/src/lib/evolution/evolution-deployment-root-rotation.js +154 -0
- package/src/lib/evolution/governed-hub-llm.js +18 -12
- package/src/lib/evolution/governed-hub-skill.js +4 -17
- package/src/lib/evolution/governed-model-turn.js +7 -8
- package/src/lib/orchestrator.js +14 -8
- package/src/lib/personal-data-hub-wiring.js +7 -1
- package/src/lib/sub-agent-context.js +5 -1
- package/src/repl/agent-repl.js +1 -1
- package/src/repl/chat-repl.js +8 -4
- package/src/runtime/agent-core.js +35 -9
- package/src/runtime/headless-runner.js +16 -0
- package/src/assets/web-panel/assets/EvolutionSettings-B3dcSq1H.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BX4PrVfN.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-yYL7-rno.js +0 -7
- package/src/assets/web-panel/assets/Skills-BBkg1Eqr.js +0 -1
- package/src/assets/web-panel/assets/Tasks-QQWSk-tc.js +0 -1
- package/src/assets/web-panel/assets/devWarning-BUooUkHH.js +0 -1
- package/src/assets/web-panel/assets/index-DVu4QCII.js +0 -1
- package/src/assets/web-panel/assets/index-XYnz-yJ_.js +0 -1
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { readFile
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
readFileSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
unlinkSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
3
9
|
import { dirname, isAbsolute, join } from "node:path";
|
|
4
10
|
import { resolveConfigDataRoot } from "../paths.js";
|
|
5
11
|
import {
|
|
@@ -7,9 +13,19 @@ import {
|
|
|
7
13
|
ensurePrivateDirectory,
|
|
8
14
|
ensurePrivateFile,
|
|
9
15
|
} from "../secure-fs.js";
|
|
16
|
+
import { withFileLock } from "../with-file-lock.js";
|
|
10
17
|
|
|
11
18
|
export const EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA =
|
|
19
|
+
"chainlesschain.evolution-deployment-profile/v4";
|
|
20
|
+
|
|
21
|
+
const LEGACY_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA =
|
|
12
22
|
"chainlesschain.evolution-deployment-profile/v1";
|
|
23
|
+
const PREVIOUS_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA =
|
|
24
|
+
"chainlesschain.evolution-deployment-profile/v2";
|
|
25
|
+
const PRIOR_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA =
|
|
26
|
+
"chainlesschain.evolution-deployment-profile/v3";
|
|
27
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
28
|
+
const MAX_TRUST_ROOT_REVISION_FLOORS = 64;
|
|
13
29
|
|
|
14
30
|
export function getEvolutionDeploymentProfilePath({
|
|
15
31
|
env = process.env,
|
|
@@ -26,10 +42,48 @@ function normalizeProfile(value) {
|
|
|
26
42
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
27
43
|
throw new TypeError("evolution deployment profile must be an object");
|
|
28
44
|
const keys = Object.keys(value).sort();
|
|
29
|
-
const
|
|
45
|
+
const legacy = [
|
|
46
|
+
LEGACY_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
47
|
+
PREVIOUS_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
48
|
+
PRIOR_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
49
|
+
].includes(value.schema);
|
|
50
|
+
const expected =
|
|
51
|
+
value.schema === LEGACY_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
52
|
+
? ["descriptorPath", "enabled", "schema", "trustRootPath"]
|
|
53
|
+
: value.schema === PREVIOUS_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
54
|
+
? [
|
|
55
|
+
"descriptorPath",
|
|
56
|
+
"enabled",
|
|
57
|
+
"revisionFloors",
|
|
58
|
+
"schema",
|
|
59
|
+
"trustRootPath",
|
|
60
|
+
]
|
|
61
|
+
: value.schema === PRIOR_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
62
|
+
? [
|
|
63
|
+
"activeTrustRootDigest",
|
|
64
|
+
"descriptorPath",
|
|
65
|
+
"enabled",
|
|
66
|
+
"revisionFloors",
|
|
67
|
+
"schema",
|
|
68
|
+
"trustRootPath",
|
|
69
|
+
]
|
|
70
|
+
: [
|
|
71
|
+
"activeTrustRootDigest",
|
|
72
|
+
"descriptorPath",
|
|
73
|
+
"enabled",
|
|
74
|
+
"revisionFloors",
|
|
75
|
+
"revokedDescriptorRevisions",
|
|
76
|
+
"schema",
|
|
77
|
+
"trustRootPath",
|
|
78
|
+
];
|
|
30
79
|
if (JSON.stringify(keys) !== JSON.stringify(expected.sort()))
|
|
31
80
|
throw new TypeError("evolution deployment profile has unexpected fields");
|
|
32
|
-
if (
|
|
81
|
+
if (
|
|
82
|
+
value.schema !== EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA &&
|
|
83
|
+
value.schema !== LEGACY_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA &&
|
|
84
|
+
value.schema !== PREVIOUS_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA &&
|
|
85
|
+
value.schema !== PRIOR_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
86
|
+
)
|
|
33
87
|
throw new TypeError("evolution deployment profile schema is invalid");
|
|
34
88
|
if (typeof value.enabled !== "boolean")
|
|
35
89
|
throw new TypeError("evolution deployment profile enabled is invalid");
|
|
@@ -42,73 +96,263 @@ function normalizeProfile(value) {
|
|
|
42
96
|
`evolution deployment profile ${name} must be absolute`,
|
|
43
97
|
);
|
|
44
98
|
}
|
|
99
|
+
const revisionFloors =
|
|
100
|
+
value.schema === LEGACY_EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
101
|
+
? {}
|
|
102
|
+
: normalizeRevisionFloors(value.revisionFloors);
|
|
103
|
+
const activeTrustRootDigest = legacy
|
|
104
|
+
? null
|
|
105
|
+
: normalizeTrustRootDigest(value.activeTrustRootDigest);
|
|
106
|
+
const revokedDescriptorRevisions =
|
|
107
|
+
value.schema === EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA
|
|
108
|
+
? normalizeRevokedDescriptorRevisions(value.revokedDescriptorRevisions)
|
|
109
|
+
: {};
|
|
45
110
|
return Object.freeze({
|
|
46
|
-
schema:
|
|
111
|
+
schema: EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
47
112
|
enabled: value.enabled,
|
|
48
113
|
descriptorPath: value.descriptorPath,
|
|
49
114
|
trustRootPath: value.trustRootPath,
|
|
115
|
+
revisionFloors,
|
|
116
|
+
activeTrustRootDigest,
|
|
117
|
+
revokedDescriptorRevisions,
|
|
50
118
|
});
|
|
51
119
|
}
|
|
52
120
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const profile = normalizeProfile(
|
|
58
|
-
JSON.parse(await readFile(filePath, "utf8")),
|
|
121
|
+
function normalizeRevokedDescriptorRevisions(value) {
|
|
122
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
123
|
+
throw new TypeError(
|
|
124
|
+
"evolution deployment profile revokedDescriptorRevisions is invalid",
|
|
59
125
|
);
|
|
60
|
-
|
|
126
|
+
const result = {};
|
|
127
|
+
for (const [trustRootDigest, revisions] of Object.entries(value)) {
|
|
128
|
+
if (!DIGEST.test(trustRootDigest) || !Array.isArray(revisions))
|
|
129
|
+
throw new TypeError(
|
|
130
|
+
"evolution deployment profile revokedDescriptorRevisions is invalid",
|
|
131
|
+
);
|
|
132
|
+
const normalized = [...revisions].sort((left, right) => left - right);
|
|
133
|
+
if (
|
|
134
|
+
normalized.length > 1024 ||
|
|
135
|
+
normalized.some(
|
|
136
|
+
(revision) => !Number.isSafeInteger(revision) || revision < 1,
|
|
137
|
+
) ||
|
|
138
|
+
new Set(normalized).size !== normalized.length
|
|
139
|
+
) {
|
|
140
|
+
throw new TypeError(
|
|
141
|
+
"evolution deployment profile revokedDescriptorRevisions is invalid",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
result[trustRootDigest] = Object.freeze(normalized);
|
|
145
|
+
}
|
|
146
|
+
return Object.freeze(result);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function normalizeTrustRootDigest(value) {
|
|
150
|
+
if (!DIGEST.test(value || ""))
|
|
151
|
+
throw new TypeError(
|
|
152
|
+
"evolution deployment profile activeTrustRootDigest is invalid",
|
|
153
|
+
);
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizeRevisionFloors(value) {
|
|
158
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
159
|
+
throw new TypeError("evolution deployment profile revisionFloors is invalid");
|
|
160
|
+
const entries = Object.entries(value).sort(([left], [right]) =>
|
|
161
|
+
left.localeCompare(right),
|
|
162
|
+
);
|
|
163
|
+
if (entries.length > MAX_TRUST_ROOT_REVISION_FLOORS)
|
|
164
|
+
throw new TypeError("evolution deployment profile revisionFloors is too large");
|
|
165
|
+
for (const [trustRootDigest, revision] of entries) {
|
|
166
|
+
if (!DIGEST.test(trustRootDigest))
|
|
167
|
+
throw new TypeError(
|
|
168
|
+
"evolution deployment profile revisionFloors trust root is invalid",
|
|
169
|
+
);
|
|
170
|
+
if (!Number.isSafeInteger(revision) || revision < 1)
|
|
171
|
+
throw new TypeError(
|
|
172
|
+
"evolution deployment profile revisionFloors revision is invalid",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return Object.freeze(Object.fromEntries(entries));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function profilePath(options = {}) {
|
|
179
|
+
return options.filePath || getEvolutionDeploymentProfilePath(options);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function readProfileSync(filePath) {
|
|
183
|
+
try {
|
|
184
|
+
return {
|
|
185
|
+
profile: normalizeProfile(JSON.parse(readFileSync(filePath, "utf8"))),
|
|
186
|
+
error: null,
|
|
187
|
+
};
|
|
61
188
|
} catch (error) {
|
|
62
|
-
if (error?.code === "ENOENT")
|
|
63
|
-
|
|
64
|
-
return Object.freeze({
|
|
65
|
-
filePath,
|
|
189
|
+
if (error?.code === "ENOENT") return { profile: null, error: null };
|
|
190
|
+
return {
|
|
66
191
|
profile: null,
|
|
67
192
|
error: error instanceof Error ? error.message : String(error),
|
|
68
|
-
}
|
|
193
|
+
};
|
|
69
194
|
}
|
|
70
195
|
}
|
|
71
196
|
|
|
72
|
-
|
|
73
|
-
const profile = normalizeProfile({
|
|
74
|
-
schema: EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
75
|
-
enabled: input?.enabled === true,
|
|
76
|
-
descriptorPath: input?.descriptorPath,
|
|
77
|
-
trustRootPath: input?.trustRootPath,
|
|
78
|
-
});
|
|
79
|
-
const filePath =
|
|
80
|
-
options.filePath || getEvolutionDeploymentProfilePath(options);
|
|
197
|
+
function writeProfileSync(profile, filePath) {
|
|
81
198
|
ensurePrivateDirectory(dirname(filePath));
|
|
82
199
|
const temporaryPath = join(
|
|
83
200
|
dirname(filePath),
|
|
84
201
|
`.deployment-profile.${randomUUID()}.tmp`,
|
|
85
202
|
);
|
|
86
203
|
try {
|
|
87
|
-
|
|
204
|
+
writeFileSync(temporaryPath, `${JSON.stringify(profile, null, 2)}\n`, {
|
|
88
205
|
encoding: "utf8",
|
|
89
206
|
mode: PRIVATE_FILE_MODE,
|
|
90
207
|
flag: "wx",
|
|
91
208
|
});
|
|
92
209
|
ensurePrivateFile(temporaryPath);
|
|
93
|
-
|
|
210
|
+
renameSync(temporaryPath, filePath);
|
|
94
211
|
ensurePrivateFile(filePath);
|
|
95
212
|
} catch (error) {
|
|
96
|
-
|
|
213
|
+
try {
|
|
214
|
+
unlinkSync(temporaryPath);
|
|
215
|
+
} catch {
|
|
216
|
+
// The temporary path either was not created or has already been renamed.
|
|
217
|
+
}
|
|
97
218
|
throw error;
|
|
98
219
|
}
|
|
99
|
-
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function profileFromInput(input) {
|
|
223
|
+
return normalizeProfile({
|
|
224
|
+
schema: EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA,
|
|
225
|
+
enabled: input?.enabled === true,
|
|
226
|
+
descriptorPath: input?.descriptorPath,
|
|
227
|
+
trustRootPath: input?.trustRootPath,
|
|
228
|
+
revisionFloors: input?.revisionFloors || {},
|
|
229
|
+
activeTrustRootDigest: input?.activeTrustRootDigest,
|
|
230
|
+
revokedDescriptorRevisions: input?.revokedDescriptorRevisions || {},
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function readEvolutionDeploymentProfile(options = {}) {
|
|
235
|
+
const filePath = profilePath(options);
|
|
236
|
+
try {
|
|
237
|
+
const profile = normalizeProfile(
|
|
238
|
+
JSON.parse(await readFile(filePath, "utf8")),
|
|
239
|
+
);
|
|
240
|
+
return Object.freeze({ filePath, profile, error: null });
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (error?.code === "ENOENT")
|
|
243
|
+
return Object.freeze({ filePath, profile: null, error: null });
|
|
244
|
+
return Object.freeze({
|
|
245
|
+
filePath,
|
|
246
|
+
profile: null,
|
|
247
|
+
error: error instanceof Error ? error.message : String(error),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function writeEvolutionDeploymentProfile(input, options = {}) {
|
|
253
|
+
const proposed = profileFromInput(input);
|
|
254
|
+
return updateEvolutionDeploymentProfile((current) => ({
|
|
255
|
+
...proposed,
|
|
256
|
+
// This lower-level writer cannot authenticate a descriptor, so it never
|
|
257
|
+
// lowers or erases a floor established by configureEvolutionDeployment().
|
|
258
|
+
revisionFloors: Object.fromEntries(
|
|
259
|
+
Object.keys({
|
|
260
|
+
...(current?.revisionFloors || {}),
|
|
261
|
+
...proposed.revisionFloors,
|
|
262
|
+
}).map((trustRootDigest) => [
|
|
263
|
+
trustRootDigest,
|
|
264
|
+
Math.max(
|
|
265
|
+
current?.revisionFloors?.[trustRootDigest] || 0,
|
|
266
|
+
proposed.revisionFloors[trustRootDigest] || 0,
|
|
267
|
+
),
|
|
268
|
+
]),
|
|
269
|
+
),
|
|
270
|
+
activeTrustRootDigest:
|
|
271
|
+
proposed.activeTrustRootDigest || current?.activeTrustRootDigest,
|
|
272
|
+
revokedDescriptorRevisions: {
|
|
273
|
+
...(current?.revokedDescriptorRevisions || {}),
|
|
274
|
+
...(proposed.revokedDescriptorRevisions || {}),
|
|
275
|
+
},
|
|
276
|
+
}), options);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Atomically read, validate and replace the owner-only profile. The callback is
|
|
281
|
+
* synchronous by design: `withFileLock` is a cross-process lock and releasing
|
|
282
|
+
* it while an async callback is pending would reintroduce lost high-water
|
|
283
|
+
* updates.
|
|
284
|
+
*/
|
|
285
|
+
export function updateEvolutionDeploymentProfile(update, options = {}) {
|
|
286
|
+
if (typeof update !== "function")
|
|
287
|
+
throw new TypeError(
|
|
288
|
+
"evolution deployment profile update must be a function",
|
|
289
|
+
);
|
|
290
|
+
const filePath = profilePath(options);
|
|
291
|
+
ensurePrivateDirectory(dirname(filePath));
|
|
292
|
+
return withFileLock(
|
|
293
|
+
filePath,
|
|
294
|
+
() => {
|
|
295
|
+
const current = readProfileSync(filePath);
|
|
296
|
+
if (current.error) throw new Error(current.error);
|
|
297
|
+
const profile = profileFromInput(update(current.profile));
|
|
298
|
+
writeProfileSync(profile, filePath);
|
|
299
|
+
return Object.freeze({ filePath, profile });
|
|
300
|
+
},
|
|
301
|
+
{ failIfUnavailable: true },
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function assertEvolutionDeploymentRevisionFloor(profile, descriptor) {
|
|
306
|
+
const floor = profile?.revisionFloors?.[descriptor?.trustRootDigest];
|
|
307
|
+
if (floor === undefined) return null;
|
|
308
|
+
if (
|
|
309
|
+
!Number.isSafeInteger(descriptor?.revision) ||
|
|
310
|
+
descriptor.revision < floor
|
|
311
|
+
) {
|
|
312
|
+
const error = new Error(
|
|
313
|
+
`evolution deployment descriptor revision rollback rejected: received ${descriptor?.revision}, floor ${floor}`,
|
|
314
|
+
);
|
|
315
|
+
error.code = "EVOLUTION_DEPLOYMENT_REVISION_ROLLBACK";
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
return floor;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function assertEvolutionDeploymentActiveTrustRoot(profile, descriptor) {
|
|
322
|
+
const activeTrustRootDigest = profile?.activeTrustRootDigest;
|
|
323
|
+
if (!activeTrustRootDigest) return null;
|
|
324
|
+
if (descriptor?.trustRootDigest !== activeTrustRootDigest) {
|
|
325
|
+
const error = new Error(
|
|
326
|
+
"evolution deployment descriptor trust root does not match the active profile root",
|
|
327
|
+
);
|
|
328
|
+
error.code = "EVOLUTION_DEPLOYMENT_TRUST_ROOT_MISMATCH";
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
return activeTrustRootDigest;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function assertEvolutionDeploymentDescriptorNotRevoked(
|
|
335
|
+
profile,
|
|
336
|
+
descriptor,
|
|
337
|
+
) {
|
|
338
|
+
const revoked = profile?.revokedDescriptorRevisions?.[
|
|
339
|
+
descriptor?.trustRootDigest
|
|
340
|
+
];
|
|
341
|
+
if (!revoked?.includes(descriptor?.revision)) return;
|
|
342
|
+
const error = new Error(
|
|
343
|
+
`evolution deployment descriptor revision ${descriptor.revision} is revoked`,
|
|
344
|
+
);
|
|
345
|
+
error.code = "EVOLUTION_DEPLOYMENT_DESCRIPTOR_REVOKED";
|
|
346
|
+
throw error;
|
|
100
347
|
}
|
|
101
348
|
|
|
102
349
|
export async function setEvolutionDeploymentProfileEnabled(
|
|
103
350
|
enabled,
|
|
104
351
|
options = {},
|
|
105
352
|
) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
{ ...current.profile, enabled: enabled === true },
|
|
112
|
-
{ ...options, filePath: current.filePath },
|
|
113
|
-
);
|
|
353
|
+
return updateEvolutionDeploymentProfile((current) => {
|
|
354
|
+
if (!current)
|
|
355
|
+
throw new Error("evolution deployment profile is not configured");
|
|
356
|
+
return { ...current, enabled: enabled === true };
|
|
357
|
+
}, options);
|
|
114
358
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { createHash, createPublicKey, verify } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { isAbsolute } from "node:path";
|
|
4
|
+
|
|
5
|
+
export const EVOLUTION_DEPLOYMENT_ROOT_ROTATION_SCHEMA =
|
|
6
|
+
"chainlesschain.evolution-deployment-root-rotation/v1";
|
|
7
|
+
|
|
8
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
9
|
+
const MAX_ROTATION_BYTES = 64 * 1024;
|
|
10
|
+
|
|
11
|
+
function canonical(value) {
|
|
12
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
13
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
14
|
+
return `{${Object.keys(value)
|
|
15
|
+
.sort()
|
|
16
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
17
|
+
.join(",")}}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function digest(bytes) {
|
|
21
|
+
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeRotation(value) {
|
|
25
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
26
|
+
throw new TypeError("evolution deployment root rotation must be an object");
|
|
27
|
+
const expected = [
|
|
28
|
+
"fromTrustRootDigest",
|
|
29
|
+
"minimumDescriptorRevision",
|
|
30
|
+
"schema",
|
|
31
|
+
"signature",
|
|
32
|
+
"toTrustRootDigest",
|
|
33
|
+
];
|
|
34
|
+
if (canonical(Object.keys(value).sort()) !== canonical(expected))
|
|
35
|
+
throw new TypeError(
|
|
36
|
+
"evolution deployment root rotation has unexpected or missing fields",
|
|
37
|
+
);
|
|
38
|
+
if (value.schema !== EVOLUTION_DEPLOYMENT_ROOT_ROTATION_SCHEMA)
|
|
39
|
+
throw new TypeError("evolution deployment root rotation schema is invalid");
|
|
40
|
+
for (const key of ["fromTrustRootDigest", "toTrustRootDigest"]) {
|
|
41
|
+
if (!DIGEST.test(value[key] || ""))
|
|
42
|
+
throw new TypeError(`evolution deployment root rotation ${key} is invalid`);
|
|
43
|
+
}
|
|
44
|
+
if (value.fromTrustRootDigest === value.toTrustRootDigest)
|
|
45
|
+
throw new TypeError("evolution deployment root rotation must change trust root");
|
|
46
|
+
if (
|
|
47
|
+
!Number.isSafeInteger(value.minimumDescriptorRevision) ||
|
|
48
|
+
value.minimumDescriptorRevision < 1
|
|
49
|
+
) {
|
|
50
|
+
throw new TypeError(
|
|
51
|
+
"evolution deployment root rotation minimumDescriptorRevision is invalid",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (
|
|
55
|
+
typeof value.signature !== "string" ||
|
|
56
|
+
!/^[A-Za-z0-9+/]+={0,2}$/u.test(value.signature) ||
|
|
57
|
+
value.signature.length === 0
|
|
58
|
+
) {
|
|
59
|
+
throw new TypeError("evolution deployment root rotation signature is invalid");
|
|
60
|
+
}
|
|
61
|
+
return Object.freeze({ ...value });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function serializeEvolutionDeploymentRootRotationPayload(rotation) {
|
|
65
|
+
const value = normalizeRotation({ ...rotation, signature: "AA==" });
|
|
66
|
+
return canonical({
|
|
67
|
+
schema: value.schema,
|
|
68
|
+
fromTrustRootDigest: value.fromTrustRootDigest,
|
|
69
|
+
toTrustRootDigest: value.toTrustRootDigest,
|
|
70
|
+
minimumDescriptorRevision: value.minimumDescriptorRevision,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Verify a root transition while the profile lock is held. The old root is
|
|
76
|
+
* authenticated by its exact bytes before it may authorize a new root.
|
|
77
|
+
*/
|
|
78
|
+
export function verifyEvolutionDeploymentRootRotationSync({
|
|
79
|
+
rotationPath,
|
|
80
|
+
fromTrustRootPath,
|
|
81
|
+
expectedFromTrustRootDigest,
|
|
82
|
+
expectedToTrustRootDigest,
|
|
83
|
+
destinationRevision,
|
|
84
|
+
}) {
|
|
85
|
+
if (
|
|
86
|
+
typeof rotationPath !== "string" ||
|
|
87
|
+
!isAbsolute(rotationPath) ||
|
|
88
|
+
typeof fromTrustRootPath !== "string" ||
|
|
89
|
+
!isAbsolute(fromTrustRootPath)
|
|
90
|
+
) {
|
|
91
|
+
throw new Error("evolution deployment root rotation paths must be absolute");
|
|
92
|
+
}
|
|
93
|
+
if (!DIGEST.test(expectedFromTrustRootDigest || ""))
|
|
94
|
+
throw new Error("evolution deployment root rotation source digest is invalid");
|
|
95
|
+
if (!DIGEST.test(expectedToTrustRootDigest || ""))
|
|
96
|
+
throw new Error("evolution deployment root rotation destination digest is invalid");
|
|
97
|
+
if (!Number.isSafeInteger(destinationRevision) || destinationRevision < 1)
|
|
98
|
+
throw new Error("evolution deployment root rotation destination revision is invalid");
|
|
99
|
+
|
|
100
|
+
let rotation;
|
|
101
|
+
let fromTrustRootBytes;
|
|
102
|
+
try {
|
|
103
|
+
const rotationBytes = readFileSync(rotationPath);
|
|
104
|
+
if (
|
|
105
|
+
rotationBytes.byteLength === 0 ||
|
|
106
|
+
rotationBytes.byteLength > MAX_ROTATION_BYTES
|
|
107
|
+
) {
|
|
108
|
+
throw new Error("evolution deployment root rotation size is invalid");
|
|
109
|
+
}
|
|
110
|
+
rotation = normalizeRotation(JSON.parse(rotationBytes.toString("utf8")));
|
|
111
|
+
fromTrustRootBytes = readFileSync(fromTrustRootPath);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
"evolution deployment root rotation could not be read",
|
|
115
|
+
error instanceof Error ? { cause: error } : undefined,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (digest(fromTrustRootBytes) !== expectedFromTrustRootDigest)
|
|
119
|
+
throw new Error(
|
|
120
|
+
"evolution deployment root rotation source trust root changed",
|
|
121
|
+
);
|
|
122
|
+
if (rotation.fromTrustRootDigest !== expectedFromTrustRootDigest)
|
|
123
|
+
throw new Error(
|
|
124
|
+
"evolution deployment root rotation source does not match profile",
|
|
125
|
+
);
|
|
126
|
+
if (rotation.toTrustRootDigest !== expectedToTrustRootDigest)
|
|
127
|
+
throw new Error(
|
|
128
|
+
"evolution deployment root rotation destination does not match descriptor",
|
|
129
|
+
);
|
|
130
|
+
if (destinationRevision < rotation.minimumDescriptorRevision)
|
|
131
|
+
throw new Error(
|
|
132
|
+
"evolution deployment root rotation destination revision is below the authorized minimum",
|
|
133
|
+
);
|
|
134
|
+
let publicKey;
|
|
135
|
+
try {
|
|
136
|
+
publicKey = createPublicKey(fromTrustRootBytes);
|
|
137
|
+
} catch {
|
|
138
|
+
throw new Error("evolution deployment root rotation source trust root is invalid");
|
|
139
|
+
}
|
|
140
|
+
if (
|
|
141
|
+
!verify(
|
|
142
|
+
null,
|
|
143
|
+
Buffer.from(
|
|
144
|
+
serializeEvolutionDeploymentRootRotationPayload(rotation),
|
|
145
|
+
"utf8",
|
|
146
|
+
),
|
|
147
|
+
publicKey,
|
|
148
|
+
Buffer.from(rotation.signature, "base64"),
|
|
149
|
+
)
|
|
150
|
+
) {
|
|
151
|
+
throw new Error("evolution deployment root rotation signature rejected");
|
|
152
|
+
}
|
|
153
|
+
return rotation;
|
|
154
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { prepareGovernedModelTurn } from "./governed-model-turn.js";
|
|
2
3
|
|
|
4
|
+
const { createAuthenticatedEvolutionModelClient } = createRequire(
|
|
5
|
+
import.meta.url,
|
|
6
|
+
)("@chainlesschain/personal-data-hub/model-egress-guard");
|
|
7
|
+
|
|
3
8
|
// A scoped wrapper preserves the Hub's independent local/cloud consent check.
|
|
4
9
|
// Never replace llm on the process-wide cached Hub or its analysis engine.
|
|
5
|
-
export function createGovernedHubLlm(llm, factory) {
|
|
10
|
+
export function createGovernedHubLlm(llm, factory, options = {}) {
|
|
6
11
|
if (
|
|
7
12
|
!llm ||
|
|
8
13
|
typeof llm.chat !== "function" ||
|
|
@@ -14,6 +19,8 @@ export function createGovernedHubLlm(llm, factory) {
|
|
|
14
19
|
}
|
|
15
20
|
if (typeof factory !== "function")
|
|
16
21
|
throw new TypeError("A Hub evolution factory is required");
|
|
22
|
+
if (options.onFailure != null && typeof options.onFailure !== "function")
|
|
23
|
+
throw new TypeError("onFailure must be a function");
|
|
17
24
|
const identity = { name: llm.name, isLocal: llm.isLocal, chat: llm.chat };
|
|
18
25
|
const assertIdentity = () => {
|
|
19
26
|
if (
|
|
@@ -27,30 +34,29 @@ export function createGovernedHubLlm(llm, factory) {
|
|
|
27
34
|
}
|
|
28
35
|
};
|
|
29
36
|
const chat = identity.chat.bind(llm);
|
|
30
|
-
return
|
|
31
|
-
|
|
37
|
+
return createAuthenticatedEvolutionModelClient({
|
|
38
|
+
getName() {
|
|
32
39
|
assertIdentity();
|
|
33
40
|
return identity.name;
|
|
34
41
|
},
|
|
35
|
-
|
|
42
|
+
getIsLocal() {
|
|
36
43
|
assertIdentity();
|
|
37
44
|
return identity.isLocal;
|
|
38
45
|
},
|
|
39
|
-
async
|
|
46
|
+
async prepare(messages, chatOptions = {}) {
|
|
40
47
|
assertIdentity();
|
|
41
|
-
|
|
48
|
+
return await prepareGovernedModelTurn(factory, {
|
|
42
49
|
mode: "hub-analysis",
|
|
43
50
|
messages,
|
|
44
|
-
signal:
|
|
51
|
+
signal: chatOptions.signal,
|
|
45
52
|
});
|
|
53
|
+
},
|
|
54
|
+
async transport(messages, chatOptions = {}) {
|
|
46
55
|
assertIdentity();
|
|
47
|
-
const response = await chat(
|
|
56
|
+
const response = await chat(messages, chatOptions);
|
|
48
57
|
assertIdentity();
|
|
49
|
-
if (!response || typeof response.text !== "string") {
|
|
50
|
-
throw new Error("Hub model response must contain text");
|
|
51
|
-
}
|
|
52
|
-
await turn.complete(response.text);
|
|
53
58
|
return response;
|
|
54
59
|
},
|
|
60
|
+
onFailure: options.onFailure,
|
|
55
61
|
});
|
|
56
62
|
}
|
|
@@ -9,25 +9,12 @@ export async function runGovernedHubSkill(
|
|
|
9
9
|
name,
|
|
10
10
|
options,
|
|
11
11
|
) {
|
|
12
|
-
const model = createGovernedHubLlm(hub.llm, factory);
|
|
13
12
|
let failure;
|
|
14
13
|
let failed = false;
|
|
15
|
-
const llm =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
get isLocal() {
|
|
20
|
-
return model.isLocal;
|
|
21
|
-
},
|
|
22
|
-
async chat(messages, chatOptions) {
|
|
23
|
-
if (failed) throw failure;
|
|
24
|
-
try {
|
|
25
|
-
return await model.chat(messages, chatOptions);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
failure = error;
|
|
28
|
-
failed = true;
|
|
29
|
-
throw error;
|
|
30
|
-
}
|
|
14
|
+
const llm = createGovernedHubLlm(hub.llm, factory, {
|
|
15
|
+
onFailure(error) {
|
|
16
|
+
failure = error;
|
|
17
|
+
failed = true;
|
|
31
18
|
},
|
|
32
19
|
});
|
|
33
20
|
const result = await runSkill({ vault: hub.vault, llm }, name, options);
|
|
@@ -34,14 +34,13 @@ export async function prepareGovernedModelTurn(
|
|
|
34
34
|
{ mode, messages, signal },
|
|
35
35
|
) {
|
|
36
36
|
signal?.throwIfAborted();
|
|
37
|
-
if (factory === null)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
};
|
|
37
|
+
if (factory === null) {
|
|
38
|
+
const error = new Error(
|
|
39
|
+
"Model egress requires an authenticated evolution composition factory",
|
|
40
|
+
);
|
|
41
|
+
error.code = "CC_AGENT_EVOLUTION_INGRESS_FAILED";
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
45
44
|
if (typeof factory !== "function")
|
|
46
45
|
throw new TypeError("Invalid evolution composition factory");
|
|
47
46
|
const runId = `${mode}-${randomUUID()}`;
|
package/src/lib/orchestrator.js
CHANGED
|
@@ -153,7 +153,9 @@ export class Orchestrator extends EventEmitter {
|
|
|
153
153
|
this.notifier =
|
|
154
154
|
options.notifier || NotificationManager.fromEnv(options.notify || {});
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
// The task-scoped evolution composition is created in addTask(), so no
|
|
157
|
+
// provider adapter may be constructed here before its ingress exists.
|
|
158
|
+
this._chat = null;
|
|
157
159
|
this._cronTimer = null;
|
|
158
160
|
|
|
159
161
|
// Forward router events
|
|
@@ -374,13 +376,17 @@ export class Orchestrator extends EventEmitter {
|
|
|
374
376
|
(task.context ? `\nAdditional context:\n${task.context}` : "");
|
|
375
377
|
|
|
376
378
|
try {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
379
|
+
if (evolutionIngress === null) {
|
|
380
|
+
const error = new Error(
|
|
381
|
+
"Orchestrator model egress requires an authenticated evolution ingress",
|
|
382
|
+
);
|
|
383
|
+
error.code = "CC_AGENT_EVOLUTION_INGRESS_FAILED";
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
const chat = createChatFn({
|
|
387
|
+
...this._llmOptions,
|
|
388
|
+
evolutionIngress,
|
|
389
|
+
});
|
|
384
390
|
const raw = await chat(
|
|
385
391
|
[
|
|
386
392
|
{ role: "system", content: systemPrompt },
|