chainlesschain 0.166.42 → 0.166.44
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 +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-CdsAAOAu.js → AIOps-HiHMDVK3.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-B8x5S-3u.js → ActionButton-H2RKuS8V.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C1eOGwSM.js → Analytics-CBPFBB61.js} +3 -3
- package/src/assets/web-panel/assets/AppLayout-Da-3Laze.js +9 -0
- package/src/assets/web-panel/assets/{Artifacts-B0tiKn2i.js → Artifacts-DiRQ9yzD.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DzGqTUvG.js → Audit-C9LtWEBh.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CMXYcy56.js → BackgroundAgents-4rpi7fVg.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DrN3N1_7.js → Backup-B8VQmHys.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BC9aoifZ.js → BaseInput-iCVpF2DK.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-F3Z9p0vk.js → Chat-BrQaXBwC.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-LMGrVg9Z.js → ChatBubbleRenderer-HIyU-9c0.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-CDXlouzl.js → Checkbox-CsV__0_R.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-9c0RpaYc.js → Codegen-BvFeCIAE.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C-60iGia.js → Col-CagxV1O8.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DXE04H2M.js → Community-DAWr4ahG.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-B4jEuBmH.js → Compact-Ba49Tdc2.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-uf13-dcK.js → Compliance-Brk-MSO4.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-D6hpxLI4.js → Cowork-DeKH1v0o.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CSJobmkN.js → Cron-AHB-T-VL.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B0_rTlsi.js → Crosschain-Zbcrtimh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D49FQsiZ.js → DID-ByFa1MWG.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BvjvIYfC.js → Dashboard-9jKovyuH.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BYe1gODU.js → Dropdown-DY8TaXi5.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CJ6znfiH.js → EmailListRenderer-rcaSveQ8.js} +1 -1
- package/src/assets/web-panel/assets/EvolutionSettings-B3dcSq1H.js +1 -0
- package/src/assets/web-panel/assets/EvolutionSettings-Bdw8ApoT.css +1 -0
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BnoKDllh.js → FamilyGuardDashboard-5ao8smvA.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-AqhVFmd4.js → Federation-RTpNXXHT.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CNVYBpNg.js → FormItemContext-DWZvRMWv.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BbdYCfjH.js → GenericCardRenderer-CWi8SGvx.js} +1 -1
- package/src/assets/web-panel/assets/{Git-B_xClOR0.js → Git-CCLgDIqs.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CIKCDF9F.js → Governance-DUuyKrQl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BdRSc0oa.js → Inference-RkFcM2A0.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DknpIQPx.js → KnowledgeGraph-C-QTauZr.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BcCPhMaZ.js → Logs-B-7Dz5Ui.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CgiHBe0A.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-DEMNuvwO.js → Marketplace-CK1kSFDt.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D-tg9wfq.js → McpTools-BcaVwoY6.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-BqCLu-X9.js → Memory-QEI4xRwF.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-bN6vwbZc.js → MobileBridge-BqD-R0Ek.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BTM0-1MZ.js → MobileProjects-CwFoXf1M.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CcGXXyEn.js → Mtc-CTZ6qICG.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-B2Z5wPnM.js → MtcAudit-Cn7y2BzO.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DNW7dKam.js → Multisig-BCjsdGiN.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Ciz-g5G3.js → NLProgramming-CupTVNtA.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CeiEA6yF.js → Notes-C8kO3mGt.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DgeiEPwB.js → NotificationSettings-Cn-MJ8sQ.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-D9DjubGK.js → OrderTableRenderer-BX4PrVfN.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CeA2EC_G.js → Organization-G_RjOL7M.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DT40ig7t.js → Overflow-BvX644ya.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-K4H13K3M.js → P2P-yWmLgYgQ.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-yYL7-rno.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DYQogL6r.js → Permissions-DtU8fhcZ.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DXhXVHar.js → PersonalDataHub-B17Vyk-x.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-DQMdn5Ij.js → Pipeline-BZUsTyhP.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BiF1thop.js → Privacy-BswdhlPL.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C2ptWEn7.js → ProjectInit-sJxSlY0C.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-B5WCiDZD.js → ProjectSettings-BnbpdOke.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BNH6LKU6.js → Projects-DUjauf-6.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CgRJ8ZT1.js → Providers-DhF5I-UR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-B3tuNJ3V.js → QrScannerModal-B3PeCAGo.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-D9CO1SNI.js → QuickAsk-Cpaxrabg.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BGmwHY_U.js → Recommend-DqXFF66-.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-CliYaolm.js → RemoteSession-DtDQ76Ox.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-sCiX2kmm.js → Reputation-BgLLKwu2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DXHx_bk0.js → Row-BuMLhd8M.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Bg79mIxI.js → RssFeed-BizxUzBA.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CnKDYqyS.js → Search-CYNrJAQY.js} +1 -1
- package/src/assets/web-panel/assets/{Security-c-0QOrC2.js → Security-CIC02soH.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BBBTb4YA.js → Services-DD3WxXxw.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CtL_5fJe.js → Skeleton-C-d9Mbos.js} +1 -1
- package/src/assets/web-panel/assets/Skills-BBkg1Eqr.js +1 -0
- package/src/assets/web-panel/assets/{Sla-C5LGNImz.js → Sla-DgrqUhns.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Btx0KgwX.js → SpeechSettings-CDXFwOHX.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DFLNSWuu.js → SyncSettings-BBio_v8M.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Btkyo0G7.js → Tasks-QQWSk-tc.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CIIt_k5J.js → Templates-RAPdM85o.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Nqo1PA2a.js → Tenant-2jI745PC.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CGwV9hW9.js → Terminal-ZCtYBMAe.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Bu3xrXyf.js → TimelineRenderer-DfI36Yg6.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-3bC3zrYj.js → Tokens-NQ1VaO0W.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DG84V7Xf.js → Trigger-DwFh5AMe.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-rPhNXoYJ.js → Trust-Dr0v45uH.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CamNxeAp.js → UkeySign-i7Q1Bdqr.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CkIuH4Gm.js → VideoEditing-CXSkk4gl.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-SD5W5uHT.js → Wallet-8_cAjou4.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn--A4Sddxn.js → WebAuthn-jn_3Gma7.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Ca7qvch8.js → WorkflowEditor-BmvodVLl.js} +1 -1
- package/src/assets/web-panel/assets/{chat-i8OBdjCB.js → chat-DGi3bmwL.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BeYbJKQ8.js → colors-D8OSTOU0.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BqpF-Sya.js → compact-item-DfNygJU_.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-qXTb29t3.js → createContext-B0lT0dbj.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BUooUkHH.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DOfL0KSW.js → hasIn-Xtzg_EDR.js} +1 -1
- package/src/assets/web-panel/assets/{index-X8TXf9Bb.js → index-4s8GZJ73.js} +1 -1
- package/src/assets/web-panel/assets/{index-0OxY_Nsh.js → index-B-B7DDjX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CebShwk1.js → index-B0kDqFPU.js} +1 -1
- package/src/assets/web-panel/assets/{index-czHvbhS0.js → index-B2bgL5vL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXXeOim8.js → index-B3ittRaJ.js} +1 -1
- package/src/assets/web-panel/assets/{index--TQi5102.js → index-B4-hl9Rw.js} +1 -1
- package/src/assets/web-panel/assets/{index-C13IM9lj.js → index-BBR-_Pkv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CH7mHJoh.js → index-BBegBTDb.js} +1 -1
- package/src/assets/web-panel/assets/{index-vNYrAXO6.js → index-BFRkiqtr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIikgsI1.js → index-BJ6nxb18.js} +1 -1
- package/src/assets/web-panel/assets/{index-B177TAO-.js → index-BVOyO-ZV.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bt7Ua7EC.js → index-BbTx5wo1.js} +1 -1
- package/src/assets/web-panel/assets/{index-nl9IxLfv.js → index-BhS9-r8Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cy8WApMp.js → index-BhYM_Rzp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmzmjSGb.js → index-BhhS0xnV.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8c3Ctj4.js → index-BpsIkWwQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4_8KnUt.js → index-Bujix_OR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLrDyxeZ.js → index-Byto86PA.js} +1 -1
- package/src/assets/web-panel/assets/{index-Byn_IHXR.js → index-CIbYQLD-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWLxMPZ_.js → index-CLRMkKeD.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByHZkqj5.js → index-CShl59Um.js} +1 -1
- package/src/assets/web-panel/assets/{index-TAvlGp5l.js → index-ConAvib0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKd03Rn4.js → index-Cp-yGWti.js} +1 -1
- package/src/assets/web-panel/assets/{index-rdW8KX_7.js → index-Cx_EAQEh.js} +1 -1
- package/src/assets/web-panel/assets/{index-JxyF4KNw.js → index-CyVOaMS7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYzQaGnV.js → index-DEH5ms1R.js} +1 -1
- package/src/assets/web-panel/assets/{index-zmvw3Ptl.js → index-DFkgqRqx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdbrE-hz.js → index-DVfE2S0f.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOwR6mHU.js → index-DVnowoxR.js} +1 -1
- package/src/assets/web-panel/assets/index-DVu4QCII.js +1 -0
- package/src/assets/web-panel/assets/{index-Cvig0IjN.js → index-DWpRgQvO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BD386d6M.js → index-DXNWsfbw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BuzGZOAK.js → index-DZKM2nm2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dio2mTWq.js → index-De9jsBiC.js} +1 -1
- package/src/assets/web-panel/assets/{index-GGEsF-NA.js → index-EV-o-rTH.js} +1 -1
- package/src/assets/web-panel/assets/index-XYnz-yJ_.js +1 -0
- package/src/assets/web-panel/assets/{index-B00rNoIx.js → index-hDo9-qDw.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-P0NHi4.js → index-kdyFlPZw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCcXU9HN.js → index-svr-L6NS.js} +5 -5
- package/src/assets/web-panel/assets/{initDefaultProps-BKpYiODg.js → initDefaultProps-CPsOp_T6.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DJ2p-V-R.js → motion-8Jakob3_.js} +1 -1
- package/src/assets/web-panel/assets/{move-CmA2ejm5.js → move-DAogsEBs.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CAIGxQzN.js → mtc-parser-CkA9zZB_.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DJlnhA7K.js → omit-DMHNkosQ.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CZPrJyhF.js → pickAttrs-ByIvo_lk.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CXy74okE.js → placementArrow-CafMUHzz.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DexkLtci.js → responsiveObserve-CTehELUQ.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BzjBJbPV.js → slide-BQISQ1KV.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-gnHIXw2X.js → statusUtils-Drm4jUoK.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DHBjT1Gk.js → styleChecker-DGniLRqn.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BmMStW87.js → useFlexGapSupport-DYz_z9wn.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BcV5VGA2.js → useFs-DRAPQMjC.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DBxxRRY2.js → usePersonalDataHub-CpgGLb8q.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Bt5yp9hj.js → vnode-p6925VWJ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CMdUkOfw.js → zoom-ClpO8REK.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/evolution-deployment.js +95 -0
- package/src/commands/evolution.js +2 -0
- package/src/constants.js +2 -2
- package/src/data/changelog.json +191 -752
- package/src/gateways/ws/evolution-deployment-topic-handlers.js +53 -0
- package/src/lib/evolution/evolution-deployment-config.js +81 -0
- package/src/lib/evolution/evolution-deployment-loader.js +62 -13
- package/src/lib/evolution/evolution-deployment-profile.js +114 -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/llm-providers.js +1 -0
- package/src/lib/public-ide-capability-manifest.js +2 -0
- package/src/lib/read-file-loop-guard.js +17 -4
- package/src/lib/remote-read-loop-guard.js +13 -0
- package/src/lib/task-model-selector.js +1 -1
- package/src/lib/task-progress-tracker.js +36 -1
- package/src/lib/text-file-search.js +11 -0
- package/src/runtime/agent-core.js +6 -2
- package/src/runtime/agent-runtime.js +2 -0
- package/src/assets/web-panel/assets/AppLayout-CdnpAzuo.js +0 -9
- package/src/assets/web-panel/assets/MarkdownRenderer-H_GkKEp-.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-CF7TzZmx.js +0 -7
- package/src/assets/web-panel/assets/Skills-DvqrC2_v.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DATyiD_X.js +0 -1
- package/src/assets/web-panel/assets/index-DmzMCELP.js +0 -1
- package/src/assets/web-panel/assets/index-TlPg2Jz6.js +0 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configureEvolutionDeployment,
|
|
3
|
+
getEvolutionDeploymentStatus,
|
|
4
|
+
setEvolutionDeploymentEnabled,
|
|
5
|
+
} from "../../lib/evolution/evolution-deployment-config.js";
|
|
6
|
+
|
|
7
|
+
function assertSecureControl(context) {
|
|
8
|
+
const server = context?.server;
|
|
9
|
+
const loopback = new Set(["127.0.0.1", "localhost", "::1"]);
|
|
10
|
+
if (!server?.token && !loopback.has(server?.host)) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"Skill evolution configuration requires a token-protected or loopback-only cc ui",
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function requiredPath(value, name) {
|
|
18
|
+
if (
|
|
19
|
+
typeof value !== "string" ||
|
|
20
|
+
value.length === 0 ||
|
|
21
|
+
value.length > 4096 ||
|
|
22
|
+
/[\r\n\0]/u.test(value)
|
|
23
|
+
) {
|
|
24
|
+
throw new Error(`${name} is invalid`);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createEvolutionDeploymentTopicHandlers(options = {}) {
|
|
30
|
+
const getStatus = options.getStatus || getEvolutionDeploymentStatus;
|
|
31
|
+
const configure = options.configure || configureEvolutionDeployment;
|
|
32
|
+
const setEnabled = options.setEnabled || setEvolutionDeploymentEnabled;
|
|
33
|
+
return {
|
|
34
|
+
"evolution.deployment.status": async (_frame, context) => {
|
|
35
|
+
assertSecureControl(context);
|
|
36
|
+
return getStatus();
|
|
37
|
+
},
|
|
38
|
+
"evolution.deployment.configure": async (frame, context) => {
|
|
39
|
+
assertSecureControl(context);
|
|
40
|
+
return configure({
|
|
41
|
+
descriptorPath: requiredPath(frame?.descriptorPath, "descriptorPath"),
|
|
42
|
+
trustRootPath: requiredPath(frame?.trustRootPath, "trustRootPath"),
|
|
43
|
+
enabled: true,
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
"evolution.deployment.set-enabled": async (frame, context) => {
|
|
47
|
+
assertSecureControl(context);
|
|
48
|
+
if (typeof frame?.enabled !== "boolean")
|
|
49
|
+
throw new Error("enabled must be a boolean");
|
|
50
|
+
return setEnabled(frame.enabled);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readEvolutionDeploymentProfile,
|
|
3
|
+
setEvolutionDeploymentProfileEnabled,
|
|
4
|
+
writeEvolutionDeploymentProfile,
|
|
5
|
+
} from "./evolution-deployment-profile.js";
|
|
6
|
+
import { verifyEvolutionDeployment } from "./evolution-deployment-loader.js";
|
|
7
|
+
|
|
8
|
+
function environmentSelection(env) {
|
|
9
|
+
const descriptorPath = env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_DESCRIPTOR;
|
|
10
|
+
const trustRootPath = env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_TRUST_ROOT;
|
|
11
|
+
if (!descriptorPath && !trustRootPath) return null;
|
|
12
|
+
return { descriptorPath, trustRootPath };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function getEvolutionDeploymentStatus(options = {}) {
|
|
16
|
+
const env = options.env || process.env;
|
|
17
|
+
const saved = await readEvolutionDeploymentProfile(options);
|
|
18
|
+
const fromEnvironment = environmentSelection(env);
|
|
19
|
+
const source = fromEnvironment
|
|
20
|
+
? "environment"
|
|
21
|
+
: saved.profile?.enabled
|
|
22
|
+
? "profile"
|
|
23
|
+
: "none";
|
|
24
|
+
const selected =
|
|
25
|
+
fromEnvironment || (saved.profile?.enabled ? saved.profile : null);
|
|
26
|
+
const base = {
|
|
27
|
+
source,
|
|
28
|
+
effectiveEnabled: source !== "none",
|
|
29
|
+
profileEnabled: saved.profile?.enabled === true,
|
|
30
|
+
profilePath: saved.filePath,
|
|
31
|
+
descriptorPath:
|
|
32
|
+
selected?.descriptorPath || saved.profile?.descriptorPath || null,
|
|
33
|
+
trustRootPath:
|
|
34
|
+
selected?.trustRootPath || saved.profile?.trustRootPath || null,
|
|
35
|
+
verified: false,
|
|
36
|
+
error: saved.error,
|
|
37
|
+
autoPromotion: "hold",
|
|
38
|
+
};
|
|
39
|
+
if (!selected || saved.error) return Object.freeze(base);
|
|
40
|
+
try {
|
|
41
|
+
const verified = await verifyEvolutionDeployment(selected, options);
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
...base,
|
|
44
|
+
verified: true,
|
|
45
|
+
error: null,
|
|
46
|
+
revision: verified.descriptor.revision,
|
|
47
|
+
modulePath: verified.modulePath,
|
|
48
|
+
moduleDigest: verified.descriptor.moduleDigest,
|
|
49
|
+
commands: verified.descriptor.commands,
|
|
50
|
+
});
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return Object.freeze({
|
|
53
|
+
...base,
|
|
54
|
+
error: error instanceof Error ? error.message : String(error),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function configureEvolutionDeployment(
|
|
60
|
+
{ descriptorPath, trustRootPath, enabled = true },
|
|
61
|
+
options = {},
|
|
62
|
+
) {
|
|
63
|
+
const verified = await verifyEvolutionDeployment(
|
|
64
|
+
{ descriptorPath, trustRootPath },
|
|
65
|
+
options,
|
|
66
|
+
);
|
|
67
|
+
await writeEvolutionDeploymentProfile(
|
|
68
|
+
{
|
|
69
|
+
enabled: enabled === true,
|
|
70
|
+
descriptorPath: verified.descriptorPath,
|
|
71
|
+
trustRootPath: verified.trustRootPath,
|
|
72
|
+
},
|
|
73
|
+
options,
|
|
74
|
+
);
|
|
75
|
+
return getEvolutionDeploymentStatus(options);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function setEvolutionDeploymentEnabled(enabled, options = {}) {
|
|
79
|
+
await setEvolutionDeploymentProfileEnabled(enabled, options);
|
|
80
|
+
return getEvolutionDeploymentStatus(options);
|
|
81
|
+
}
|
|
@@ -2,6 +2,7 @@ import { createHash, createPublicKey, verify } from "node:crypto";
|
|
|
2
2
|
import { readFile, realpath } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute } from "node:path";
|
|
4
4
|
import { types as utilTypes } from "node:util";
|
|
5
|
+
import { readEvolutionDeploymentProfile } from "./evolution-deployment-profile.js";
|
|
5
6
|
|
|
6
7
|
export const EVOLUTION_DEPLOYMENT_DESCRIPTOR_SCHEMA =
|
|
7
8
|
"chainlesschain.evolution-deployment-descriptor/v1";
|
|
@@ -223,7 +224,9 @@ async function loadBuiltInFactories(commandName) {
|
|
|
223
224
|
import("./evolution-eval-process-supervisor.js"),
|
|
224
225
|
import("./governed-skill-synthesis-attestor-trust-approval-client.js"),
|
|
225
226
|
import("./governed-skill-synthesis-attestor-trust-operations-client.js"),
|
|
226
|
-
import(
|
|
227
|
+
import(
|
|
228
|
+
"./governed-skill-synthesis-attestor-trust-operations-cli-host.js"
|
|
229
|
+
),
|
|
227
230
|
]);
|
|
228
231
|
factories.createEvolutionWorkbenchCliHost = createEvolutionWorkbenchCliHost;
|
|
229
232
|
factories.createEvolutionWorkbenchReviewRuntime =
|
|
@@ -350,8 +353,9 @@ async function loadBuiltInFactories(commandName) {
|
|
|
350
353
|
commandName === "stream" ||
|
|
351
354
|
commandName === "ui"
|
|
352
355
|
) {
|
|
353
|
-
const { createAgentEvolutionRuntimeComposition } =
|
|
354
|
-
|
|
356
|
+
const { createAgentEvolutionRuntimeComposition } = await import(
|
|
357
|
+
"./agent-evolution-runtime-composition.js"
|
|
358
|
+
);
|
|
355
359
|
factories.createAgentEvolutionRuntimeComposition =
|
|
356
360
|
createAgentEvolutionRuntimeComposition;
|
|
357
361
|
}
|
|
@@ -414,20 +418,14 @@ function bindFactoriesToModule(factories, moduleDigest) {
|
|
|
414
418
|
return Object.freeze(result);
|
|
415
419
|
}
|
|
416
420
|
|
|
417
|
-
export async function
|
|
418
|
-
|
|
421
|
+
export async function verifyEvolutionDeployment(
|
|
422
|
+
{ descriptorPath, trustRootPath },
|
|
419
423
|
{
|
|
420
|
-
env = process.env,
|
|
421
424
|
read = readFile,
|
|
422
425
|
resolveRealPath = realpath,
|
|
423
|
-
|
|
424
|
-
additionalFactories = {},
|
|
426
|
+
includeModuleBytes = false,
|
|
425
427
|
} = {},
|
|
426
428
|
) {
|
|
427
|
-
if (!SUPPORTED_COMMANDS.has(commandName)) return null;
|
|
428
|
-
const descriptorPath = env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_DESCRIPTOR;
|
|
429
|
-
const trustRootPath = env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_TRUST_ROOT;
|
|
430
|
-
if (!descriptorPath && !trustRootPath) return null;
|
|
431
429
|
if (!descriptorPath || !trustRootPath)
|
|
432
430
|
throw new Error(
|
|
433
431
|
"evolution deployment requires both descriptor and trust root",
|
|
@@ -458,7 +456,6 @@ export async function loadEvolutionDeploymentCommandDependencies(
|
|
|
458
456
|
)
|
|
459
457
|
)
|
|
460
458
|
throw new Error("evolution deployment descriptor signature rejected");
|
|
461
|
-
if (!descriptor.commands.includes(commandName)) return null;
|
|
462
459
|
|
|
463
460
|
const modulePath = await resolveRealPath(descriptor.modulePath);
|
|
464
461
|
const moduleBytes = await read(modulePath);
|
|
@@ -466,6 +463,58 @@ export async function loadEvolutionDeploymentCommandDependencies(
|
|
|
466
463
|
throw new Error("evolution deployment module size is invalid");
|
|
467
464
|
if (sha256(moduleBytes) !== descriptor.moduleDigest)
|
|
468
465
|
throw new Error("evolution deployment module digest mismatch");
|
|
466
|
+
return Object.freeze({
|
|
467
|
+
descriptor,
|
|
468
|
+
descriptorPath: await resolveRealPath(descriptorPath),
|
|
469
|
+
trustRootPath: await resolveRealPath(trustRootPath),
|
|
470
|
+
modulePath,
|
|
471
|
+
...(includeModuleBytes ? { moduleBytes } : {}),
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export async function loadEvolutionDeploymentCommandDependencies(
|
|
476
|
+
commandName,
|
|
477
|
+
{
|
|
478
|
+
env = process.env,
|
|
479
|
+
read = readFile,
|
|
480
|
+
resolveRealPath = realpath,
|
|
481
|
+
importModule = (url) => import(url),
|
|
482
|
+
additionalFactories = {},
|
|
483
|
+
} = {},
|
|
484
|
+
) {
|
|
485
|
+
if (!SUPPORTED_COMMANDS.has(commandName)) return null;
|
|
486
|
+
let descriptorPath = env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_DESCRIPTOR;
|
|
487
|
+
let trustRootPath = env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_TRUST_ROOT;
|
|
488
|
+
let fromSavedProfile = false;
|
|
489
|
+
if (!descriptorPath && !trustRootPath) {
|
|
490
|
+
const saved = await readEvolutionDeploymentProfile({ env });
|
|
491
|
+
if (!saved.error && saved.profile?.enabled) {
|
|
492
|
+
descriptorPath = saved.profile.descriptorPath;
|
|
493
|
+
trustRootPath = saved.profile.trustRootPath;
|
|
494
|
+
fromSavedProfile = true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (!descriptorPath && !trustRootPath) return null;
|
|
498
|
+
if (!descriptorPath || !trustRootPath)
|
|
499
|
+
throw new Error(
|
|
500
|
+
"evolution deployment requires both descriptor and trust root",
|
|
501
|
+
);
|
|
502
|
+
let verified;
|
|
503
|
+
try {
|
|
504
|
+
verified = await verifyEvolutionDeployment(
|
|
505
|
+
{ descriptorPath, trustRootPath },
|
|
506
|
+
{ read, resolveRealPath, includeModuleBytes: true },
|
|
507
|
+
);
|
|
508
|
+
} catch (error) {
|
|
509
|
+
// A saved profile may become stale after files are moved or rotated. Keep
|
|
510
|
+
// governed capabilities fail-closed while allowing `deployment status`,
|
|
511
|
+
// `configure`, and `disable` to start so the operator can recover. Explicit
|
|
512
|
+
// environment overrides remain strict and surface the error immediately.
|
|
513
|
+
if (fromSavedProfile) return null;
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
const { descriptor, moduleBytes } = verified;
|
|
517
|
+
if (!descriptor.commands.includes(commandName)) return null;
|
|
469
518
|
// Import the exact bytes that were authenticated. Importing modulePath here
|
|
470
519
|
// would reopen a pathname-replacement window between hashing and execution.
|
|
471
520
|
// Deployment entrypoints are therefore single-file ESM bundles; any external
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
4
|
+
import { resolveConfigDataRoot } from "../paths.js";
|
|
5
|
+
import {
|
|
6
|
+
PRIVATE_FILE_MODE,
|
|
7
|
+
ensurePrivateDirectory,
|
|
8
|
+
ensurePrivateFile,
|
|
9
|
+
} from "../secure-fs.js";
|
|
10
|
+
|
|
11
|
+
export const EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA =
|
|
12
|
+
"chainlesschain.evolution-deployment-profile/v1";
|
|
13
|
+
|
|
14
|
+
export function getEvolutionDeploymentProfilePath({
|
|
15
|
+
env = process.env,
|
|
16
|
+
cwd = process.cwd(),
|
|
17
|
+
} = {}) {
|
|
18
|
+
return join(
|
|
19
|
+
resolveConfigDataRoot({ env, cwd }).path,
|
|
20
|
+
"evolution",
|
|
21
|
+
"deployment-profile.json",
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeProfile(value) {
|
|
26
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
27
|
+
throw new TypeError("evolution deployment profile must be an object");
|
|
28
|
+
const keys = Object.keys(value).sort();
|
|
29
|
+
const expected = ["descriptorPath", "enabled", "schema", "trustRootPath"];
|
|
30
|
+
if (JSON.stringify(keys) !== JSON.stringify(expected.sort()))
|
|
31
|
+
throw new TypeError("evolution deployment profile has unexpected fields");
|
|
32
|
+
if (value.schema !== EVOLUTION_DEPLOYMENT_PROFILE_SCHEMA)
|
|
33
|
+
throw new TypeError("evolution deployment profile schema is invalid");
|
|
34
|
+
if (typeof value.enabled !== "boolean")
|
|
35
|
+
throw new TypeError("evolution deployment profile enabled is invalid");
|
|
36
|
+
for (const [name, path] of [
|
|
37
|
+
["descriptorPath", value.descriptorPath],
|
|
38
|
+
["trustRootPath", value.trustRootPath],
|
|
39
|
+
]) {
|
|
40
|
+
if (typeof path !== "string" || !isAbsolute(path))
|
|
41
|
+
throw new TypeError(
|
|
42
|
+
`evolution deployment profile ${name} must be absolute`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return Object.freeze({
|
|
46
|
+
schema: value.schema,
|
|
47
|
+
enabled: value.enabled,
|
|
48
|
+
descriptorPath: value.descriptorPath,
|
|
49
|
+
trustRootPath: value.trustRootPath,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function readEvolutionDeploymentProfile(options = {}) {
|
|
54
|
+
const filePath =
|
|
55
|
+
options.filePath || getEvolutionDeploymentProfilePath(options);
|
|
56
|
+
try {
|
|
57
|
+
const profile = normalizeProfile(
|
|
58
|
+
JSON.parse(await readFile(filePath, "utf8")),
|
|
59
|
+
);
|
|
60
|
+
return Object.freeze({ filePath, profile, error: null });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error?.code === "ENOENT")
|
|
63
|
+
return Object.freeze({ filePath, profile: null, error: null });
|
|
64
|
+
return Object.freeze({
|
|
65
|
+
filePath,
|
|
66
|
+
profile: null,
|
|
67
|
+
error: error instanceof Error ? error.message : String(error),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function writeEvolutionDeploymentProfile(input, options = {}) {
|
|
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);
|
|
81
|
+
ensurePrivateDirectory(dirname(filePath));
|
|
82
|
+
const temporaryPath = join(
|
|
83
|
+
dirname(filePath),
|
|
84
|
+
`.deployment-profile.${randomUUID()}.tmp`,
|
|
85
|
+
);
|
|
86
|
+
try {
|
|
87
|
+
await writeFile(temporaryPath, `${JSON.stringify(profile, null, 2)}\n`, {
|
|
88
|
+
encoding: "utf8",
|
|
89
|
+
mode: PRIVATE_FILE_MODE,
|
|
90
|
+
flag: "wx",
|
|
91
|
+
});
|
|
92
|
+
ensurePrivateFile(temporaryPath);
|
|
93
|
+
await rename(temporaryPath, filePath);
|
|
94
|
+
ensurePrivateFile(filePath);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
await unlink(temporaryPath).catch(() => {});
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
return Object.freeze({ filePath, profile });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function setEvolutionDeploymentProfileEnabled(
|
|
103
|
+
enabled,
|
|
104
|
+
options = {},
|
|
105
|
+
) {
|
|
106
|
+
const current = await readEvolutionDeploymentProfile(options);
|
|
107
|
+
if (current.error) throw new Error(current.error);
|
|
108
|
+
if (!current.profile)
|
|
109
|
+
throw new Error("evolution deployment profile is not configured");
|
|
110
|
+
return writeEvolutionDeploymentProfile(
|
|
111
|
+
{ ...current.profile, enabled: enabled === true },
|
|
112
|
+
{ ...options, filePath: current.filePath },
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { types as utilTypes } from "node:util";
|
|
5
|
+
|
|
6
|
+
import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
|
|
7
|
+
import { withFileLock } from "../with-file-lock.js";
|
|
8
|
+
import { readBoundedDescriptor } from "./bounded-descriptor-read.js";
|
|
9
|
+
import { EVOLUTION_LEDGER_MANIFEST_HEAD_CAS_RESULT_SCHEMA } from "./evolution-ledger-manifest-head-store.js";
|
|
10
|
+
|
|
11
|
+
export const EVOLUTION_LEDGER_FILE_MANIFEST_HEAD_BACKEND_SCHEMA =
|
|
12
|
+
"chainlesschain.evolution-ledger-file-manifest-head-backend/v2";
|
|
13
|
+
|
|
14
|
+
const STATE_SCHEMA =
|
|
15
|
+
"chainlesschain.evolution-ledger-file-manifest-head-state/v2";
|
|
16
|
+
const STATE_KEYS = new Set(["head", "schema"]);
|
|
17
|
+
const DEFAULT_MAXIMUM_BYTES = 256 * 1024;
|
|
18
|
+
|
|
19
|
+
function exactRecord(value, keys, label) {
|
|
20
|
+
if (
|
|
21
|
+
!value ||
|
|
22
|
+
typeof value !== "object" ||
|
|
23
|
+
Array.isArray(value) ||
|
|
24
|
+
utilTypes.isProxy(value)
|
|
25
|
+
) {
|
|
26
|
+
throw new Error(`${label} is invalid`);
|
|
27
|
+
}
|
|
28
|
+
const fields = Object.getOwnPropertyDescriptors(value);
|
|
29
|
+
const names = Object.keys(fields);
|
|
30
|
+
if (names.length !== keys.size || names.some((name) => !keys.has(name))) {
|
|
31
|
+
throw new Error(`${label} fields are invalid`);
|
|
32
|
+
}
|
|
33
|
+
for (const name of names) {
|
|
34
|
+
if (!("value" in fields[name]))
|
|
35
|
+
throw new Error(`${label}.${name} must be own data`);
|
|
36
|
+
}
|
|
37
|
+
return fields;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function requiredDirectory(value) {
|
|
41
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
42
|
+
throw new TypeError("manifest head directoryPath is required");
|
|
43
|
+
}
|
|
44
|
+
return path.resolve(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function syncDirectory(fsImpl, directory) {
|
|
48
|
+
let descriptor;
|
|
49
|
+
try {
|
|
50
|
+
descriptor = fsImpl.openSync(directory, "r");
|
|
51
|
+
fsImpl.fsyncSync(descriptor);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (
|
|
54
|
+
process.platform !== "win32" ||
|
|
55
|
+
!["EACCES", "EINVAL", "EISDIR", "EPERM"].includes(error?.code)
|
|
56
|
+
) {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
} finally {
|
|
60
|
+
if (descriptor !== undefined) fsImpl.closeSync(descriptor);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function headDigest(value) {
|
|
65
|
+
if (value === null) return null;
|
|
66
|
+
const field = Object.getOwnPropertyDescriptor(value, "headDigest");
|
|
67
|
+
if (!field || !("value" in field) || typeof field.value !== "string") {
|
|
68
|
+
throw new Error("manifest head record has no own headDigest");
|
|
69
|
+
}
|
|
70
|
+
return field.value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createEvolutionLedgerFileManifestHeadBackend({
|
|
74
|
+
directoryPath,
|
|
75
|
+
fsImpl = fs,
|
|
76
|
+
lock = withFileLock,
|
|
77
|
+
maximumBytes = DEFAULT_MAXIMUM_BYTES,
|
|
78
|
+
random = () => crypto.randomBytes(16).toString("hex"),
|
|
79
|
+
crashHook = null,
|
|
80
|
+
} = {}) {
|
|
81
|
+
const directory = requiredDirectory(directoryPath);
|
|
82
|
+
if (
|
|
83
|
+
typeof lock !== "function" ||
|
|
84
|
+
typeof random !== "function" ||
|
|
85
|
+
(crashHook !== null && typeof crashHook !== "function")
|
|
86
|
+
) {
|
|
87
|
+
throw new TypeError(
|
|
88
|
+
"manifest head lock, random, and crashHook ports are invalid",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 4096) {
|
|
92
|
+
throw new TypeError("manifest head maximumBytes must be at least 4096");
|
|
93
|
+
}
|
|
94
|
+
const file = path.join(directory, "manifest-head.json");
|
|
95
|
+
const secureOptions = { deps: { fs: fsImpl }, failIfUnavailable: true };
|
|
96
|
+
ensurePrivateDirectory(directory, secureOptions);
|
|
97
|
+
|
|
98
|
+
const invokeCrashHook = (phase, details) => {
|
|
99
|
+
if (crashHook !== null) crashHook(phase, Object.freeze(details));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const readBytes = () => {
|
|
103
|
+
if (!fsImpl.existsSync(file)) return null;
|
|
104
|
+
const stat = fsImpl.lstatSync(file);
|
|
105
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
|
|
106
|
+
throw new Error("manifest head file identity is invalid");
|
|
107
|
+
}
|
|
108
|
+
if (stat.size > maximumBytes)
|
|
109
|
+
throw new Error("manifest head exceeds maximumBytes");
|
|
110
|
+
let descriptor;
|
|
111
|
+
try {
|
|
112
|
+
descriptor = fsImpl.openSync(
|
|
113
|
+
file,
|
|
114
|
+
fsImpl.constants.O_RDONLY | (fsImpl.constants.O_NOFOLLOW || 0),
|
|
115
|
+
);
|
|
116
|
+
const opened = fsImpl.fstatSync(descriptor);
|
|
117
|
+
if (!opened.isFile() || opened.nlink !== 1 || opened.size !== stat.size) {
|
|
118
|
+
throw new Error("manifest head changed while opening");
|
|
119
|
+
}
|
|
120
|
+
const bytes = readBoundedDescriptor(
|
|
121
|
+
fsImpl,
|
|
122
|
+
descriptor,
|
|
123
|
+
stat.size,
|
|
124
|
+
maximumBytes,
|
|
125
|
+
);
|
|
126
|
+
const after = fsImpl.lstatSync(file);
|
|
127
|
+
if (
|
|
128
|
+
after.isSymbolicLink() ||
|
|
129
|
+
after.nlink !== 1 ||
|
|
130
|
+
after.size !== stat.size
|
|
131
|
+
) {
|
|
132
|
+
throw new Error("manifest head changed while reading");
|
|
133
|
+
}
|
|
134
|
+
return bytes;
|
|
135
|
+
} finally {
|
|
136
|
+
if (descriptor !== undefined) fsImpl.closeSync(descriptor);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const readState = () => {
|
|
141
|
+
const bytes = readBytes();
|
|
142
|
+
if (bytes === null) return { head: null, schema: STATE_SCHEMA };
|
|
143
|
+
let parsed;
|
|
144
|
+
try {
|
|
145
|
+
parsed = JSON.parse(
|
|
146
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
147
|
+
);
|
|
148
|
+
} catch {
|
|
149
|
+
throw new Error("manifest head is not valid UTF-8 JSON");
|
|
150
|
+
}
|
|
151
|
+
const fields = exactRecord(parsed, STATE_KEYS, "manifest head state");
|
|
152
|
+
if (fields.schema.value !== STATE_SCHEMA) {
|
|
153
|
+
throw new Error("manifest head state schema is invalid");
|
|
154
|
+
}
|
|
155
|
+
const head = fields.head.value;
|
|
156
|
+
if (
|
|
157
|
+
head !== null &&
|
|
158
|
+
(!head ||
|
|
159
|
+
typeof head !== "object" ||
|
|
160
|
+
Array.isArray(head) ||
|
|
161
|
+
utilTypes.isProxy(head))
|
|
162
|
+
) {
|
|
163
|
+
throw new Error("manifest head state record is invalid");
|
|
164
|
+
}
|
|
165
|
+
headDigest(head);
|
|
166
|
+
return parsed;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const writeState = (state) => {
|
|
170
|
+
const bytes = Buffer.from(`${JSON.stringify(state)}\n`, "utf8");
|
|
171
|
+
if (bytes.length > maximumBytes)
|
|
172
|
+
throw new Error("manifest head exceeds maximumBytes");
|
|
173
|
+
const token = random();
|
|
174
|
+
if (typeof token !== "string" || !/^[a-zA-Z0-9_-]{8,128}$/u.test(token)) {
|
|
175
|
+
throw new Error("manifest head random token is invalid");
|
|
176
|
+
}
|
|
177
|
+
const temporary = `${file}.${token}.tmp`;
|
|
178
|
+
let descriptor;
|
|
179
|
+
try {
|
|
180
|
+
descriptor = fsImpl.openSync(temporary, "wx", 0o600);
|
|
181
|
+
fsImpl.writeFileSync(descriptor, bytes);
|
|
182
|
+
fsImpl.fsyncSync(descriptor);
|
|
183
|
+
fsImpl.closeSync(descriptor);
|
|
184
|
+
descriptor = undefined;
|
|
185
|
+
invokeCrashHook("after-stage", { file, temporary });
|
|
186
|
+
fsImpl.renameSync(temporary, file);
|
|
187
|
+
ensurePrivateFile(file, secureOptions);
|
|
188
|
+
descriptor = fsImpl.openSync(file, "r+");
|
|
189
|
+
fsImpl.fsyncSync(descriptor);
|
|
190
|
+
fsImpl.closeSync(descriptor);
|
|
191
|
+
descriptor = undefined;
|
|
192
|
+
syncDirectory(fsImpl, directory);
|
|
193
|
+
invokeCrashHook("after-rename", { file });
|
|
194
|
+
const readback = readBytes();
|
|
195
|
+
if (readback === null || !readback.equals(bytes)) {
|
|
196
|
+
throw new Error("manifest head durable readback differs");
|
|
197
|
+
}
|
|
198
|
+
} finally {
|
|
199
|
+
if (descriptor !== undefined) fsImpl.closeSync(descriptor);
|
|
200
|
+
try {
|
|
201
|
+
if (fsImpl.existsSync(temporary)) fsImpl.unlinkSync(temporary);
|
|
202
|
+
} catch {
|
|
203
|
+
// A private incomplete staging file cannot change the committed head.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
return Object.freeze({
|
|
209
|
+
descriptor: Object.freeze({
|
|
210
|
+
file,
|
|
211
|
+
localOnly: true,
|
|
212
|
+
schema: EVOLUTION_LEDGER_FILE_MANIFEST_HEAD_BACKEND_SCHEMA,
|
|
213
|
+
}),
|
|
214
|
+
load() {
|
|
215
|
+
return structuredClone(readState().head);
|
|
216
|
+
},
|
|
217
|
+
compareAndSet(request) {
|
|
218
|
+
return lock(
|
|
219
|
+
file,
|
|
220
|
+
() => {
|
|
221
|
+
const state = readState();
|
|
222
|
+
const current = state.head;
|
|
223
|
+
if (headDigest(current) !== request.expectedHeadDigest) {
|
|
224
|
+
return Object.freeze({
|
|
225
|
+
committed: false,
|
|
226
|
+
head: structuredClone(current),
|
|
227
|
+
schema: EVOLUTION_LEDGER_MANIFEST_HEAD_CAS_RESULT_SCHEMA,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
const next = structuredClone(request.nextHead);
|
|
231
|
+
writeState({ head: next, schema: STATE_SCHEMA });
|
|
232
|
+
return Object.freeze({
|
|
233
|
+
committed: true,
|
|
234
|
+
head: structuredClone(next),
|
|
235
|
+
schema: EVOLUTION_LEDGER_MANIFEST_HEAD_CAS_RESULT_SCHEMA,
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
{ _fs: fsImpl, failIfUnavailable: true },
|
|
239
|
+
);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|