chainlesschain 0.166.105 → 0.166.107

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.
Files changed (211) hide show
  1. package/README.md +182 -8
  2. package/package.json +2 -2
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/assets/{AIOps-CcD-AVDw.js → AIOps-DCx6L_HW.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-CYy-g36A.js → ActionButton-DYm1CN3Z.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-DPFDNaBL.js → Analytics-D5ItUS_y.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-PVIuRdgN.js → AppLayout-Cb7XeRdH.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-BXGD3bM-.js → Artifacts-DDDeqCDQ.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-Bu5s_4tu.js → Audit-BRt3x13w.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-BHmgpnzo.js → BackgroundAgents-C_oNALMd.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-C872jURx.js → Backup-PSU2p6CR.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-Bh7xO30H.js → BaseInput-G3oL4hcX.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-DaacK53T.js → Chat-C2cLI10b.js} +6 -6
  15. package/src/assets/web-panel/assets/{ChatBubbleRenderer-PYrSKMM_.js → ChatBubbleRenderer-KX1OITf8.js} +1 -1
  16. package/src/assets/web-panel/assets/{Checkbox-Cnt7_8I2.js → Checkbox-CHLnEgFD.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-AZRz6kmW.js → Codegen-DZm9g_Bc.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-BtOXzMFt.js → Col-D5Z4zSay.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-BFmFygS7.js → Community-DHw69NSk.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-2z5CfKpR.js → Compact-B-vjYNWz.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-B5hKrPVF.js → Compliance-DaIYVZQY.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-CxEyp2ww.js → Cowork-CoA9U-gF.js} +3 -3
  23. package/src/assets/web-panel/assets/{Cron-B-8f9_qF.js → Cron-DXqmxlRC.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-XARMOcc4.js → Crosschain-Cdkc3ue1.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-DwLtEKAe.js → DID-oKfYyNSR.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-Cjw0L_iA.js → Dashboard-n_ycvgWu.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-BBGWIP7l.js → Dropdown-D38rZ8WY.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-DG4kZFkm.js → EmailListRenderer-BjreeBQ1.js} +1 -1
  29. package/src/assets/web-panel/assets/{EvolutionSettings-D8vuNDde.js → EvolutionSettings-CTxA_SIq.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-COXCzDXI.js → FamilyGuardDashboard-DmECNwV5.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-Btb-aMhW.js → Federation-B2IaaDmQ.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-TO-E6l6e.js → FormItemContext-DgcgqUIk.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-BWXdJCT0.js → GenericCardRenderer-B1-du6Qm.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-B6icGhEv.js → Git-XceM21Il.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-C5Y__K6S.js → Governance-CV97iz3z.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-DZGEKnbN.js → Inference-CI7fj305.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph--Kga5yvm.js → KnowledgeGraph-B5mekWRy.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BXs3EPKj.js → Logs-BlxFA0xY.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-rDE-XyMY.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-JSEAu0su.js → Marketplace-DL1BVCiX.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-BUBifkKP.js → McpTools-BPtwrO3c.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-Cb7sE4a-.js → Memory-DuSNw4E-.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-BOzmmNV2.js → MobileBridge-DTgY9ywI.js} +1 -1
  44. package/src/assets/web-panel/assets/MobileProjects-CTpVHJMa.js +1 -0
  45. package/src/assets/web-panel/assets/{Mtc-DmMWErG1.js → Mtc-h4fCLjXw.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-DnGkRVXF.js → MtcAudit-fqMDY3-w.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-C3mzascL.js → Multisig-Do3FehBL.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-CrmMZZ1t.js → NLProgramming-CQVY0kQF.js} +1 -1
  49. package/src/assets/web-panel/assets/Notes-4c3oNvWh.js +7 -0
  50. package/src/assets/web-panel/assets/{NotificationSettings-gn3UrWwW.js → NotificationSettings-BKr2JD4k.js} +1 -1
  51. package/src/assets/web-panel/assets/OrderTableRenderer-Bq4jgzRt.js +1 -0
  52. package/src/assets/web-panel/assets/{Organization-fMlkdgpW.js → Organization-Dy3XidyA.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-DGwCUPU1.js → Overflow-DFF0qN5K.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-BDGcpPmf.js → P2P-DAgYYykd.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-CccRPAB9.js → PdhVaultBrowser-DgNHa8b1.js} +3 -3
  56. package/src/assets/web-panel/assets/{Permissions-C33dNECx.js → Permissions-Cev6uSBc.js} +3 -3
  57. package/src/assets/web-panel/assets/{PersonalDataHub-BeqbCStc.js → PersonalDataHub-DJzbN06K.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-CTmasgbQ.js → Pipeline-CoJWvbQ5.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-ktd37BSp.js → Privacy-DZxV6etm.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-KOGvQtBX.js → ProjectInit-BFW87JnM.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-CUvH_hC6.js → ProjectSettings-LBrRT27p.js} +2 -2
  62. package/src/assets/web-panel/assets/Projects-O3uz76R1.js +1 -0
  63. package/src/assets/web-panel/assets/{Providers-C-hmLXlO.js → Providers-CnRbjOYk.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-DzG_rbSL.js → QrScannerModal-BVFnIS3r.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-ShwiATg4.js → QuickAsk-DrcHx5f1.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-DkV8Ruep.js → Recommend-CbRgrSJm.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-Ca2dOx_G.js → RemoteSession-B2Uz_ElB.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-DYA5BJ6A.js → Reputation-DS0ljTxL.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-DvGDgxLS.js → Row-ClLobFht.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-DbwTBSPw.js → RssFeed-DxxcTD0x.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-C5XAMQ9T.js → Search-BbHN0Fik.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-CKWT60Qt.js → Security-BAVU_EcY.js} +3 -3
  73. package/src/assets/web-panel/assets/{Services-CZvASlWL.js → Services-CtqRSVq_.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-DFkMVqws.js → Skeleton-IJ8ZKdSZ.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-hslZ_GyP.js → Skills-DXxcznnO.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-BLW5rmZ5.js → Sla-fRkhnA3i.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-Cn4ffCi5.js → SpeechSettings-Bi8sYJ-I.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-D1K0nJuo.js → SyncSettings-CjeplUtS.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-CCRu0xoN.js → Tasks-D4KB2Hbx.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-L19GYuS4.js → Templates-BwVugi8n.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-CZ6HE3PF.js → Tenant-CsPuu5xK.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BiAwzaBT.js → Terminal-DuaeOPjY.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-DRHSWQT5.js → TimelineRenderer-D-PMrFxO.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-Cm-XATgh.js → Tokens-BXZuiQPZ.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-BVXtx3kn.js → Trigger-DgpTaHL3.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-Bb0TmkLJ.js → Trust-Bn7lEGRY.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-Dbt9R7l0.js → UkeySign-zz-8JAAQ.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-z0-Ti8eJ.js → VideoEditing-DCzfQGTc.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-BZh31tI-.js → Wallet-BiRHM_zB.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-DQ0yIr5K.js → WebAuthn-QmPo2JwU.js} +5 -5
  91. package/src/assets/web-panel/assets/{WorkflowEditor-DCt_yuWG.js → WorkflowEditor-gL7SsPnY.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-BLgtmFeS.js → chat-BysvK4hk.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-xU4UH8M1.js → colors-CQq20vqN.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-C4w4v16T.js → compact-item-CAHlOf1X.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-CZM9hAaj.js → createContext-BeF3ESj_.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-D-Iugqxv.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-DvHqEpE7.js → hasIn-CiqnRf-U.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BkTKWAgq.js → index-4a0Lmz_y.js} +1 -1
  99. package/src/assets/web-panel/assets/index-B0Oz2_rQ.js +1 -0
  100. package/src/assets/web-panel/assets/{index-B0IvKEVO.js → index-B2AOvjgR.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BaQZvM4m.js → index-B2xbtl_c.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BRhD9RMV.js → index-B5941vyo.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CqDiY-jl.js → index-BHpHGGmB.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CQEzxnYS.js → index-BOiyW31p.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BTxhDCtg.js → index-BcVmt6u_.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CPDw21XY.js → index-BfJabF_8.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CExiTobS.js → index-Br5tGVbC.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-QHmhwgfc.js → index-BsnLkZjz.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BlN5KPq6.js → index-BtWchYfW.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-SNV95Zen.js → index-Bvkbj3x-.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BHK3CQHq.js → index-CCX0daFQ.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CPC7OGCe.js → index-CImCEo8S.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CKugxsw3.js → index-CTp89vmg.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-55Rkd1KA.js → index-CcccefJc.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Ds6wKTMh.js → index-CeIL3-nY.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DifMvnMQ.js → index-CgkUkmEJ.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bf8Wm4ks.js → index-CowbNBJs.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Cm00787d.js → index-Cp3IRTDK.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-C9YnZFv2.js → index-D7q5VH2n.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BHg9NoJ5.js → index-DBmGQHEz.js} +3 -3
  121. package/src/assets/web-panel/assets/{index-B6AQ2b5U.js → index-DHGtFwwR.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CYrQnkqA.js → index-DKYbru3i.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CyV8fNBg.js → index-DTPUVm71.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B4jIcYSv.js → index-DXT7MKkD.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B64Vf-8H.js → index-DXsXhR7p.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CpiuuIww.js → index-Dqkls2_6.js} +1 -1
  127. package/src/assets/web-panel/assets/index-DwoycEQa.js +1 -0
  128. package/src/assets/web-panel/assets/{index-DGdLkss8.js → index-QALafVsT.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Ds1tM734.js → index-TdVEaJfk.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Bh14b1k0.js → index-ZBuJ6O6g.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C6ZrTSw5.js → index-i9aJV9qk.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CF9ebAIB.js → index-jJhemX1R.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-BIWumeQl.js → index-qjaCk1Mj.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-n6SAUmmI.js → index-xwtRrgVY.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CRLP56-W.js → index-yFQWSRoW.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-wYRjvvbl.js → index-zNkw0IY7.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-C44ZER6c.js → initDefaultProps-BQR2HnKb.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-DbNzzScd.js → motion-gAz6l-nZ.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-BrRrDH2u.js → move-Cy5TN856.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-DZ_T_Pw8.js → mtc-parser-BeT7VrGi.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-5QHHk_V3.js → omit-BLN3mlgK.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-BdxEl0G2.js → pickAttrs-DtwZ8u2d.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-CGyD615D.js → placementArrow-ViMwQSa-.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-QqcWAQmN.js → responsiveObserve-dEL3u4E7.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-BumQrJ5R.js → slide-l05us-Gz.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-Pf64olM7.js → statusUtils-Bk-Qn2Og.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-DNwm5SQZ.js → styleChecker-Bn186qeY.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-c3gLKs6e.js → useFlexGapSupport-WP-ugR3H.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-TzTd6NDM.js → useFs-1SvoeSCL.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-BP5E_0Fe.js → usePersonalDataHub-CfNU8N3z.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-CVEkRfZU.js → vnode-Bd2Ya101.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-C18X_8TJ.js → zoom-Def_MeZv.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +2 -2
  155. package/src/commands/agent.js +20 -20
  156. package/src/commands/goal.js +299 -1
  157. package/src/commands/plugin.js +129 -68
  158. package/src/constants.js +2 -2
  159. package/src/data/changelog.json +26 -0
  160. package/src/lib/cli-goal-catalog.js +129 -0
  161. package/src/lib/cli-goal-source.js +119 -0
  162. package/src/lib/coding-task-guidance.js +3 -1
  163. package/src/lib/config-schema.js +1 -1
  164. package/src/lib/eval/stream-terminal.js +27 -2
  165. package/src/lib/evolution/evolution-artifact-ports.js +22 -11
  166. package/src/lib/evolution/evolution-file-witness.js +86 -63
  167. package/src/lib/evolution/evolution-ledger-file-backend.js +22 -0
  168. package/src/lib/evolution/evolution-ledger-file-manifest-catalog.js +34 -35
  169. package/src/lib/evolution/evolution-ledger-file-manifest-head-store.js +32 -39
  170. package/src/lib/evolution/evolution-ledger.js +367 -169
  171. package/src/lib/evolution/evolution-manifest-state-reader.js +58 -0
  172. package/src/lib/evolution/evolution-physical-state-reader.js +308 -0
  173. package/src/lib/evolution/evolution-witness-segments.js +12 -13
  174. package/src/lib/evolution/governed-wiki-pruning-ledger-adapter.js +82 -56
  175. package/src/lib/evolution/rrsi-observed-text-index.js +17 -1
  176. package/src/lib/evolution/rrsi-tenant-index-anchor-history.js +5 -0
  177. package/src/lib/execution-location-failure-site.js +20 -3
  178. package/src/lib/goal-store.js +8 -1
  179. package/src/lib/image-input.js +43 -14
  180. package/src/lib/llm-providers.js +1 -0
  181. package/src/lib/plugin-runtime/capabilities.js +10 -0
  182. package/src/lib/plugin-runtime/cli-goal-draft-request.js +138 -0
  183. package/src/lib/plugin-runtime/cli-goal-drafts.js +413 -0
  184. package/src/lib/plugin-runtime/cli-goal-extension-host.js +234 -0
  185. package/src/lib/plugin-runtime/examples/goal-description-drafter/plugin.json +17 -0
  186. package/src/lib/plugin-runtime/examples/goal-evidence-viewer/plugin.json +25 -0
  187. package/src/lib/plugin-runtime/examples/goal-progress-viewer/plugin.json +21 -0
  188. package/src/lib/plugin-runtime/examples/project-delivery-assistant/plugin.json +38 -0
  189. package/src/lib/plugin-runtime/goal-extension-contract.js +190 -0
  190. package/src/lib/plugin-runtime/goal-extensions.js +277 -0
  191. package/src/lib/plugin-runtime/install.js +40 -40
  192. package/src/lib/plugin-runtime/manifest.js +12 -0
  193. package/src/lib/plugin-runtime/policy.js +1 -0
  194. package/src/lib/project-goal-connection.js +225 -0
  195. package/src/lib/project-goal-draft-connection.js +217 -0
  196. package/src/lib/provider-http-error.js +4 -5
  197. package/src/lib/task-model-selector.js +1 -1
  198. package/src/repl/agent-repl.js +14 -7
  199. package/src/repl/config-summary.js +1 -1
  200. package/src/runtime/agent-core.js +1 -1
  201. package/src/runtime/coding-agent-contract-shared.cjs +3 -2
  202. package/src/runtime/headless-stream.js +4 -6
  203. package/src/runtime/policies/agent-policy.js +1 -0
  204. package/src/assets/web-panel/assets/MarkdownRenderer-BsSiyE-A.js +0 -1
  205. package/src/assets/web-panel/assets/MobileProjects-ClRlavdF.js +0 -1
  206. package/src/assets/web-panel/assets/Notes-4S6uts9Q.js +0 -7
  207. package/src/assets/web-panel/assets/OrderTableRenderer-CDVAlBUf.js +0 -1
  208. package/src/assets/web-panel/assets/Projects-BvP_Os-1.js +0 -1
  209. package/src/assets/web-panel/assets/devWarning-BZUZgMkO.js +0 -1
  210. package/src/assets/web-panel/assets/index--_-pNhk0.js +0 -1
  211. package/src/assets/web-panel/assets/index-ftwO31cp.js +0 -1
@@ -7,7 +7,7 @@ import {
7
7
  withEvolutionDirectoryFileIdentity,
8
8
  withEvolutionFileIdentity,
9
9
  } from "./evolution-file-identity.js";
10
- import { sameFileStatIdentity } from "../secure-file-identity.js";
10
+ import { artifactPhysicalIdentity } from "./evolution-artifact-identity.js";
11
11
  import { getHomeDir } from "../paths.js";
12
12
  import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
13
13
  import { withFileLock } from "../with-file-lock.js";
@@ -59,6 +59,7 @@ export const EVOLUTION_LEDGER_V2_MIGRATION_INTENT =
59
59
  export const EVOLUTION_LEDGER_V2_CUTOVER_COMPLETED =
60
60
  "evolution.ledger-v2.cutover-completed";
61
61
  const MIGRATION_SOURCES = new WeakMap();
62
+ const DIRECTORY_BOUNDARIES = new WeakMap();
62
63
 
63
64
  const IDENTITY_DOMAIN = "chainlesschain.evolution-ledger-identity/v1\0";
64
65
  const EVENT_DOMAIN = "chainlesschain.evolution-event/v2\0";
@@ -557,7 +558,7 @@ function isUnsafeAsyncResult(value) {
557
558
  const descriptor = Object.getOwnPropertyDescriptor(value, "then");
558
559
  return Boolean(
559
560
  descriptor &&
560
- (!("value" in descriptor) || typeof descriptor.value === "function"),
561
+ (!("value" in descriptor) || typeof descriptor.value === "function"),
561
562
  );
562
563
  }
563
564
 
@@ -1656,6 +1657,75 @@ function fileFingerprint(stat) {
1656
1657
  ].join(":");
1657
1658
  }
