chainlesschain 0.166.117 → 0.166.121

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 (195) hide show
  1. package/README.md +36 -4
  2. package/package.json +3 -3
  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/.build-hash +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-C9tSCImB.js → AIOps-DYEi6MCC.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-CXKExmT_.js → ActionButton-kLXp42k3.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DE8WBWHo.js → Analytics-BOgKma-R.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-BdpwNM08.js → AppLayout-D8z-uRpv.js} +5 -5
  10. package/src/assets/web-panel/assets/{Artifacts-94TQpe38.js → Artifacts-TFX0t24T.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-DdDEv5Ej.js → Audit-GjRCAv0V.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-TU9nlV7j.js → BackgroundAgents-CpQcMxi1.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-C0GPoUUq.js → Backup-BuQNWrhT.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-iyrVn2I6.js → BaseInput-QuOtSaFy.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-Dqo8spwp.js → Chat-BC0W1L_3.js} +5 -5
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CDqHSzc4.js → ChatBubbleRenderer-B6SagRW5.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-BF_XJvQi.js → Checkbox-BsdQ1Ku0.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-5tUWoOa9.js → Codegen-CPsAAoqV.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-BQxSeRvG.js → Col-C9LgCMN0.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-B2Ra7w-J.js → Community-Xj0vGvD5.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-GjxJwlr0.js → Compact-CXoqTgrf.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-BTUP6RTA.js → Compliance-CeshjrVy.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-DiwfL16s.js → Cowork-DjXRRrg1.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-1ceOo5ux.js → Cron-vGoU419C.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-aHYxLu0u.js → Crosschain-DZaChZ5s.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-eeTjzOLV.js → DID-BojMQqQM.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-CT3fpkvX.js → Dashboard-pKAYt4UL.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-B3UpOy-N.js → Dropdown-CqJa8N05.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-Ba2Rvoru.js → EmailListRenderer-fZ4_KAvw.js} +1 -1
  30. package/src/assets/web-panel/assets/{EvolutionSettings-CGtn93on.js → EvolutionSettings-C3kenWes.js} +1 -1
  31. package/src/assets/web-panel/assets/{FamilyGuardDashboard-C0IUdHQt.js → FamilyGuardDashboard-B3tqj1Hu.js} +1 -1
  32. package/src/assets/web-panel/assets/{Federation-1QIDwemn.js → Federation-CS1M1oJN.js} +1 -1
  33. package/src/assets/web-panel/assets/{FormItemContext-NGbSsdje.js → FormItemContext-C5q-KYPu.js} +1 -1
  34. package/src/assets/web-panel/assets/{GenericCardRenderer-DzVZc2Ba.js → GenericCardRenderer-Bl43BLxD.js} +1 -1
  35. package/src/assets/web-panel/assets/{Git-B_1TDbSr.js → Git-BiN2Uo-z.js} +2 -2
  36. package/src/assets/web-panel/assets/{Governance-BDl_ySyI.js → Governance-weglJWG4.js} +1 -1
  37. package/src/assets/web-panel/assets/{Inference-CAp-wcCK.js → Inference-B4qBxVSK.js} +1 -1
  38. package/src/assets/web-panel/assets/{KnowledgeGraph-DRjRcxul.js → KnowledgeGraph-iePl1vLv.js} +1 -1
  39. package/src/assets/web-panel/assets/Logs-t2lKo4HX.js +2 -0
  40. package/src/assets/web-panel/assets/MarkdownRenderer-CxSrBHYC.js +1 -0
  41. package/src/assets/web-panel/assets/{Marketplace-DfiXL6UO.js → Marketplace-D0Y_OmXs.js} +1 -1
  42. package/src/assets/web-panel/assets/{McpTools-D9rIvFjN.js → McpTools-B7zxPsFE.js} +3 -3
  43. package/src/assets/web-panel/assets/{Memory-D-p7yDzj.js → Memory-DDpmVHpl.js} +2 -2
  44. package/src/assets/web-panel/assets/{MobileBridge-D0XT5B6m.js → MobileBridge-BLXNdKH0.js} +1 -1
  45. package/src/assets/web-panel/assets/{MobileProjects-CL9cHVIk.js → MobileProjects-CF6DqNfk.js} +1 -1
  46. package/src/assets/web-panel/assets/{Mtc-CME4kDmC.js → Mtc-7OFckDUe.js} +4 -4
  47. package/src/assets/web-panel/assets/{MtcAudit-BjGOXFnn.js → MtcAudit-2QFWPXVR.js} +4 -4
  48. package/src/assets/web-panel/assets/{Multisig-CSzc53e0.js → Multisig-D9opE0iP.js} +3 -3
  49. package/src/assets/web-panel/assets/{NLProgramming-BYjZnaH8.js → NLProgramming-jOD2E7Rs.js} +1 -1
  50. package/src/assets/web-panel/assets/{Notes-BUPOYE0C.js → Notes-CgtysN6L.js} +3 -3
  51. package/src/assets/web-panel/assets/{NotificationSettings-BK8YADru.js → NotificationSettings-Itz3X5DM.js} +1 -1
  52. package/src/assets/web-panel/assets/OrderTableRenderer-C2ylWgSI.js +1 -0
  53. package/src/assets/web-panel/assets/{Organization-B0WNlhMd.js → Organization-CKgpbSoy.js} +4 -4
  54. package/src/assets/web-panel/assets/{Overflow-CED6ZO87.js → Overflow-C0Pi9LvB.js} +1 -1
  55. package/src/assets/web-panel/assets/{P2P-CBdc8fkP.js → P2P-DdIDorai.js} +2 -2
  56. package/src/assets/web-panel/assets/{PdhVaultBrowser-DuauYi03.js → PdhVaultBrowser-DVJz6cGr.js} +4 -4
  57. package/src/assets/web-panel/assets/{Permissions-kfgY0ukN.js → Permissions-D19dtcL0.js} +4 -4
  58. package/src/assets/web-panel/assets/{PersonalDataHub-CDY3I1Do.js → PersonalDataHub-A-QMQUJW.js} +5 -5
  59. package/src/assets/web-panel/assets/{Pipeline-B9x0wKSK.js → Pipeline-7YF1yhkH.js} +1 -1
  60. package/src/assets/web-panel/assets/{Privacy-Cqhcr_Qv.js → Privacy-CcsoAU7d.js} +1 -1
  61. package/src/assets/web-panel/assets/{ProjectInit-CFhoyo57.js → ProjectInit-CMU0Z5in.js} +2 -2
  62. package/src/assets/web-panel/assets/{ProjectSettings-DCBjV2Hm.js → ProjectSettings-D3l0RXK6.js} +2 -2
  63. package/src/assets/web-panel/assets/Projects-BCcHN_fQ.js +1 -0
  64. package/src/assets/web-panel/assets/{Providers-BuCIWZY1.js → Providers-DB0pw3DM.js} +1 -1
  65. package/src/assets/web-panel/assets/{QrScannerModal-CEXNL5CB.js → QrScannerModal-SVvLX_VH.js} +1 -1
  66. package/src/assets/web-panel/assets/{QuickAsk-DgK8aLAU.js → QuickAsk-B4dJgwL4.js} +1 -1
  67. package/src/assets/web-panel/assets/{Recommend-BxEYQRAs.js → Recommend-9GxSlYt5.js} +1 -1
  68. package/src/assets/web-panel/assets/{RemoteSession-PRsxNsYU.js → RemoteSession-C5vzfpTM.js} +2 -2
  69. package/src/assets/web-panel/assets/{Reputation-S-tQH70r.js → Reputation-7AeSkJz1.js} +1 -1
  70. package/src/assets/web-panel/assets/{Row-U1Ws3CJl.js → Row-Dc3RwqWR.js} +1 -1
  71. package/src/assets/web-panel/assets/{RssFeed-BftCyeii.js → RssFeed-Y8iSgp9o.js} +3 -3
  72. package/src/assets/web-panel/assets/{Search-o4gK0cPy.js → Search-bxkz-UtW.js} +1 -1
  73. package/src/assets/web-panel/assets/{Security-U2ZRpsbA.js → Security-cs5yf-AE.js} +3 -3
  74. package/src/assets/web-panel/assets/{Services-D0tRjWl_.js → Services-2va9CJFi.js} +2 -2
  75. package/src/assets/web-panel/assets/{Skeleton-DzZ8TFsB.js → Skeleton-n8HOSPcE.js} +1 -1
  76. package/src/assets/web-panel/assets/{Skills-B1cC7rju.js → Skills-BcV5aWYc.js} +1 -1
  77. package/src/assets/web-panel/assets/{Sla-CtIHOR0J.js → Sla-B73zPlDG.js} +1 -1
  78. package/src/assets/web-panel/assets/{SpeechSettings-CD8ZlQpW.js → SpeechSettings-OhhEZsxi.js} +1 -1
  79. package/src/assets/web-panel/assets/{SyncSettings-BilFQWQk.js → SyncSettings-4HT8618-.js} +2 -2
  80. package/src/assets/web-panel/assets/{Tasks-Bmg2qLXl.js → Tasks-Dw7nEG94.js} +1 -1
  81. package/src/assets/web-panel/assets/{Templates-D6yofyC1.js → Templates-BX7zHI9M.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tenant-BzttD-Gp.js → Tenant-SrFN1ZTF.js} +1 -1
  83. package/src/assets/web-panel/assets/{Terminal-DyRXeYuH.js → Terminal-B-ohas9S.js} +3 -3
  84. package/src/assets/web-panel/assets/{TimelineRenderer-Bdmkx-Tr.js → TimelineRenderer-exEpcIMD.js} +1 -1
  85. package/src/assets/web-panel/assets/{Tokens-10RcVAVJ.js → Tokens-Fucr0-Ek.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trigger-CKV-9DdV.js → Trigger-CPlH4qBU.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trust-BEc-sW1q.js → Trust-DCNisGxI.js} +1 -1
  88. package/src/assets/web-panel/assets/{UkeySign-BSql8piX.js → UkeySign-Crf4bea0.js} +1 -1
  89. package/src/assets/web-panel/assets/{VideoEditing-C4OH2f4s.js → VideoEditing-JAq5jURW.js} +1 -1
  90. package/src/assets/web-panel/assets/{Wallet-BSSvWKmv.js → Wallet-BaOfcyqF.js} +4 -4
  91. package/src/assets/web-panel/assets/{WebAuthn-LxIzXo9d.js → WebAuthn-DMtCTRsk.js} +5 -5
  92. package/src/assets/web-panel/assets/{WorkflowEditor-DWwdfcte.js → WorkflowEditor-BAKiA5p0.js} +1 -1
  93. package/src/assets/web-panel/assets/{addon-fit-CK6X9sAG.js → addon-fit-CbkjSUJl.js} +1 -1
  94. package/src/assets/web-panel/assets/{chat-DWxYYLD5.js → chat-D3MA12Z1.js} +1 -1
  95. package/src/assets/web-panel/assets/{colors-BxEcQqtg.js → colors-C7xYOAG-.js} +1 -1
  96. package/src/assets/web-panel/assets/{compact-item-DNbezFOJ.js → compact-item-ClybpIV-.js} +1 -1
  97. package/src/assets/web-panel/assets/{createContext-DaJXc5nx.js → createContext-Dyp-8LSs.js} +1 -1
  98. package/src/assets/web-panel/assets/devWarning-CTazgK0z.js +1 -0
  99. package/src/assets/web-panel/assets/{hasIn-BMrw3sg3.js → hasIn-D4xsaFD1.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Cm9DYzhC.js → index-B-eU0wCl.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BaByEdBj.js → index-B7jiI-31.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-T4T84G6X.js → index-BKyEhMUl.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-LRb0QX6S.js → index-BMybVxE3.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dwg38xTG.js → index-BagQvwmy.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Bum_aSyd.js → index-BcmJBS7G.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BCCd8Rj6.js → index-BfZW-iaH.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-B9b6UaZl.js → index-BmBqpU1f.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-6W0V5G2t.js → index-Bn8O_DC1.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BR4nWOmR.js → index-BpLWtYzy.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-2c0bL3SE.js → index-CHJUN9dR.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DpSp97J8.js → index-CNBhQp0_.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DEvdSKT4.js → index-CS7E_bsQ.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BNZDGV2q.js → index-CSOgUWox.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-VD-dTYQE.js → index-CiRxc4Y0.js} +1 -1
  115. package/src/assets/web-panel/assets/index-CrZlBv0O.js +1 -0
  116. package/src/assets/web-panel/assets/{index-CpwgF7vh.js → index-CvsWZZzU.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BcuytRTa.js → index-Cvwa0445.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-B_b9_6wQ.js → index-CxSVA3yj.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CaxzeSIM.js → index-D5aA2hc_.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-cCqCEa9X.js → index-D6ETHrUX.js} +3 -3
  121. package/src/assets/web-panel/assets/{index-DzZHBzCb.js → index-D6GFzqYv.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-hPy_f08G.js → index-DDDP6-gb.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-NEW5EN2w.js → index-DJVzi25x.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Dr18agDl.js → index-DMlcfkHz.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DarVV5yG.js → index-DNeHM7F3.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-7zzLqfvI.js → index-DT57ltH_.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BAV07GDK.js → index-DWHngGhj.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-cKBpcInt.js → index-DuGdqMTo.js} +1 -1
  129. package/src/assets/web-panel/assets/index-DyNp52Uj.js +1 -0
  130. package/src/assets/web-panel/assets/{index-CL-36X3A.js → index-KvhXXaLi.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CiEdfbB9.js → index-Rk6e-ajq.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-rFaCnjXi.js → index-ZmHm5rHI.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-IX0g0s2t.js → index-_9loRV63.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-C25ig6EV.js → index-avRJM_PM.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-Bs2QyMPQ.js → index-ciHSB-CC.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-pzYLf5qu.js → index-faSaFsUp.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-DL0k35f0.js → index-nV4PAkOC.js} +1 -1
  138. package/src/assets/web-panel/assets/{index-Ca2hwPMo.js → index-v5sQDXJx.js} +1 -1
  139. package/src/assets/web-panel/assets/{initDefaultProps-BWVLgWhF.js → initDefaultProps-xrojGNIo.js} +1 -1
  140. package/src/assets/web-panel/assets/{markdown-Yo_c6Krv.js → markdown-Bq2x0XsR.js} +1 -1
  141. package/src/assets/web-panel/assets/markdown-DUKxbhE4.js +51 -0
  142. package/src/assets/web-panel/assets/{motion-KJZ1xfDE.js → motion-DzhD1BmE.js} +1 -1
  143. package/src/assets/web-panel/assets/{move-DGqU6H7M.js → move-BAq6xac8.js} +1 -1
  144. package/src/assets/web-panel/assets/{mtc-parser-Dg0xkI5J.js → mtc-parser-CbbkHqsY.js} +1 -1
  145. package/src/assets/web-panel/assets/{omit-B6RnMJAQ.js → omit-DvK7nt_t.js} +1 -1
  146. package/src/assets/web-panel/assets/{pickAttrs-Ctw2i3jb.js → pickAttrs-B8N30ZEF.js} +1 -1
  147. package/src/assets/web-panel/assets/{placementArrow-D3F3x8Jm.js → placementArrow-cUxaRoU7.js} +1 -1
  148. package/src/assets/web-panel/assets/{responsiveObserve-W6eHE0nM.js → responsiveObserve-CxMioios.js} +1 -1
  149. package/src/assets/web-panel/assets/{slide-BlFf8sY5.js → slide-CQx7R0mc.js} +1 -1
  150. package/src/assets/web-panel/assets/{statusUtils-Cd6LMwmy.js → statusUtils-kUphM3-c.js} +1 -1
  151. package/src/assets/web-panel/assets/{styleChecker-Bg5-U4Y1.js → styleChecker-BrGZ1xWq.js} +1 -1
  152. package/src/assets/web-panel/assets/{useFlexGapSupport-DTkLS3tn.js → useFlexGapSupport-Sb5Vj_ag.js} +1 -1
  153. package/src/assets/web-panel/assets/{useFs-CwYGzoBt.js → useFs-DrMnDCO5.js} +1 -1
  154. package/src/assets/web-panel/assets/{usePersonalDataHub-PCgFQXZP.js → usePersonalDataHub-C48ZIkOr.js} +1 -1
  155. package/src/assets/web-panel/assets/{vnode-CJM7MkrB.js → vnode-BQP6PZY_.js} +1 -1
  156. package/src/assets/web-panel/assets/{xterm-BZcWGsqw.js → xterm-zbskxxGA.js} +1 -1
  157. package/src/assets/web-panel/assets/{zoom-CP0mgaFi.js → zoom-DOJny9bH.js} +1 -1
  158. package/src/assets/web-panel/index.html +1 -1
  159. package/src/data/changelog.json +28 -2
  160. package/src/harness/jsonl-session-store.js +2 -1
  161. package/src/lib/artifact-store.js +129 -20
  162. package/src/lib/bounded-artifact-file-read.js +128 -46
  163. package/src/lib/evolution/agent-evolution-ingress.js +41 -7
  164. package/src/lib/evolution/agent-evolution-runtime-composition.js +65 -27
  165. package/src/lib/evolution/evidence-json-text.js +124 -8
  166. package/src/lib/evolution/evolution-artifact-ports.js +1005 -83
  167. package/src/lib/evolution/evolution-evidence-artifact-adapter.js +59 -15
  168. package/src/lib/evolution/evolution-evidence-projector.js +59 -3
  169. package/src/lib/evolution/evolution-evidence-storage-layout.js +115 -0
  170. package/src/lib/evolution/evolution-file-witness.js +7 -3
  171. package/src/lib/evolution/evolution-ledger-file-manifest-catalog.js +6 -2
  172. package/src/lib/evolution/evolution-ledger-file-manifest-head-store.js +6 -2
  173. package/src/lib/evolution/evolution-ledger.js +226 -55
  174. package/src/lib/evolution/evolution-private-storage.js +20 -0
  175. package/src/lib/evolution/evolution-run-ledger-adapter.js +298 -11
  176. package/src/lib/evolution/evolution-windows-directory-durability.js +215 -32
  177. package/src/lib/evolution/local-ui-evolution-runtime.js +3 -38
  178. package/src/lib/evolution/local-ui-signing-authority.js +69 -0
  179. package/src/lib/ollama-provider-fetch.js +33 -0
  180. package/src/lib/provider-stream.js +14 -13
  181. package/src/lib/session-history-origins.js +71 -32
  182. package/src/lib/session-message-provenance.js +26 -2
  183. package/src/lib/session-runtime-retention.js +10 -1
  184. package/src/lib/session-transcript-migration.js +67 -12
  185. package/src/lib/storage-failure-cause-codes.js +449 -0
  186. package/src/runtime/agent-core.js +54 -33
  187. package/src/runtime/headless-stream.js +89 -10
  188. package/src/assets/web-panel/assets/Logs-qSuwMwk7.js +0 -2
  189. package/src/assets/web-panel/assets/MarkdownRenderer-BgnYTegK.js +0 -1
  190. package/src/assets/web-panel/assets/OrderTableRenderer-B5F8bnWt.js +0 -1
  191. package/src/assets/web-panel/assets/Projects-C51w0LnM.js +0 -1
  192. package/src/assets/web-panel/assets/devWarning-CRyXoGo9.js +0 -1
  193. package/src/assets/web-panel/assets/index-C-W0Ntk_.js +0 -1
  194. package/src/assets/web-panel/assets/index-CCk_qhBx.js +0 -1
  195. package/src/assets/web-panel/assets/markdown-CsiA8-E5.js +0 -50
