chainlesschain 0.166.43 → 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/README.md +75 -14
- package/package.json +3 -3
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-BIl-Z62M.js → AIOps-CrMhYNeH.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BgPi_Te6.js → ActionButton-DLY5blZ3.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CaNIzlAG.js → Analytics-C0NhKQB0.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-D9GSx2hl.js → AppLayout-DLyepgMq.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Kq7kotzv.js → Artifacts-Depk01Sa.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-e1RTJLco.js → Audit-jKe6NQ3J.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BfyNT0BW.js → BackgroundAgents-CG4GKT9s.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BEAq7i6S.js → Backup-DYWCkzwM.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BHuP82DF.js → BaseInput-xm5vodmJ.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BLPgCQsB.js → Chat-B20_PNd3.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-D4d-eh0F.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-DDJrOqEh.js → Checkbox-CfgwkV_r.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CxyKcM6S.js → Codegen-DbnoWI44.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BYFh9rE1.js → Col-CfzzhgGw.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D8JPppoh.js → Community-BU7mGM8p.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-B5VGSqZe.js → Compact-DpYim0an.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-C7KiFcUK.js → Compliance-BNJcO6ra.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-D-F2fgA_.js → Cowork-CecZ-pWj.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-B6TmPqRO.js → Cron-BG_C1fe3.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BeGcBXhk.js → Crosschain-BAeILQLL.js} +1 -1
- package/src/assets/web-panel/assets/{DID-9QklLcmQ.js → DID-CYqOEkXi.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DIadmsuS.js → Dashboard-BYU8wmL1.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-0I5gks3i.js → Dropdown-DYlJeTTa.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BdTAjT2q.js → EmailListRenderer-C0M6py3K.js} +1 -1
- package/src/assets/web-panel/assets/EvolutionSettings-_QnjPFZJ.js +1 -0
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DQUQWwUY.js → FamilyGuardDashboard-Du90iDch.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Drgi--Zn.js → Federation-DkFp_hzB.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BOf-9P8n.js → FormItemContext-BGgtGTfG.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CBBnIDaW.js → GenericCardRenderer-BIDsSCei.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BKimGd5P.js → Git--d3Te93J.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DoXCXppu.js → Governance-CZwD0vmO.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DzOhGhOE.js → Inference-DiDcSFEW.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DawgmU58.js → KnowledgeGraph-Bi0hSfzg.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Co3EJL2Y.js → Logs-C8NHSc0K.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-VUfiDRZN.js → MarkdownRenderer-DfDuADqF.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BqpMHnMW.js → Marketplace-D6kD0bNf.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C7ZSokcR.js → McpTools-mTSamCr5.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-DuJrpeDx.js → Memory-YQBXfLjU.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CaM744NU.js → MobileBridge-D2awuupp.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bl7lZc_-.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-5oUB7lPh.js → Mtc-CBqjLK2Q.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-D9DNF6DB.js → MtcAudit-C4poRInZ.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-CfMpFQSr.js → Multisig-C_J0zXYZ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CuhPZQ7q.js → NLProgramming-Ci43sWOU.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-NtSeMoyM.js → Notes-DN7A-GFZ.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CB4e7V83.js → NotificationSettings-D5jNm71V.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-C9UsPOG8.js +1 -0
- package/src/assets/web-panel/assets/{Organization-K_0bjm7y.js → Organization-BapYMJrP.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-D2vwxiPv.js → Overflow-BwxwjhFw.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-WQMHBu8q.js → P2P-TObXXOu3.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CTZVBMZZ.js → PdhVaultBrowser-UwJvMbFO.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DcLy9Nfp.js → Permissions-Damnxv-O.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-zc-cm-02.js → PersonalDataHub-d_7jlBb-.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CCnI8peM.js → Pipeline-DA87VjLw.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DO1Xor8L.js → Privacy-DzO_UvWn.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CZqC-jyt.js → ProjectInit-CwqECDhs.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DqJtyavX.js → ProjectSettings-CT5YdIl6.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Dx14NAO3.js → Projects-D3KdZPCv.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DH13a6ro.js → Providers-BjBIS8kk.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C9bwzVfA.js → QrScannerModal-mMFqiKkI.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Cbt-oIPf.js → QuickAsk-BwGi9ZU1.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BYIimGcY.js → Recommend-BY65SR5s.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C8OWTG1H.js → RemoteSession-BGsVH_XU.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DpUGT_jD.js → Reputation-DpPbxSgI.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DZ5Izi2A.js → Row-B5YBYoVz.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CAn94No1.js → RssFeed-DvM5jaoh.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BqNaxcgj.js → Search-6RX39EPO.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Ck6vFTWn.js → Security-Beal-7cM.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Y3RdvvDj.js → Services-D6rjVLe-.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Cx9Iqcp7.js → Skeleton-Dur7R7vz.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Oiqz7wLM.js → Skills-D1QkGwMk.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DaKJQADm.js → Sla-3ObrX8Es.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BWoOpPW4.js → SpeechSettings-DG_KAiH1.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CgFle8GX.js → SyncSettings-BaHq0SJ2.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-C1tQNvWx.js → Tasks-BDpAkBAB.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CvvycaWi.js → Templates-tYdoRd-c.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-D1FwaD12.js → Tenant-yGS5zIQ1.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-oorFjObO.js → Terminal-B3QbmVFa.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BjMs48Sj.js → TimelineRenderer-B_7Pr5dS.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BkRb95xG.js → Tokens-DPvpDa53.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B94RYYAF.js → Trigger-DcSAD0GZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DnBVpOWt.js → Trust-BBzEq-uy.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DouHO6Up.js → UkeySign-Dssav7-5.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DDgRerWV.js → VideoEditing-CPOZf4yi.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CQN9Un7X.js → Wallet-BzBVRXEq.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-DyHl2G3J.js → WebAuthn-DdSGaCCh.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-BQlN77Ey.js → WorkflowEditor-CKjBFoSs.js} +1 -1
- package/src/assets/web-panel/assets/{chat-0jOqe7ZD.js → chat-B9aMGLg0.js} +1 -1
- package/src/assets/web-panel/assets/{colors-kp5TAaKx.js → colors-GFCMctSV.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DqF6Rml7.js → compact-item-BqSDFAYJ.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-lDyLBjy5.js → createContext-BwUm3Ffp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-LxtRBsCm.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CkiF91kH.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-BvFFY-2V.js → index-7W1e9Vi4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQGk8bV8.js → index-B4MfxM5W.js} +1 -1
- package/src/assets/web-panel/assets/{index-kenTha3h.js → index-B4dI_csl.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6V9l2P-.js → index-B7Yu5wwV.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bq2s5obn.js → index-B9Pe-EyB.js} +3 -3
- package/src/assets/web-panel/assets/{index-aHKeys8c.js → index-BBMW8bQO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPwtIqRo.js → index-BCnbC2z7.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByggSxTQ.js → index-BDL2MwxN.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPcjLn2w.js → index-BHUxz4dy.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bu14YcwM.js → index-BL_0ndQc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DrVVi5jX.js → index-BPGlcDvs.js} +1 -1
- package/src/assets/web-panel/assets/{index-DzU_SfdF.js → index-BeycSy7n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnI__Fwu.js → index-BpiR6uzH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CI-H0ljO.js → index-BwssFWI1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DPHmcQWh.js → index-By9GbJhu.js} +1 -1
- package/src/assets/web-panel/assets/index-C0b78zWZ.js +1 -0
- package/src/assets/web-panel/assets/{index-DX40v3aL.js → index-CEMl-Qte.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRu6Pe98.js → index-CH2wK5dx.js} +1 -1
- package/src/assets/web-panel/assets/{index-8Hxbv1SG.js → index-CRikhGgx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgGNW2ep.js → index-Ci0zIpSX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUqE17wu.js → index-Cli0VREv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAtpuJQ7.js → index-CmEHzOsw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DK47iA2d.js → index-Cwih67e8.js} +1 -1
- package/src/assets/web-panel/assets/{index-mkwJZMBT.js → index-D8jcp9Rj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSDM-VNz.js → index-DAK28GY8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNPCAYHk.js → index-DI90Wo_P.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLchRTsS.js → index-DO-IKERp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKFL4DfP.js → index-DaFr7XB1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DduPk1RS.js → index-DbDhKroq.js} +1 -1
- package/src/assets/web-panel/assets/{index-X1l5-ive.js → index-DnJt9DaX.js} +1 -1
- package/src/assets/web-panel/assets/{index-INxX1TMz.js → index-DvvJWwT8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHnFhMOL.js → index-PJt1Zrx2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHVNtkrR.js → index-PKwZkQDC.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZU13W4o.js → index-THsr_UUJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-CS_-jC.js → index-bwb4cg_D.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSAeSPQe.js → index-rCTtYCGu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnU8hi3c.js → index-tMF_deZb.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTA_B9gU.js → index-y7cfh9rP.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CYYQ0SI1.js → initDefaultProps-B6e5CZ5p.js} +1 -1
- package/src/assets/web-panel/assets/{motion--Tyo36te.js → motion-riMM1KfJ.js} +1 -1
- package/src/assets/web-panel/assets/{move-D2BjgQv7.js → move-C1NSDMjc.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-dyIu5RU_.js → mtc-parser-Bibuny9B.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CDEAhC5H.js → omit-Cmf6FoRn.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DLKDlIQs.js → pickAttrs-BSsYBUCY.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BveC8fe2.js → placementArrow-gSB7-GAS.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-51urgYrW.js → responsiveObserve-qaqttvKa.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CCHbhZg-.js → slide-RX2gxl3k.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-W4mtUXRP.js → statusUtils-DEJmkhqw.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-D4Drrz2a.js → styleChecker-CIWmJ78O.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BBCd4kCM.js → useFlexGapSupport-D-UPSQBE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-4Khxvw5Y.js → useFs-9QFtqADc.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-7c72tleZ.js → usePersonalDataHub-DM6kyift.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DzZ7Y0LN.js → vnode-C-q6FNSJ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C5jhaET3.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/constants.js +2 -2
- package/src/data/changelog.json +188 -762
- 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/evolution-file-identity.js +42 -0
- package/src/lib/evolution/evolution-ledger-file-manifest-head-store.js +242 -0
- package/src/lib/evolution/evolution-ledger-manifest-chain.js +74 -18
- package/src/lib/evolution/evolution-ledger-v2-manifest-backend.js +135 -20
- package/src/lib/evolution/evolution-ledger.js +353 -209
- 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/llm-providers.js +1 -0
- 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/lib/task-model-selector.js +1 -1
- package/src/repl/agent-repl.js +1 -1
- package/src/repl/chat-repl.js +8 -4
- package/src/runtime/agent-core.js +36 -10
- package/src/runtime/headless-runner.js +16 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DWXPJh-J.js +0 -1
- package/src/assets/web-panel/assets/EvolutionSettings-C7_tq643.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-Bd78jDwm.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Bu8C6Qlj.js +0 -1
- package/src/assets/web-panel/assets/devWarning-IfrMTwCK.js +0 -1
- package/src/assets/web-panel/assets/index-C7nsIbMa.js +0 -1
- package/src/assets/web-panel/assets/index-DFXFLdKN.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,3 +1,5 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
isAffectedWindowsZeroDeviceStatRuntime,
|
|
3
5
|
sameFileStatIdentity,
|
|
@@ -48,3 +50,43 @@ export function withEvolutionFileIdentity(
|
|
|
48
50
|
{ runtime },
|
|
49
51
|
);
|
|
50
52
|
}
|
|
53
|
+
|
|
54
|
+
// Verify any number of children while holding one authenticated parent
|
|
55
|
+
// directory identity. This keeps the affected Windows/libuv device bridge
|
|
56
|
+
// exact without reopening the volume root and parent once per child.
|
|
57
|
+
export function withEvolutionDirectoryFileIdentity(
|
|
58
|
+
fsImpl,
|
|
59
|
+
directoryPath,
|
|
60
|
+
operation,
|
|
61
|
+
runtime = undefined,
|
|
62
|
+
) {
|
|
63
|
+
if (typeof operation !== "function") {
|
|
64
|
+
throw new TypeError("evolution directory identity operation is required");
|
|
65
|
+
}
|
|
66
|
+
const sentinel = path.join(path.resolve(directoryPath), ".identity-scope");
|
|
67
|
+
return withTrustedFileParentSync(
|
|
68
|
+
fsImpl,
|
|
69
|
+
sentinel,
|
|
70
|
+
({ parentDevice, parentPath }) =>
|
|
71
|
+
operation(
|
|
72
|
+
isAffectedWindowsZeroDeviceStatRuntime(runtime)
|
|
73
|
+
? (pathStat, handleStat) =>
|
|
74
|
+
samePathHandleFileIdentity(
|
|
75
|
+
pathStat,
|
|
76
|
+
handleStat,
|
|
77
|
+
parentDevice,
|
|
78
|
+
runtime,
|
|
79
|
+
)
|
|
80
|
+
: sameFileStatIdentity,
|
|
81
|
+
(pathStat, handleStat) =>
|
|
82
|
+
samePathHandleStableFileIdentity(
|
|
83
|
+
pathStat,
|
|
84
|
+
handleStat,
|
|
85
|
+
parentDevice,
|
|
86
|
+
runtime,
|
|
87
|
+
),
|
|
88
|
+
Object.freeze({ parentPath }),
|
|
89
|
+
),
|
|
90
|
+
{ runtime },
|
|
91
|
+
);
|
|
92
|
+
}
|