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
@@ -0,0 +1,119 @@
1
+ /** Bounded standalone file reads shared by explicit CLI snapshots. */
2
+ import fs from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { createGoalFileAdapter } from "./goal-store.js";
5
+
6
+ function fail(code) {
7
+ throw new Error(code);
8
+ }
9
+ function sameFile(a, b) {
10
+ return (
11
+ a.dev === b.dev &&
12
+ a.ino === b.ino &&
13
+ a.size === b.size &&
14
+ a.mtimeMs === b.mtimeMs &&
15
+ a.ctimeMs === b.ctimeMs
16
+ );
17
+ }
18
+
19
+ /** Pin the configured store, including directory identity, for this snapshot. */
20
+ function sourceReader(root, onRead) {
21
+ const realRoot = fs.realpathSync(root);
22
+ const initialRoot = fs.statSync(root);
23
+ function checkRoot() {
24
+ const stat = fs.statSync(root);
25
+ if (
26
+ !stat.isDirectory() ||
27
+ fs.realpathSync(root) !== realRoot ||
28
+ stat.dev !== initialRoot.dev ||
29
+ stat.ino !== initialRoot.ino
30
+ )
31
+ fail("CLI_GOAL_STORE_CHANGED");
32
+ }
33
+ const readJson = (file) => {
34
+ checkRoot();
35
+ const entry = fs.lstatSync(file);
36
+ if (!entry.isFile() || entry.isSymbolicLink())
37
+ fail("CLI_GOAL_REGULAR_FILE_REQUIRED");
38
+ let fd;
39
+ try {
40
+ fd = fs.openSync(
41
+ file,
42
+ fs.constants.O_RDONLY |
43
+ (fs.constants.O_NOFOLLOW || 0) |
44
+ (fs.constants.O_NONBLOCK || 0),
45
+ );
46
+ const before = fs.fstatSync(fd);
47
+ if (!before.isFile() || !sameFile(entry, before))
48
+ fail("CLI_GOAL_SOURCE_CHANGED");
49
+ if (before.size > 64 * 1024) fail("CLI_GOAL_SOURCE_TOO_LARGE");
50
+ const buffer = Buffer.alloc(before.size + 1);
51
+ let count = 0;
52
+ while (count < buffer.length) {
53
+ const size = fs.readSync(
54
+ fd,
55
+ buffer,
56
+ count,
57
+ buffer.length - count,
58
+ count,
59
+ );
60
+ if (!size) break;
61
+ count += size;
62
+ }
63
+ checkRoot();
64
+ const after = fs.lstatSync(file);
65
+ if (
66
+ count !== before.size ||
67
+ !sameFile(before, fs.fstatSync(fd)) ||
68
+ !after.isFile() ||
69
+ after.isSymbolicLink() ||
70
+ !sameFile(before, after)
71
+ )
72
+ fail("CLI_GOAL_SOURCE_CHANGED");
73
+ const bytes = buffer.subarray(0, count);
74
+ const value = JSON.parse(
75
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes),
76
+ );
77
+ onRead(createHash("sha256").update(bytes).digest("hex"));
78
+ return value;
79
+ } finally {
80
+ if (fd !== undefined) fs.closeSync(fd);
81
+ }
82
+ };
83
+ return { readJson, checkRoot };
84
+ }
85
+
86
+ export function createCliGoalSnapshotReader(root) {
87
+ let rawDigest;
88
+ const source = sourceReader(root, (value) => {
89
+ rawDigest = value;
90
+ });
91
+ const adapter = createGoalFileAdapter({ root, readJson: source.readJson });
92
+ function read(id) {
93
+ const goal = adapter.get(id);
94
+ if (!goal) fail("CLI_GOAL_NOT_FOUND");
95
+ return { goal, digest: rawDigest };
96
+ }
97
+ function revalidate(snapshot) {
98
+ let current;
99
+ try {
100
+ current = read(snapshot.goal.id);
101
+ } catch {
102
+ fail("CLI_GOAL_SOURCE_CHANGED");
103
+ }
104
+ if (
105
+ current.digest !== snapshot.digest ||
106
+ current.goal.storeId !== snapshot.goal.storeId ||
107
+ current.goal.revision !== snapshot.goal.revision ||
108
+ current.goal.controlGeneration !== snapshot.goal.controlGeneration
109
+ )
110
+ fail("CLI_GOAL_SOURCE_CHANGED");
111
+ return current.goal;
112
+ }
113
+ return {
114
+ storeId: adapter.storeId,
115
+ read,
116
+ revalidate,
117
+ checkRoot: source.checkRoot,
118
+ };
119
+ }
@@ -1,10 +1,12 @@
1
1
  /** Shared correctness guidance for normal and file-only coding sessions. */