@@ -12,22 +12,55 @@ import crypto from "node:crypto";
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
14
  import { types as utilTypes } from "node:util";
15
- import { ArtifactStore } from "../artifact-store.js";
15
+ import {
16
+ ArtifactStore,
17
+ ARTIFACT_STORE_DEFAULT_READ_METHODS,
18
+ ARTIFACT_STORE_DEFAULT_PUBLISH_DATA_ONCE,
19
+ captureArtifactStoreTypedIndexPublisher,
20
+ } from "../artifact-store.js";
21
+ import { withFileLock } from "../with-file-lock.js";
22
+ import {
23
+ sameDirectoryStatIdentity,
24
+ samePathHandleDirectoryIdentity,
25
+ withTrustedFileParentSync,
26
+ } from "../secure-file-identity.js";
27
+ import {
28
+ recordStorageFailureReason,
29
+ recordStorageFailureStage,
30
+ } from "../storage-failure-cause-codes.js";
16
31
  import { artifactPhysicalIdentity as physicalIdentity } from "./evolution-artifact-identity.js";
17
32
  import { withEvolutionFileIdentity } from "./evolution-file-identity.js";
18
33
  import { readBoundedDescriptor } from "./bounded-descriptor-read.js";
19
- import { readBoundedArtifactFile } from "../bounded-artifact-file-read.js";
34
+ import {
35
+ readBoundedArtifactFile,
36
+ withBoundedArtifactDirectoryRead,
37
+ } from "../bounded-artifact-file-read.js";
20
38
  import {
21
39
  EVOLUTION_ARTIFACT_REF_SCHEMA,
22
40
  EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA,
23
41
  } from "./evolution-ledger.js";
24
42
 
25
43
  const ARTIFACT_STORE_DIRECTORY_BOUNDARIES = new WeakMap();
44
+ // Derived lookup tables belong only to deeply frozen parsed index rows.
45
+ // Every reuse reattests current descriptor bytes; authority and payloads are
46
+ // never cached. All duplicate matches remain in these derived lookup tables.
47
+ const LEDGER_BATCH_INDEX_LOOKUPS = new WeakMap();
48
+ // Captured layout object identity, never a caller pathname or digest, owns a
49
+ // latest compact bytes and optional hot parse. Bound aggregate retention plus
50
+ // in-flight compact reservations, as well as each snapshot;
51
+ // an overflow only disables reuse, never changes index admission limits.
52
+ const TRUSTED_INDEX_PARSES = new Map();
53
+ const MAX_RETAINED_INDEX_LAYOUTS = 8;
54
+ const MAX_RETAINED_INDEX_ROWS = 10_000;
55
+ const MAX_RETAINED_INDEX_BYTES = 24 * 1024 * 1024;
56
+ const MAX_RETAINED_INDEX_MEMORY = 112 * 1024 * 1024;
57
+ let retainedIndexMemory = 0;
58
+ let reservedIndexMemory = 0;
26
59
  // Capture repository function identity before binding instance methods. Only
