chainlesschain 0.166.34 → 0.166.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-D8PJmSrh.js → AIOps-B_Nt1Qe4.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DG6DqHA3.js → ActionButton-CHQbie4X.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DiLleeWs.js → Analytics-Bm9364Lw.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DxYkhlHK.js → AppLayout-DTwlIh2v.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-BAW3Kdsi.js → Artifacts-OibP0qB4.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DBr4uY6E.js → Audit-B8Fwavkg.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Dw0r-bHX.js → BackgroundAgents-p1Vb2K2B.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-AyhwYsio.js → Backup-BI3QT-wI.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Cj8qJotr.js → BaseInput-BW_VRWWu.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DcvimQ0u.js → Chat-CUEhDQfq.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer--joJ1e2f.js → ChatBubbleRenderer-C5UVXdVE.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-C2mlU77l.js → Checkbox-URYsaaxv.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CAfc3YaV.js → Codegen-DJAwC80a.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CIxoxtJI.js → Col-DfRKFzc9.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DvufZoRg.js → Community-GOkGHuly.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-55JTa99J.js → Compact-CIMs13NC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BllWog6P.js → Compliance-BE-TQIxV.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-ThsFGc5p.js → Cowork-CYmbJiWw.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-CZjl5Ig2.js → Cron-Be3OIWyM.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DIAKAoQT.js → Crosschain-2xB2HS2D.js} +1 -1
- package/src/assets/web-panel/assets/{DID-ayygwEil.js → DID-B_lJO2yF.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C3Xjm2UO.js → Dashboard-DnVWqUER.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-C1S3zf-j.js → Dropdown-C7ZJvHVa.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DRvriU-H.js → EmailListRenderer-DLevT-0F.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-iSX2bmsD.js → FamilyGuardDashboard-BwhQ5BRU.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Dk0Stkv-.js → Federation-BkGI-Xkq.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DasXSGff.js → FormItemContext-BgIIbMd7.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Dv9XGlxI.js → GenericCardRenderer-qL-qN8Nb.js} +1 -1
- package/src/assets/web-panel/assets/{Git-B5PgXL3Z.js → Git-BTzgrC4U.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-B78x11e_.js → Governance-p28vdCN4.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-b7CX1RqW.js → Inference-Cqephrur.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph--MgBzMVp.js → KnowledgeGraph-C3v5Q4Zp.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CYCiGmVc.js → Logs-T6f0w32w.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-yrl10SWw.js → MarkdownRenderer-Dj5XPm3S.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CsbwQ0eM.js → Marketplace-ClDsvHDH.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-0GBWHVra.js → McpTools-C4P-_snx.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-fAywmAYr.js → Memory-BZZfG6y6.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BeNEyy3s.js → MobileBridge-DpIv_BLs.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Buz3NIQg.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-CqRhJB70.js → Mtc-HS2PJB79.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-BEyGZflc.js → MtcAudit-UmBrcU27.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-luz3EBT2.js → Multisig-C00sXiZf.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Dkbvp8IX.js → NLProgramming-Dj1ng_ph.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cf9I2K8j.js → Notes-C4LEVKlk.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-BTfuzynL.js → NotificationSettings-BWXwnekv.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CZHfK8Nn.js +1 -0
- package/src/assets/web-panel/assets/{Organization-BbG3-vDy.js → Organization-CFEtFPrV.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CMtFKEIQ.js → Overflow-DSz4MJK1.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-6SAEb_lU.js → P2P-DyTEytwG.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-B5QNGLVx.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-ClZxZKZl.js → Permissions-wEC_X7x4.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BYkyoZDT.js → PersonalDataHub-If5GZlGZ.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-B28ZgADI.js → Pipeline-LB4gqqmX.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BxtZywbB.js → Privacy-QVLhoxcN.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DPqoRnxK.js → ProjectInit-FaAbTYTo.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-QjKvMkau.js → ProjectSettings-DhMeNA21.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Cp0Z8ZG0.js → Projects-5L4lkWAZ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CrS-rbyb.js → Providers-DhZFp8fO.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-xTrhbVAU.js → QrScannerModal-BZuJ7_xi.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DFOqJoq-.js → QuickAsk-TXddPYYb.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DXlKqu2-.js → Recommend-DDuXIKYt.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BLLaZH8K.js → RemoteSession-CDrTXImv.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-CUgAdJ11.js → Reputation-B2xKaKzt.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DzOO3wP-.js → Row-DzAjWAOX.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B6GrRvh8.js → RssFeed-CKH38BLF.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Cj3zKLb3.js → Search-CuPqJYoQ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-LnktzidI.js → Security-DmAd2hxz.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BQ6sIc2K.js → Services-Bt3nljnY.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CCSV3Jbw.js → Skeleton-CP7RH093.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CpoQ4SNk.js → Skills-DKNeMBTx.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DbAbgRPq.js → Sla-CbpM8Nrd.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C4cJ_hoD.js → SpeechSettings-DTk-vTzF.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BKFzjN9Z.js → SyncSettings-DKiplwmc.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-z-lOm8fx.js +1 -0
- package/src/assets/web-panel/assets/{Templates-DTEQfFAQ.js → Templates-Rb-Q0DGX.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-wToJWKXB.js → Tenant-C2sqDUnR.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-1s3c7kmF.js → Terminal-DcyzskEE.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BcfsYW3u.js → TimelineRenderer-lg0Eam7N.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Byez6FY0.js → Tokens-D9USIMIA.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Cq8mZO88.js → Trigger-BQpaQJ5k.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DUrMmEwR.js → Trust-C-Mik04s.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CVHwXrVU.js → UkeySign-DrnZ_o56.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CDKbTRUS.js → VideoEditing-BDOsoZi0.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BqnEBIOy.js → Wallet-Ct_bleI7.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CeTMpj7_.js → WebAuthn-B6b6FDao.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-F6OgjV3C.js → WorkflowEditor-ZUJMaIHr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CMHUCwq3.js → chat-BII20OJy.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DzVM_fpJ.js → colors-DEsJRV3n.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Hhmg51yT.js → compact-item-DoDYAzN9.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-SwHt7x3Q.js → createContext-o_nofnVW.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-0tgDCszN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-HGNKkn9A.js → hasIn-CAsABTHC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cc-d6f0F.js → index-B0ApxOMw.js} +1 -1
- package/src/assets/web-panel/assets/{index-E-bI8HpV.js → index-B4SfcwZA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJqNTF1S.js → index-B9Z75LfA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgoefbLs.js → index-B9hYHPhs.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDtzUt75.js → index-BDRh3v44.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_HgLPC5.js → index-BcYbQoY7.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1QJxiOL.js → index-Bmwo5wQG.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChLHLBkF.js → index-Bn2ZPUXO.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5dcWBqg.js → index-BnbnmRqg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bq9MMKno.js → index-Bqyg3PME.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ba2Kszdy.js → index-BrIIJ2s-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDt4wSl6.js → index-BxT8yTwS.js} +1 -1
- package/src/assets/web-panel/assets/{index-COOcGOyK.js → index-Bycj011d.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQT5thlV.js → index-BzMA6Ra3.js} +1 -1
- package/src/assets/web-panel/assets/{index-F7Grz3-G.js → index-C1YDdyQ1.js} +1 -1
- package/src/assets/web-panel/assets/{index-jrRtmiUh.js → index-CE2J04dA.js} +1 -1
- package/src/assets/web-panel/assets/index-CFF8XEFE.js +1 -0
- package/src/assets/web-panel/assets/{index-BDE-WiE5.js → index-CQE-WMEU.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6YQN5_R.js → index-CVQSl1uY.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlHB5eax.js → index-CeJSTcXw.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-YkiVyL.js → index-CeWJqXNX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRikmONJ.js → index-Cf3sFEoE.js} +1 -1
- package/src/assets/web-panel/assets/{index-MDdTkUpA.js → index-D5Fd4tdL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMG_2fK2.js → index-DDQzBK3l.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co4RcBua.js → index-DQmX_-hk.js} +1 -1
- package/src/assets/web-panel/assets/{index--iOqM_AI.js → index-DRAA2McB.js} +1 -1
- package/src/assets/web-panel/assets/{index-pwLuJGFh.js → index-DU2Rpx1m.js} +1 -1
- package/src/assets/web-panel/assets/index-DVUEISry.js +1 -0
- package/src/assets/web-panel/assets/{index-AQcDW8bB.js → index-DWZNYecl.js} +3 -3
- package/src/assets/web-panel/assets/{index-oz0Xxs8D.js → index-D_CMB_wC.js} +1 -1
- package/src/assets/web-panel/assets/{index-aKV4Py_2.js → index-Dbb34cAr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BstLYy9q.js → index-DciLb_Ww.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyZUrnje.js → index-Dgt1mKby.js} +1 -1
- package/src/assets/web-panel/assets/{index-sYkFp9A4.js → index-Dhnau6uw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDhCx17_.js → index-DrsAF-Ar.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dcg7nQDe.js → index-DsoFTdA2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWkWh48N.js → index-TSwbtCYG.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cnm4zroJ.js → index-kIyLNyoF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmPYmoY3.js → index-tCeNfxv4.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C_2QM45F.js → initDefaultProps-CNjeeoKE.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DkxY04tR.js → motion-CBT8IZDm.js} +1 -1
- package/src/assets/web-panel/assets/{move-D0CJlA7D.js → move-h1MOxjqv.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DjP38YUD.js → mtc-parser-C_3pqNmn.js} +1 -1
- package/src/assets/web-panel/assets/{omit-C9CJDaGT.js → omit-CMUnPFDk.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D5Wq2fa8.js → pickAttrs-p8_c1xq0.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DJ6Dixs7.js → placementArrow-D6O67r23.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DMmM0Dee.js → responsiveObserve-DquhepAK.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DipmpYA-.js → slide-CZ-m6gmS.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DSqw6_nU.js → statusUtils-k9ejZFtd.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-t828MYjI.js → styleChecker-NX6_137R.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-COsSNzDQ.js → useFlexGapSupport-bCqR70lP.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Bq5IEzv_.js → useFs-2l0vSqyY.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CUKM0HdW.js → usePersonalDataHub-1yccm4O7.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-l-JwAt2M.js → vnode-CxQzajDo.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D7xkW-gD.js → zoom-CrTKwFyb.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/hub.js +21 -6
- package/src/commands/llm.js +4 -3
- package/src/commands/stream.js +28 -7
- package/src/commands/ui.js +10 -2
- package/src/data/changelog.json +39 -0
- package/src/gateways/ws/chat-intent-protocol.js +4 -1
- package/src/gateways/ws/llm-chat-protocol.js +29 -3
- package/src/gateways/ws/message-dispatcher.js +3 -1
- package/src/gateways/ws/personal-data-hub-protocol.js +24 -10
- package/src/gateways/ws/session-core-protocol.js +23 -6
- package/src/gateways/ws/session-protocol.js +1 -0
- package/src/lib/__tests__/provider-stream.test.js +6 -8
- package/src/lib/chat-core.js +67 -9
- package/src/lib/chat-intent-service.js +138 -39
- package/src/lib/evolution/evolution-artifact-ports.js +40 -3
- package/src/lib/evolution/evolution-deployment-loader.js +7 -1
- package/src/lib/evolution/evolution-file-identity.js +50 -0
- package/src/lib/evolution/evolution-file-witness.js +206 -91
- package/src/lib/evolution/evolution-ledger-file-backend.js +4 -0
- package/src/lib/evolution/evolution-ledger.js +37 -5
- package/src/lib/evolution/evolution-witness-segments.js +330 -0
- package/src/lib/evolution/governed-hub-llm.js +56 -0
- package/src/lib/evolution/governed-hub-resolver.js +65 -0
- package/src/lib/evolution/governed-hub-skill.js +36 -0
- package/src/lib/evolution/governed-model-turn.js +88 -0
- package/src/lib/evolution/progressive-canary-external-watchdog.js +6 -4
- package/src/lib/evolution/skill-candidate-registry.js +45 -11
- package/src/lib/evolution/skill-release-registry.js +46 -12
- package/src/lib/llm-connection-probe.js +21 -4
- package/src/lib/personal-data-hub-wiring.js +49 -0
- package/src/lib/provider-stream.js +94 -45
- package/src/lib/ws-chat-handler.js +19 -2
- package/src/assets/web-panel/assets/MobileProjects-Zt1cpErd.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-DBmCtl2H.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-CWG2OLvh.js +0 -7
- package/src/assets/web-panel/assets/Tasks-Cf4bl7NS.js +0 -1
- package/src/assets/web-panel/assets/devWarning-5q_Y0E88.js +0 -1
- package/src/assets/web-panel/assets/index-dl9TJMh5.js +0 -1
- package/src/assets/web-panel/assets/index-x0DDgUaT.js +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isAffectedWindowsZeroDeviceStatRuntime,
|
|
3
|
+
sameFileStatIdentity,
|
|
4
|
+
samePathHandleFileIdentity,
|
|
5
|
+
samePathHandleStableFileIdentity,
|
|
6
|
+
withTrustedFileParentSync,
|
|
7
|
+
} from "../secure-file-identity.js";
|
|
8
|
+
|
|
9
|
+
// Keep ordinary same-API comparisons exact. Only affected Windows runtimes
|
|
10
|
+
// bridge path/handle device projections, with a separately held directory
|
|
11
|
+
// device; neither file inode nor mutation metadata is relaxed.
|
|
12
|
+
export function withEvolutionFileIdentity(
|
|
13
|
+
fsImpl,
|
|
14
|
+
filePath,
|
|
15
|
+
operation,
|
|
16
|
+
runtime = undefined,
|
|
17
|
+
) {
|
|
18
|
+
if (!isAffectedWindowsZeroDeviceStatRuntime(runtime)) {
|
|
19
|
+
return operation(sameFileStatIdentity, (pathStat, handleStat) =>
|
|
20
|
+
samePathHandleStableFileIdentity(
|
|
21
|
+
pathStat,
|
|
22
|
+
handleStat,
|
|
23
|
+
pathStat.dev,
|
|
24
|
+
runtime,
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return withTrustedFileParentSync(
|
|
29
|
+
fsImpl,
|
|
30
|
+
filePath,
|
|
31
|
+
({ parentDevice }) =>
|
|
32
|
+
operation(
|
|
33
|
+
(pathStat, handleStat) =>
|
|
34
|
+
samePathHandleFileIdentity(
|
|
35
|
+
pathStat,
|
|
36
|
+
handleStat,
|
|
37
|
+
parentDevice,
|
|
38
|
+
runtime,
|
|
39
|
+
),
|
|
40
|
+
(pathStat, handleStat) =>
|
|
41
|
+
samePathHandleStableFileIdentity(
|
|
42
|
+
pathStat,
|
|
43
|
+
handleStat,
|
|
44
|
+
parentDevice,
|
|
45
|
+
runtime,
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
{ runtime },
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { types } from "node:util";
|
|
4
5
|
import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
|
|
5
6
|
import { withFileLock } from "../with-file-lock.js";
|
|
6
7
|
import { readBoundedDescriptor } from "./bounded-descriptor-read.js";
|
|
8
|
+
import { withEvolutionFileIdentity } from "./evolution-file-identity.js";
|
|
9
|
+
import {
|
|
10
|
+
createEvolutionWitnessSegments,
|
|
11
|
+
EVOLUTION_SEGMENTED_WITNESS_SCHEMA,
|
|
12
|
+
} from "./evolution-witness-segments.js";
|
|
7
13
|
import {
|
|
8
14
|
EVOLUTION_LEDGER_WITNESS_ANCESTRY_SCHEMA,
|
|
9
15
|
EVOLUTION_LEDGER_WITNESS_SCHEMA,
|
|
@@ -137,16 +143,27 @@ function nullableIdentifier(value, label) {
|
|
|
137
143
|
}
|
|
138
144
|
|
|
139
145
|
function capture(owner, name) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return
|
|
146
|
+
const method = captureOptional(owner, name);
|
|
147
|
+
if (method === null) throw new TypeError(`${name} port is required`);
|
|
148
|
+
return method;
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
function captureOptional(owner, name) {
|
|
146
|
-
if (owner
|
|
147
|
-
if (typeof owner
|
|
148
|
-
throw new TypeError(`${name} port must be
|
|
149
|
-
|
|
152
|
+
if (owner === null || owner === undefined) return null;
|
|
153
|
+
if (typeof owner !== "object" || types.isProxy(owner)) {
|
|
154
|
+
throw new TypeError(`${name} port owner must be an object without Proxy`);
|
|
155
|
+
}
|
|
156
|
+
const descriptor = Object.getOwnPropertyDescriptor(owner, name);
|
|
157
|
+
if (!descriptor) return null;
|
|
158
|
+
if (
|
|
159
|
+
!("value" in descriptor) ||
|
|
160
|
+
typeof descriptor.value !== "function" ||
|
|
161
|
+
types.isProxy(descriptor.value)
|
|
162
|
+
) {
|
|
163
|
+
throw new TypeError(`${name} port must be an own data function`);
|
|
164
|
+
}
|
|
165
|
+
const method = descriptor.value;
|
|
166
|
+
return (...args) => Reflect.apply(method, owner, args);
|
|
150
167
|
}
|
|
151
168
|
|
|
152
169
|
function normalizeTrustEpoch(value) {
|
|
@@ -205,6 +222,7 @@ export function createEvolutionFileWitness({
|
|
|
205
222
|
lock = withFileLock,
|
|
206
223
|
random = () => crypto.randomBytes(16).toString("hex"),
|
|
207
224
|
maximumBytes = DEFAULT_MAXIMUM_BYTES,
|
|
225
|
+
maximumHistoryBytes = maximumBytes,
|
|
208
226
|
} = {}) {
|
|
209
227
|
if (!IDENTIFIER.test(id || "")) throw new TypeError("witness id is invalid");
|
|
210
228
|
const target = path.resolve(requiredString(filePath, "filePath"));
|
|
@@ -219,6 +237,14 @@ export function createEvolutionFileWitness({
|
|
|
219
237
|
if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 4096) {
|
|
220
238
|
throw new TypeError("maximumBytes must be a safe integer of at least 4096");
|
|
221
239
|
}
|
|
240
|
+
if (
|
|
241
|
+
!Number.isSafeInteger(maximumHistoryBytes) ||
|
|
242
|
+
maximumHistoryBytes < 4096
|
|
243
|
+
) {
|
|
244
|
+
throw new TypeError(
|
|
245
|
+
"maximumHistoryBytes must be a safe integer of at least 4096",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
222
248
|
const secureOptions = { deps: { fs: fsImpl }, failIfUnavailable: true };
|
|
223
249
|
ensurePrivateDirectory(directory, secureOptions);
|
|
224
250
|
// Cache immutable encodings, never an authority decision. Keep the earliest
|
|
@@ -470,7 +496,7 @@ export function createEvolutionFileWitness({
|
|
|
470
496
|
};
|
|
471
497
|
};
|
|
472
498
|
|
|
473
|
-
const validateStore = (store, trustEpoch = null) => {
|
|
499
|
+
const validateStore = (store, trustEpoch = null, priorRecord = null) => {
|
|
474
500
|
assertExactKeys(store, STORE_KEYS, "witness store");
|
|
475
501
|
if (
|
|
476
502
|
store.schema !== EVOLUTION_FILE_WITNESS_STORE_SCHEMA ||
|
|
@@ -482,23 +508,24 @@ export function createEvolutionFileWitness({
|
|
|
482
508
|
}
|
|
483
509
|
const genesis = store.history[0];
|
|
484
510
|
if (
|
|
485
|
-
|
|
486
|
-
genesis.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
511
|
+
!priorRecord &&
|
|
512
|
+
(genesis.status !== "absent" ||
|
|
513
|
+
genesis.generation !== 0 ||
|
|
514
|
+
genesis.previousWitnessDigest !== null ||
|
|
515
|
+
genesis.discardAccumulatorDigest !== emptyDiscardDigest ||
|
|
516
|
+
[
|
|
517
|
+
genesis.anchorDigest,
|
|
518
|
+
genesis.epoch,
|
|
519
|
+
genesis.headDigest,
|
|
520
|
+
genesis.identityDigest,
|
|
521
|
+
genesis.ledgerId,
|
|
522
|
+
genesis.payloadDigest,
|
|
523
|
+
genesis.segmentDigest,
|
|
524
|
+
genesis.sequence,
|
|
525
|
+
genesis.storeMarkerDigest,
|
|
526
|
+
genesis.storeMarkerEntryDigest,
|
|
527
|
+
genesis.storeMarkerId,
|
|
528
|
+
].some((entry) => entry !== null))
|
|
502
529
|
) {
|
|
503
530
|
throw new Error("witness genesis is invalid");
|
|
504
531
|
}
|
|
@@ -514,10 +541,13 @@ export function createEvolutionFileWitness({
|
|
|
514
541
|
safeCounter(discard.sequence, "witness discard sequence");
|
|
515
542
|
}
|
|
516
543
|
let discardIndex = 0;
|
|
544
|
+
let previous = priorRecord;
|
|
517
545
|
for (const [index, record] of store.history.entries()) {
|
|
518
546
|
verifySigned(record, trustEpoch);
|
|
519
|
-
if (index === 0)
|
|
520
|
-
|
|
547
|
+
if (index === 0 && !previous) {
|
|
548
|
+
previous = record;
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
521
551
|
if (
|
|
522
552
|
record.status !== "committed" ||
|
|
523
553
|
record.previousWitnessDigest !== previous.witnessDigest ||
|
|
@@ -544,6 +574,7 @@ export function createEvolutionFileWitness({
|
|
|
544
574
|
throw new Error("witness discard history is invalid");
|
|
545
575
|
}
|
|
546
576
|
}
|
|
577
|
+
previous = record;
|
|
547
578
|
}
|
|
548
579
|
if (
|
|
549
580
|
discardIndex !== store.discardedAnchors.length ||
|
|
@@ -556,16 +587,24 @@ export function createEvolutionFileWitness({
|
|
|
556
587
|
return store;
|
|
557
588
|
};
|
|
558
589
|
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
590
|
+
const readBytes = (file, byteLimit = maximumBytes) =>
|
|
591
|
+
withEvolutionFileIdentity(fsImpl, file, (samePathHandle) =>
|
|
592
|
+
readBytesWithIdentity(
|
|
593
|
+
file,
|
|
594
|
+
Math.min(maximumBytes, byteLimit),
|
|
595
|
+
samePathHandle,
|
|
596
|
+
),
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
const readBytesWithIdentity = (file, byteLimit, samePathHandle) => {
|
|
600
|
+
ensurePrivateFile(file, secureOptions);
|
|
601
|
+
const stat = fsImpl.lstatSync(file);
|
|
563
602
|
if (
|
|
564
603
|
!stat.isFile() ||
|
|
565
604
|
stat.isSymbolicLink() ||
|
|
566
605
|
stat.nlink !== 1 ||
|
|
567
606
|
stat.size < 2 ||
|
|
568
|
-
stat.size >
|
|
607
|
+
stat.size > byteLimit
|
|
569
608
|
) {
|
|
570
609
|
throw new Error("witness store size is invalid");
|
|
571
610
|
}
|
|
@@ -580,48 +619,56 @@ export function createEvolutionFileWitness({
|
|
|
580
619
|
observed.ctimeMs === stat.ctimeMs;
|
|
581
620
|
try {
|
|
582
621
|
descriptor = fsImpl.openSync(
|
|
583
|
-
|
|
622
|
+
file,
|
|
584
623
|
fsImpl.constants.O_RDONLY | (fsImpl.constants.O_NOFOLLOW || 0),
|
|
585
624
|
);
|
|
586
|
-
if (!
|
|
625
|
+
if (!samePathHandle(stat, fsImpl.fstatSync(descriptor))) {
|
|
587
626
|
throw new Error("witness store changed while opening");
|
|
588
627
|
}
|
|
589
628
|
const bytes = readBoundedDescriptor(
|
|
590
629
|
fsImpl,
|
|
591
630
|
descriptor,
|
|
592
631
|
stat.size,
|
|
593
|
-
|
|
632
|
+
byteLimit,
|
|
594
633
|
);
|
|
595
|
-
const afterPath = fsImpl.lstatSync(
|
|
634
|
+
const afterPath = fsImpl.lstatSync(file);
|
|
596
635
|
if (
|
|
597
|
-
!
|
|
636
|
+
!samePathHandle(stat, fsImpl.fstatSync(descriptor)) ||
|
|
598
637
|
afterPath.isSymbolicLink() ||
|
|
599
638
|
!sameFile(afterPath)
|
|
600
639
|
) {
|
|
601
640
|
throw new Error("witness store changed while reading");
|
|
602
641
|
}
|
|
603
|
-
|
|
604
|
-
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
605
|
-
);
|
|
606
|
-
for (
|
|
607
|
-
let attempt = 0;
|
|
608
|
-
attempt < MAXIMUM_TRUST_EPOCH_RETRIES;
|
|
609
|
-
attempt += 1
|
|
610
|
-
) {
|
|
611
|
-
const trustEpoch = currentTrustEpoch();
|
|
612
|
-
const store = validateStore(parsed, trustEpoch);
|
|
613
|
-
if (trustEpoch === currentTrustEpoch()) return store;
|
|
614
|
-
}
|
|
615
|
-
throw new Error("witness verifier trust epoch changed while reading");
|
|
642
|
+
return bytes;
|
|
616
643
|
} finally {
|
|
617
644
|
if (descriptor !== undefined) fsImpl.closeSync(descriptor);
|
|
618
645
|
}
|
|
619
646
|
};
|
|
620
647
|
|
|
621
|
-
const
|
|
648
|
+
const readStore = (options = {}) => {
|
|
649
|
+
if (!fsImpl.existsSync(target)) return emptyStore();
|
|
650
|
+
const bytes = readBytes(target, maximumHistoryBytes);
|
|
651
|
+
const parsed = JSON.parse(
|
|
652
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
653
|
+
);
|
|
654
|
+
for (let attempt = 0; attempt < MAXIMUM_TRUST_EPOCH_RETRIES; attempt += 1) {
|
|
655
|
+
const trustEpoch = currentTrustEpoch();
|
|
656
|
+
const store =
|
|
657
|
+
parsed.schema === EVOLUTION_SEGMENTED_WITNESS_SCHEMA
|
|
658
|
+
? segments.load(parsed, trustEpoch, {
|
|
659
|
+
...options,
|
|
660
|
+
headBytes: bytes.length,
|
|
661
|
+
})
|
|
662
|
+
: validateStore(parsed, trustEpoch);
|
|
663
|
+
if (trustEpoch === currentTrustEpoch()) return store;
|
|
664
|
+
}
|
|
665
|
+
throw new Error("witness verifier trust epoch changed while reading");
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
const syncDirectory = (directoryPath = directory) => {
|
|
622
669
|
let descriptor;
|
|
623
670
|
try {
|
|
624
|
-
descriptor = fsImpl.openSync(
|
|
671
|
+
descriptor = fsImpl.openSync(directoryPath, "r");
|
|
625
672
|
fsImpl.fsyncSync(descriptor);
|
|
626
673
|
} catch (error) {
|
|
627
674
|
if (
|
|
@@ -634,12 +681,8 @@ export function createEvolutionFileWitness({
|
|
|
634
681
|
}
|
|
635
682
|
};
|
|
636
683
|
|
|
637
|
-
const
|
|
638
|
-
const temporary = `${
|
|
639
|
-
// Preserve the canonical on-disk format without reserializing every
|
|
640
|
-
// unchanged historical record. Uncached records take the normal path.
|
|
641
|
-
const encoded = `{"current":${encodeRecord(store.current)},"discardedAnchors":${canonical(store.discardedAnchors)},"history":[${store.history.map(encodeRecord).join(",")}],"schema":${JSON.stringify(store.schema)}}`;
|
|
642
|
-
const bytes = Buffer.from(`${encoded}\n`, "utf8");
|
|
684
|
+
const writeBytes = (file, bytes) => {
|
|
685
|
+
const temporary = `${file}.${requiredString(random(), "random token")}.tmp`;
|
|
643
686
|
if (bytes.length > maximumBytes) {
|
|
644
687
|
throw new Error("witness store exceeds its configured maximum size");
|
|
645
688
|
}
|
|
@@ -650,14 +693,16 @@ export function createEvolutionFileWitness({
|
|
|
650
693
|
fsImpl.fsyncSync(descriptor);
|
|
651
694
|
fsImpl.closeSync(descriptor);
|
|
652
695
|
descriptor = undefined;
|
|
653
|
-
fsImpl.renameSync(temporary,
|
|
654
|
-
ensurePrivateFile(
|
|
655
|
-
descriptor = fsImpl.openSync(
|
|
696
|
+
fsImpl.renameSync(temporary, file);
|
|
697
|
+
ensurePrivateFile(file, secureOptions);
|
|
698
|
+
descriptor = fsImpl.openSync(file, "r+");
|
|
656
699
|
fsImpl.fsyncSync(descriptor);
|
|
657
700
|
fsImpl.closeSync(descriptor);
|
|
658
701
|
descriptor = undefined;
|
|
659
|
-
syncDirectory();
|
|
660
|
-
|
|
702
|
+
syncDirectory(path.dirname(file));
|
|
703
|
+
if (!readBytes(file).equals(bytes)) {
|
|
704
|
+
throw new Error("witness published bytes failed durable readback");
|
|
705
|
+
}
|
|
661
706
|
} finally {
|
|
662
707
|
if (descriptor !== undefined) fsImpl.closeSync(descriptor);
|
|
663
708
|
try {
|
|
@@ -668,9 +713,57 @@ export function createEvolutionFileWitness({
|
|
|
668
713
|
}
|
|
669
714
|
};
|
|
670
715
|
|
|
716
|
+
const segments = createEvolutionWitnessSegments({
|
|
717
|
+
filePath: target,
|
|
718
|
+
fsImpl,
|
|
719
|
+
readBytes,
|
|
720
|
+
writeBytes,
|
|
721
|
+
validateStore,
|
|
722
|
+
legacySchema: EVOLUTION_FILE_WITNESS_STORE_SCHEMA,
|
|
723
|
+
canonical,
|
|
724
|
+
hash,
|
|
725
|
+
encodeRecord,
|
|
726
|
+
maximumHistoryBytes,
|
|
727
|
+
confirmDurable(file, expectedBytes) {
|
|
728
|
+
let descriptor;
|
|
729
|
+
try {
|
|
730
|
+
descriptor = fsImpl.openSync(
|
|
731
|
+
file,
|
|
732
|
+
fsImpl.constants.O_RDWR | (fsImpl.constants.O_NOFOLLOW || 0),
|
|
733
|
+
);
|
|
734
|
+
const observed = fsImpl.fstatSync(descriptor);
|
|
735
|
+
if (!observed.isFile() || observed.nlink !== 1) {
|
|
736
|
+
throw new Error(
|
|
737
|
+
"witness segment identity changed before durability confirmation",
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
fsImpl.fsyncSync(descriptor);
|
|
741
|
+
} finally {
|
|
742
|
+
if (descriptor !== undefined) fsImpl.closeSync(descriptor);
|
|
743
|
+
}
|
|
744
|
+
syncDirectory(path.dirname(file));
|
|
745
|
+
if (!readBytes(file).equals(expectedBytes)) {
|
|
746
|
+
throw new Error(
|
|
747
|
+
"witness segment changed during durability confirmation",
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
const publish = (store) => {
|
|
754
|
+
if (segments.handles(store)) return segments.publish(store);
|
|
755
|
+
const encoded = `{"current":${encodeRecord(store.current)},"discardedAnchors":${canonical(store.discardedAnchors)},"history":[${store.history.map(encodeRecord).join(",")}],"schema":${JSON.stringify(store.schema)}}`;
|
|
756
|
+
const bytes = Buffer.from(`${encoded}\n`, "utf8");
|
|
757
|
+
if (bytes.length > maximumHistoryBytes)
|
|
758
|
+
throw new Error("witness history exceeds its configured maximum size");
|
|
759
|
+
writeBytes(target, bytes);
|
|
760
|
+
return store.current;
|
|
761
|
+
};
|
|
762
|
+
|
|
671
763
|
const underLock = (operation) =>
|
|
672
764
|
lock(target, operation, { failIfUnavailable: true, _fs: fsImpl });
|
|
673
765
|
const discarded = (store, snapshot) =>
|
|
766
|
+
segments.isDiscarded(store, snapshot) ||
|
|
674
767
|
store.discardedAnchors.some(
|
|
675
768
|
(entry) =>
|
|
676
769
|
entry.anchorDigest === snapshot.anchorDigest ||
|
|
@@ -681,8 +774,10 @@ export function createEvolutionFileWitness({
|
|
|
681
774
|
const port = {
|
|
682
775
|
id,
|
|
683
776
|
read: () => readStore().current,
|
|
684
|
-
initialize: ({ expected, snapshot }) =>
|
|
685
|
-
|
|
777
|
+
initialize: ({ expected, snapshot }) => {
|
|
778
|
+
expected = clone(expected);
|
|
779
|
+
snapshot = clone(snapshot);
|
|
780
|
+
return underLock(() => {
|
|
686
781
|
const store = readStore();
|
|
687
782
|
if (store.current.witnessDigest !== expected.witnessDigest) {
|
|
688
783
|
return store.current;
|
|
@@ -691,10 +786,14 @@ export function createEvolutionFileWitness({
|
|
|
691
786
|
store.current = witnessRecord(snapshot, store.current);
|
|
692
787
|
store.history.push(store.current);
|
|
693
788
|
return publish(store);
|
|
694
|
-
})
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
789
|
+
});
|
|
790
|
+
},
|
|
791
|
+
compareAndSwap: ({ discard = null, expected, next }) => {
|
|
792
|
+
expected = clone(expected);
|
|
793
|
+
next = clone(next);
|
|
794
|
+
discard = clone(discard);
|
|
795
|
+
return underLock(() => {
|
|
796
|
+
const store = readStore({ discards: [next, discard] });
|
|
698
797
|
if (store.current.witnessDigest !== expected.witnessDigest) {
|
|
699
798
|
return store.current;
|
|
700
799
|
}
|
|
@@ -715,9 +814,22 @@ export function createEvolutionFileWitness({
|
|
|
715
814
|
store.current = witnessRecord(next, store.current, discard);
|
|
716
815
|
store.history.push(store.current);
|
|
717
816
|
return publish(store);
|
|
718
|
-
})
|
|
817
|
+
});
|
|
818
|
+
},
|
|
719
819
|
proveAncestry: ({ ancestor, descendant }) => {
|
|
720
|
-
|
|
820
|
+
ancestor = clone(ancestor);
|
|
821
|
+
descendant = clone(descendant);
|
|
822
|
+
const store = readStore({ records: [ancestor, descendant] });
|
|
823
|
+
if (segments.isSegmented(store)) {
|
|
824
|
+
const first = segments.lookup(store, ancestor);
|
|
825
|
+
const last = segments.lookup(store, descendant);
|
|
826
|
+
if (!first || !last || first.generation > last.generation) {
|
|
827
|
+
throw new Error(
|
|
828
|
+
"witness ancestry is absent or checkpoint is not exactly bound",
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
return ancestryProof(first, last);
|
|
832
|
+
}
|
|
721
833
|
const first = store.history.findIndex(
|
|
722
834
|
(entry) => entry.witnessDigest === ancestor.witnessDigest,
|
|
723
835
|
);
|
|
@@ -744,27 +856,30 @@ export function createEvolutionFileWitness({
|
|
|
744
856
|
throw new Error("witness ancestry is not contiguous");
|
|
745
857
|
}
|
|
746
858
|
}
|
|
747
|
-
|
|
748
|
-
...trust,
|
|
749
|
-
ancestorDigest: persistedAncestor.witnessDigest,
|
|
750
|
-
ancestorGeneration: persistedAncestor.generation,
|
|
751
|
-
authenticated: true,
|
|
752
|
-
descendantDigest: persistedDescendant.witnessDigest,
|
|
753
|
-
descendantGeneration: persistedDescendant.generation,
|
|
754
|
-
durable: true,
|
|
755
|
-
epoch: persistedAncestor.epoch,
|
|
756
|
-
included: true,
|
|
757
|
-
ledgerId: persistedAncestor.ledgerId,
|
|
758
|
-
schema: EVOLUTION_LEDGER_WITNESS_ANCESTRY_SCHEMA,
|
|
759
|
-
witnessId: id,
|
|
760
|
-
};
|
|
761
|
-
return signed(
|
|
762
|
-
ANCESTRY_DOMAIN,
|
|
763
|
-
core,
|
|
764
|
-
"proofDigest",
|
|
765
|
-
"evolution-ledger-witness-ancestry",
|
|
766
|
-
);
|
|
859
|
+
return ancestryProof(persistedAncestor, persistedDescendant);
|
|
767
860
|
},
|
|
768
861
|
};
|
|
862
|
+
const ancestryProof = (persistedAncestor, persistedDescendant) => {
|
|
863
|
+
const core = {
|
|
864
|
+
...trust,
|
|
865
|
+
ancestorDigest: persistedAncestor.witnessDigest,
|
|
866
|
+
ancestorGeneration: persistedAncestor.generation,
|
|
867
|
+
authenticated: true,
|
|
868
|
+
descendantDigest: persistedDescendant.witnessDigest,
|
|
869
|
+
descendantGeneration: persistedDescendant.generation,
|
|
870
|
+
durable: true,
|
|
871
|
+
epoch: persistedAncestor.epoch,
|
|
872
|
+
included: true,
|
|
873
|
+
ledgerId: persistedAncestor.ledgerId,
|
|
874
|
+
schema: EVOLUTION_LEDGER_WITNESS_ANCESTRY_SCHEMA,
|
|
875
|
+
witnessId: id,
|
|
876
|
+
};
|
|
877
|
+
return signed(
|
|
878
|
+
ANCESTRY_DOMAIN,
|
|
879
|
+
core,
|
|
880
|
+
"proofDigest",
|
|
881
|
+
"evolution-ledger-witness-ancestry",
|
|
882
|
+
);
|
|
883
|
+
};
|
|
769
884
|
return Object.freeze(port);
|
|
770
885
|
}
|
|
@@ -109,6 +109,7 @@ export function createEvolutionLedgerFileBackend({
|
|
|
109
109
|
lockTimeoutMs,
|
|
110
110
|
crashHook = null,
|
|
111
111
|
witnessMaximumBytes,
|
|
112
|
+
witnessMaximumHistoryBytes,
|
|
112
113
|
} = {}) {
|
|
113
114
|
if (typeof artifactResolver !== "function") {
|
|
114
115
|
throw new TypeError("artifactResolver port is required");
|
|
@@ -145,6 +146,9 @@ export function createEvolutionLedgerFileBackend({
|
|
|
145
146
|
...(witnessMaximumBytes === undefined
|
|
146
147
|
? {}
|
|
147
148
|
: { maximumBytes: witnessMaximumBytes }),
|
|
149
|
+
...(witnessMaximumHistoryBytes === undefined
|
|
150
|
+
? {}
|
|
151
|
+
: { maximumHistoryBytes: witnessMaximumHistoryBytes }),
|
|
148
152
|
};
|
|
149
153
|
const witness = createEvolutionFileWitness(witnessOptions);
|
|
150
154
|
const ledger = new EvolutionLedger({
|
|
@@ -3,6 +3,8 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { types as utilTypes } from "node:util";
|
|
5
5
|
import { readBoundedDescriptor } from "./bounded-descriptor-read.js";
|
|
6
|
+
import { withEvolutionFileIdentity } from "./evolution-file-identity.js";
|
|
7
|
+
import { sameFileStatIdentity } from "../secure-file-identity.js";
|
|
6
8
|
import { getHomeDir } from "../paths.js";
|
|
7
9
|
import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
|
|
8
10
|
import { withFileLock } from "../with-file-lock.js";
|
|
@@ -2768,6 +2770,24 @@ export class EvolutionLedger {
|
|
|
2768
2770
|
}
|
|
2769
2771
|
|
|
2770
2772
|
#readCanonicalFile(filePath, maximum, label, { allowMissing = false } = {}) {
|
|
2773
|
+
return withEvolutionFileIdentity(this.#fs, filePath, (samePathHandle) =>
|
|
2774
|
+
this.#readCanonicalFileWithIdentity(
|
|
2775
|
+
filePath,
|
|
2776
|
+
maximum,
|
|
2777
|
+
label,
|
|
2778
|
+
allowMissing,
|
|
2779
|
+
samePathHandle,
|
|
2780
|
+
),
|
|
2781
|
+
);
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
#readCanonicalFileWithIdentity(
|
|
2785
|
+
filePath,
|
|
2786
|
+
maximum,
|
|
2787
|
+
label,
|
|
2788
|
+
allowMissing,
|
|
2789
|
+
samePathHandle,
|
|
2790
|
+
) {
|
|
2771
2791
|
this.#assertBoundaries();
|
|
2772
2792
|
let before;
|
|
2773
2793
|
try {
|
|
@@ -2805,7 +2825,7 @@ export class EvolutionLedger {
|
|
|
2805
2825
|
if (
|
|
2806
2826
|
!opened.isFile() ||
|
|
2807
2827
|
opened.nlink !== 1 ||
|
|
2808
|
-
|
|
2828
|
+
!samePathHandle(before, opened) ||
|
|
2809
2829
|
opened.size !== before.size
|
|
2810
2830
|
) {
|
|
2811
2831
|
throw ledgerError(
|
|
@@ -2831,7 +2851,7 @@ export class EvolutionLedger {
|
|
|
2831
2851
|
const after = this.#fs.fstatSync(descriptor);
|
|
2832
2852
|
if (
|
|
2833
2853
|
after.nlink !== 1 ||
|
|
2834
|
-
|
|
2854
|
+
!sameFileStatIdentity(opened, after) ||
|
|
2835
2855
|
after.size !== opened.size ||
|
|
2836
2856
|
bytes.length !== opened.size
|
|
2837
2857
|
) {
|
|
@@ -2875,7 +2895,9 @@ export class EvolutionLedger {
|
|
|
2875
2895
|
return {
|
|
2876
2896
|
bytes,
|
|
2877
2897
|
contentDigest: sha256(bytes),
|
|
2878
|
-
|
|
2898
|
+
// Cache fingerprints belong to the pathname API. On affected Windows
|
|
2899
|
+
// runtimes the handle API has a different device projection.
|
|
2900
|
+
fingerprint: fileFingerprint(before),
|
|
2879
2901
|
record,
|
|
2880
2902
|
};
|
|
2881
2903
|
} finally {
|
|
@@ -3361,6 +3383,16 @@ export class EvolutionLedger {
|
|
|
3361
3383
|
}
|
|
3362
3384
|
|
|
3363
3385
|
#matchesCachedFile(filePath, expected) {
|
|
3386
|
+
try {
|
|
3387
|
+
return withEvolutionFileIdentity(this.#fs, filePath, (samePathHandle) =>
|
|
3388
|
+
this.#matchesCachedFileWithIdentity(filePath, expected, samePathHandle),
|
|
3389
|
+
);
|
|
3390
|
+
} catch {
|
|
3391
|
+
return false;
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
#matchesCachedFileWithIdentity(filePath, expected, samePathHandle) {
|
|
3364
3396
|
let descriptor = null;
|
|
3365
3397
|
try {
|
|
3366
3398
|
const before = this.#fs.lstatSync(filePath);
|
|
@@ -3381,7 +3413,7 @@ export class EvolutionLedger {
|
|
|
3381
3413
|
if (
|
|
3382
3414
|
!opened.isFile() ||
|
|
3383
3415
|
opened.nlink !== 1 ||
|
|
3384
|
-
|
|
3416
|
+
!samePathHandle(before, opened) ||
|
|
3385
3417
|
opened.size !== before.size
|
|
3386
3418
|
) {
|
|
3387
3419
|
return false;
|
|
@@ -3396,7 +3428,7 @@ export class EvolutionLedger {
|
|
|
3396
3428
|
return (
|
|
3397
3429
|
after.nlink === 1 &&
|
|
3398
3430
|
entryIdentity(after) === entryIdentity(opened) &&
|
|
3399
|
-
|
|
3431
|
+
samePathHandle(before, after) &&
|
|
3400
3432
|
bytes.length === opened.size &&
|
|
3401
3433
|
sha256(bytes) === expected.contentDigest
|
|
3402
3434
|
);
|