2
2
  export const CODING_TASK_QUALITY_GUIDANCE = `Coding task quality:
3
3
  - Read the actual implementation and its exported API before writing a caller or test. Check parameter order, return shapes, asynchronous lifecycle and fixture requirements against the source; an example in another directory can have different relative imports.
4
+ - Keep progress explanations to one or two sentences. Once the relevant API and behavior are clear, make the next tool call; resolve a remaining uncertainty with a targeted read. Put implementation code directly in write/edit tool arguments instead of rehearsing the code or repeatedly analyzing the same source in ordinary assistant text.
4
5
  - Resolve each relative import from the destination file's directory. Read back the completed file and fix newDiagnostics from write/edit results before finishing. A successful write confirms persistence, not syntax, test execution or acceptance.
5
6
  - Regression tests need a working positive control and a negative control that exercises the requested behavior through the real production entry point. Assert observable effects; avoid mocks or alternate calls that bypass the behavior under test. Complete the current worker/request when testing cancellation and replacement.
7
+ - For boundary regressions, derive expected defaults, computed values and rejection rules from the implementation or the explicit requested contract. Inject mismatched, tampered or stale input through the real entry point while keeping other prerequisites valid; verify that the fixture reaches the intended check rather than failing during setup. Compare observable results with independent expected values or authoritative state, not a helper that merely recomputes the same result.
6
8
  - Use available execution tools for a focused check when permitted. When execution tools are unavailable, inspect imports, API contracts and fixtures using the available file tools, and clearly say that tests were not run. Never describe a read-back or static check as a passing test or end-to-end verification.
7
- - Finish requested file changes before the final response. For large files, write a smaller complete section and extend it with bounded edits; do not spend the remaining output budget repeatedly rewriting the entire file. State what was delivered, what was checked and what remains unverified.`;
9
+ - Finish requested file changes before the final response. Reserve output and remaining requests for writing, read-back and focused validation. For large files, aim for a few thousand characters of new content per call: write a small complete section, then extend it with edit_file using a unique existing anchor and a replacement containing that anchor plus the next complete section. Keep each tool call complete and wait for its result before extending the file again; do not repeatedly rewrite the growing file. State what was delivered, what was checked and what remains unverified.`;
8
10
 
9
11
  export const HERMETIC_CODING_SYSTEM_PROMPT = `You are a hermetic coding assistant. The exposed file tools are the complete tool surface. Inspect and mutate only the exact paths authorized by the user request. Do not invoke hidden tools, commands, language servers or other execution paths.
10
12
 
@@ -83,7 +83,7 @@ const DESCRIPTORS = [
83
83
  "Requested live service tier; model and endpoint support are checked before dispatch",
84
84
  }),
85
85
  descriptor("llm.visionModel", ["string", "null"], {
86
- description: "Model used for image turns",
86
+ description: "Fallback for image turns when the main model lacks vision",
87
87
  }),