27
60
  // these exact defaults may skip legacy list/get I/O; overrides still execute
28
61
  // and must match the independently authenticated descriptor snapshots.
29
- const DEFAULT_STORE_LIST = ArtifactStore.prototype.list;
30
- const DEFAULT_STORE_GET = ArtifactStore.prototype.get;
62
+ const DEFAULT_STORE_LIST = ARTIFACT_STORE_DEFAULT_READ_METHODS.list;
63
+ const DEFAULT_STORE_GET = ARTIFACT_STORE_DEFAULT_READ_METHODS.get;
31
64
  // Only the shared identity helper's directory opens use this adapter. A POSIX
32
65
  // directory/FIFO swap must fail before reaching a potentially blocking open.
33
66
  const INDEX_DIRECTORY_FS = Object.freeze({
@@ -406,6 +439,9 @@ const CONSTRUCTOR_REQUIRED_KEYS = new Set([
406
439
  const LEDGER_RESOLVER_OPTION_KEYS = new Set(["purpose"]);
407
440
  const EVOLUTION_LEDGER_ARTIFACT_RESOLVERS = new WeakSet();
408
441
  const EVOLUTION_LEDGER_BATCH_RESOLVERS = new WeakMap();
442
+ const EVOLUTION_LEDGER_BATCH_READ_WINDOWS = new WeakMap();
443
+ const EVOLUTION_LEDGER_WHOLE_HISTORY_READERS = new WeakMap();
444
+ const EVOLUTION_LEDGER_DEFAULT_PUBLICATIONS = new WeakMap();
409
445
  const EVOLUTION_LEDGER_RESOLVER_BINDINGS = new WeakMap();
410
446
  const isProxy = Object.freeze(utilTypes.isProxy.bind(utilTypes));
411
447
  const isDate = Object.freeze(utilTypes.isDate.bind(utilTypes));
@@ -822,6 +858,68 @@ function canonicalJson(
822
858
  return state.fragments.join("");
823
859
  }
824
860
 
861
+ // Only explicitly opted-in repository publications reach this formatter. Keep
862
+ // the envelope opaque and reuse the exact encoder used by index fingerprints.
863
+ function encodeTypedEvolutionIndexRow(entry) {
864
+ const exactData = (value, keys) => {
865
+ if (
866
+ !value ||
867
+ typeof value !== "object" ||
868
+ isProxy(value) ||
869
+ Object.getPrototypeOf(value) !== Object.prototype
870
+ )
871
+ return false;
872
+ const ownKeys = Reflect.ownKeys(value);
873
+ return (
874
+ ownKeys.length === keys.size &&
875
+ ownKeys.every((key) => {
876
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
877
+ return (
878
+ keys.has(key) &&
879
+ descriptor?.enumerable === true &&
880
+ "value" in descriptor
881
+ );
882
+ })
883
+ );
884
+ };
885
+ if (
886
+ !exactData(entry, INDEX_ENTRY_KEYS) ||
887
+ !exactData(entry.lineage, LINEAGE_KEYS)
888
+ )
889
+ return null;
890
+ if (
891
+ [...LINEAGE_KEYS].some((key) => typeof entry.lineage[key] !== "string") ||
892
+ entry.lineage.schema !== EVOLUTION_ARTIFACT_LINEAGE_SCHEMA ||
893
+ entry.immutable !== true ||
894
+ entry.kind !== "data" ||
895
+ entry.mime !== "application/json" ||
896
+ entry.sessionId !== null ||
897
+ entry.sourcePath !== null ||
898
+ !Number.isSafeInteger(entry.size) ||
899
+ entry.size < 1 ||
900
+ entry.size > EVOLUTION_ARTIFACT_MAX_CANONICAL_BYTES ||
901
+ [...INDEX_ENTRY_KEYS].some(
902
+ (key) =>
903
+ !["lineage", "immutable", "sessionId", "sourcePath", "size"].includes(
904
+ key,
905
+ ) && typeof entry[key] !== "string",
906
+ )
907
+ )
908
+ return null;
909
+ try {
910
+ const canonical = canonicalJson(entry, MAX_INDEX_ENTRY_BYTES);
911
+ // Fixed keys contain no integer indices; all scalars use JSON.stringify in
912
+ // canonicalWalk. Preserve the original path if these bounds ever diverge.
913
+ return JSON.stringify(JSON.parse(canonical)) === canonical
914
+ ? canonical
915
+ : null;
916
+ } catch {
917
+ // Formatting is optional, not a new publication admission gate. Original
918
+ // serialization/readback still determines the outcome for an ineligible row.
919
+ return null;
920
+ }
921
+ }
922
+
825
923
  function deepFreeze(value, seen = new WeakSet()) {
826
924
  if (!value || typeof value !== "object" || seen.has(value)) return value;
827
925
  rejectProxy(value, "value being frozen");
@@ -1045,6 +1143,18 @@ function captureMethod(port, method, label) {
1045
1143
  return Object.freeze(captureDataMethod(port, method, label).bind(port));
1046
1144
  }
1047
1145
 
1146
+ function hasRepositoryDataMethod(store, method, expected) {
1147
+ let cursor = store;
1148
+ while (cursor) {
1149
+ rejectProxy(cursor, "artifactStore read method prototype");
1150
+ const descriptor = Object.getOwnPropertyDescriptor(cursor, method);
1151
+ if (descriptor)
1152
+ return "value" in descriptor && descriptor.value === expected;
1153
+ cursor = Object.getPrototypeOf(cursor);
1154
+ }
1155
+ return false;
1156
+ }
1157
+
1048
1158
  function captureArtifactStore(store) {
1049
1159
  rejectProxy(store, "artifactStore");
1050
1160
  if (!hasStablePrototype(store, ArtifactStore.prototype, "artifactStore")) {
@@ -1060,14 +1170,74 @@ function captureArtifactStore(store) {
1060
1170
  const layout = establishStoreLayout(resolvedDir);
1061
1171
  const list = captureDataMethod(store, "list", "artifactStore");
1062
1172
  const get = captureDataMethod(store, "get", "artifactStore");
1173
+ const storedPath = captureDataMethod(store, "storedPath", "artifactStore");
1174
+ const verifyIntegrity = captureDataMethod(
1175
+ store,
1176
+ "verifyIntegrity",
1177
+ "artifactStore",
1178
+ );
1179
+ const publishDataOnce = captureDataMethod(
1180
+ store,
1181
+ "publishDataOnce",
1182
+ "artifactStore",
1183
+ );
1184
+ const defaultPublication =
1185
+ Object.getPrototypeOf(store) === ArtifactStore.prototype &&
1186
+ publishDataOnce === ARTIFACT_STORE_DEFAULT_PUBLISH_DATA_ONCE &&
1187
+ Object.getOwnPropertyDescriptor(store, "_indexLock")?.value ===
1188
+ withFileLock;
1189
+ const defaults = ARTIFACT_STORE_DEFAULT_READ_METHODS;
1190
+ const initiallyDefaultRead =
1191
+ storedPath === defaults.storedPath &&
1192
+ verifyIntegrity === defaults.verifyIntegrity &&
1193
+ hasRepositoryDataMethod(store, "_filesDir", defaults.filesDir);
1063
1194
  const captured = Object.freeze({
1064
1195
  dir: resolvedDir,
1196
+ repositoryInstance:
1197
+ Object.getPrototypeOf(store) === ArtifactStore.prototype,
1065
1198
  get: get === DEFAULT_STORE_GET ? null : Object.freeze(get.bind(store)),
1066
1199
  layout,
1067
1200
  list: list === DEFAULT_STORE_LIST ? null : Object.freeze(list.bind(store)),
1068
- publishDataOnce: captureMethod(store, "publishDataOnce", "artifactStore"),
1069
- storedPath: captureMethod(store, "storedPath", "artifactStore"),
1070
- verifyIntegrity: captureMethod(store, "verifyIntegrity", "artifactStore"),
1201
+ publishDataOnce: captureArtifactStoreTypedIndexPublisher(
1202
+ store,
1203
+ publishDataOnce,
1204
+ encodeTypedEvolutionIndexRow,
1205
+ ),
1206
+ canUseDefaultPublication: Object.freeze(
1207
+ () =>
1208
+ defaultPublication &&
1209
+ hasRepositoryDataMethod(
1210
+ store,
1211
+ "publishDataOnce",
1212
+ ARTIFACT_STORE_DEFAULT_PUBLISH_DATA_ONCE,
1213
+ ),
1214
+ ),
1215
+ storedPath: Object.freeze(storedPath.bind(store)),
1216
+ verifyIntegrity: Object.freeze(verifyIntegrity.bind(store)),
1217
+ // Default verifyIntegrity repeats the same bounded physical read and hash
1218
+ // performed below. Its dynamic path dependencies must still be repository
1219
+ // defaults at EACH skipped hook point; freezing the instance does not freeze
1220
+ // its prototype. Custom implementations retain both original hook calls.
1221
+ canUseSingleBoundedRead: Object.freeze(
1222
+ () =>
1223
+ initiallyDefaultRead &&
1224
+ hasRepositoryDataMethod(store, "storedPath", defaults.storedPath) &&
1225
+ hasRepositoryDataMethod(store, "_filesDir", defaults.filesDir),
1226
+ ),
1227
+ canUseSealedDirectoryRead: Object.freeze(
1228
+ () =>
1229
+ initiallyDefaultRead &&
1230
+ Object.getPrototypeOf(store) === ArtifactStore.prototype &&
1231
+ hasRepositoryDataMethod(store, "list", defaults.list) &&
1232
+ hasRepositoryDataMethod(store, "get", defaults.get) &&
1233
+ hasRepositoryDataMethod(store, "storedPath", defaults.storedPath) &&
1234
+ hasRepositoryDataMethod(store, "_filesDir", defaults.filesDir) &&
1235
+ hasRepositoryDataMethod(
1236
+ store,
1237
+ "verifyIntegrity",
1238
+ defaults.verifyIntegrity,
1239
+ ),
1240
+ ),
1071
1241
  });
1072
1242
  // ArtifactStore methods consult public instance fields such as `dir` on
1073
1243
  // every call. Freeze the trusted instance after capturing its methods so a
@@ -1421,6 +1591,111 @@ function attestStoreDirectories(layout) {
1421
1591
  }
1422
1592
  }
1423
1593
 
1594
+ // Only the sealed whole-history reader owns this synchronous capability. A
1595
+ // held descriptor does not anchor absolute-path opens like openat: each item
1596
+ // must still bind BOTH current directory paths to their captured, held FDs.
1597
+ // Only repeated directory realpath calls move to the complete read boundary.
1598
+ function withSealedStoreDirectoryRead(store, operation) {
1599
+ const layout = store.layout;
1600
+ const fail = () => {
1601
+ throw artifactError(
1602
+ EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
1603
+ "ArtifactStore sealed directory identity or reader changed",
1604
+ );
1605
+ };
1606
+ attestStoreDirectories(layout);
1607
+ return withTrustedFileParentSync(
1608
+ INDEX_DIRECTORY_FS,
1609
+ path.join(layout.rootDir, ".sealed-directory-scope"),
1610
+ (root) =>
1611
+ withTrustedFileParentSync(
1612
+ INDEX_DIRECTORY_FS,
1613
+ path.join(layout.filesDir, ".sealed-directory-scope"),
1614
+ (files) => {
1615
+ const held = [
1616
+ {
1617
+ ...root,
1618
+ path: layout.rootDir,
1619
+ identity: layout.rootIdentity,
1620
+ },
1621
+ {
1622
+ ...files,
1623
+ path: layout.filesDir,
1624
+ identity: layout.filesIdentity,
1625
+ },
1626
+ ].map((entry) => ({
1627
+ ...entry,
1628
+ initial: fs.fstatSync(entry.parentDescriptor, { bigint: true }),
1629
+ }));
1630
+ let active = true;
1631
+ const assertReader = () => {
1632
+ if (!active || !store.canUseSealedDirectoryRead()) fail();
1633
+ };
1634
+ const check = (leaf = null) => {
1635
+ assertReader();
1636
+ for (const entry of held) {
1637
+ if (!samePath(entry.parentPath, entry.path)) fail();
1638
+ const current = fs.lstatSync(entry.path, { bigint: true });
1639
+ const descriptor = fs.fstatSync(entry.parentDescriptor, {
1640
+ bigint: true,
1641
+ });
1642
+ assertDirectory(current, "ArtifactStore held directory path");
1643
+ assertDirectory(descriptor, "ArtifactStore held directory FD");
1644
+ const identity = physicalIdentity(current);
1645
+ const stableFields = ["ino", "mode", "uid", "gid", "birthtimeNs"];
1646
+ if (
1647
+ typeof current.dev !== "bigint" ||
1648
+ typeof descriptor.dev !== "bigint" ||
1649
+ stableFields.some(
1650
+ (field) =>
1651
+ typeof current[field] !== "bigint" ||
1652
+ typeof descriptor[field] !== "bigint" ||
1653
+ current[field] !== descriptor[field] ||
1654
+ descriptor[field] !== entry.initial[field],
1655
+ ) ||
1656
+ identity.dev !== entry.identity.dev ||
1657
+ identity.ino !== entry.identity.ino ||
1658
+ identity.birthtimeMs !== entry.identity.birthtimeMs ||
1659
+ !sameDirectoryStatIdentity(descriptor, entry.initial) ||
1660
+ !samePathHandleDirectoryIdentity(
1661
+ current,
1662
+ descriptor,
1663
+ entry.parentDevice,
1664
+ )
1665
+ )
1666
+ fail();
1667
+ }
1668
+ // This runs at the original post-attest point, AFTER hashing and
1669
+ // any required hook, not merely at the earlier file read/close.
1670
+ if (
1671
+ leaf !== null &&
1672
+ (!samePath(path.dirname(leaf), layout.filesDir) ||
1673
+ !samePath(physicalRealpath(leaf), leaf))
1674
+ )
1675
+ fail();
1676
+ };
1677
+ const scope = Object.freeze({
1678
+ check: Object.freeze(check),
1679
+ assertReader: Object.freeze(assertReader),
1680
+ });
1681
+ try {
1682
+ check();
1683
+ return operation(scope);
1684
+ } finally {
1685
+ try {
1686
+ check();
1687
+ // Includes the last caller-supplied clock/authority operation;
1688
+ // no result escapes before these checks and both FD closes.
1689
+ attestStoreDirectories(layout);
1690
+ } finally {
1691
+ active = false;
1692
+ }
1693
+ }
1694
+ },
1695
+ ),
1696
+ );
1697
+ }
1698
+
1424
1699
  function indexEntriesFingerprint(entries, label) {
1425
1700
  assertDenseDataArray(entries, label, EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES);
1426
1701
  const hash = crypto.createHash("sha256");
@@ -1435,38 +1710,216 @@ function indexEntriesFingerprint(entries, label) {
1435
1710
  return `sha256:${hash.digest("hex")}`;
1436
1711
  }
1437
1712
 
1438
- function parseTrustedIndexBytes(bytes) {
1713
+ // Conservative accounting, not an exact VM heap size. Include UTF-16 strings,
1714
+ // parsed nodes/property storage and the per-row allowance for lookup maps.
1715
+ function parsedIndexMemory(value) {
1716
+ if (typeof value === "string") return 64 + value.length * 2;
1717
+ if (!value || typeof value !== "object") return 16;
1718
+ if (Array.isArray(value))
1719
+ return (
1720
+ 64 + value.reduce((sum, child) => sum + 16 + parsedIndexMemory(child), 0)
1721
+ );
1722
+ let size = 64;
1723
+ for (const [key, child] of Object.entries(value))
1724
+ size += 64 + key.length * 2 + parsedIndexMemory(child);
1725
+ return size;
1726
+ }
1727
+
1728
+ function forgetTrustedIndexParse(layout) {
1729
+ const previous = TRUSTED_INDEX_PARSES.get(layout);
1730
+ if (!previous) return;
1731
+ retainedIndexMemory -= previous.memory + (previous.hot?.memory || 0);
1732
+ TRUSTED_INDEX_PARSES.delete(layout);
1733
+ }
1734
+
1735
+ function downgradeTrustedIndexParse(entry) {
1736
+ if (!entry.hot) return;
1737
+ retainedIndexMemory -= entry.hot.memory;
1738
+ entry.hot = null;
1739
+ }
1740
+
1741
+ function countTrustedIndexRows(bytes) {
1742
+ let count = 0;
1743
+ for (
1744
+ let offset = bytes.indexOf(10);
1745
+ offset !== -1;
1746
+ offset = bytes.indexOf(10, offset + 1)
1747
+ )
1748
+ count += 1;
1749
+ return count;
1750
+ }
1751
+
1752
+ function reserveTrustedIndexParse(layout, bytes, prefix) {
1753
+ if (
1754
+ bytes.length > MAX_RETAINED_INDEX_BYTES ||
1755
+ countTrustedIndexRows(bytes) > MAX_RETAINED_INDEX_ROWS
1756
+ )
1757
+ return null;
1758
+ let scratchBytes = 0;
1759
+ let start = 0;
1760
+ for (
1761
+ let end = bytes.indexOf(10);
1762
+ end !== -1;
1763
+ end = bytes.indexOf(10, start)
1764
+ ) {
1765
+ scratchBytes = Math.max(scratchBytes, end - start);
1766
+ start = end + 1;
1767
+ }
1768
+ // An oversized row is still rejected by the original parser, in its original
1769
+ // order. Cache admission must not allocate an oversized comparison buffer.
1770
+ if (scratchBytes > MAX_INDEX_ENTRY_BYTES) return null;
1771
+ // Charge the full raw backing store (including any shared slab), a potential
1772
+ // standalone canonical buffer, and the largest-row comparison scratch. The
1773
+ // canonical reservation is NOT allocated unless a byte difference is found.
1774
+ const compactMemory =
1775
+ bytes.buffer.byteLength + bytes.length + 4096 + parsedIndexMemory(layout);
1776
+ const memory = compactMemory + scratchBytes;
1777
+ if (memory > MAX_RETAINED_INDEX_MEMORY) return null;
1778
+ const needsRoom = () =>
1779
+ retainedIndexMemory + reservedIndexMemory + memory >
1780
+ MAX_RETAINED_INDEX_MEMORY;
1781
+ // No prefix entries have been captured yet. Dropping its optional hot tier
1782
+ // here is safe; the subsequent parser then reconstructs from compact bytes.
1783
+ for (const entry of TRUSTED_INDEX_PARSES.values()) {
1784
+ if (!needsRoom()) break;
1785
+ downgradeTrustedIndexParse(entry);
1786
+ }
1787
+ while (
1788
+ needsRoom() ||
1789
+ (!TRUSTED_INDEX_PARSES.has(layout) &&
1790
+ TRUSTED_INDEX_PARSES.size >= MAX_RETAINED_INDEX_LAYOUTS)
1791
+ ) {
1792
+ const victim = [...TRUSTED_INDEX_PARSES].find(
1793
+ ([, entry]) => entry !== prefix,
1794
+ );
1795
+ if (!victim) return null;
1796
+ forgetTrustedIndexParse(victim[0]);
1797
+ }
1798
+ reservedIndexMemory += memory;
1799
+ return {
1800
+ memory,
1801
+ compactMemory,
1802
+ scratchBytes,
1803
+ active: true,
1804
+ retain: true,
1805
+ canonicalBytes: null,
1806
+ scratch: null,
1807
+ retiring: null,
1808
+ };
1809
+ }
1810
+
1811
+ function releaseTrustedIndexReservation(reservation) {
1812
+ if (!reservation?.active) return;
1813
+ reservedIndexMemory -= reservation.memory;
1814
+ reservation.active = false;
1815
+ reservation.scratch = null;
1816
+ reservation.retiring = null;
1817
+ }
1818
+
1819
+ function promoteTrustedIndexParse(entry, parsed) {
1820
+ if (
1821
+ entry.hot ||
1822
+ parsed.memory + retainedIndexMemory + reservedIndexMemory >
1823
+ MAX_RETAINED_INDEX_MEMORY
1824
+ )
1825
+ return;
1826
+ entry.hot = parsed;
1827
+ retainedIndexMemory += parsed.memory;
1828
+ }
1829
+
1830
+ function retainTrustedIndexParse(layout, bytes, parsed, reservation) {
1831
+ const previous = TRUSTED_INDEX_PARSES.get(layout);
1832
+ if (previous) {
1833
+ // The parser may still hold the old prefix. Transfer its entire charge to
1834
+ // this transaction, rather than using replacement as free promotion room.
1835
+ const memory = previous.memory + (previous.hot?.memory || 0);
1836
+ retainedIndexMemory -= memory;
1837
+ reservedIndexMemory += memory;
1838
+ reservation.memory += memory;
1839
+ reservation.retiring = previous;
1840
+ TRUSTED_INDEX_PARSES.delete(layout);
1841
+ }
1842
+ // Transfer only compact ownership. Scratch and the retiring generation stay
1843
+ // reserved through promotion and are released at the outer finally boundary.
1844
+ reservedIndexMemory -= reservation.compactMemory;
1845
+ reservation.memory -= reservation.compactMemory;
1846
+ const entry = {
1847
+ bytes,
1848
+ canonicalBytes: reservation.canonicalBytes,
1849
+ rowCount: parsed.entries.length,
1850
+ entriesFingerprint: parsed.entriesFingerprint,
1851
+ hotMemoryEstimate: parsed.memory,
1852
+ memory: reservation.compactMemory,
1853
+ hot: null,
1854
+ };
1855
+ TRUSTED_INDEX_PARSES.set(layout, entry);
1856
+ retainedIndexMemory += entry.memory;
1857
+ promoteTrustedIndexParse(entry, parsed);
1858
+ }
1859
+
1860
+ function decodeTrustedIndexParse(entry) {
1861
+ // Only exact freshly read private-buffer matches reach this path. Decode the
1862
+ // original bytes, preserving property enumeration order for custom hooks.
1863
+ const entries = [];
1864
+ let start = 0;
1865
+ for (
1866
+ let end = entry.bytes.indexOf(10);
1867
+ end !== -1;
1868
+ end = entry.bytes.indexOf(10, start)
1869
+ ) {
1870
+ const value = JSON.parse(entry.bytes.toString("utf8", start, end));
1871
+ deepFreeze(value);
1872
+ entries.push(value);
1873
+ start = end + 1;
1874
+ }
1875
+ return Object.freeze({
1876
+ entries: Object.freeze(entries),
1877
+ entriesFingerprint: entry.entriesFingerprint,
1878
+ memory: entry.hotMemoryEstimate,
1879
+ });
1880
+ }
1881
+
1882
+ function hashTrustedCanonicalRows(hash, bytes) {
1883
+ let start = 0;
1884
+ for (
1885
+ let end = bytes.indexOf(10);
1886
+ end !== -1;
1887
+ end = bytes.indexOf(10, start)
1888
+ ) {
1889
+ hash.update(`${end - start}:`, "utf8");
1890
+ hash.update(bytes.subarray(start, end));
1891
+ start = end + 1;
1892
+ }
1893
+ }
1894
+
1895
+ function parseTrustedIndexBytes(bytes, prefix = null, reservation = null) {
1439
1896
  if (!Buffer.isBuffer(bytes)) {
1440
1897
  throw artifactError(
1441
1898
  EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
1442
1899
  "ArtifactStore index snapshot is not a Buffer",
1443
1900
  );
1444
1901
  }
1445
- const text = bytes.toString("utf8");
1446
- if (!Buffer.from(text, "utf8").equals(bytes)) {
1902
+ const suffix = prefix ? bytes.subarray(prefix.bytes.length) : bytes;
1903
+ const text = suffix.toString("utf8");
1904
+ if (!Buffer.from(text, "utf8").equals(suffix)) {
1447
1905
  throw artifactError(
1448
1906
  EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
1449
1907
  "ArtifactStore index is not exact UTF-8",
1450
1908
  );
1451
1909
  }
1452
- if (text === "") {
1453
- return Object.freeze({
1454
- entries: Object.freeze([]),
1455
- entriesFingerprint: indexEntriesFingerprint(
1456
- [],
1457
- "trusted ArtifactStore index entries",
1458
- ),
1459
- });
1460
- }
1461
- if (!text.endsWith("\n")) {
1910
+ if (text !== "" && !text.endsWith("\n")) {
1462
1911
  throw artifactError(
1463
1912
  EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
1464
1913
  "ArtifactStore index must end at a complete JSONL record",
1465
1914
  );
1466
1915
  }
1467
- const lines = text.slice(0, -1).split("\n");
1916
+ const lines = text === "" ? [] : text.slice(0, -1).split("\n");
1917
+ const entries = prefix
1918
+ ? [...(prefix.hot || decodeTrustedIndexParse(prefix)).entries]
1919
+ : [];
1920
+ const rowCount = entries.length + lines.length;
1468
1921
  if (
1469
- lines.length > EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES ||
1922
+ rowCount > EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES ||
1470
1923
  lines.some(
1471
1924
  (line) =>
1472
1925
  line.length < 2 ||
@@ -1478,8 +1931,26 @@ function parseTrustedIndexBytes(bytes) {
1478
1931
  "ArtifactStore index exceeds its trusted snapshot limits",
1479
1932
  );
1480
1933
  }
1481
- const entries = [];
1482
- for (const [index, line] of lines.entries()) {
1934
+ let memory = prefix ? prefix.hotMemoryEstimate : 256;
1935
+ let canonicalOffset = prefix ? prefix.canonicalBytes.length : 0;
1936
+ let rawOffset = prefix ? prefix.bytes.length : 0;
1937
+ const allocateCanonical = () => {
1938
+ reservation.canonicalBytes = Buffer.allocUnsafeSlow(bytes.length);
1939
+ if (prefix && prefix.canonicalBytes !== prefix.bytes)
1940
+ prefix.canonicalBytes.copy(reservation.canonicalBytes);
1941
+ else bytes.copy(reservation.canonicalBytes, 0, 0, canonicalOffset);
1942
+ };
1943
+ if (reservation) {
1944
+ reservation.scratch = Buffer.allocUnsafeSlow(reservation.scratchBytes);
1945
+ // A dual prefix already proves a noncanonical row in this generation.
1946
+ if (prefix && prefix.canonicalBytes !== prefix.bytes) allocateCanonical();
1947
+ }
1948
+ // The new count is part of the original framing; never extend an old hash.
1949
+ const hash = crypto.createHash("sha256");
1950
+ hash.update(`${rowCount}\0`, "utf8");
1951
+ if (prefix) hashTrustedCanonicalRows(hash, prefix.canonicalBytes);
1952
+ for (const line of lines) {
1953
+ const index = entries.length;
1483
1954
  let entry;
1484
1955
  try {
1485
1956
  entry = JSON.parse(line);
@@ -1512,25 +1983,126 @@ function parseTrustedIndexBytes(bytes) {
1512
1983
  }
1513
1984
  entries.push(entry);
1514
1985
  }
1986
+ // Preserve the original JSON/row-before-canonical validation error precedence.
1987
+ for (let index = rowCount - lines.length; index < rowCount; index += 1) {
1988
+ const entry = entries[index];
1989
+ const canonical = canonicalJson(entry, MAX_INDEX_ENTRY_BYTES);
1990
+ const length = Buffer.byteLength(canonical, "utf8");
1991
+ hash.update(`${length}:`, "utf8");
1992
+ hash.update(canonical, "utf8");
1993
+ if (reservation) {
1994
+ const line = lines[index - (rowCount - lines.length)];
1995
+ const rawLength = Buffer.byteLength(line, "utf8");
1996
+ if (reservation.retain) {
1997
+ if (
1998
+ length !== rawLength ||
1999
+ length > reservation.scratchBytes ||
2000
+ canonicalOffset + length >= bytes.length
2001
+ ) {
2002
+ // The budget relies on scalar encoding and key reordering preserving
2003
+ // row lengths. If that ever stops holding, only bypass this cache;
2004
+ // keep the original canonical validation and fingerprint semantics.
2005
+ reservation.retain = false;
2006
+ } else {
2007
+ const written = reservation.scratch.write(
2008
+ canonical,
2009
+ 0,
2010
+ length,
2011
+ "utf8",
2012
+ );
2013
+ if (written !== length) reservation.retain = false;
2014
+ else {
2015
+ const same = reservation.scratch
2016
+ .subarray(0, length)
2017
+ .equals(bytes.subarray(rawOffset, rawOffset + length));
2018
+ if (!same && reservation.canonicalBytes === null)
2019
+ allocateCanonical();
2020
+ if (reservation.canonicalBytes !== null) {
2021
+ reservation.scratch.copy(
2022
+ reservation.canonicalBytes,
2023
+ canonicalOffset,
2024
+ 0,
2025
+ length,
2026
+ );
2027
+ reservation.canonicalBytes[canonicalOffset + length] = 10;
2028
+ }
2029
+ }
2030
+ }
2031
+ }
2032
+ canonicalOffset += length + 1;
2033
+ rawOffset += rawLength + 1;
2034
+ memory += 1024 + parsedIndexMemory(entry);
2035
+ }
2036
+ deepFreeze(entry);
2037
+ }
2038
+ if (reservation?.retain) {
2039
+ if (canonicalOffset !== bytes.length || rawOffset !== bytes.length) {
2040
+ reservation.retain = false;
2041
+ } else if (reservation.canonicalBytes === null) {
2042
+ // Every row and LF has passed all original validation and exact byte
2043
+ // comparison. Release only the canonical backing that was NEVER allocated.
2044
+ reservation.canonicalBytes = bytes;
2045
+ reservation.memory -= bytes.length;
2046
+ reservation.compactMemory -= bytes.length;
2047
+ reservedIndexMemory -= bytes.length;
2048
+ }
2049
+ }
1515
2050
  return Object.freeze({
1516
2051
  entries: Object.freeze(entries),
1517
- entriesFingerprint: indexEntriesFingerprint(
1518
- entries,
1519
- "trusted ArtifactStore index entries",
1520
- ),
2052
+ entriesFingerprint: `sha256:${hash.digest("hex")}`,
2053
+ memory,
1521
2054
  });
1522
2055
  }
1523
2056
 
1524
- function readTrustedIndexSnapshot(layout) {
2057
+ function reuseTrustedIndexParse(layout, bytes) {
2058
+ let previous = TRUSTED_INDEX_PARSES.get(layout);
2059
+ if (previous && bytes.equals(previous.bytes)) {
2060
+ TRUSTED_INDEX_PARSES.delete(layout);
2061
+ TRUSTED_INDEX_PARSES.set(layout, previous);
2062
+ if (previous.hot) return previous.hot;
2063
+ const parsed = decodeTrustedIndexParse(previous);
2064
+ promoteTrustedIndexParse(previous, parsed);
2065
+ return parsed;
2066
+ }
2067
+ const prefix =
2068
+ previous &&
2069
+ previous.bytes.length > 0 &&
2070
+ previous.bytes[previous.bytes.length - 1] === 10 &&
2071
+ bytes.length > previous.bytes.length &&
2072
+ bytes.subarray(0, previous.bytes.length).equals(previous.bytes)
2073
+ ? previous
2074
+ : null;
2075
+ if (!prefix) forgetTrustedIndexParse(layout);
2076
+ previous = null;
2077
+ const reservation = reserveTrustedIndexParse(layout, bytes, prefix);
2078
+ try {
2079
+ const parsed = parseTrustedIndexBytes(bytes, prefix, reservation);
2080
+ if (reservation?.retain)
2081
+ retainTrustedIndexParse(layout, bytes, parsed, reservation);
2082
+ return parsed;
2083
+ } finally {
2084
+ releaseTrustedIndexReservation(reservation);
2085
+ }
2086
+ }
2087
+
2088
+ function readTrustedIndexSnapshot(layout, expectedSnapshot = null) {
1525
2089
  return withEvolutionFileIdentity(
1526
2090
  INDEX_DIRECTORY_FS,
1527
2091
  layout.indexPath,
1528
2092
  (samePathHandle) =>
1529
- readTrustedIndexSnapshotWithIdentity(layout, samePathHandle),
2093
+ readTrustedIndexSnapshotWithIdentity(
2094
+ layout,
2095
+ samePathHandle,
2096
+ expectedSnapshot,
2097
+ ),
1530
2098
  );
1531
2099
  }
1532
2100
 
1533
- function readTrustedIndexSnapshotWithIdentity(layout, samePathHandle) {
2101
+ function readTrustedIndexSnapshotWithIdentity(
2102
+ layout,
2103
+ samePathHandle,
2104
+ expectedSnapshot = null,
2105
+ ) {
1534
2106
  attestStoreDirectories(layout);
1535
2107
  let beforePathStat;
1536
2108
  let beforeDescriptorStat;
@@ -1625,9 +2197,20 @@ function readTrustedIndexSnapshotWithIdentity(layout, samePathHandle) {
1625
2197
  );
1626
2198
  }
1627
2199
  attestStoreDirectories(layout);
1628
- const parsed = parseTrustedIndexBytes(bytes);
2200
+ const bytesDigest = sha256(bytes);
2201
+ if (expectedSnapshot !== null) {
2202
+ // Repeat every directory, handle, identity, mutation, size and byte-read
2203
+ // check above. Only identical freshly read bytes reuse their derived parse.
2204
+ if (bytesDigest !== expectedSnapshot.bytesDigest)
2205
+ throw artifactError(
2206
+ EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
2207
+ "ArtifactStore index changed during ledger batch readback",
2208
+ );
2209
+ return expectedSnapshot;
2210
+ }
2211
+ const parsed = reuseTrustedIndexParse(layout, bytes);
1629
2212
  return Object.freeze({
1630
- bytesDigest: sha256(bytes),
2213
+ bytesDigest,
1631
2214
  entries: parsed.entries,
1632
2215
  entriesFingerprint: parsed.entriesFingerprint,
1633
2216
  });
@@ -2214,10 +2797,9 @@ export class EvolutionArtifactPorts {
2214
2797
  );
2215
2798
  let storeFingerprint;
2216
2799
  try {
2217
- storeFingerprint = indexEntriesFingerprint(
2218
- entries,
2219
- "ArtifactStore.list entries",
2220
- );
2800
+ storeFingerprint = this.#store.list
2801
+ ? indexEntriesFingerprint(entries, "ArtifactStore.list entries")
2802
+ : trustedBefore.entriesFingerprint;
2221
2803
  } catch (cause) {
2222
2804
  if (isEvolutionArtifactPortError(cause)) throw cause;
2223
2805
  throw artifactError(
@@ -2241,6 +2823,8 @@ export class EvolutionArtifactPorts {
2241
2823
  }
2242
2824
 
2243
2825
  #entriesByDataField(entries, field, expected) {
2826
+ const lookup = LEDGER_BATCH_INDEX_LOOKUPS.get(entries)?.get(field);
2827
+ if (lookup) return lookup.get(expected) || [];
2244
2828
  const matches = [];
2245
2829
  for (let index = 0; index < entries.length; index += 1) {
2246
2830
  const entry = ownData(
@@ -2268,9 +2852,62 @@ export class EvolutionArtifactPorts {
2268
2852
  return matches;
2269
2853
  }
2270
2854
 
2271
- #loadCurrentEntryById(artifactId) {
2855
+ #indexLedgerBatchEntries(entries) {
2856
+ if (LEDGER_BATCH_INDEX_LOOKUPS.has(entries)) return;
2857
+ const lookups = new Map([
2858
+ ["id", new Map()],
2859
+ ["recordDigest", new Map()],
2860
+ ]);
2861
+ // Retain every match, including duplicates. A last-wins table would hide
2862
+ // ambiguous ids or digests that the descriptor resolver must reject.
2863
+ for (let index = 0; index < entries.length; index += 1) {
2864
+ const entry = ownData(
2865
+ entries,
2866
+ String(index),
2867
+ "ArtifactStore index entries",
2868
+ );
2869
+ if (!entry || typeof entry !== "object") continue;
2870
+ rejectProxy(
2871
+ entry,
2872
+ `ArtifactStore index entry ${index}`,
2873
+ EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
2874
+ );
2875
+ if (Array.isArray(entry)) continue;
2876
+ for (const [field, lookup] of lookups) {
2877
+ const descriptor = Object.getOwnPropertyDescriptor(entry, field);
2878
+ if (!descriptor) continue;
2879
+ if (!("value" in descriptor) || descriptor.enumerable !== true) {
2880
+ throw artifactError(
2881
+ EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
2882
+ `ArtifactStore index ${field} must be an enumerable data property`,
2883
+ );
2884
+ }
2885
+ const matches = lookup.get(descriptor.value) || [];
2886
+ matches.push(entry);
2887
+ lookup.set(descriptor.value, matches);
2888
+ }
2889
+ }
2890
+ for (const lookup of lookups.values())
2891
+ for (const matches of lookup.values()) Object.freeze(matches);
2892
+ LEDGER_BATCH_INDEX_LOOKUPS.set(entries, lookups);
2893
+ }
2894
+
2895
+ #defaultReadbackSnapshot() {
2896
+ // One immutable descriptor snapshot may serve the pure default lookups
2897
+ // within this phase. Recheck dynamic methods independently before each
2898
+ // phase; never reuse this snapshot across payload I/O or authority calls.
2899
+ if (
2900
+ this.#store.list !== null ||
2901
+ this.#store.get !== null ||
2902
+ !this.#store.canUseSealedDirectoryRead()
2903
+ )
2904
+ return null;
2905
+ return readTrustedIndexSnapshot(this.#store.layout);
2906
+ }
2907
+
2908
+ #loadCurrentEntryById(artifactId, snapshot = null) {
2272
2909
  const listed = this.#entriesByDataField(
2273
- this.#boundedIndexEntries(),
2910
+ snapshot?.entries ?? this.#boundedIndexEntries(),
2274
2911
  "id",
2275
2912
  artifactId,
2276
2913
  );
@@ -2288,6 +2925,7 @@ export class EvolutionArtifactPorts {
2288
2925
  { artifactId },
2289
2926
  );
2290
2927
  }
2928
+ if (snapshot !== null) return listed[0];
2291
2929
  let current;
2292
2930
  try {
2293
2931
  current = rejectPromise(
@@ -2337,8 +2975,8 @@ export class EvolutionArtifactPorts {
2337
2975
  return current;
2338
2976
  }
2339
2977
 
2340
- #assertEntryStable(artifactId, original) {
2341
- const current = this.#loadCurrentEntryById(artifactId);
2978
+ #assertEntryStable(artifactId, original, snapshot = null) {
2979
+ const current = this.#loadCurrentEntryById(artifactId, snapshot);
2342
2980
  if (canonicalJson(current) !== canonicalJson(original)) {
2343
2981
  throw artifactError(
2344
2982
  EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
@@ -2386,8 +3024,9 @@ export class EvolutionArtifactPorts {
2386
3024
  #loadLedgerEntry(artifactId, recordDigest = null, snapshot = null) {
2387
3025
  // Ledger resolution is read-only. Read the already attested physical index
2388
3026
  // directly instead of invoking ArtifactStore.list/get (both acquire writer
2389
- // locks). Each read/batch obtains a fresh bounded descriptor snapshot;
2390
- // nothing caches index contents, signatures or current authority.
3027
+ // locks). Each read/batch obtains fresh bounded descriptor bytes. Only an
3028
+ // identical parse may be shared within one synchronous read window;
3029
+ // signatures, current authority and payloads are always reauthenticated.
2391
3030
  const { entries } =
2392
3031
  snapshot ?? readTrustedIndexSnapshot(this.#store.layout);
2393
3032
  const matches = this.#entriesByDataField(entries, "id", artifactId);
@@ -2507,8 +3146,15 @@ export class EvolutionArtifactPorts {
2507
3146
  return Object.freeze({ createdAt, expiresAt, file, id, lineage, size });
2508
3147
  }
2509
3148
 
2510
- #readStoredBytes(entry, normalizedEntry, expectedDigest) {
2511
- attestStoreDirectories(this.#store.layout);
3149
+ #readStoredBytes(
3150
+ entry,
3151
+ normalizedEntry,
3152
+ expectedDigest,
3153
+ boundedRead = null,
3154
+ directoryScope = null,
3155
+ ) {
3156
+ if (directoryScope) directoryScope.check();
3157
+ else attestStoreDirectories(this.#store.layout);
2512
3158
  const expectedHex = expectedDigest.slice("sha256:".length);
2513
3159
  let storedPath;
2514
3160
  try {
@@ -2543,21 +3189,32 @@ export class EvolutionArtifactPorts {
2543
3189
  expectedSize: normalizedEntry.size,
2544
3190
  maximumBytes: EVOLUTION_ARTIFACT_MAX_CANONICAL_BYTES,
2545
3191
  });
2546
- let integrityBefore;
2547
- try {
2548
- integrityBefore = this.#store.verifyIntegrity(entry, readBounds);
2549
- } catch (cause) {
2550
- throw artifactError(
2551
- EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
2552
- "ArtifactStore integrity verification threw",
2553
- { cause },
2554
- );
3192
+ const preserveIntegrityHooks = !this.#store.canUseSingleBoundedRead();
3193
+ directoryScope?.assertReader();
3194
+ if (preserveIntegrityHooks) {
3195
+ let integrityBefore;
3196
+ try {
3197
+ integrityBefore = this.#store.verifyIntegrity(entry, readBounds);
3198
+ } catch (cause) {
3199
+ throw artifactError(
3200
+ EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
3201
+ "ArtifactStore integrity verification threw",
3202
+ { cause },
3203
+ );
3204
+ }
3205
+ validateIntegrityResult(integrityBefore, expectedHex);
2555
3206
  }
2556
- validateIntegrityResult(integrityBefore, expectedHex);
2557
3207
 
2558
3208
  let bytes;
2559
3209
  try {
2560
- bytes = readBoundedArtifactFile(
3210
+ directoryScope?.assertReader();
3211
+ const read =
3212
+ boundedRead !== null &&
3213
+ !preserveIntegrityHooks &&
3214
+ this.#store.canUseSingleBoundedRead()
3215
+ ? boundedRead
3216
+ : readBoundedArtifactFile;
3217
+ bytes = read(
2561
3218
  resolvedStoredPath,
2562
3219
  readBounds.expectedSize,
2563
3220
  readBounds.maximumBytes,
@@ -2579,18 +3236,22 @@ export class EvolutionArtifactPorts {
2579
3236
  "stored artifact bytes do not match the signed recordDigest",
2580
3237
  );
2581
3238
  }
2582
- let integrityAfter;
2583
- try {
2584
- integrityAfter = this.#store.verifyIntegrity(entry, readBounds);
2585
- } catch (cause) {
2586
- throw artifactError(
2587
- EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
2588
- "ArtifactStore post-read integrity verification threw",
2589
- { cause },
2590
- );
3239
+ directoryScope?.assertReader();
3240
+ if (preserveIntegrityHooks || !this.#store.canUseSingleBoundedRead()) {
3241
+ let integrityAfter;
3242
+ try {
3243
+ integrityAfter = this.#store.verifyIntegrity(entry, readBounds);
3244
+ } catch (cause) {
3245
+ throw artifactError(
3246
+ EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
3247
+ "ArtifactStore post-read integrity verification threw",
3248
+ { cause },
3249
+ );
3250
+ }
3251
+ validateIntegrityResult(integrityAfter, expectedHex);
2591
3252
  }
2592
- validateIntegrityResult(integrityAfter, expectedHex);
2593
- attestStoreDirectories(this.#store.layout);
3253
+ if (directoryScope) directoryScope.check(resolvedStoredPath);
3254
+ else attestStoreDirectories(this.#store.layout);
2594
3255
  return bytes;
2595
3256
  }
2596
3257
 
@@ -3011,10 +3672,20 @@ export class EvolutionArtifactPorts {
3011
3672
  completionClock.milliseconds >= core.expiresAtMs) ||
3012
3673
  completionClock.milliseconds >= authority.decisionExpiresAtMs
3013
3674
  ) {
3014
- throw artifactError(
3675
+ const authenticationFailureReason =
3676
+ completionClock.milliseconds < verificationClock.milliseconds
3677
+ ? "auth-clock-backward"
3678
+ : core.expiresAtMs !== null &&
3679
+ completionClock.milliseconds >= core.expiresAtMs
3680
+ ? "auth-envelope-expired"
3681
+ : "auth-authority-lease-expired";
3682
+ const error = artifactError(
3015
3683
  EVOLUTION_ARTIFACT_EXPIRED_CODE,
3016
3684
  "artifact authority expired before envelope authentication completed",
3017
3685
  );
3686
+ recordStorageFailureReason(error, authenticationFailureReason);
3687
+ recordStorageFailureStage(error, core.type, "authentication");
3688
+ throw error;
3018
3689
  }
3019
3690
  return Object.freeze({
3020
3691
  authority,
@@ -3033,18 +3704,25 @@ export class EvolutionArtifactPorts {
3033
3704
  ledgerRead = false,
3034
3705
  capturedLedgerEntry = null,
3035
3706
  batchWindow = null,
3707
+ boundedRead = null,
3708
+ observePhase = null,
3709
+ directoryScope = null,
3036
3710
  ) {
3711
+ observePhase?.("authentication");
3037
3712
  const authentication =
3038
3713
  preauthenticated || this.#authenticateEnvelope(envelope, options);
3714
+ observePhase?.("post-index");
3715
+ const beforeSnapshot = ledgerRead ? null : this.#defaultReadbackSnapshot();
3039
3716
  if (!ledgerRead)
3040
3717
  this.#assertUniqueRecordDigest(
3041
3718
  authentication.core.recordDigest,
3042
3719
  artifactId,
3720
+ beforeSnapshot?.entries,
3043
3721
  );
3044
3722
  const entry = ledgerRead
3045
3723
  ? (capturedLedgerEntry ??
3046
3724
  this.#loadLedgerEntry(artifactId, authentication.core.recordDigest))
3047
- : this.#loadCurrentEntryById(artifactId);
3725
+ : this.#loadCurrentEntryById(artifactId, beforeSnapshot);
3048
3726
  const normalizedEntry = this.#validateIndexEntry(
3049
3727
  entry,
3050
3728
  envelope,
@@ -3057,12 +3735,16 @@ export class EvolutionArtifactPorts {
3057
3735
  "ArtifactStore entry id does not match the requested locator",
3058
3736
  );
3059
3737
  }
3738
+ observePhase?.("read-bytes");
3060
3739
  const bytes = this.#readStoredBytes(
3061
3740
  entry,
3062
3741
  normalizedEntry,
3063
3742
  authentication.core.recordDigest,
3743
+ boundedRead,
3744
+ directoryScope,
3064
3745
  );
3065
3746
  const record = this.#parseRecordBytes(bytes, authentication.core);
3747
+ observePhase?.("post-index");
3066
3748
  if (ledgerRead && batchWindow === null) {
3067
3749
  const after = this.#loadLedgerEntry(
3068
3750
  artifactId,
@@ -3076,24 +3758,34 @@ export class EvolutionArtifactPorts {
3076
3758
  );
3077
3759
  }
3078
3760
  } else if (!ledgerRead) {
3079
- this.#assertEntryStable(artifactId, entry);
3761
+ const afterSnapshot = this.#defaultReadbackSnapshot();
3762
+ this.#assertEntryStable(artifactId, entry, afterSnapshot);
3080
3763
  this.#assertUniqueRecordDigest(
3081
3764
  authentication.core.recordDigest,
3082
3765
  artifactId,
3766
+ afterSnapshot?.entries,
3083
3767
  );
3084
3768
  }
3769
+ observePhase?.("final-lease");
3085
3770
  const releaseClock = this.#clock();
3086
- if (
3087
- releaseClock.milliseconds <
3088
- new Date(authentication.resolvedAt).getTime() ||
3089
- (authentication.core.expiresAtMs !== null &&
3090
- releaseClock.milliseconds >= authentication.core.expiresAtMs) ||
3091
- releaseClock.milliseconds >= authentication.authority.decisionExpiresAtMs
3092
- ) {
3093
- throw artifactError(
3771
+ const releaseFailureReason =
3772
+ releaseClock.milliseconds < new Date(authentication.resolvedAt).getTime()
3773
+ ? "release-clock-backward"
3774
+ : authentication.core.expiresAtMs !== null &&
3775
+ releaseClock.milliseconds >= authentication.core.expiresAtMs
3776
+ ? "release-envelope-expired"
3777
+ : releaseClock.milliseconds >=
3778
+ authentication.authority.decisionExpiresAtMs
3779
+ ? "release-authority-lease-expired"
3780
+ : null;
3781
+ if (releaseFailureReason !== null) {
3782
+ const error = artifactError(
3094
3783
  EVOLUTION_ARTIFACT_EXPIRED_CODE,
3095
3784
  "artifact authority expired before canonical bytes could be released",
3096
3785
  );
3786
+ recordStorageFailureReason(error, releaseFailureReason);
3787
+ recordStorageFailureStage(error, authentication.core.type, "final-lease");
3788
+ throw error;
3097
3789
  }
3098
3790
  const artifactRef = buildArtifactRef(
3099
3791
  artifactId,
@@ -3172,6 +3864,18 @@ export class EvolutionArtifactPorts {
3172
3864
  }
3173
3865
 
3174
3866
  putCanonical(type, value, context) {
3867
+ let phase = "validate";
3868
+ try {
3869
+ return this.#putCanonical(type, value, context, (next) => {
3870
+ phase = next;
3871
+ });
3872
+ } catch (error) {
3873
+ recordStorageFailureStage(error, type, phase);
3874
+ throw error;
3875
+ }
3876
+ }
3877
+
3878
+ #putCanonical(type, value, context, observePhase) {
3175
3879
  const normalizedType = normalizeType(type, this.#allowedTypes);
3176
3880
  const normalizedContext = normalizePutContext(
3177
3881
  context,
@@ -3191,6 +3895,7 @@ export class EvolutionArtifactPorts {
3191
3895
  const recordJson = canonicalJson(record);
3192
3896
  const recordBytes = Buffer.from(recordJson, "utf8");
3193
3897
  const recordDigest = sha256(recordBytes);
3898
+ observePhase("pre-index");
3194
3899
  readTrustedIndexSnapshot(this.#store.layout);
3195
3900
  const issuanceClock = this.#clock();
3196
3901
  const expiresAt =
@@ -3208,6 +3913,7 @@ export class EvolutionArtifactPorts {
3208
3913
  1,
3209
3914
  Math.ceil(normalizedContext.ttlMs / MILLISECONDS_PER_DAY),
3210
3915
  );
3916
+ observePhase("authority");
3211
3917
  const authority = this.#currentAuthority({
3212
3918
  action: "publish",
3213
3919
  digest: recordDigest,
@@ -3233,6 +3939,7 @@ export class EvolutionArtifactPorts {
3233
3939
  tenantId: this.#tenantId,
3234
3940
  type: normalizedType,
3235
3941
  });
3942
+ observePhase("sign");
3236
3943
  const signature = this.#sign(core, authority);
3237
3944
  const signingClock = this.#clock();
3238
3945
  if (
@@ -3278,7 +3985,9 @@ export class EvolutionArtifactPorts {
3278
3985
  // Validate the captured physical store/index generation immediately before
3279
3986
  // granting ArtifactStore the append. Post-publication readback re-attests
3280
3987
  // it and cross-checks the repository API against a descriptor snapshot.
3988
+ observePhase("pre-index");
3281
3989
  readTrustedIndexSnapshot(this.#store.layout);
3990
+ observePhase("publish");
3282
3991
  let publication;
3283
3992
  try {
3284
3993
  publication = rejectPromise(
@@ -3348,6 +4057,7 @@ export class EvolutionArtifactPorts {
3348
4057
  // A response-loss retry may find the prior immutable row. In that case its
3349
4058
  // exact signed envelope is authoritative and must be returned, rather than
3350
4059
  // replacing lineage with a newly issued envelope.
4060
+ observePhase("post-index");
3351
4061
  const currentEntry = this.#loadCurrentEntryById(artifactId);
3352
4062
  assertExactRecord(
3353
4063
  currentEntry,
@@ -3387,7 +4097,14 @@ export class EvolutionArtifactPorts {
3387
4097
  tenantId: this.#tenantId,
3388
4098
  }),
3389
4099
  artifactId,
3390
- );
4100
+ null,
4101
+ false,
4102
+ null,
4103
+ null,
4104
+ null,
4105
+ observePhase,
4106
+ );
4107
+ observePhase("read-bytes");
3391
4108
  if (
3392
4109
  canonicalJson(resolved.record) !== recordJson ||
3393
4110
  resolved.authentication.envelopeDigest !==
@@ -3398,6 +4115,7 @@ export class EvolutionArtifactPorts {
3398
4115
  "canonical artifact readback does not match the requested value and lineage",
3399
4116
  );
3400
4117
  }
4118
+ observePhase("receipt");
3401
4119
  const receiptCore = {
3402
4120
  audience: this.#audience,
3403
4121
  createdAt: ownData(
@@ -3481,7 +4199,14 @@ export class EvolutionArtifactPorts {
3481
4199
  });
3482
4200
  }
3483
4201
 
3484
- #resolveLedgerRequest(request, purpose, snapshot = null, batchWindow = null) {
4202
+ #resolveLedgerRequest(
4203
+ request,
4204
+ purpose,
4205
+ snapshot = null,
4206
+ batchWindow = null,
4207
+ boundedRead = null,
4208
+ directoryScope = null,
4209
+ ) {
3485
4210
  assertExactRecord(
3486
4211
  request,
3487
4212
  LEDGER_REQUEST_KEYS,
@@ -3550,6 +4275,9 @@ export class EvolutionArtifactPorts {
3550
4275
  true,
3551
4276
  entry,
3552
4277
  batchWindow,
4278
+ boundedRead,
4279
+ null,
4280
+ directoryScope,
3553
4281
  );
3554
4282
  const ledgerReceiptCore = {
3555
4283
  artifactReceiptDigest: resolved.receipt.receiptDigest,
@@ -3574,10 +4302,14 @@ export class EvolutionArtifactPorts {
3574
4302
  });
3575
4303
  }
3576
4304
 
3577
- #resolveLedgerBatch(input, purpose) {
4305
+ #resolveLedgerBatch(input, purpose, windowSnapshot = null) {
3578
4306
  assertDenseDataArray(input, "ledger artifact batch", 128);
3579
4307
  const requests = frozenCanonicalClone(input);
3580
- const snapshot = readTrustedIndexSnapshot(this.#store.layout);
4308
+ const snapshot = readTrustedIndexSnapshot(
4309
+ this.#store.layout,
4310
+ windowSnapshot,
4311
+ );
4312
+ this.#indexLedgerBatchEntries(snapshot.entries);
3581
4313
  const window = [];
3582
4314
  const results = requests.map((request) =>
3583
4315
  this.#resolveLedgerRequest(request, purpose, snapshot, window),
@@ -3585,7 +4317,7 @@ export class EvolutionArtifactPorts {
3585
4317
  // One bounded synchronous read, not a cache: every envelope/current
3586
4318
  // authority and payload is authenticated individually. Re-attest the full
3587
4319
  // index after ALL reads, and all decision leases before releasing bytes.
3588
- const after = readTrustedIndexSnapshot(this.#store.layout);
4320
+ const after = readTrustedIndexSnapshot(this.#store.layout, snapshot);
3589
4321
  if (after.bytesDigest !== snapshot.bytesDigest)
3590
4322
  throw artifactError(
3591
4323
  EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
@@ -3604,6 +4336,152 @@ export class EvolutionArtifactPorts {
3604
4336
  return Object.freeze(results);
3605
4337
  }
3606
4338
 
4339
+ #resolveLedgerWholeHistory(input, purpose) {
4340
+ assertDenseDataArray(
4341
+ input,
4342
+ "ledger artifact history",
4343
+ EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES,
4344
+ );
4345
+ // Capture every request before invoking any authority port. Retain the
4346
+ // established per-chunk canonical budget rather than imposing a smaller
4347
+ // whole-history byte limit on an otherwise admitted read window.
4348
+ const chunks = [];
4349
+ for (let offset = 0; offset < input.length; offset += 128) {
4350
+ const chunk = [];
4351
+ for (
4352
+ let index = offset;
4353
+ index < Math.min(offset + 128, input.length);
4354
+ index += 1
4355
+ ) {
4356
+ chunk.push(ownData(input, String(index), "ledger artifact history"));
4357
+ }
4358
+ chunks.push(frozenCanonicalClone(chunk));
4359
+ }
4360
+ const readHistory = (directoryScope) => {
4361
+ const snapshot = readTrustedIndexSnapshot(this.#store.layout);
4362
+ this.#indexLedgerBatchEntries(snapshot.entries);
4363
+ const window = [];
4364
+ const results = [];
4365
+ const resolveItems = (read) => {
4366
+ for (const requests of chunks) {
4367
+ for (const request of requests) {
4368
+ results.push(
4369
+ this.#resolveLedgerRequest(
4370
+ request,
4371
+ purpose,
4372
+ snapshot,
4373
+ window,
4374
+ read,
4375
+ directoryScope,
4376
+ ),
4377
+ );
4378
+ }
4379
+ }
4380
+ };
4381
+ if (this.#store.canUseSingleBoundedRead()) {
4382
+ try {
4383
+ withBoundedArtifactDirectoryRead(
4384
+ this.#store.layout.filesDir,
4385
+ resolveItems,
4386
+ );
4387
+ } catch (cause) {
4388
+ if (isEvolutionArtifactPortError(cause)) throw cause;
4389
+ throw artifactError(
4390
+ EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
4391
+ "stored artifact history could not be opened safely",
4392
+ { cause },
4393
+ );
4394
+ }
4395
+ } else {
4396
+ // A retained reader may outlive a prototype change. Preserve the
4397
+ // original per-file reads and integrity hooks when defaults changed.
4398
+ resolveItems(null);
4399
+ }
4400
+ // Unlike the public chunk callback API, this operation exposes no partial
4401
+ // results between chunks. Every item's payload, signature and current
4402
+ // authority are still read/verified above. Reattest the complete index and
4403
+ // ALL decision leases before releasing any result from the sealed read.
4404
+ const after = readTrustedIndexSnapshot(this.#store.layout, snapshot);
4405
+ if (after.bytesDigest !== snapshot.bytesDigest) {
4406
+ throw artifactError(
4407
+ EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
4408
+ "ArtifactStore index changed during ledger history readback",
4409
+ );
4410
+ }
4411
+ const completed = this.#clock().milliseconds;
4412
+ if (
4413
+ window.some(
4414
+ (entry) =>
4415
+ completed < entry.checkedAt || completed >= entry.expiresAt,
4416
+ )
4417
+ ) {
4418
+ throw artifactError(
4419
+ EVOLUTION_ARTIFACT_EXPIRED_CODE,
4420
+ "artifact authority expired before ledger history release",
4421
+ );
4422
+ }
4423
+ return Object.freeze(results);
4424
+ };
4425
+ if (this.#store.canUseSealedDirectoryRead()) {
4426
+ try {
4427
+ return withSealedStoreDirectoryRead(this.#store, readHistory);
4428
+ } catch (cause) {
4429
+ if (isEvolutionArtifactPortError(cause)) throw cause;
4430
+ throw artifactError(
4431
+ EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
4432
+ "stored artifact history directory identity changed",
4433
+ { cause },
4434
+ );
4435
+ }
4436
+ }
4437
+ return readHistory(null);
4438
+ }
4439
+
4440
+ #withLedgerBatchRead(callback, purpose) {
4441
+ rejectProxy(
4442
+ callback,
4443
+ "ledger batch read callback",
4444
+ EVOLUTION_ARTIFACT_INVALID_CODE,
4445
+ );
4446
+ if (typeof callback !== "function")
4447
+ throw artifactError(
4448
+ EVOLUTION_ARTIFACT_INVALID_CODE,
4449
+ "ledger batch read callback is required",
4450
+ );
4451
+ const snapshot = readTrustedIndexSnapshot(this.#store.layout);
4452
+ let active = true;
4453
+ let requests = 0;
4454
+ const resolve = Object.freeze((input) => {
4455
+ if (!active)
4456
+ throw artifactError(
4457
+ EVOLUTION_ARTIFACT_INVALID_CODE,
4458
+ "ledger batch read window is closed",
4459
+ );
4460
+ assertDenseDataArray(input, "ledger artifact batch", 128);
4461
+ requests += input.length;
4462
+ if (requests > EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES)
4463
+ throw artifactError(
4464
+ EVOLUTION_ARTIFACT_INVALID_CODE,
4465
+ "ledger batch read window exceeds the index entry limit",
4466
+ );
4467
+ // Each chunk reopens and authenticates current index bytes before/after
4468
+ // resolving fresh payloads, envelopes and current authority. Parsed rows
4469
+ // alone survive within this synchronous window; decision leases do not.
4470
+ return this.#resolveLedgerBatch(input, purpose, snapshot);
4471
+ });
4472
+ try {
4473
+ const result = rejectPromise(
4474
+ callback(resolve),
4475
+ "ledger batch read callback",
4476
+ EVOLUTION_ARTIFACT_INVALID_CODE,
4477
+ );
4478
+ readTrustedIndexSnapshot(this.#store.layout, snapshot);
4479
+ return result;
4480
+ } finally {
4481
+ active = false;
4482
+ }
4483
+ }
4484
+
3607
4485
  /** Return a frozen read-only function; it exposes no put/signing authority. */
3608
4486
  createEvolutionLedgerArtifactResolver(options = {}) {
3609
4487
  assertOptionalExactRecord(
@@ -3627,6 +4505,28 @@ export class EvolutionArtifactPorts {
3627
4505
  EVOLUTION_LEDGER_BATCH_RESOLVERS.set(resolveReadOnly, (requests) =>
3628
4506
  this.#resolveLedgerBatch(requests, normalizedPurpose),
3629
4507
  );
4508
+ EVOLUTION_LEDGER_BATCH_READ_WINDOWS.set(resolveReadOnly, (callback) =>
4509
+ this.#withLedgerBatchRead(callback, normalizedPurpose),
4510
+ );
4511
+ if (Object.getPrototypeOf(this) === EvolutionArtifactPorts.prototype) {
4512
+ EVOLUTION_LEDGER_DEFAULT_PUBLICATIONS.set(
4513
+ resolveReadOnly,
4514
+ this.#store.canUseDefaultPublication,
4515
+ );
4516
+ EVOLUTION_LEDGER_WHOLE_HISTORY_READERS.set(
4517
+ resolveReadOnly,
4518
+ Object.freeze({
4519
+ eligible: () =>
4520
+ this.#store.repositoryInstance &&
4521
+ this.#store.list === null &&
4522
+ this.#store.get === null &&
4523
+ this.#store.canUseSingleBoundedRead(),
4524
+ read: Object.freeze((requests) =>
4525
+ this.#resolveLedgerWholeHistory(requests, normalizedPurpose),
4526
+ ),
4527
+ }),
4528
+ );
4529
+ }
3630
4530
  EVOLUTION_LEDGER_RESOLVER_BINDINGS.set(
3631
4531
  resolveReadOnly,
3632
4532
  Object.freeze({
@@ -3670,6 +4570,28 @@ export function captureEvolutionLedgerBatchResolver(value) {
3670
4570
  return resolve;
3671
4571
  }
3672
4572
 
4573
+ export function captureEvolutionLedgerBatchReadWindow(value) {
4574
+ const read = EVOLUTION_LEDGER_BATCH_READ_WINDOWS.get(value);
4575
+ if (!read)
4576
+ throw new TypeError("a branded ledger artifact resolver is required");
4577
+ return read;
4578
+ }
4579
+
4580
+ // Optional original-implementation capability. Overrides retain the existing
4581
+ // callback/chunk protocol, including its observable integrity-hook phases.
4582
+ export function captureEvolutionLedgerWholeHistoryReader(value) {
4583
+ if (!isEvolutionLedgerArtifactResolver(value)) {
4584
+ throw new TypeError("a branded ledger artifact resolver is required");
4585
+ }
4586
+ const reader = EVOLUTION_LEDGER_WHOLE_HISTORY_READERS.get(value);
4587
+ return reader?.eligible() ? reader.read : null;
4588
+ }
4589
+
4590
+ /** Optional append composition check; grants no publication or read authority. */
4591
+ export function hasEvolutionLedgerDefaultPublication(value) {
4592
+ return EVOLUTION_LEDGER_DEFAULT_PUBLICATIONS.get(value)?.() === true;
4593
+ }
4594
+
3673
4595
  /** Private construction identity, independent of matching resolver descriptors. */
3674
4596
  export function captureEvolutionLedgerArtifactResolverBinding(value) {
3675
4597
  const binding = EVOLUTION_LEDGER_RESOLVER_BINDINGS.get(value);