1658
1659
 
1660
+ const PHYSICAL_FILE_FIELDS = Object.freeze([
1661
+ "dev",
1662
+ "ino",
1663
+ "mode",
1664
+ "uid",
1665
+ "gid",
1666
+ "nlink",
1667
+ "size",
1668
+ "mtimeNs",
1669
+ "ctimeNs",
1670
+ ]);
1671
+ const PHYSICAL_DIRECTORY_FIELDS = Object.freeze([
1672
+ "dev",
1673
+ "ino",
1674
+ "mode",
1675
+ "uid",
1676
+ "gid",
1677
+ ]);
1678
+ const physicalBirthtime = (stat) => artifactPhysicalIdentity(stat).birthtimeMs;
1679
+ const samePhysicalFile = (left, right) =>
1680
+ PHYSICAL_FILE_FIELDS.every((field) => left[field] === right[field]) &&
1681
+ physicalBirthtime(left) === physicalBirthtime(right);
1682
+ const samePhysicalOwner = (left, right) =>
1683
+ left.uid === right.uid &&
1684
+ left.gid === right.gid &&
1685
+ physicalBirthtime(left) === physicalBirthtime(right);
1686
+
1687
+ function assertPhysicalFile(stat, maximum, label) {
1688
+ if (PHYSICAL_FILE_FIELDS.some((field) => typeof stat?.[field] !== "bigint")) {
1689
+ throw ledgerError(
1690
+ "CC_EVOLUTION_LEDGER_CORRUPT",
1691
+ `${label} requires full-precision stat fields`,
1692
+ );
1693
+ }
1694
+ if (
1695
+ !stat.isFile() ||
1696
+ stat.isSymbolicLink() ||
1697
+ stat.nlink !== 1n ||
1698
+ stat.size < 2n ||
1699
+ stat.size > BigInt(maximum)
1700
+ ) {
1701
+ throw ledgerError(
1702
+ "CC_EVOLUTION_LEDGER_CORRUPT",
1703
+ `${label} must be a bounded, single-link, non-symlink file`,
1704
+ );
1705
+ }
1706
+ }
1707
+
1708
+ function physicalDirectoryIdentity(stat) {
1709
+ if (
1710
+ PHYSICAL_DIRECTORY_FIELDS.some(
1711
+ (field) => typeof stat?.[field] !== "bigint",
1712
+ ) ||
1713
+ !stat.isDirectory() ||
1714
+ stat.isSymbolicLink()
1715
+ ) {
1716
+ throw ledgerError(
1717
+ "CC_EVOLUTION_LEDGER_STORE_UNSAFE",
1718
+ "ledger boundary requires a full-precision directory identity",
1719
+ );
1720
+ }
1721
+ return Object.freeze({
1722
+ ...Object.fromEntries(
1723
+ PHYSICAL_DIRECTORY_FIELDS.map((field) => [field, stat[field]]),
1724
+ ),
1725
+ birthtimeMs: physicalBirthtime(stat),
1726
+ });
1727
+ }
1728
+
1659
1729
  function storeMarkerEntryDigest(storeMarkerFile) {
1660
1730
  return domainDigest(STORE_MARKER_ENTRY_DOMAIN, {
1661
1731
  contentDigest: storeMarkerFile.contentDigest,
@@ -1831,9 +1901,11 @@ function defaultAuthorityRoot(rootDir) {
1831
1901
  export class EvolutionLedger {
1832
1902
  #artifactResolver;
1833
1903
  #boundaries;
1904
+ #physicalBoundaries;
1834
1905
  #clock;
1835
1906
  #crashHook;
1836
1907
  #fs;
1908
+ #identityFs;
1837
1909
  #lock;
1838
1910
  #lockClock;
1839
1911
  #lockTimeoutMs;
@@ -1900,6 +1972,17 @@ export class EvolutionLedger {
1900
1972
  }
1901
1973
 
1902
1974
  this.#fs = captureFs(fsImpl);
1975
+ // Identity helpers only open parent/volume directories through this port.
1976
+ this.#identityFs = Object.freeze({
1977
+ ...this.#fs,
1978
+ openSync: (target, flags) =>
1979
+ this.#fs.openSync(
1980
+ target,
1981
+ flags |
1982
+ (this.#fs.constants.O_NONBLOCK || 0) |
1983
+ (this.#fs.constants.O_DIRECTORY || 0),
1984
+ ),
1985
+ });
1903
1986
  this.#trust = normalizeTrust(trust);
1904
1987
  this.#witnessTrust = normalizeTrust(witnessTrust);
1905
1988
  this.#witness = captureWitness(witness);
@@ -1984,6 +2067,23 @@ export class EvolutionLedger {
1984
2067
  STORE_MARKER_FILE_NAME,
1985
2068
  ),
1986
2069
  });
2070
+ // These per-instance physical pins do not alter the persisted Number
2071
+ // directory identities used by the original store-binding signature.
2072
+ this.#physicalBoundaries = Object.freeze(
2073
+ [
2074
+ this.#paths.rootDir,
2075
+ this.#paths.authorityRootDir,
2076
+ this.#paths.segmentDir,
2077
+ this.#paths.anchorDir,
2078
+ ].map((directory) =>
2079
+ Object.freeze({
2080
+ directory,
2081
+ identity: physicalDirectoryIdentity(
2082
+ this.#fs.lstatSync(directory, { bigint: true }),
2083
+ ),
2084
+ }),
2085
+ ),
2086
+ );
1987
2087
  this.#boundaries = deepFreeze(
1988
2088
  [
1989
2089
  this.#paths.rootDir,
@@ -2039,6 +2139,53 @@ export class EvolutionLedger {
2039
2139
  this.#appendEvents(inputs, options, true, true, true),
2040
2140
  }),
2041
2141
  );