88
88
  descriptor("llm.fallbackModel", ["string", "null"], {
89
89
  deprecated: true,
@@ -1,5 +1,5 @@
1
1
  /** Parse raw agent NDJSON. A declaration of success is never terminal evidence. */
2
- export function verifyAgentTerminal(output) {
2
+ export function verifyAgentTerminal(output, { allowSessionEnd = false } = {}) {
3
3
  try {
4
4
  const events = output
5
5
  .split(/\r?\n/u)
@@ -7,6 +7,31 @@ export function verifyAgentTerminal(output) {
7
7
  .map((line) => JSON.parse(line));
8
8
  const results = events.filter((event) => event?.type === "result");
9
9
  const terminal = results[0];
10
+ const last = events.at(-1);
11
+ const init = events.find(
12
+ (event) => event?.type === "system" && event.subtype === "init",
13
+ );
14
+ const sessionEnded =
15
+ allowSessionEnd &&
16
+ events.at(-2) === terminal &&
17
+ last?.type === "system" &&
18
+ last.subtype === "end" &&
19
+ typeof terminal?.session_id === "string" &&
20
+ terminal.session_id.length > 0 &&
21
+ last.session_id === terminal.session_id &&
22
+ init?.session_id === terminal.session_id &&
23
+ typeof terminal.trace_id === "string" &&
24
+ terminal.trace_id.length > 0 &&
25
+ last.trace_id === terminal.trace_id &&
26
+ Number.isSafeInteger(terminal.seq) &&
27
+ terminal.seq > 0 &&
28
+ last.seq === terminal.seq + 1 &&
29
+ Number.isSafeInteger(terminal.turn) &&
30
+ terminal.turn > 0 &&
31
+ last.turns === terminal.turn &&
32
+ events.filter(
33
+ (event) => event?.type === "system" && event.subtype === "end",
34
+ ).length === 1;
10
35
  const usage = terminal?.usage;
11
36
  const normalizedUsage =
12
37
  usage &&
@@ -26,7 +51,7 @@ export function verifyAgentTerminal(output) {
26
51
  return {
27
52
  terminalVerified:
28
53
  results.length === 1 &&
29
- events.at(-1) === terminal &&
54
+ (last === terminal || sessionEnded) &&
30
55
  terminal.subtype === "success" &&
31
56
  terminal.is_error === false &&
32
57
  !events.some((event) => event?.type === "error"),
@@ -23,6 +23,11 @@ import {
23
23
  } from "./evolution-ledger.js";
24
24
 
25
25
  const ARTIFACT_STORE_DIRECTORY_BOUNDARIES = new WeakMap();
26
+ // Capture repository function identity before binding instance methods. Only
27
+ // these exact defaults may skip legacy list/get I/O; overrides still execute
28
+ // and must match the independently authenticated descriptor snapshots.
29
+ const DEFAULT_STORE_LIST = ArtifactStore.prototype.list;
30
+ const DEFAULT_STORE_GET = ArtifactStore.prototype.get;
26
31
  // Only the shared identity helper's directory opens use this adapter. A POSIX
27
32
  // directory/FIFO swap must fail before reaching a potentially blocking open.
28
33
  const INDEX_DIRECTORY_FS = Object.freeze({
@@ -1014,7 +1019,7 @@ function normalizeKeyId(value, label) {
1014
1019
  return value;
1015
1020
  }
1016
1021
 
1017
- function captureMethod(port, method, label) {
1022
+ function captureDataMethod(port, method, label) {
1018
1023
  if (!port || (typeof port !== "object" && typeof port !== "function")) {
1019
1024
  throw new TypeError(`EvolutionArtifactPorts requires ${label}.${method}`);
1020
1025
  }
@@ -1029,13 +1034,17 @@ function captureMethod(port, method, label) {
1029
1034
  `EvolutionArtifactPorts requires ${label}.${method} as a data method`,
1030
1035
  );
1031
1036
  }
1032
- return Object.freeze(descriptor.value.bind(port));
1037
+ return descriptor.value;
1033
1038
  }
1034
1039
  cursor = Object.getPrototypeOf(cursor);
1035
1040
  }
1036
1041
  throw new TypeError(`EvolutionArtifactPorts requires ${label}.${method}`);
1037
1042
  }
1038
1043
 
1044
+ function captureMethod(port, method, label) {
1045
+ return Object.freeze(captureDataMethod(port, method, label).bind(port));
1046
+ }
1047
+
1039
1048
  function captureArtifactStore(store) {
1040
1049
  rejectProxy(store, "artifactStore");
1041
1050
  if (!hasStablePrototype(store, ArtifactStore.prototype, "artifactStore")) {
@@ -1049,19 +1058,21 @@ function captureArtifactStore(store) {
1049
1058
  }
1050
1059
  const resolvedDir = path.resolve(dir);
1051
1060
  const layout = establishStoreLayout(resolvedDir);
1061
+ const list = captureDataMethod(store, "list", "artifactStore");
1062
+ const get = captureDataMethod(store, "get", "artifactStore");
1052
1063
  const captured = Object.freeze({
1053
1064
  dir: resolvedDir,
1054
- get: captureMethod(store, "get", "artifactStore"),
1065
+ get: get === DEFAULT_STORE_GET ? null : Object.freeze(get.bind(store)),
1055
1066
  layout,
1056
- list: captureMethod(store, "list", "artifactStore"),
1067
+ list: list === DEFAULT_STORE_LIST ? null : Object.freeze(list.bind(store)),
1057
1068
  publishDataOnce: captureMethod(store, "publishDataOnce", "artifactStore"),
1058
1069
  storedPath: captureMethod(store, "storedPath", "artifactStore"),
1059
1070
  verifyIntegrity: captureMethod(store, "verifyIntegrity", "artifactStore"),
1060
1071
  });
1061
1072
  // ArtifactStore methods consult public instance fields such as `dir` on
1062
1073
  // every call. Freeze the trusted instance after capturing its methods so a
1063
- // later collaborator cannot redirect a bound get/list/integrity call to a
1064
- // different store root.
1074
+ // later collaborator cannot redirect a bound custom get/list/integrity call
1075
+ // to a different store root. Null index methods select trusted snapshots.
1065
1076
  Object.freeze(store);
1066
1077
  return captured;
1067
1078
  }
@@ -2164,7 +2175,7 @@ export class EvolutionArtifactPorts {
2164
2175
  let entries;
2165
2176
  try {
2166
2177
  entries = rejectPromise(
2167
- this.#store.list(),
2178
+ this.#store.list ? this.#store.list() : trustedBefore.entries,
2168
2179
  "ArtifactStore.list",
2169
2180
  EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
2170
2181
  );
@@ -2187,9 +2198,9 @@ export class EvolutionArtifactPorts {
2187
2198
  "ArtifactStore.list did not return an array",
2188
2199
  );
2189
2200
  }
2190
- // ArtifactStore currently materializes its whole JSONL index before this
2191
- // adapter can enforce a limit. Reject immediately after return; this is an
2192
- // explicit scalability boundary, not a claim of bounded store-side query.
2201
+ // Default entries already came from a bounded descriptor read. Custom
2202
+ // list hooks retain their original execution and exact cross-check; their
2203
+ // own I/O cannot be constrained by this adapter's snapshot byte limit.
2193
2204
  if (entries.length > EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES) {
2194
2205
  throw artifactError(
2195
2206
  EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
@@ -2280,7 +2291,7 @@ export class EvolutionArtifactPorts {
2280
2291
  let current;
2281
2292
  try {
2282
2293
  current = rejectPromise(
2283
- this.#store.get(artifactId),
2294
+ this.#store.get ? this.#store.get(artifactId) : listed[0],
2284
2295
  "ArtifactStore.get",
2285
2296
  EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
2286
2297
  );
@@ -4,8 +4,7 @@ import path from "node:path";
4
4
  import { types } from "node:util";
5
5
  import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
6
6
  import { withFileLock } from "../with-file-lock.js";
7
- import { readBoundedDescriptor } from "./bounded-descriptor-read.js";
8
- import { withEvolutionFileIdentity } from "./evolution-file-identity.js";
7
+ import { createEvolutionPhysicalStateReader } from "./evolution-physical-state-reader.js";
9
8
  import {
10
9
  createEvolutionWitnessSegments,
11
10
  EVOLUTION_SEGMENTED_WITNESS_SCHEMA,
@@ -17,6 +16,8 @@ import {
17
16
 
18
17
  export const EVOLUTION_FILE_WITNESS_STORE_SCHEMA =
19
18
  "chainlesschain.evolution-file-witness-store/v1";
19
+ export const EVOLUTION_WITNESS_STATE_READ_FAILED_CODE =
20
+ "CC_EVOLUTION_WITNESS_STATE_READ_FAILED";
20
21
 
21
22
  const WITNESS_DOMAIN = `${EVOLUTION_LEDGER_WITNESS_SCHEMA}\0`;
22
23
  const ANCESTRY_DOMAIN = `${EVOLUTION_LEDGER_WITNESS_ANCESTRY_SCHEMA}\0`;
@@ -247,6 +248,63 @@ export function createEvolutionFileWitness({
247
248
  }
248
249
  const secureOptions = { deps: { fs: fsImpl }, failIfUnavailable: true };
249
250
  ensurePrivateDirectory(directory, secureOptions);
251
+ const readerOptions = {
252
+ fsImpl,
253
+ maximumBytes,
254
+ minimumBytes: 2,
255
+ directoryMode: process.platform === "win32" ? null : 0o700n,
256
+ sizeError: "store size is invalid",
257
+ label: "witness",
258
+ errorCode: EVOLUTION_WITNESS_STATE_READ_FAILED_CODE,
259
+ readFailureMessage: "witness bounded read failed",
260
+ };
261
+ const rootReader = createEvolutionPhysicalStateReader({
262
+ ...readerOptions,
263
+ directoryPath: directory,
264
+ });
265
+ const segmentDirectory = `${target}.segments-v2`;
266
+ let segmentReader = null;
267
+ const captureSegments = () => {
268
+ rootReader.recheck();
269
+ segmentReader = createEvolutionPhysicalStateReader({
270
+ ...readerOptions,
271
+ label: "witness segment",
272
+ readFailureMessage: "witness segment bounded read failed",
273
+ directoryPath: segmentDirectory,
274
+ });
275
+ rootReader.recheck();
276
+ return segmentReader;
277
+ };
278
+ // A constructor may secure existing local storage. Read paths never create
279
+ // or repair a missing segment directory; first publication retains that job.
280
+ let existingSegments = false;
281
+ try {
282
+ fsImpl.lstatSync(segmentDirectory, { bigint: true });
283
+ existingSegments = true;
284
+ } catch (cause) {
285
+ if (cause?.code !== "ENOENT") throw cause;
286
+ }
287
+ if (existingSegments) {
288
+ ensurePrivateDirectory(segmentDirectory, secureOptions);
289
+ captureSegments();
290
+ }
291
+ const checkSegments = () => {
292
+ const reader = segmentReader ?? captureSegments();
293
+ rootReader.recheck();
294
+ reader.recheck();
295
+ return reader;
296
+ };
297
+ const prepareSegments = () => {
298
+ rootReader.recheck();
299
+ if (segmentReader !== null) return segmentReader.recheck();
300
+ ensurePrivateDirectory(segmentDirectory, secureOptions);
301
+ captureSegments();
302
+ };
303
+ const recheckStorage = () => {
304
+ rootReader.recheck();
305
+ segmentReader?.recheck();
306
+ rootReader.recheck();
307
+ };
250
308
  // Cache immutable encodings, never an authority decision. Keep the earliest
251
309
  // entries when full so full-history scans cannot churn the bounded cache.
252
310
  const recordEncodings = new Map();
@@ -587,67 +645,25 @@ export function createEvolutionFileWitness({
587
645
  return store;
588
646
  };
589
647
 
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);
602
- if (
603
- !stat.isFile() ||
604
- stat.isSymbolicLink() ||
605
- stat.nlink !== 1 ||
606
- stat.size < 2 ||
607
- stat.size > byteLimit
608
- ) {
609
- throw new Error("witness store size is invalid");
610
- }
611
- let descriptor;
612
- const sameFile = (observed) =>
613
- observed.isFile() &&
614
- observed.nlink === 1 &&
615
- observed.dev === stat.dev &&
616
- observed.ino === stat.ino &&
617
- observed.size === stat.size &&
618
- observed.mtimeMs === stat.mtimeMs &&
619
- observed.ctimeMs === stat.ctimeMs;
620
- try {
621
- descriptor = fsImpl.openSync(
622
- file,
623
- fsImpl.constants.O_RDONLY | (fsImpl.constants.O_NOFOLLOW || 0),
624
- );
625
- if (!samePathHandle(stat, fsImpl.fstatSync(descriptor))) {
626
- throw new Error("witness store changed while opening");
627
- }
628
- const bytes = readBoundedDescriptor(
629
- fsImpl,
630
- descriptor,
631
- stat.size,
632
- byteLimit,
633
- );
634
- const afterPath = fsImpl.lstatSync(file);
635
- if (
636
- !samePathHandle(stat, fsImpl.fstatSync(descriptor)) ||
637
- afterPath.isSymbolicLink() ||
638
- !sameFile(afterPath)
639
- ) {
640
- throw new Error("witness store changed while reading");
641
- }
642
- return bytes;
643
- } finally {
644
- if (descriptor !== undefined) fsImpl.closeSync(descriptor);
645
- }
648
+ const readBytes = (file, byteLimit = maximumBytes, allowAbsent = false) => {
649
+ recheckStorage();
650
+ const reader = file === target ? rootReader : checkSegments();
651
+ const bytes = reader.read(file, {
652
+ maximumBytes: byteLimit,
653
+ allowAbsent,
654
+ prepareFile: (name) => ensurePrivateFile(name, secureOptions),
655
+ });
656
+ recheckStorage();
657
+ return bytes;
646
658
  };
647
659
 
648
660
  const readStore = (options = {}) => {
649
- if (!fsImpl.existsSync(target)) return emptyStore();
650
- const bytes = readBytes(target, maximumHistoryBytes);
661
+ const bytes = readBytes(target, maximumHistoryBytes, true);
662
+ if (bytes === null) {
663
+ const store = emptyStore();
664
+ recheckStorage();
665
+ return store;
666
+ }
651
667
  const parsed = JSON.parse(
652
668
  new TextDecoder("utf-8", { fatal: true }).decode(bytes),
653
669
  );
@@ -660,7 +676,10 @@ export function createEvolutionFileWitness({
660
676
  headBytes: bytes.length,
661
677
  })
662
678
  : validateStore(parsed, trustEpoch);
663
- if (trustEpoch === currentTrustEpoch()) return store;
679
+ if (trustEpoch === currentTrustEpoch()) {
680
+ recheckStorage();
681
+ return store;
682
+ }
664
683
  }
665
684
  throw new Error("witness verifier trust epoch changed while reading");
666
685
  };
@@ -716,6 +735,8 @@ export function createEvolutionFileWitness({
716
735
  const segments = createEvolutionWitnessSegments({
717
736
  filePath: target,
718
737
  fsImpl,
738
+ checkDirectory: checkSegments,
739
+ prepareDirectory: prepareSegments,
719
740
  readBytes,
720
741
  writeBytes,
721
742
  validateStore,
@@ -760,8 +781,10 @@ export function createEvolutionFileWitness({
760
781
  return store.current;
761
782
  };
762
783
 
763
- const underLock = (operation) =>
764
- lock(target, operation, { failIfUnavailable: true, _fs: fsImpl });
784
+ const underLock = (operation) => {
785
+ recheckStorage();
786
+ return lock(target, operation, { failIfUnavailable: true, _fs: fsImpl });
787
+ };
765
788
  const discarded = (store, snapshot) =>
766
789
  segments.isDiscarded(store, snapshot) ||
767
790
  store.discardedAnchors.some(
@@ -4,6 +4,7 @@ import { createEvolutionFileWitness } from "./evolution-file-witness.js";
4
4
  import {
5
5
  EvolutionLedger,
6
6
  captureEvolutionLedgerMigrationSource,
7
+ captureEvolutionLedgerDirectoryBoundary,
7
8
  } from "./evolution-ledger.js";
8
9
  import { createEvolutionLedgerV2JournalFromFactory } from "./evolution-ledger-v2-journal.js";
9
10
 
@@ -12,6 +13,7 @@ export const EVOLUTION_LEDGER_FILE_BACKEND_SCHEMA =
12
13
 
13
14
  const BACKENDS = new WeakSet();
14
15
  const BACKEND_BINDINGS = new WeakMap();
16
+ const SOURCE_DIRECTORY_BOUNDARIES = new WeakMap();
15
17
 
16
18
  function requiredString(value, label) {
17
19
  if (typeof value !== "string" || value.trim() === "") {
@@ -205,7 +207,10 @@ export function createEvolutionLedgerFileBackend({
205
207
  ledger,
206
208
  witness,
207
209
  });
210
+ const sourceDirectoryBoundary =
211
+ captureEvolutionLedgerDirectoryBoundary(sourceLedger);
208
212
  BACKENDS.add(backend);
213
+ SOURCE_DIRECTORY_BOUNDARIES.set(backend, sourceDirectoryBoundary);
209
214
  BACKEND_BINDINGS.set(
210
215
  backend,
211
216
  Object.freeze({
@@ -234,3 +239,20 @@ export function captureEvolutionLedgerFileBackendBinding(value) {
234
239
  );
235
240
  return binding;
236
241
  }
242
+
243
+ /** Captured private v1 source even when the public journal is a v2 wrapper. */
244
+ export function captureEvolutionLedgerFileBackendSourceDirectoryBoundary(
245
+ value,
246
+ ) {
247
+ if (arguments.length !== 1)
248
+ throw new TypeError(
249
+ "source directory boundary capture takes exactly one original backend",
250
+ );
251
+ const boundary = SOURCE_DIRECTORY_BOUNDARIES.get(value);
252
+ if (!boundary)
253
+ throw new TypeError(
254
+ "a branded file backend source directory boundary is required",
255
+ );
256
+ boundary.recheck();
257
+ return boundary;
258
+ }
@@ -5,7 +5,10 @@ import { types as utilTypes } from "node:util";
5
5
 
6
6
  import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
7
7
  import { withFileLock } from "../with-file-lock.js";
8
- import { readBoundedDescriptor } from "./bounded-descriptor-read.js";
8
+ import {
9
+ createEvolutionManifestStateReader,
10
+ captureEvolutionManifestStateReaderDirectoryBoundary,
11
+ } from "./evolution-manifest-state-reader.js";
9
12
  import { EVOLUTION_LEDGER_MANIFEST_CATALOG_APPEND_RESULT_SCHEMA } from "./evolution-ledger-manifest-catalog.js";
10
13
 
11
14
  export const EVOLUTION_LEDGER_FILE_MANIFEST_CATALOG_SCHEMA =
@@ -15,6 +18,7 @@ const STATE_SCHEMA =
15
18
  "chainlesschain.evolution-ledger-file-manifest-catalog-state/v2";
16
19
  const STATE_KEYS = new Set(["manifests", "schema"]);
17
20
  const DEFAULT_MAXIMUM_BYTES = 64 * 1024 * 1024;
21
+ const DIRECTORY_BOUNDARIES = new WeakMap();
18
22
 
19
23
  function exactRecord(value, keys, label) {
20
24
  if (
@@ -75,39 +79,13 @@ export function createEvolutionLedgerFileManifestCatalogBackend({
75
79
  const secureOptions = { deps: { fs: fsImpl }, failIfUnavailable: true };
76
80
  ensurePrivateDirectory(directory, secureOptions);
77
81
 
78
- const readBytes = () => {
79
- if (!fsImpl.existsSync(file)) return null;
80
- const stat = fsImpl.lstatSync(file);
81
- if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
82
- throw new Error("manifest catalog file identity is invalid");
83
- }
84
- if (stat.size > maximumBytes)
85
- throw new Error("manifest catalog exceeds maximumBytes");
86
- let descriptor;
87
- try {
88
- descriptor = fsImpl.openSync(
89
- file,
90
- fsImpl.constants.O_RDONLY | (fsImpl.constants.O_NOFOLLOW || 0),
91
- );
92
- const opened = fsImpl.fstatSync(descriptor);
93
- if (!opened.isFile() || opened.nlink !== 1 || opened.size !== stat.size) {
94
- throw new Error("manifest catalog changed while opening");
95
- }
96
- const bytes = readBoundedDescriptor(
97
- fsImpl,
98
- descriptor,
99
- stat.size,
100
- maximumBytes,
101
- );
102
- const after = fsImpl.lstatSync(file);
103
- if (after.isSymbolicLink() || after.size !== stat.size) {
104
- throw new Error("manifest catalog changed while reading");
105
- }
106
- return bytes;
107
- } finally {
108
- if (descriptor !== undefined) fsImpl.closeSync(descriptor);
109
- }
110
- };
82
+ const stateReader = createEvolutionManifestStateReader({
83
+ file,
84
+ fsImpl,
85
+ maximumBytes,
86
+ label: "manifest catalog",
87
+ });
88
+ const readBytes = stateReader.read;
111
89
 
112
90
  const readState = () => {
113
91
  const bytes = readBytes();
@@ -173,13 +151,14 @@ export function createEvolutionLedgerFileManifestCatalogBackend({
173
151
  }
174
152
  };
175
153
 
176
- return Object.freeze({
154
+ const backend = Object.freeze({
177
155
  descriptor: Object.freeze({
178
156
  file,
179
157
  localOnly: true,
180
158
  schema: EVOLUTION_LEDGER_FILE_MANIFEST_CATALOG_SCHEMA,
181
159
  }),
182
160
  compareAndAppend(request) {
161
+ stateReader.recheck();
183
162
  return lock(
184
163
  file,
185
164
  () => {
@@ -215,4 +194,24 @@ export function createEvolutionLedgerFileManifestCatalogBackend({
215
194
  return structuredClone(readState().manifests.at(-1) ?? null);
216
195
  },
217
196
  });
197
+ DIRECTORY_BOUNDARIES.set(
198
+ backend,
199
+ captureEvolutionManifestStateReaderDirectoryBoundary(stateReader),
200
+ );
201
+ return backend;
202
+ }
203
+
204
+ /** Original local catalog directory only; structural wrappers are not owners. */
205
+ export function captureEvolutionLedgerFileManifestCatalogDirectoryBoundary(
206
+ backend,
207
+ ) {
208
+ if (arguments.length !== 1)
209
+ throw new TypeError("catalog directory capture takes one original backend");
210
+ const boundary = DIRECTORY_BOUNDARIES.get(backend);
211
+ if (!boundary)
212
+ throw new TypeError(
213
+ "an original file manifest catalog backend is required",
214
+ );
215
+ boundary.recheck();
216
+ return boundary;
218
217
  }