2142
+ // Project the original constructor pins, never a fresh claim of the paths
2143
+ // first supplied to a later caller. Mutable directory metadata is excluded.
2144
+ const directoryBoundary = deepFreeze({
2145
+ schema: "chainlesschain.evolution-ledger-directory-boundary/v1",
2146
+ scope: "source-journal-four-directories",
2147
+ directories: this.#physicalBoundaries.map(
2148
+ ({ directory, identity }, index) => ({
2149
+ role: ["events", "authority", "segments", "anchors"][index],
2150
+ path: directory,
2151
+ identity: {
2152
+ ...Object.fromEntries(
2153
+ PHYSICAL_DIRECTORY_FIELDS.map((field) => [
2154
+ field,
2155
+ String(identity[field]),
2156
+ ]),
2157
+ ),
2158
+ birthtimeMs: identity.birthtimeMs,
2159
+ },
2160
+ }),
2161
+ ),
2162
+ journalFileIdentityVerified: false,
2163
+ journalContentsVerified: false,
2164
+ witnessDirectoriesVerified: false,
2165
+ retentionStorageVerified: false,
2166
+ fullPhysicalStorageGraphVerified: false,
2167
+ rootAuthorityVerified: false,
2168
+ crossProcessRollbackProtectionVerified: false,
2169
+ tenantWideIndexAuthorityVerified: false,
2170
+ grantsMutationOrPromotionAuthority: false,
2171
+ });
2172
+ const recheck = (...args) => {
2173
+ if (args.length)
2174
+ throw new TypeError(
2175
+ "ledger directory boundary takes no replacement context",
2176
+ );
2177
+ this.#assertBoundaries();
2178
+ return directoryBoundary;
2179
+ };
2180
+ recheck();
2181
+ if (new.target === EvolutionLedger)
2182
+ DIRECTORY_BOUNDARIES.set(
2183
+ this,
2184
+ Object.freeze({
2185
+ descriptor: directoryBoundary,
2186
+ recheck: Object.freeze(recheck),
2187
+ }),
2188
+ );
2042
2189
  Object.freeze(this);
2043
2190
  }
2044
2191
 
@@ -2071,11 +2218,11 @@ export class EvolutionLedger {
2071
2218
  }
2072
2219
 
2073
2220
  #assertBoundaries() {
2074
- for (const boundary of this.#boundaries) {
2221
+ for (const boundary of this.#physicalBoundaries) {
2075
2222
  let stat;
2076
2223
  let canonical;
2077
2224
  try {
2078
- stat = this.#fs.lstatSync(boundary.directory);
2225
+ stat = this.#fs.lstatSync(boundary.directory, { bigint: true });
2079
2226
  canonical = realpath(this.#fs, boundary.directory);
2080
2227
  } catch (cause) {
2081
2228
  if (cause instanceof EvolutionLedgerError) throw cause;
@@ -2085,10 +2232,12 @@ export class EvolutionLedger {
2085
2232
  { cause },
2086
2233
  );
2087
2234
  }
2235
+ const identity = physicalDirectoryIdentity(stat);
2088
2236
  if (
2089
- !stat.isDirectory() ||
2090
- stat.isSymbolicLink() ||
2091
- entryIdentity(stat) !== boundary.identity ||
2237
+ !PHYSICAL_DIRECTORY_FIELDS.every(
2238
+ (field) => identity[field] === boundary.identity[field],
2239
+ ) ||
2240
+ identity.birthtimeMs !== boundary.identity.birthtimeMs ||
2092
2241
  !samePath(canonical, boundary.directory)
2093
2242
  ) {
2094
2243
  throw ledgerError(
@@ -2805,24 +2954,27 @@ export class EvolutionLedger {
2805
2954
  }
2806
2955
 
2807
2956
  #readCanonicalFile(filePath, maximum, label, { allowMissing = false } = {}) {
2808
- return withEvolutionFileIdentity(this.#fs, filePath, (samePathHandle) =>
2809
- this.#readCanonicalFileWithIdentity(
2810
- filePath,
2811
- maximum,
2812
- label,
2813
- allowMissing,
2814
- samePathHandle,
2815
- ),
2957
+ return withEvolutionFileIdentity(
2958
+ this.#identityFs,
2959
+ filePath,
2960
+ (samePathHandle) =>
2961
+ this.#readCanonicalFileWithIdentity(
2962
+ filePath,
2963
+ maximum,
2964
+ label,
2965
+ allowMissing,
2966
+ samePathHandle,
2967
+ ),
2816
2968
  );
2817
2969
  }
2818
2970
 
2819
- #readCanonicalFileWithIdentity(
2971
+ #readPhysicalFileWithIdentity(
2820
2972
  filePath,
2821
2973
  maximum,
2822
2974
  label,
2823
2975
  allowMissing,
2824
2976
  samePathHandle,
2825
- { expectedBytes = null, trustedParentPath = null } = {},
2977
+ { trustedParentPath = null } = {},
2826
2978
  ) {
2827
2979
  const heldParent =
2828
2980
  trustedParentPath !== null &&
@@ -2830,9 +2982,12 @@ export class EvolutionLedger {
2830
2982
  if (!heldParent) this.#assertBoundaries();
2831
2983
  let before;
2832
2984
  try {
2833
- before = this.#fs.lstatSync(filePath);
2985
+ before = this.#fs.lstatSync(filePath, { bigint: true });
2834
2986
  } catch (cause) {
2835
- if (allowMissing && cause?.code === "ENOENT") return null;
2987
+ if (allowMissing && cause?.code === "ENOENT") {
2988
+ if (!heldParent) this.#assertBoundaries();
2989
+ return null;
2990
+ }
2836
2991
  throw ledgerError(
2837
2992
  cause?.code === "ENOENT"
2838
2993
  ? "CC_EVOLUTION_LEDGER_CORRUPT"
@@ -2841,31 +2996,36 @@ export class EvolutionLedger {
2841
2996
  { cause },
2842
2997
  );
2843
2998
  }
2844
- if (
2845
- !before.isFile() ||
2846
- before.isSymbolicLink() ||
2847
- before.nlink !== 1 ||
2848
- before.size < 2 ||
2849
- before.size > maximum ||
2850
- (!heldParent && !samePath(realpath(this.#fs, filePath), filePath))
2851
- ) {
2852
- throw ledgerError(
2853
- "CC_EVOLUTION_LEDGER_CORRUPT",
2854
- `${label} must be a bounded, single-link, non-symlink file`,
2855
- );
2856
- }
2999
+ assertPhysicalFile(before, maximum, label);
2857
3000
  let descriptor = null;
2858
3001
  try {
3002
+ // Preserve the exact legacy Number fingerprint already signed into the
3003
+ // store marker binding. It is sampled inside a separate BigInt window and
3004
+ // is never used as the authority for a physical file/handle comparison.
3005
+ const legacyBefore = this.#fs.lstatSync(filePath);
3006
+ const sampled = this.#fs.lstatSync(filePath, { bigint: true });
3007
+ assertPhysicalFile(sampled, maximum, label);
3008
+ if (
3009
+ !samePhysicalFile(before, sampled) ||
3010
+ (!heldParent && !samePath(realpath(this.#fs, filePath), filePath))
3011
+ ) {
3012
+ throw ledgerError(
3013
+ "CC_EVOLUTION_LEDGER_CORRUPT",
3014
+ `${label} changed while its fingerprint was sampled`,
3015
+ );
3016
+ }
3017
+ const fingerprint = fileFingerprint(legacyBefore);
2859
3018
  descriptor = this.#fs.openSync(
2860
3019
  filePath,
2861
- this.#fs.constants.O_RDONLY | (this.#fs.constants.O_NOFOLLOW || 0),
3020
+ this.#fs.constants.O_RDONLY |
3021
+ (this.#fs.constants.O_NOFOLLOW || 0) |
3022
+ (this.#fs.constants.O_NONBLOCK || 0),
2862
3023
  );
2863
- const opened = this.#fs.fstatSync(descriptor);
3024
+ const opened = this.#fs.fstatSync(descriptor, { bigint: true });
3025
+ assertPhysicalFile(opened, maximum, label);
2864
3026
  if (
2865
- !opened.isFile() ||
2866
- opened.nlink !== 1 ||
2867
3027
  !samePathHandle(before, opened) ||
2868
- opened.size !== before.size
3028
+ !samePhysicalOwner(before, opened)
2869
3029
  ) {
2870
3030
  throw ledgerError(
2871
3031
  "CC_EVOLUTION_LEDGER_CORRUPT",
@@ -2877,7 +3037,7 @@ export class EvolutionLedger {
2877
3037
  bytes = readBoundedDescriptor(
2878
3038
  this.#fs,
2879
3039
  descriptor,
2880
- opened.size,
3040
+ Number(opened.size),
2881
3041
  maximum,
2882
3042
  );
2883
3043
  } catch (cause) {
@@ -2887,76 +3047,106 @@ export class EvolutionLedger {
2887
3047
  { cause },
2888
3048
  );
2889
3049
  }
2890
- const after = this.#fs.fstatSync(descriptor);
3050
+ const after = this.#fs.fstatSync(descriptor, { bigint: true });
3051
+ const legacyAfter = this.#fs.lstatSync(filePath);
3052
+ const afterPath = this.#fs.lstatSync(filePath, { bigint: true });
3053
+ assertPhysicalFile(after, maximum, label);
3054
+ assertPhysicalFile(afterPath, maximum, label);
2891
3055
  if (
2892
- after.nlink !== 1 ||
2893
- !sameFileStatIdentity(opened, after) ||
2894
- after.size !== opened.size ||
2895
- bytes.length !== opened.size
3056
+ !samePhysicalFile(opened, after) ||
3057
+ bytes.length !== Number(opened.size)
2896
3058
  ) {
2897
3059
  throw ledgerError(
2898
3060
  "CC_EVOLUTION_LEDGER_CORRUPT",
2899
3061
  `${label} changed while it was read`,
2900
3062
  );
2901
3063
  }
2902
- if (expectedBytes !== null) {
2903
- if (!Buffer.isBuffer(expectedBytes) || !bytes.equals(expectedBytes)) {
2904
- throw ledgerError(
2905
- "CC_EVOLUTION_LEDGER_CORRUPT",
2906
- `${label} differs from its signed snapshot bytes`,
2907
- );
2908
- }
2909
- return {
2910
- bytes,
2911
- contentDigest: sha256(bytes),
2912
- fingerprint: fileFingerprint(before),
2913
- record: null,
2914
- };
2915
- }
2916
- let text;
2917
- try {
2918
- text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
2919
- } catch (cause) {
2920
- throw ledgerError(
2921
- "CC_EVOLUTION_LEDGER_CORRUPT",
2922
- `${label} is not valid UTF-8`,
2923
- { cause },
2924
- );
2925
- }
2926
- if (!text.endsWith("\n") || text.slice(0, -1).includes("\n")) {
2927
- throw ledgerError(
2928
- "CC_EVOLUTION_LEDGER_CORRUPT",
2929
- `${label} is truncated or not a single canonical record`,
2930
- );
2931
- }
2932
- let record;
2933
- try {
2934
- record = JSON.parse(text.slice(0, -1));
2935
- } catch (cause) {
3064
+ if (
3065
+ !samePhysicalFile(before, afterPath) ||
3066
+ !samePathHandle(afterPath, after) ||
3067
+ !samePhysicalOwner(afterPath, after) ||
3068
+ fileFingerprint(legacyAfter) !== fingerprint ||
3069
+ (!heldParent && !samePath(realpath(this.#fs, filePath), filePath))
3070
+ ) {
2936
3071
  throw ledgerError(
2937
3072
  "CC_EVOLUTION_LEDGER_CORRUPT",
2938
- `${label} contains invalid JSON`,
2939
- { cause },
3073
+ `${label} pathname changed during bounded read`,
2940
3074
  );
2941
3075
  }
2942
- if (`${canonicalJson(record)}\n` !== text) {
3076
+ return { bytes, fingerprint };
3077
+ } catch (cause) {
3078
+ if (cause instanceof EvolutionLedgerError) throw cause;
3079
+ throw ledgerError(
3080
+ "CC_EVOLUTION_LEDGER_CORRUPT",
3081
+ `${label} changed during bounded read`,
3082
+ { cause },
3083
+ );
3084
+ } finally {
3085
+ if (descriptor !== null) this.#fs.closeSync(descriptor);
3086
+ if (!heldParent) this.#assertBoundaries();
3087
+ }
3088
+ }
3089
+
3090
+ #readCanonicalFileWithIdentity(
3091
+ filePath,
3092
+ maximum,
3093
+ label,
3094
+ allowMissing,
3095
+ samePathHandle,
3096
+ { expectedBytes = null, trustedParentPath = null } = {},
3097
+ ) {
3098
+ const file = this.#readPhysicalFileWithIdentity(
3099
+ filePath,
3100
+ maximum,
3101
+ label,
3102
+ allowMissing,
3103
+ samePathHandle,
3104
+ { trustedParentPath },
3105
+ );
3106
+ if (file === null) return null;
3107
+ const { bytes, fingerprint } = file;
3108
+ if (expectedBytes !== null) {
3109
+ if (!Buffer.isBuffer(expectedBytes) || !bytes.equals(expectedBytes)) {
2943
3110
  throw ledgerError(
2944
3111
  "CC_EVOLUTION_LEDGER_CORRUPT",
2945
- `${label} bytes are not canonical`,
3112
+ `${label} differs from its signed snapshot bytes`,
2946
3113
  );
2947
3114
  }
2948
- return {
2949
- bytes,
2950
- contentDigest: sha256(bytes),
2951
- // Cache fingerprints belong to the pathname API. On affected Windows
2952
- // runtimes the handle API has a different device projection.
2953
- fingerprint: fileFingerprint(before),
2954
- record,
2955
- };
2956
- } finally {
2957
- if (descriptor !== null) this.#fs.closeSync(descriptor);
2958
- if (!heldParent) this.#assertBoundaries();
3115
+ return { bytes, contentDigest: sha256(bytes), fingerprint, record: null };
3116
+ }
3117
+ let text;
3118
+ try {
3119
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
3120
+ } catch (cause) {
3121
+ throw ledgerError(
3122
+ "CC_EVOLUTION_LEDGER_CORRUPT",
3123
+ `${label} is not valid UTF-8`,
3124
+ { cause },
3125
+ );
3126
+ }
3127
+ if (!text.endsWith("\n") || text.slice(0, -1).includes("\n")) {
3128
+ throw ledgerError(
3129
+ "CC_EVOLUTION_LEDGER_CORRUPT",
3130
+ `${label} is truncated or not a single canonical record`,
3131
+ );
2959
3132
  }
3133
+ let record;
3134
+ try {
3135
+ record = JSON.parse(text.slice(0, -1));
3136
+ } catch (cause) {
3137
+ throw ledgerError(
3138
+ "CC_EVOLUTION_LEDGER_CORRUPT",
3139
+ `${label} contains invalid JSON`,
3140
+ { cause },
3141
+ );
3142
+ }
3143
+ if (`${canonicalJson(record)}\n` !== text) {
3144
+ throw ledgerError(
3145
+ "CC_EVOLUTION_LEDGER_CORRUPT",
3146
+ `${label} bytes are not canonical`,
3147
+ );
3148
+ }
3149
+ return { bytes, contentDigest: sha256(bytes), fingerprint, record };
2960
3150
  }
2961
3151
 
2962
3152
  #writeImmutable(filePath, bytes, label, onPublished = null) {
@@ -3435,61 +3625,24 @@ export class EvolutionLedger {
3435
3625
  );
3436
3626
  }
3437
3627
 
3438
- #matchesCachedFile(filePath, expected) {
3439
- try {
3440
- return withEvolutionFileIdentity(this.#fs, filePath, (samePathHandle) =>
3441
- this.#matchesCachedFileWithIdentity(filePath, expected, samePathHandle),
3442
- );
3443
- } catch {
3444
- return false;
3445
- }
3446
- }
3447
-
3448
- #matchesCachedFileWithIdentity(filePath, expected, samePathHandle) {
3449
- let descriptor = null;
3450
- try {
3451
- const before = this.#fs.lstatSync(filePath);
3452
- if (
3453
- !before.isFile() ||
3454
- before.isSymbolicLink() ||
3455
- before.nlink !== 1 ||
3456
- fileFingerprint(before) !== expected.fingerprint ||
3457
- !samePath(realpath(this.#fs, filePath), filePath)
3458
- ) {
3459
- return false;
3460
- }
3461
- descriptor = this.#fs.openSync(
3462
- filePath,
3463
- this.#fs.constants.O_RDONLY | (this.#fs.constants.O_NOFOLLOW || 0),
3464
- );
3465
- const opened = this.#fs.fstatSync(descriptor);
3466
- if (
3467
- !opened.isFile() ||
3468
- opened.nlink !== 1 ||
3469
- !samePathHandle(before, opened) ||
3470
- opened.size !== before.size
3471
- ) {
3472
- return false;
3473
- }
3474
- const bytes = readBoundedDescriptor(
3475
- this.#fs,
3476
- descriptor,
3477
- opened.size,
3478
- EVOLUTION_LEDGER_MAX_EVENT_BYTES,
3479
- );
3480
- const after = this.#fs.fstatSync(descriptor);
3481
- return (
3482
- after.nlink === 1 &&
3483
- entryIdentity(after) === entryIdentity(opened) &&
3484
- samePathHandle(before, after) &&
3485
- bytes.length === opened.size &&
3486
- sha256(bytes) === expected.contentDigest
3487
- );
3488
- } catch {
3489
- return false;
3490
- } finally {
3491
- if (descriptor !== null) this.#fs.closeSync(descriptor);
3492
- }
3628
+ #matchesCachedFileWithIdentity(
3629
+ filePath,
3630
+ expected,
3631
+ samePathHandle,
3632
+ trustedParentPath,
3633
+ ) {
3634
+ const file = this.#readPhysicalFileWithIdentity(
3635
+ filePath,
3636
+ EVOLUTION_LEDGER_MAX_EVENT_BYTES,
3637
+ "cached ledger file",
3638
+ false,
3639
+ samePathHandle,
3640
+ { trustedParentPath },
3641
+ );
3642
+ return (
3643
+ file.fingerprint === expected.fingerprint &&
3644
+ sha256(file.bytes) === expected.contentDigest
3645
+ );
3493
3646
  }
3494
3647
 
3495
3648
  #snapshotFileRecords(values, label, expectedNames) {
@@ -3674,7 +3827,7 @@ export class EvolutionLedger {
3674
3827
  const anchorFingerprints = {};
3675
3828
  const segmentFingerprints = {};
3676
3829
  withEvolutionDirectoryFileIdentity(
3677
- this.#fs,
3830
+ this.#identityFs,
3678
3831
  this.#paths.anchorDir,
3679
3832
  (samePathHandle, _sameStableHandle, { parentPath }) => {
3680
3833
  for (let index = 0; index < anchorFiles.length; index += 1) {
@@ -3712,7 +3865,7 @@ export class EvolutionLedger {
3712
3865
  },
3713
3866
  );
3714
3867
  withEvolutionDirectoryFileIdentity(
3715
- this.#fs,
3868
+ this.#identityFs,
3716
3869
  this.#paths.segmentDir,
3717
3870
  (samePathHandle, _sameStableHandle, { parentPath }) => {
3718
3871
  for (let index = 0; index < segmentFiles.length; index += 1) {
@@ -3851,27 +4004,52 @@ export class EvolutionLedger {
3851
4004
  ) {
3852
4005
  return null;
3853
4006
  }
3854
- for (const name of cached.anchorNames) {
3855
- if (
3856
- !this.#matchesCachedFile(
3857
- path.join(this.#paths.anchorDir, name),
3858
- cached.anchorFingerprints[name],
3859
- )
3860
- ) {
3861
- return null;
3862
- }
3863
- }
3864
- for (const name of cached.segmentNames) {
3865
- if (
3866
- !this.#matchesCachedFile(
3867
- path.join(this.#paths.segmentDir, name),
3868
- cached.segmentFingerprints[name],
3869
- )
3870
- ) {
3871
- return null;
3872
- }
4007
+ const matchesDirectory = (directoryPath, names, fingerprints) => {
4008
+ let matches = true;
4009
+ withEvolutionDirectoryFileIdentity(
4010
+ this.#identityFs,
4011
+ directoryPath,
4012
+ (samePathHandle, _sameStableHandle, { parentPath }) => {
4013
+ for (const name of names) {
4014
+ if (
4015
+ !this.#matchesCachedFileWithIdentity(
4016
+ path.join(directoryPath, name),
4017
+ fingerprints[name],
4018
+ samePathHandle,
4019
+ parentPath,
4020
+ )
4021
+ ) {
4022
+ matches = false;
4023
+ break;
4024
+ }
4025
+ }
4026
+ },
4027
+ );
4028
+ return matches;
4029
+ };
4030
+
4031
+ // Keep one boundary check around the entire cached-prefix validation. The
4032
+ // retained directory identity authenticates each child while avoiding a
4033
+ // full store-boundary walk and path realpath for every immutable file.
4034
+ this.#assertBoundaries();
4035
+ let matches = false;
4036
+ try {
4037
+ matches =
4038
+ matchesDirectory(
4039
+ this.#paths.anchorDir,
4040
+ cached.anchorNames,
4041
+ cached.anchorFingerprints,
4042
+ ) &&
4043
+ matchesDirectory(
4044
+ this.#paths.segmentDir,
4045
+ cached.segmentNames,
4046
+ cached.segmentFingerprints,
4047
+ );
4048
+ } catch {
4049
+ matches = false;
3873
4050
  }
3874
- return cached;
4051
+ this.#assertBoundaries();
4052
+ return matches ? cached : null;
3875
4053
  }
3876
4054
 
3877
4055
  #rememberState(
@@ -3881,6 +4059,7 @@ export class EvolutionLedger {
3881
4059
  anchorFingerprints,
3882
4060
  segmentFingerprints,
3883
4061
  ) {
4062
+ this.#assertBoundaries();
3884
4063
  const byEventDigest = new Map();
3885
4064
  const byEventId = new Map();
3886
4065
  for (const event of state.events) {
@@ -5265,6 +5444,25 @@ export function captureEvolutionLedgerMigrationSource(ledger) {
5265
5444
  return source;
5266
5445
  }
5267
5446
 
5447
+ /** Original source journal directories only; no file inventory or installer grant. */
5448
+ export function captureEvolutionLedgerDirectoryBoundary(ledger) {
5449
+ if (arguments.length !== 1)
5450
+ throw new TypeError(
5451
+ "ledger directory boundary capture takes exactly one original ledger",
5452
+ );
5453
+ if (
5454
+ utilTypes.isProxy(ledger) ||
5455
+ !DIRECTORY_BOUNDARIES.has(ledger) ||
5456
+ Object.getPrototypeOf(ledger) !== EvolutionLedger.prototype
5457
+ )
5458
+ throw new TypeError(
5459
+ "a genuine EvolutionLedger directory boundary is required",
5460
+ );
5461
+ const boundary = DIRECTORY_BOUNDARIES.get(ledger);
5462
+ boundary.recheck();
5463
+ return boundary;
5464
+ }
5465
+
5268
5466
  function artifactRefsForAudit(input) {
5269
5467
  const refs = Object.hasOwn(input, "subjectRef")
5270
5468
  ? [input.subjectRef, ...input.sourceRefs]