@remnic/core 10.0.92 → 10.0.94

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 (501) hide show
  1. package/dist/access-admin-ops-surface.js +332 -59
  2. package/dist/access-authorization-probe.js +2 -283
  3. package/dist/access-boundary.d.ts +1 -1
  4. package/dist/access-boundary.d.ts.map +1 -1
  5. package/dist/access-boundary.js +1 -282
  6. package/dist/access-cli.js +287 -303
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-coding-context-resolution.js +3 -3
  9. package/dist/access-extraction-force-flush.js +332 -59
  10. package/dist/access-http-briefing.js +332 -60
  11. package/dist/access-http-query.js +332 -61
  12. package/dist/access-http.js +330 -112
  13. package/dist/access-identity-continuity-surface.js +332 -59
  14. package/dist/access-lcm-surface.js +332 -59
  15. package/dist/access-mcp-frames.d.ts +20 -6
  16. package/dist/access-mcp-frames.d.ts.map +1 -1
  17. package/dist/access-mcp-frames.js +1 -1
  18. package/dist/access-mcp.d.ts +5 -0
  19. package/dist/access-mcp.d.ts.map +1 -1
  20. package/dist/access-mcp.js +330 -87
  21. package/dist/access-namespace-preflight.js +2 -282
  22. package/dist/access-observe-transcript.js +3 -3
  23. package/dist/access-observe-write-category.js +6 -6
  24. package/dist/access-observe-write-surface.js +332 -59
  25. package/dist/access-offline-manifest.js +12 -11
  26. package/dist/access-operations-batch.js +330 -64
  27. package/dist/access-operations.js +330 -73
  28. package/dist/access-recall-concurrency.js +332 -59
  29. package/dist/access-recall-diagnostics.js +5 -4
  30. package/dist/access-recall-response.js +332 -59
  31. package/dist/access-recall-surface.js +332 -59
  32. package/dist/access-schema.js +2 -2
  33. package/dist/access-service.js +335 -62
  34. package/dist/access-surface-catalog.js +6 -283
  35. package/dist/access-surface-catalog.js.map +1 -1
  36. package/dist/active-context-transform.js +1 -1
  37. package/dist/active-memory-bridge.js +3 -2
  38. package/dist/active-recall.js +36 -37
  39. package/dist/active-recall.js.map +1 -1
  40. package/dist/activity/journal-extract.d.ts +13 -1
  41. package/dist/activity/journal-extract.d.ts.map +1 -1
  42. package/dist/{auto-sync-3AZW7MXI.js → auto-sync-BO5YZKM6.js} +13 -15
  43. package/dist/{auto-sync-3AZW7MXI.js.map → auto-sync-BO5YZKM6.js.map} +1 -1
  44. package/dist/briefing.js +20 -20
  45. package/dist/buffer.js +16 -16
  46. package/dist/calibration.js +10 -10
  47. package/dist/{capsule-merge-2IIXFXN5.js → capsule-merge-6IDAYTKG.js} +6 -6
  48. package/dist/causal-consolidation.js +23 -23
  49. package/dist/{chunk-RGUUBAT5.js → chunk-2WCLTWV2.js} +3 -3
  50. package/dist/{chunk-BQZEGOZ4.js → chunk-2YODOG2O.js} +2 -2
  51. package/dist/{chunk-WYG5UNDY.js → chunk-3AUDELZL.js} +2 -2
  52. package/dist/{chunk-MB6ZJDN6.js → chunk-3FBAEPCU.js} +2 -2
  53. package/dist/{chunk-PIITB2YZ.js → chunk-3ZS3NUEI.js} +4 -4
  54. package/dist/{chunk-ATPWZ4F7.js → chunk-4EZWSDFL.js} +5 -5
  55. package/dist/{chunk-ANZZLRUX.js → chunk-4IVRQMZW.js} +2 -2
  56. package/dist/{chunk-63E37JCN.js → chunk-4RAJT5L2.js} +4 -4
  57. package/dist/{chunk-45VUHDV2.js → chunk-5CO5G4MN.js} +2 -2
  58. package/dist/{chunk-LUJCQQ3L.js → chunk-65HWAQ4F.js} +2 -2
  59. package/dist/{chunk-T67LUYCR.js → chunk-77NTFICT.js} +5 -5
  60. package/dist/chunk-A2WFUMUN.js +86 -0
  61. package/dist/chunk-A2WFUMUN.js.map +1 -0
  62. package/dist/{chunk-UE6RRXMF.js → chunk-A4F7OIC5.js} +2 -2
  63. package/dist/chunk-AHA5VQGP.js +117 -0
  64. package/dist/chunk-AHA5VQGP.js.map +1 -0
  65. package/dist/{chunk-RRJBDXAQ.js → chunk-B377OKUP.js} +2 -2
  66. package/dist/{chunk-IUKYFTGW.js → chunk-B3DR2JBZ.js} +140 -125
  67. package/dist/chunk-B3DR2JBZ.js.map +1 -0
  68. package/dist/{chunk-Z7JIZ42P.js → chunk-BN2EBCLB.js} +3 -3
  69. package/dist/{chunk-YYOMGOHB.js → chunk-BOK6BN5M.js} +2 -2
  70. package/dist/{chunk-U6UNF5ZS.js → chunk-BPK32FRM.js} +3 -3
  71. package/dist/{chunk-OH7QLI6W.js → chunk-BPPUH5JM.js} +5 -5
  72. package/dist/{chunk-O6ZSWHVR.js → chunk-C2KE5DMD.js} +4 -4
  73. package/dist/{chunk-47F6WWLQ.js → chunk-C73N66PR.js} +2 -2
  74. package/dist/{chunk-WRT7GJ2D.js → chunk-CIWMTNHW.js} +4 -4
  75. package/dist/{chunk-HF3BKUVV.js → chunk-CL4JU3DF.js} +7 -7
  76. package/dist/{chunk-O55EKIEC.js → chunk-D3ZKHLN2.js} +2 -2
  77. package/dist/chunk-D5S6RL32.js +67137 -0
  78. package/dist/chunk-D5S6RL32.js.map +1 -0
  79. package/dist/{chunk-ETZLQOUR.js → chunk-D5XG2KPS.js} +2 -2
  80. package/dist/{chunk-CD4BCCNP.js → chunk-DJ7WVEWX.js} +2 -2
  81. package/dist/{chunk-PWXPRVLK.js → chunk-DNY63QWJ.js} +2 -2
  82. package/dist/{chunk-4CJTQHEG.js → chunk-ESLOLCD4.js} +3 -3
  83. package/dist/{chunk-VILPVT4S.js → chunk-EZ47HOJ6.js} +4 -4
  84. package/dist/{chunk-EFYN5I7Z.js → chunk-F4QFU5IZ.js} +2 -2
  85. package/dist/{chunk-T6OZW2WR.js → chunk-FA7SLQ5X.js} +7 -1
  86. package/dist/chunk-FA7SLQ5X.js.map +1 -0
  87. package/dist/{chunk-BRLXOZLM.js → chunk-FQAQ63QA.js} +729 -17
  88. package/dist/chunk-FQAQ63QA.js.map +1 -0
  89. package/dist/{chunk-FSO3G4JJ.js → chunk-FWRXW7CZ.js} +5000 -2748
  90. package/dist/chunk-FWRXW7CZ.js.map +1 -0
  91. package/dist/{chunk-XYKBWTWI.js → chunk-FZZMLGWQ.js} +2 -2
  92. package/dist/{chunk-FNXQZGUS.js → chunk-GBJUTX44.js} +2 -2
  93. package/dist/{chunk-YLXETSAH.js → chunk-GCZJ7XRI.js} +2 -2
  94. package/dist/{chunk-UDKWFDV2.js → chunk-HLUHEMN4.js} +27 -3
  95. package/dist/chunk-HLUHEMN4.js.map +1 -0
  96. package/dist/{chunk-SUSW74WU.js → chunk-IL3RHBWM.js} +56 -5
  97. package/dist/chunk-IL3RHBWM.js.map +1 -0
  98. package/dist/{chunk-JV4S6VDH.js → chunk-ILY5SSOM.js} +2 -2
  99. package/dist/{chunk-EXHXJETE.js → chunk-IQ55QGY5.js} +2 -2
  100. package/dist/{chunk-4J363OAA.js → chunk-J7BIFOYP.js} +2 -2
  101. package/dist/{chunk-QFEDITXM.js → chunk-JNDMIAJ4.js} +2 -2
  102. package/dist/chunk-JTSSSR4L.js +359 -0
  103. package/dist/chunk-JTSSSR4L.js.map +1 -0
  104. package/dist/{chunk-5KBIOI4G.js → chunk-JVBAPAWZ.js} +217 -13
  105. package/dist/chunk-JVBAPAWZ.js.map +1 -0
  106. package/dist/{chunk-E5PPE4VA.js → chunk-K2VRKCPA.js} +2 -2
  107. package/dist/{chunk-MKRYQROF.js → chunk-LGEHAQHK.js} +4 -4
  108. package/dist/{chunk-7HTUYFR3.js → chunk-LLPMMMWG.js} +27 -10
  109. package/dist/chunk-LLPMMMWG.js.map +1 -0
  110. package/dist/{chunk-XYTQJD7F.js → chunk-LMJVP5CM.js} +2 -2
  111. package/dist/{chunk-E7YZXWIS.js → chunk-LNGELD2M.js} +2 -2
  112. package/dist/{chunk-FQKBJJYB.js → chunk-LPV72KRQ.js} +2 -2
  113. package/dist/{chunk-2LCRG2YV.js → chunk-M2XR6B6V.js} +2 -2
  114. package/dist/{chunk-EQ4U73UH.js → chunk-MLAMXQAW.js} +14 -2
  115. package/dist/chunk-MLAMXQAW.js.map +1 -0
  116. package/dist/{chunk-ZU5JDLOH.js → chunk-MMRTPH3J.js} +2 -2
  117. package/dist/{chunk-TVIVY7KU.js → chunk-NED7IR7B.js} +4 -4
  118. package/dist/{chunk-6F5MJFXY.js → chunk-NHVAX7MH.js} +2 -2
  119. package/dist/{chunk-TG3R2SID.js → chunk-NMGHGTVR.js} +5 -5
  120. package/dist/{chunk-YCNIGRRF.js → chunk-NS7U4ZYU.js} +13 -117
  121. package/dist/chunk-NS7U4ZYU.js.map +1 -0
  122. package/dist/{chunk-NGHDW6UU.js → chunk-NUQEREIP.js} +2 -2
  123. package/dist/{chunk-5IIQU4JZ.js → chunk-OB72KNMF.js} +4 -4
  124. package/dist/{chunk-RIMK327Y.js → chunk-OUVYYKYU.js} +7 -7
  125. package/dist/{chunk-QJPJXCS4.js → chunk-P3WRNWWM.js} +157 -4
  126. package/dist/chunk-P3WRNWWM.js.map +1 -0
  127. package/dist/{chunk-E6M225BJ.js → chunk-PC5DRBGD.js} +18 -8
  128. package/dist/chunk-PC5DRBGD.js.map +1 -0
  129. package/dist/{chunk-RWGRSKN5.js → chunk-PT4DZDJA.js} +2 -2
  130. package/dist/{chunk-7EPY3IQL.js → chunk-PZJQ46DH.js} +2 -2
  131. package/dist/{chunk-MXHMNKWG.js → chunk-QDESY76F.js} +3 -3
  132. package/dist/{chunk-E3A7ELSG.js → chunk-QHARXWTC.js} +2 -2
  133. package/dist/{chunk-BLJT6JP2.js → chunk-QMFAMOT2.js} +2 -2
  134. package/dist/{chunk-I6QGI65T.js → chunk-QRMG4HK4.js} +3 -3
  135. package/dist/{chunk-ZU3NJNO7.js → chunk-QSF5SJAM.js} +4 -4
  136. package/dist/{chunk-5IYOKDM4.js → chunk-R23JWJD3.js} +5 -5
  137. package/dist/{chunk-RHQLIHMB.js → chunk-R4BBGPI2.js} +218 -218
  138. package/dist/chunk-R4BBGPI2.js.map +1 -0
  139. package/dist/{chunk-NT7F7WJQ.js → chunk-RFUL2ZIQ.js} +291 -93
  140. package/dist/chunk-RFUL2ZIQ.js.map +1 -0
  141. package/dist/{chunk-E4IRW2FG.js → chunk-RKABLU5M.js} +233 -40
  142. package/dist/chunk-RKABLU5M.js.map +1 -0
  143. package/dist/{chunk-P4WNBSTW.js → chunk-RNG7S3FY.js} +2 -2
  144. package/dist/{chunk-IPT2OY4B.js → chunk-RQFXZYTF.js} +35 -33
  145. package/dist/chunk-RQFXZYTF.js.map +1 -0
  146. package/dist/{chunk-5HFXGXE5.js → chunk-RUTN5MMC.js} +7 -9
  147. package/dist/{chunk-5HFXGXE5.js.map → chunk-RUTN5MMC.js.map} +1 -1
  148. package/dist/{chunk-AP6NCP3V.js → chunk-S3S72HRM.js} +8 -6
  149. package/dist/chunk-S3S72HRM.js.map +1 -0
  150. package/dist/{chunk-ZGFLIK5H.js → chunk-SPAKBUJY.js} +2 -2
  151. package/dist/{chunk-ER7XANP3.js → chunk-SQ3GEIEL.js} +4 -4
  152. package/dist/{chunk-PMJFRDJI.js → chunk-SUTEBYGI.js} +2 -2
  153. package/dist/{chunk-HVX4LPNY.js → chunk-T4KY6UYF.js} +5 -3
  154. package/dist/{chunk-HVX4LPNY.js.map → chunk-T4KY6UYF.js.map} +1 -1
  155. package/dist/{chunk-R6HMAIKM.js → chunk-TOSPNM4T.js} +2 -2
  156. package/dist/{chunk-RMXS4I66.js → chunk-TVDJEUV5.js} +2 -2
  157. package/dist/{chunk-PLCYKAWH.js → chunk-TYRWSE4B.js} +2 -2
  158. package/dist/{chunk-B4QYQFGY.js → chunk-UBEKET2V.js} +383 -36
  159. package/dist/chunk-UBEKET2V.js.map +1 -0
  160. package/dist/{chunk-IQWUACVG.js → chunk-UHMHJX5G.js} +3 -3
  161. package/dist/{chunk-YODUDA6R.js → chunk-UN4RVUG2.js} +22 -22
  162. package/dist/{chunk-E6Z4FBX6.js → chunk-V2QSNFCT.js} +2 -2
  163. package/dist/{chunk-NBH6K6G5.js → chunk-V65H7MI6.js} +345 -16
  164. package/dist/chunk-V65H7MI6.js.map +1 -0
  165. package/dist/chunk-W54Z2MOP.js +4500 -0
  166. package/dist/chunk-W54Z2MOP.js.map +1 -0
  167. package/dist/{chunk-7W3RXOIG.js → chunk-WGGRZYSQ.js} +14 -14
  168. package/dist/{chunk-RIZ5NUNN.js → chunk-WQ4XKIOX.js} +4 -4
  169. package/dist/{chunk-QEYTRFR3.js → chunk-X26YOFGS.js} +2 -2
  170. package/dist/{chunk-ITHZFHST.js → chunk-X2BVB74H.js} +2 -2
  171. package/dist/{chunk-6F42QONS.js → chunk-X5HGU2EM.js} +4 -4
  172. package/dist/{chunk-GVGAO3WV.js → chunk-XJ2S5UBF.js} +3 -3
  173. package/dist/{chunk-IBJ6U753.js → chunk-XKZBZOKV.js} +4 -4
  174. package/dist/{chunk-65AUYLU3.js → chunk-XPXHQI6N.js} +2 -2
  175. package/dist/chunk-XRWWCW4H.js +102 -0
  176. package/dist/chunk-XRWWCW4H.js.map +1 -0
  177. package/dist/{chunk-HH5LY6RR.js → chunk-YIXJSW5M.js} +5 -5
  178. package/dist/{chunk-2JSDPUKD.js → chunk-YMFPFXEX.js} +2 -2
  179. package/dist/{chunk-N4KAE2NB.js → chunk-Z3FTSHT5.js} +3 -3
  180. package/dist/{chunk-7GLJMDEQ.js → chunk-ZDV5YWID.js} +6 -6
  181. package/dist/{chunk-645WOU5I.js → chunk-ZDZJNXL6.js} +4 -4
  182. package/dist/{chunk-6TCXIRP5.js → chunk-ZKE4CO6B.js} +4 -4
  183. package/dist/cli.js +303 -185
  184. package/dist/coding/export-okf-codegraph.js +29 -29
  185. package/dist/compounding/engine.js +23 -24
  186. package/dist/config.js +36 -37
  187. package/dist/connectors/codex-materialize-runner.js +20 -20
  188. package/dist/connectors/index.js +21 -21
  189. package/dist/connectors/live/google-drive.d.ts +2 -0
  190. package/dist/connectors/live/google-drive.d.ts.map +1 -1
  191. package/dist/consolidation-provenance-check.js +4 -4
  192. package/dist/consolidation-undo.js +5 -5
  193. package/dist/contradiction/index.js +7 -6
  194. package/dist/{contradiction-scan-P2MHYFVG.js → contradiction-scan-IBPYGQZF.js} +4 -3
  195. package/dist/convergence-refresh.js +22 -22
  196. package/dist/corpus-watermark.js +23 -23
  197. package/dist/deep-recall-http-glue.js +2 -2
  198. package/dist/deep-recall-policy-llm.js +4 -4
  199. package/dist/direct-answer-wiring.js +2 -2
  200. package/dist/embedding-fallback.js +1 -1
  201. package/dist/entity-retrieval.js +21 -21
  202. package/dist/eval-trajectory-store.js +2 -2
  203. package/dist/eval-trajectory.js +2 -2
  204. package/dist/event-order-recall.js +2 -2
  205. package/dist/explicit-capture.d.ts +3 -2
  206. package/dist/explicit-capture.d.ts.map +1 -1
  207. package/dist/explicit-capture.js +530 -6
  208. package/dist/explicit-cue-recall.js +2 -2
  209. package/dist/external-wiki-access.js +2 -283
  210. package/dist/extraction-faithfulness.js +4 -4
  211. package/dist/extraction-judge.js +5 -5
  212. package/dist/extraction-normalization.js +1 -1
  213. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  214. package/dist/extraction-quote-grounding.js +1 -1
  215. package/dist/extraction-source-grounding-rules.js +2 -2
  216. package/dist/extraction-source-grounding.js +3 -3
  217. package/dist/extraction.js +43 -44
  218. package/dist/fallback-llm-codex-subscription.js +2 -2
  219. package/dist/fallback-llm.js +3 -3
  220. package/dist/{first-start-migration-QFI7B5SH.js → first-start-migration-FILJH3RO.js} +6 -5
  221. package/dist/{first-start-migration-QFI7B5SH.js.map → first-start-migration-FILJH3RO.js.map} +1 -1
  222. package/dist/focused-list-recall.js +2 -2
  223. package/dist/harmonic-retrieval.js +20 -20
  224. package/dist/index.js +773 -794
  225. package/dist/lcm/index.js +2 -2
  226. package/dist/live-connectors-runner.js +2 -3
  227. package/dist/local-llm-config.js +2 -2
  228. package/dist/local-llm-helpers.d.ts +4 -0
  229. package/dist/local-llm-helpers.d.ts.map +1 -1
  230. package/dist/local-llm-helpers.js +3 -1
  231. package/dist/local-llm.d.ts +4 -1
  232. package/dist/local-llm.d.ts.map +1 -1
  233. package/dist/local-llm.js +4 -4
  234. package/dist/location/index.js +27 -29
  235. package/dist/maintenance/memory-governance.js +20 -20
  236. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -20
  237. package/dist/maintenance/rebuild-memory-projection-runner.js +22 -22
  238. package/dist/maintenance/rebuild-memory-projection.js +21 -21
  239. package/dist/memory-browse-operations.js +2 -283
  240. package/dist/memory-browse.js +8 -7
  241. package/dist/memory-controller.js +2 -2
  242. package/dist/memory-subject.js +2 -2
  243. package/dist/namespaces/migrate.js +21 -21
  244. package/dist/namespaces/search.js +5 -5
  245. package/dist/namespaces/storage.js +20 -20
  246. package/dist/offline-sync-changeset-generations.js +1 -1
  247. package/dist/offline-sync-embedding-generation.js +1 -1
  248. package/dist/offline-sync-impression-drain.js +6 -6
  249. package/dist/offline-sync-snapshot-stream.js +1 -1
  250. package/dist/offline-sync.js +1 -1
  251. package/dist/operator-doctor-corpus.js +24 -24
  252. package/dist/operator-toolkit.js +58 -61
  253. package/dist/orchestration/backlog-scheduler.js +278 -294
  254. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  255. package/dist/orchestration/buffer-drain-runner.js +278 -294
  256. package/dist/orchestration/extraction-run.d.ts +4 -0
  257. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  258. package/dist/orchestration/fact-dedup-window.d.ts +1 -1
  259. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
  260. package/dist/orchestration/maintenance.js +39 -42
  261. package/dist/orchestrator.d.ts +9 -11
  262. package/dist/orchestrator.d.ts.map +1 -1
  263. package/dist/orchestrator.js +290 -307
  264. package/dist/page-versioning.js +3 -3
  265. package/dist/qmd-daemon-session.js +2 -2
  266. package/dist/qmd.js +3 -3
  267. package/dist/recall-explain-renderer.js +1 -1
  268. package/dist/recall-navigation-operations.js +2 -283
  269. package/dist/recall-navigation.js +332 -59
  270. package/dist/recall-planner-llm.js +3 -3
  271. package/dist/recall-state-view-admission.js +6 -6
  272. package/dist/recall-why-service.js +10 -9
  273. package/dist/recall-why.js +4 -3
  274. package/dist/recall-xray-cli.js +2 -2
  275. package/dist/recall-xray-renderer.js +1 -1
  276. package/dist/reconcile/manifest.js +3 -3
  277. package/dist/{resolution-N7H3F2E4.js → resolution-OVVIF7ZY.js} +8 -7
  278. package/dist/response-guidance-recall.js +2 -2
  279. package/dist/resume-bundles.js +40 -41
  280. package/dist/salvage-envelope.js +4 -4
  281. package/dist/search/factory.js +4 -4
  282. package/dist/search/index.js +4 -4
  283. package/dist/semantic-consolidation.js +23 -23
  284. package/dist/semantic-rule-promotion.js +20 -20
  285. package/dist/semantic-rule-verifier.js +20 -20
  286. package/dist/shared-context/manager.js +2 -3
  287. package/dist/source-agent-qualifier.js +32 -33
  288. package/dist/standup/build.js +20 -20
  289. package/dist/storage/buffer-drain-claims.d.ts +97 -12
  290. package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
  291. package/dist/storage/buffer-drain-claims.js +1 -1
  292. package/dist/storage/buffer-session-files.d.ts +4 -0
  293. package/dist/storage/buffer-session-files.d.ts.map +1 -1
  294. package/dist/storage/buffer-session-files.js +1 -1
  295. package/dist/storage/content-hash-index.d.ts +10 -0
  296. package/dist/storage/content-hash-index.d.ts.map +1 -1
  297. package/dist/storage.d.ts.map +1 -1
  298. package/dist/storage.js +19 -19
  299. package/dist/subject-config.js +3 -3
  300. package/dist/summarizer.d.ts +4 -1
  301. package/dist/summarizer.d.ts.map +1 -1
  302. package/dist/summarizer.js +6 -6
  303. package/dist/support-passport/index.js +53 -297
  304. package/dist/targeted-fact-recall.js +2 -2
  305. package/dist/tier-routing.js +3 -2
  306. package/dist/{tier-stats-FQGQSFET.js → tier-stats-FXEUJN73.js} +6 -5
  307. package/dist/{tier-stats-FQGQSFET.js.map → tier-stats-FXEUJN73.js.map} +1 -1
  308. package/dist/transcript.js +2 -2
  309. package/dist/transfer/capsule-import.js +4 -4
  310. package/dist/transfer/export-okf.js +20 -20
  311. package/dist/verified-recall.js +20 -20
  312. package/dist/wearables/memory-gen.d.ts +5 -1
  313. package/dist/wearables/memory-gen.d.ts.map +1 -1
  314. package/dist/wearables/service.d.ts.map +1 -1
  315. package/dist/who-knows-cli.js +21 -21
  316. package/dist/who-knows.js +20 -20
  317. package/dist/write-envelope.js +3 -3
  318. package/package.json +2 -2
  319. package/src/access-boundary.ts +5 -1
  320. package/src/access-category-alias.test.ts +5 -0
  321. package/src/access-mcp-frames.test.ts +60 -5
  322. package/src/access-mcp-frames.ts +72 -18
  323. package/src/access-mcp.test.ts +66 -2
  324. package/src/access-mcp.ts +19 -13
  325. package/src/access-service-coding-write.test.ts +7 -0
  326. package/src/activity/journal-extract-storage.test.ts +31 -0
  327. package/src/activity/journal-extract.test.ts +1 -0
  328. package/src/activity/journal-extract.ts +24 -3
  329. package/src/buffer-drain-handover.test.ts +5 -1
  330. package/src/connectors/live/google-drive.test.ts +39 -0
  331. package/src/connectors/live/google-drive.ts +41 -12
  332. package/src/explicit-capture.test.ts +109 -17
  333. package/src/explicit-capture.ts +90 -37
  334. package/src/extraction-quote-grounding.test.ts +20 -0
  335. package/src/extraction-quote-grounding.ts +21 -11
  336. package/src/fact-dedup-window.test.ts +37 -0
  337. package/src/local-llm-helpers.ts +12 -0
  338. package/src/local-llm-shutdown.test.ts +194 -0
  339. package/src/local-llm.ts +32 -30
  340. package/src/orchestration/extraction-run.ts +12 -3
  341. package/src/orchestration/fact-dedup-window.ts +5 -1
  342. package/src/orchestrator-connector-dedup.test.ts +9 -5
  343. package/src/orchestrator.ts +47 -50
  344. package/src/storage/buffer-drain-claims.test.ts +385 -1
  345. package/src/storage/buffer-drain-claims.ts +380 -97
  346. package/src/storage/buffer-session-files.ts +4 -0
  347. package/src/storage/content-hash-index.ts +27 -2
  348. package/src/storage-fact-hash-publish-failure.test.ts +87 -0
  349. package/src/storage.ts +5 -1
  350. package/src/summarizer-shutdown.test.ts +195 -0
  351. package/src/summarizer.ts +15 -2
  352. package/src/wearables/memory-gen.test.ts +20 -0
  353. package/src/wearables/memory-gen.ts +14 -7
  354. package/src/wearables/service.ts +14 -1
  355. package/src/wearables/storage-io.test.ts +25 -0
  356. package/dist/chunk-2FGLEF7C.js +0 -61
  357. package/dist/chunk-2FGLEF7C.js.map +0 -1
  358. package/dist/chunk-3RLK65ZK.js +0 -203
  359. package/dist/chunk-3RLK65ZK.js.map +0 -1
  360. package/dist/chunk-4WR2OD7K.js +0 -43
  361. package/dist/chunk-4WR2OD7K.js.map +0 -1
  362. package/dist/chunk-5KBIOI4G.js.map +0 -1
  363. package/dist/chunk-5M5M22GW.js +0 -346
  364. package/dist/chunk-5M5M22GW.js.map +0 -1
  365. package/dist/chunk-7HTUYFR3.js.map +0 -1
  366. package/dist/chunk-AP6NCP3V.js.map +0 -1
  367. package/dist/chunk-B4QYQFGY.js.map +0 -1
  368. package/dist/chunk-BJRTMNCB.js +0 -1919
  369. package/dist/chunk-BJRTMNCB.js.map +0 -1
  370. package/dist/chunk-BRLXOZLM.js.map +0 -1
  371. package/dist/chunk-BSACVS6R.js +0 -621
  372. package/dist/chunk-BSACVS6R.js.map +0 -1
  373. package/dist/chunk-DKAA2IHQ.js +0 -3644
  374. package/dist/chunk-DKAA2IHQ.js.map +0 -1
  375. package/dist/chunk-E4IRW2FG.js.map +0 -1
  376. package/dist/chunk-E6M225BJ.js.map +0 -1
  377. package/dist/chunk-EQ4U73UH.js.map +0 -1
  378. package/dist/chunk-ETSJD5IV.js +0 -361
  379. package/dist/chunk-ETSJD5IV.js.map +0 -1
  380. package/dist/chunk-F55YQP7N.js +0 -336
  381. package/dist/chunk-F55YQP7N.js.map +0 -1
  382. package/dist/chunk-FHGWKODN.js +0 -2300
  383. package/dist/chunk-FHGWKODN.js.map +0 -1
  384. package/dist/chunk-FLRP63GU.js +0 -32219
  385. package/dist/chunk-FLRP63GU.js.map +0 -1
  386. package/dist/chunk-FSO3G4JJ.js.map +0 -1
  387. package/dist/chunk-ID6JGIMD.js +0 -25
  388. package/dist/chunk-ID6JGIMD.js.map +0 -1
  389. package/dist/chunk-IGLJNHOX.js +0 -62
  390. package/dist/chunk-IGLJNHOX.js.map +0 -1
  391. package/dist/chunk-IPT2OY4B.js.map +0 -1
  392. package/dist/chunk-IUKYFTGW.js.map +0 -1
  393. package/dist/chunk-NBH6K6G5.js.map +0 -1
  394. package/dist/chunk-NT7F7WJQ.js.map +0 -1
  395. package/dist/chunk-NTRUKAAE.js +0 -223
  396. package/dist/chunk-NTRUKAAE.js.map +0 -1
  397. package/dist/chunk-OGXXGHW6.js +0 -223
  398. package/dist/chunk-OGXXGHW6.js.map +0 -1
  399. package/dist/chunk-PPCLZ4JU.js +0 -28
  400. package/dist/chunk-PPCLZ4JU.js.map +0 -1
  401. package/dist/chunk-QJPJXCS4.js.map +0 -1
  402. package/dist/chunk-R2LBF4HH.js +0 -729
  403. package/dist/chunk-R2LBF4HH.js.map +0 -1
  404. package/dist/chunk-RHQLIHMB.js.map +0 -1
  405. package/dist/chunk-RRTVGD3J.js +0 -792
  406. package/dist/chunk-RRTVGD3J.js.map +0 -1
  407. package/dist/chunk-SUSW74WU.js.map +0 -1
  408. package/dist/chunk-T6OZW2WR.js.map +0 -1
  409. package/dist/chunk-UDKWFDV2.js.map +0 -1
  410. package/dist/chunk-V7IZKJI3.js +0 -22
  411. package/dist/chunk-V7IZKJI3.js.map +0 -1
  412. package/dist/chunk-X562NQFA.js +0 -10144
  413. package/dist/chunk-X562NQFA.js.map +0 -1
  414. package/dist/chunk-XDSCJOH7.js +0 -3991
  415. package/dist/chunk-XDSCJOH7.js.map +0 -1
  416. package/dist/chunk-YCNIGRRF.js.map +0 -1
  417. package/dist/chunk-ZTTCDQ4I.js +0 -18796
  418. package/dist/chunk-ZTTCDQ4I.js.map +0 -1
  419. /package/dist/{capsule-merge-2IIXFXN5.js.map → capsule-merge-6IDAYTKG.js.map} +0 -0
  420. /package/dist/{chunk-RGUUBAT5.js.map → chunk-2WCLTWV2.js.map} +0 -0
  421. /package/dist/{chunk-BQZEGOZ4.js.map → chunk-2YODOG2O.js.map} +0 -0
  422. /package/dist/{chunk-WYG5UNDY.js.map → chunk-3AUDELZL.js.map} +0 -0
  423. /package/dist/{chunk-MB6ZJDN6.js.map → chunk-3FBAEPCU.js.map} +0 -0
  424. /package/dist/{chunk-PIITB2YZ.js.map → chunk-3ZS3NUEI.js.map} +0 -0
  425. /package/dist/{chunk-ATPWZ4F7.js.map → chunk-4EZWSDFL.js.map} +0 -0
  426. /package/dist/{chunk-ANZZLRUX.js.map → chunk-4IVRQMZW.js.map} +0 -0
  427. /package/dist/{chunk-63E37JCN.js.map → chunk-4RAJT5L2.js.map} +0 -0
  428. /package/dist/{chunk-45VUHDV2.js.map → chunk-5CO5G4MN.js.map} +0 -0
  429. /package/dist/{chunk-LUJCQQ3L.js.map → chunk-65HWAQ4F.js.map} +0 -0
  430. /package/dist/{chunk-T67LUYCR.js.map → chunk-77NTFICT.js.map} +0 -0
  431. /package/dist/{chunk-UE6RRXMF.js.map → chunk-A4F7OIC5.js.map} +0 -0
  432. /package/dist/{chunk-RRJBDXAQ.js.map → chunk-B377OKUP.js.map} +0 -0
  433. /package/dist/{chunk-Z7JIZ42P.js.map → chunk-BN2EBCLB.js.map} +0 -0
  434. /package/dist/{chunk-YYOMGOHB.js.map → chunk-BOK6BN5M.js.map} +0 -0
  435. /package/dist/{chunk-U6UNF5ZS.js.map → chunk-BPK32FRM.js.map} +0 -0
  436. /package/dist/{chunk-OH7QLI6W.js.map → chunk-BPPUH5JM.js.map} +0 -0
  437. /package/dist/{chunk-O6ZSWHVR.js.map → chunk-C2KE5DMD.js.map} +0 -0
  438. /package/dist/{chunk-47F6WWLQ.js.map → chunk-C73N66PR.js.map} +0 -0
  439. /package/dist/{chunk-WRT7GJ2D.js.map → chunk-CIWMTNHW.js.map} +0 -0
  440. /package/dist/{chunk-HF3BKUVV.js.map → chunk-CL4JU3DF.js.map} +0 -0
  441. /package/dist/{chunk-O55EKIEC.js.map → chunk-D3ZKHLN2.js.map} +0 -0
  442. /package/dist/{chunk-ETZLQOUR.js.map → chunk-D5XG2KPS.js.map} +0 -0
  443. /package/dist/{chunk-CD4BCCNP.js.map → chunk-DJ7WVEWX.js.map} +0 -0
  444. /package/dist/{chunk-PWXPRVLK.js.map → chunk-DNY63QWJ.js.map} +0 -0
  445. /package/dist/{chunk-4CJTQHEG.js.map → chunk-ESLOLCD4.js.map} +0 -0
  446. /package/dist/{chunk-VILPVT4S.js.map → chunk-EZ47HOJ6.js.map} +0 -0
  447. /package/dist/{chunk-EFYN5I7Z.js.map → chunk-F4QFU5IZ.js.map} +0 -0
  448. /package/dist/{chunk-XYKBWTWI.js.map → chunk-FZZMLGWQ.js.map} +0 -0
  449. /package/dist/{chunk-FNXQZGUS.js.map → chunk-GBJUTX44.js.map} +0 -0
  450. /package/dist/{chunk-YLXETSAH.js.map → chunk-GCZJ7XRI.js.map} +0 -0
  451. /package/dist/{chunk-JV4S6VDH.js.map → chunk-ILY5SSOM.js.map} +0 -0
  452. /package/dist/{chunk-EXHXJETE.js.map → chunk-IQ55QGY5.js.map} +0 -0
  453. /package/dist/{chunk-4J363OAA.js.map → chunk-J7BIFOYP.js.map} +0 -0
  454. /package/dist/{chunk-QFEDITXM.js.map → chunk-JNDMIAJ4.js.map} +0 -0
  455. /package/dist/{chunk-E5PPE4VA.js.map → chunk-K2VRKCPA.js.map} +0 -0
  456. /package/dist/{chunk-MKRYQROF.js.map → chunk-LGEHAQHK.js.map} +0 -0
  457. /package/dist/{chunk-XYTQJD7F.js.map → chunk-LMJVP5CM.js.map} +0 -0
  458. /package/dist/{chunk-E7YZXWIS.js.map → chunk-LNGELD2M.js.map} +0 -0
  459. /package/dist/{chunk-FQKBJJYB.js.map → chunk-LPV72KRQ.js.map} +0 -0
  460. /package/dist/{chunk-2LCRG2YV.js.map → chunk-M2XR6B6V.js.map} +0 -0
  461. /package/dist/{chunk-ZU5JDLOH.js.map → chunk-MMRTPH3J.js.map} +0 -0
  462. /package/dist/{chunk-TVIVY7KU.js.map → chunk-NED7IR7B.js.map} +0 -0
  463. /package/dist/{chunk-6F5MJFXY.js.map → chunk-NHVAX7MH.js.map} +0 -0
  464. /package/dist/{chunk-TG3R2SID.js.map → chunk-NMGHGTVR.js.map} +0 -0
  465. /package/dist/{chunk-NGHDW6UU.js.map → chunk-NUQEREIP.js.map} +0 -0
  466. /package/dist/{chunk-5IIQU4JZ.js.map → chunk-OB72KNMF.js.map} +0 -0
  467. /package/dist/{chunk-RIMK327Y.js.map → chunk-OUVYYKYU.js.map} +0 -0
  468. /package/dist/{chunk-RWGRSKN5.js.map → chunk-PT4DZDJA.js.map} +0 -0
  469. /package/dist/{chunk-7EPY3IQL.js.map → chunk-PZJQ46DH.js.map} +0 -0
  470. /package/dist/{chunk-MXHMNKWG.js.map → chunk-QDESY76F.js.map} +0 -0
  471. /package/dist/{chunk-E3A7ELSG.js.map → chunk-QHARXWTC.js.map} +0 -0
  472. /package/dist/{chunk-BLJT6JP2.js.map → chunk-QMFAMOT2.js.map} +0 -0
  473. /package/dist/{chunk-I6QGI65T.js.map → chunk-QRMG4HK4.js.map} +0 -0
  474. /package/dist/{chunk-ZU3NJNO7.js.map → chunk-QSF5SJAM.js.map} +0 -0
  475. /package/dist/{chunk-5IYOKDM4.js.map → chunk-R23JWJD3.js.map} +0 -0
  476. /package/dist/{chunk-P4WNBSTW.js.map → chunk-RNG7S3FY.js.map} +0 -0
  477. /package/dist/{chunk-ZGFLIK5H.js.map → chunk-SPAKBUJY.js.map} +0 -0
  478. /package/dist/{chunk-ER7XANP3.js.map → chunk-SQ3GEIEL.js.map} +0 -0
  479. /package/dist/{chunk-PMJFRDJI.js.map → chunk-SUTEBYGI.js.map} +0 -0
  480. /package/dist/{chunk-R6HMAIKM.js.map → chunk-TOSPNM4T.js.map} +0 -0
  481. /package/dist/{chunk-RMXS4I66.js.map → chunk-TVDJEUV5.js.map} +0 -0
  482. /package/dist/{chunk-PLCYKAWH.js.map → chunk-TYRWSE4B.js.map} +0 -0
  483. /package/dist/{chunk-IQWUACVG.js.map → chunk-UHMHJX5G.js.map} +0 -0
  484. /package/dist/{chunk-YODUDA6R.js.map → chunk-UN4RVUG2.js.map} +0 -0
  485. /package/dist/{chunk-E6Z4FBX6.js.map → chunk-V2QSNFCT.js.map} +0 -0
  486. /package/dist/{chunk-7W3RXOIG.js.map → chunk-WGGRZYSQ.js.map} +0 -0
  487. /package/dist/{chunk-RIZ5NUNN.js.map → chunk-WQ4XKIOX.js.map} +0 -0
  488. /package/dist/{chunk-QEYTRFR3.js.map → chunk-X26YOFGS.js.map} +0 -0
  489. /package/dist/{chunk-ITHZFHST.js.map → chunk-X2BVB74H.js.map} +0 -0
  490. /package/dist/{chunk-6F42QONS.js.map → chunk-X5HGU2EM.js.map} +0 -0
  491. /package/dist/{chunk-GVGAO3WV.js.map → chunk-XJ2S5UBF.js.map} +0 -0
  492. /package/dist/{chunk-IBJ6U753.js.map → chunk-XKZBZOKV.js.map} +0 -0
  493. /package/dist/{chunk-65AUYLU3.js.map → chunk-XPXHQI6N.js.map} +0 -0
  494. /package/dist/{chunk-HH5LY6RR.js.map → chunk-YIXJSW5M.js.map} +0 -0
  495. /package/dist/{chunk-2JSDPUKD.js.map → chunk-YMFPFXEX.js.map} +0 -0
  496. /package/dist/{chunk-N4KAE2NB.js.map → chunk-Z3FTSHT5.js.map} +0 -0
  497. /package/dist/{chunk-7GLJMDEQ.js.map → chunk-ZDV5YWID.js.map} +0 -0
  498. /package/dist/{chunk-645WOU5I.js.map → chunk-ZDZJNXL6.js.map} +0 -0
  499. /package/dist/{chunk-6TCXIRP5.js.map → chunk-ZKE4CO6B.js.map} +0 -0
  500. /package/dist/{contradiction-scan-P2MHYFVG.js.map → contradiction-scan-IBPYGQZF.js.map} +0 -0
  501. /package/dist/{resolution-N7H3F2E4.js.map → resolution-OVVIF7ZY.js.map} +0 -0
@@ -1,2300 +0,0 @@
1
- import {
2
- pathMayCarryEntityRefs,
3
- requestEntityCanonicalIdReconcile,
4
- withRawEntityPageMutation
5
- } from "./chunk-IAYUTGF2.js";
6
- import {
7
- TombstoneBlockedCaptureWriteLock,
8
- buildCapturePathLockIdentity,
9
- buildExplicitCaptureDedupKey,
10
- captureIdentityHash,
11
- isQueuedReviewMemory,
12
- runTombstoneBlockedMutation,
13
- tombstoneBlocked
14
- } from "./chunk-Y33KJ556.js";
15
- import {
16
- bumpArchiveMutationForPath
17
- } from "./chunk-W5BIQOAJ.js";
18
- import {
19
- markProjectedMemoryPathInvalid
20
- } from "./chunk-AL3UYZ5N.js";
21
- import {
22
- CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS,
23
- ContentHashIndex
24
- } from "./chunk-UDKWFDV2.js";
25
- import {
26
- withHeldFileLock
27
- } from "./chunk-EBV6VXKF.js";
28
- import {
29
- resolveSafeStoragePath
30
- } from "./chunk-LDPPTPO5.js";
31
- import {
32
- readMaybeEncryptedFileFromChunks,
33
- writeMaybeEncryptedFileFromChunks
34
- } from "./chunk-EYGOM6AI.js";
35
- import {
36
- RECALL_FALLBACK_DIRS
37
- } from "./chunk-VS2IYZRU.js";
38
- import {
39
- isErrnoCode
40
- } from "./chunk-KNA3BXXL.js";
41
- import {
42
- log
43
- } from "./chunk-3WCASNQZ.js";
44
-
45
- // src/storage/deletion-revision-store.ts
46
- import { createHash, randomUUID } from "crypto";
47
- import { lstat, mkdir, open, readFile, rename, unlink } from "fs/promises";
48
- import path from "path";
49
- var DELETION_REVISION_MAX_MTIME_MS = 864e13;
50
- var DELETION_REVISION_LOCK_STALE_MS = 6e4;
51
- var DELETION_REVISION_LOCK_MAX_WAIT_MS = 12e4;
52
- var INVALIDATION_COMMIT_MAX_TIMESTAMP_MS = 864e13;
53
- var INVALIDATION_PROOF_QUARANTINE_MAX_IDS = 1024;
54
- function canonicalizeInvalidationEvidence(value) {
55
- if (Array.isArray(value)) return value.map((item) => canonicalizeInvalidationEvidence(item));
56
- if (value && typeof value === "object") {
57
- const result = {};
58
- for (const key of Object.keys(value).sort()) {
59
- result[key] = canonicalizeInvalidationEvidence(value[key]);
60
- }
61
- return result;
62
- }
63
- return value;
64
- }
65
- function invalidationCommitFingerprint(memory) {
66
- const frontmatter = { ...memory.frontmatter };
67
- delete frontmatter.accessCount;
68
- delete frontmatter.lastAccessed;
69
- return createHash("sha256").update(
70
- JSON.stringify(
71
- canonicalizeInvalidationEvidence({
72
- content: memory.content,
73
- frontmatter
74
- })
75
- )
76
- ).digest("hex");
77
- }
78
- function markCasCommittedRevision(err, revision) {
79
- if (err instanceof Error) {
80
- err.casCommittedRevision = revision;
81
- }
82
- }
83
- function casCommittedRevisionOf(err) {
84
- return err instanceof Error ? err.casCommittedRevision : void 0;
85
- }
86
- function nextCasRevisionIso(previous, now = /* @__PURE__ */ new Date()) {
87
- const previousMs = previous !== void 0 ? new Date(previous).getTime() : Number.NaN;
88
- if (Number.isFinite(previousMs) && now.getTime() <= previousMs) {
89
- return new Date(previousMs + 1).toISOString();
90
- }
91
- return now.toISOString();
92
- }
93
- function isSemanticFrontmatterChange(before, after) {
94
- return invalidationCommitFingerprint({ content: "", frontmatter: before }) !== invalidationCommitFingerprint({ content: "", frontmatter: after });
95
- }
96
- async function withCasCommitReceipt(revision, run) {
97
- try {
98
- return await run();
99
- } catch (err) {
100
- markCasCommittedRevision(err, revision);
101
- throw err;
102
- }
103
- }
104
- function isValidInvalidationCommitTimestamp(value) {
105
- return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= INVALIDATION_COMMIT_MAX_TIMESTAMP_MS;
106
- }
107
- function isValidInvalidationCommitFingerprint(value) {
108
- return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
109
- }
110
- function isValidMemoryId(value) {
111
- return typeof value === "string" && value.length > 0 && !value.includes("\0");
112
- }
113
- function isValidManagedStoragePath(value) {
114
- return typeof value === "string" && value.length > 0 && !value.includes("\\") && !value.includes("\0") && !path.posix.isAbsolute(value) && path.posix.normalize(value) === value && value !== "." && value !== ".." && !value.startsWith("../");
115
- }
116
- function deletionRevisionPathIdentity(value) {
117
- return value.normalize("NFC").toUpperCase().toLowerCase();
118
- }
119
- var DeletionRevisionStore = class {
120
- options;
121
- invalidationProofQuarantine = /* @__PURE__ */ new Set();
122
- invalidationProofQuarantineFull = false;
123
- invalidationProofQuarantineFullAt = null;
124
- constructor(options) {
125
- this.options = options;
126
- }
127
- quarantineInvalidationProof(memoryId) {
128
- if (this.invalidationProofQuarantineFull) {
129
- const nextBoundary = Date.now() + 1;
130
- this.invalidationProofQuarantineFullAt = Math.max(
131
- this.invalidationProofQuarantineFullAt ?? 0,
132
- nextBoundary
133
- );
134
- return;
135
- }
136
- if (this.invalidationProofQuarantine.has(memoryId)) return;
137
- if (this.invalidationProofQuarantine.size >= INVALIDATION_PROOF_QUARANTINE_MAX_IDS) {
138
- this.invalidationProofQuarantineFull = true;
139
- this.invalidationProofQuarantineFullAt = Date.now() + 1;
140
- return;
141
- }
142
- this.invalidationProofQuarantine.add(memoryId);
143
- }
144
- async resolveConfiguredStoragePath(targetPath) {
145
- const baseDir = path.resolve(this.options.baseDir);
146
- const relativePath = path.relative(baseDir, path.resolve(targetPath));
147
- return resolveSafeStoragePath(baseDir, relativePath);
148
- }
149
- async resolveManagedStoragePath(filePath, method) {
150
- const assertedPath = this.options.assertManagedStoragePath(filePath, method);
151
- const target = await this.resolveConfiguredStoragePath(assertedPath);
152
- const relativePath = path.relative(path.resolve(this.options.baseDir), target).split(path.sep).join("/");
153
- return { target, relativePath };
154
- }
155
- parseDeletionRevisionMetadata(raw) {
156
- let parsed;
157
- try {
158
- parsed = JSON.parse(raw);
159
- } catch {
160
- throw new Error("Deletion revision metadata is invalid.");
161
- }
162
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
163
- throw new Error("Deletion revision metadata is invalid.");
164
- }
165
- const root = parsed;
166
- if (Object.keys(root).sort().join(",") !== "deletions,version" || root.version !== 1 || !Array.isArray(root.deletions)) {
167
- throw new Error("Deletion revision metadata is invalid.");
168
- }
169
- const revisions = /* @__PURE__ */ new Map();
170
- const pathByIdentity = /* @__PURE__ */ new Map();
171
- for (const rawEntry of root.deletions) {
172
- if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
173
- throw new Error("Deletion revision metadata is invalid.");
174
- }
175
- const entry = rawEntry;
176
- if (Object.keys(entry).sort().join(",") !== "mtimeMs,path" || !isValidManagedStoragePath(entry.path) || typeof entry.mtimeMs !== "number" || !Number.isFinite(entry.mtimeMs) || entry.mtimeMs < 0 || entry.mtimeMs > DELETION_REVISION_MAX_MTIME_MS || revisions.has(entry.path)) {
177
- throw new Error("Deletion revision metadata is invalid.");
178
- }
179
- const identity = deletionRevisionPathIdentity(entry.path);
180
- if (pathByIdentity.has(identity)) {
181
- throw new Error("Deletion revision metadata is invalid.");
182
- }
183
- pathByIdentity.set(identity, entry.path);
184
- revisions.set(entry.path, entry.mtimeMs);
185
- }
186
- return new Map([...revisions.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0));
187
- }
188
- async readDeletionRevisionMetadata() {
189
- const metadataPath = await this.resolveConfiguredStoragePath(this.options.deletionRevisionMetadataPath);
190
- let raw;
191
- try {
192
- raw = await readFile(metadataPath, "utf8");
193
- } catch (error) {
194
- if (isErrnoCode(error, "ENOENT")) return /* @__PURE__ */ new Map();
195
- throw new Error("Deletion revision metadata is unavailable.");
196
- }
197
- return this.parseDeletionRevisionMetadata(raw);
198
- }
199
- async writeDeletionRevisionMetadata(revisions, lock) {
200
- const metadataPath = await this.resolveConfiguredStoragePath(this.options.deletionRevisionMetadataPath);
201
- const deletions = [...revisions.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([entryPath, mtimeMs]) => ({ path: entryPath, mtimeMs }));
202
- const metadata = { version: 1, deletions };
203
- const temporaryPath = await this.resolveConfiguredStoragePath(
204
- `${metadataPath}.${process.pid}.${randomUUID()}.tmp`
205
- );
206
- let handle = null;
207
- try {
208
- handle = await open(temporaryPath, "wx", 384);
209
- await handle.writeFile(`${JSON.stringify(metadata)}
210
- `, "utf8");
211
- await handle.sync();
212
- await handle.close();
213
- handle = null;
214
- if (!await lock.refresh()) {
215
- throw new Error("Deletion revision metadata lock was lost.");
216
- }
217
- const renameTarget = await this.resolveConfiguredStoragePath(this.options.deletionRevisionMetadataPath);
218
- await rename(temporaryPath, renameTarget);
219
- } finally {
220
- if (handle !== null) {
221
- await handle.close().catch((error) => {
222
- log.warn("failed to close deletion revision metadata temporary file", error);
223
- });
224
- }
225
- const cleanupPath = await this.resolveConfiguredStoragePath(temporaryPath).catch((error) => {
226
- log.warn("failed to resolve deletion revision metadata temporary file for cleanup", error);
227
- return null;
228
- });
229
- if (cleanupPath !== null) {
230
- await unlink(cleanupPath).catch((error) => {
231
- if (!isErrnoCode(error, "ENOENT")) {
232
- log.warn("failed to clean up deletion revision metadata temporary file", error);
233
- }
234
- });
235
- }
236
- }
237
- }
238
- async withDeletionRevisionLock(task) {
239
- const lockPath = await this.resolveConfiguredStoragePath(this.options.deletionRevisionLockPath);
240
- return withHeldFileLock(
241
- lockPath,
242
- {
243
- staleMs: DELETION_REVISION_LOCK_STALE_MS,
244
- maxWaitMs: DELETION_REVISION_LOCK_MAX_WAIT_MS
245
- },
246
- async (acquired, lock) => {
247
- if (!acquired) throw new Error("Deletion revision metadata lock is unavailable.");
248
- return task(lock);
249
- }
250
- );
251
- }
252
- async readDeletionRevisions() {
253
- return this.withDeletionRevisionLock(async () => this.readDeletionRevisionMetadata());
254
- }
255
- parseInvalidationCommitMetadata(raw) {
256
- let parsed;
257
- try {
258
- parsed = JSON.parse(raw);
259
- } catch {
260
- throw new Error("Invalidation commit metadata is invalid.");
261
- }
262
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
263
- throw new Error("Invalidation commit metadata is invalid.");
264
- }
265
- const root = parsed;
266
- if (Object.keys(root).sort().join(",") !== "commits,version" || root.version !== 1 || !Array.isArray(root.commits)) {
267
- throw new Error("Invalidation commit metadata is invalid.");
268
- }
269
- const commits = /* @__PURE__ */ new Map();
270
- for (const rawEntry of root.commits) {
271
- if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
272
- throw new Error("Invalidation commit metadata is invalid.");
273
- }
274
- const entry = rawEntry;
275
- if (Object.keys(entry).sort().join(",") !== "committedAt,fingerprint,memoryId" || !isValidMemoryId(entry.memoryId) || !isValidInvalidationCommitFingerprint(entry.fingerprint) || !isValidInvalidationCommitTimestamp(entry.committedAt) || commits.has(entry.memoryId)) {
276
- throw new Error("Invalidation commit metadata is invalid.");
277
- }
278
- commits.set(entry.memoryId, {
279
- fingerprint: entry.fingerprint,
280
- committedAt: entry.committedAt
281
- });
282
- }
283
- return commits;
284
- }
285
- async readInvalidationCommitMetadata() {
286
- const metadataPath = await this.resolveConfiguredStoragePath(this.options.invalidationCommitMetadataPath);
287
- let raw;
288
- try {
289
- raw = await readFile(metadataPath, "utf8");
290
- } catch (error) {
291
- if (isErrnoCode(error, "ENOENT")) return /* @__PURE__ */ new Map();
292
- throw new Error("Invalidation commit metadata is unavailable.");
293
- }
294
- return this.parseInvalidationCommitMetadata(raw);
295
- }
296
- async writeInvalidationCommitMetadata(commits, lock) {
297
- const metadataPath = await this.resolveConfiguredStoragePath(this.options.invalidationCommitMetadataPath);
298
- const entries = [...commits.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([memoryId, value]) => ({
299
- memoryId,
300
- fingerprint: value.fingerprint,
301
- committedAt: value.committedAt
302
- }));
303
- const metadata = { version: 1, commits: entries };
304
- const temporaryPath = await this.resolveConfiguredStoragePath(
305
- `${metadataPath}.${process.pid}.${randomUUID()}.tmp`
306
- );
307
- const metadataDir = await this.resolveConfiguredStoragePath(path.dirname(metadataPath));
308
- let handle = null;
309
- let renamed = false;
310
- try {
311
- await mkdir(metadataDir, { recursive: true });
312
- handle = await open(temporaryPath, "wx", 384);
313
- await handle.writeFile(`${JSON.stringify(metadata)}
314
- `, "utf8");
315
- await handle.sync();
316
- await handle.close();
317
- handle = null;
318
- if (!await lock.refresh()) {
319
- throw new Error("Invalidation commit metadata lock was lost.");
320
- }
321
- const renameTarget = await this.resolveConfiguredStoragePath(this.options.invalidationCommitMetadataPath);
322
- await rename(temporaryPath, renameTarget);
323
- renamed = true;
324
- } finally {
325
- if (handle !== null) {
326
- await handle.close().catch((error) => {
327
- log.warn("failed to close invalidation commit metadata temporary file", error);
328
- });
329
- }
330
- const cleanupPath = await this.resolveConfiguredStoragePath(temporaryPath).catch((error) => {
331
- log.warn("failed to resolve invalidation commit metadata temporary file for cleanup", error);
332
- return null;
333
- });
334
- if (cleanupPath !== null) {
335
- await unlink(cleanupPath).catch((error) => {
336
- if (!isErrnoCode(error, "ENOENT")) {
337
- log.warn(
338
- `failed to clean up invalidation commit metadata temporary file after ${renamed ? "commit" : "failure"}`,
339
- error
340
- );
341
- }
342
- });
343
- }
344
- }
345
- }
346
- async hasCommittedInvalidation(memory) {
347
- const memoryId = memory.frontmatter.id;
348
- const fingerprint = invalidationCommitFingerprint(memory);
349
- return this.withDeletionRevisionLock(async () => {
350
- const entry = (await this.readInvalidationCommitMetadata()).get(memoryId);
351
- if (entry?.fingerprint !== fingerprint) return false;
352
- if (this.invalidationProofQuarantine.has(memoryId)) return false;
353
- if (!this.invalidationProofQuarantineFull) return true;
354
- const fullAt = this.invalidationProofQuarantineFullAt;
355
- return fullAt !== null && entry.committedAt >= fullAt;
356
- });
357
- }
358
- async recordCommittedInvalidation(memory) {
359
- const fingerprint = invalidationCommitFingerprint(memory);
360
- await this.withDeletionRevisionLock(async (lock) => {
361
- const fullAt = this.invalidationProofQuarantineFullAt;
362
- const committedAt = fullAt === null ? Date.now() : Math.max(Date.now(), fullAt + 1);
363
- const commits = await this.readInvalidationCommitMetadata();
364
- commits.set(memory.frontmatter.id, {
365
- fingerprint,
366
- committedAt
367
- });
368
- await this.writeInvalidationCommitMetadata(commits, lock);
369
- this.invalidationProofQuarantine.delete(memory.frontmatter.id);
370
- });
371
- }
372
- async clearCommittedInvalidation(memory) {
373
- const memoryId = memory.frontmatter.id;
374
- this.quarantineInvalidationProof(memoryId);
375
- const fingerprint = invalidationCommitFingerprint(memory);
376
- let cleared = false;
377
- try {
378
- await this.withDeletionRevisionLock(async (lock) => {
379
- const commits = await this.readInvalidationCommitMetadata();
380
- const current = commits.get(memoryId);
381
- if (current?.fingerprint !== fingerprint) return;
382
- commits.delete(memoryId);
383
- await this.writeInvalidationCommitMetadata(commits, lock);
384
- });
385
- cleared = true;
386
- } finally {
387
- if (cleared) this.invalidationProofQuarantine.delete(memoryId);
388
- }
389
- }
390
- async recordReplicatedDeletionRevision(filePath, mtimeMs) {
391
- const { relativePath } = await this.resolveManagedStoragePath(
392
- filePath,
393
- "storage.recordReplicatedDeletionRevision"
394
- );
395
- if (typeof mtimeMs !== "number" || !Number.isFinite(mtimeMs) || mtimeMs < 0 || mtimeMs > DELETION_REVISION_MAX_MTIME_MS) {
396
- throw new Error("Deletion revision timestamp is invalid.");
397
- }
398
- if (!isValidManagedStoragePath(relativePath)) {
399
- throw new Error("Deletion revision path is invalid.");
400
- }
401
- await this.withDeletionRevisionLock(async (lock) => {
402
- const revisions = await this.readDeletionRevisionMetadata();
403
- const identity = deletionRevisionPathIdentity(relativePath);
404
- let existingPath;
405
- for (const candidatePath of revisions.keys()) {
406
- if (deletionRevisionPathIdentity(candidatePath) === identity) {
407
- existingPath = candidatePath;
408
- break;
409
- }
410
- }
411
- const existingMtimeMs = existingPath === void 0 ? void 0 : revisions.get(existingPath);
412
- if (existingMtimeMs !== void 0 && existingMtimeMs >= mtimeMs) return;
413
- const updated = new Map(revisions);
414
- if (existingPath !== void 0) updated.delete(existingPath);
415
- updated.set(relativePath, mtimeMs);
416
- await this.writeDeletionRevisionMetadata(updated, lock);
417
- });
418
- }
419
- async writeManagedStorageFile(filePath, write) {
420
- const { relativePath } = await this.resolveManagedStoragePath(filePath, "storage.writeManagedStorageFile");
421
- if (!isValidManagedStoragePath(relativePath)) {
422
- throw new Error("Deletion revision path is invalid.");
423
- }
424
- await this.withDeletionRevisionLock(async (lock) => {
425
- const before = await this.readDeletionRevisionMetadata();
426
- const identity = deletionRevisionPathIdentity(relativePath);
427
- const existingPath = [...before.keys()].find(
428
- (candidatePath) => deletionRevisionPathIdentity(candidatePath) === identity
429
- );
430
- await write();
431
- if (existingPath === void 0) return;
432
- const updated = new Map(before);
433
- updated.delete(existingPath);
434
- await this.writeDeletionRevisionMetadata(updated, lock);
435
- });
436
- }
437
- async deleteManagedStorageFile(filePath, deletionMtimeMs) {
438
- const { target, relativePath } = await this.resolveManagedStoragePath(
439
- filePath,
440
- "storage.deleteManagedStorageFile"
441
- );
442
- if (deletionMtimeMs !== void 0 && deletionMtimeMs !== null && (typeof deletionMtimeMs !== "number" || !Number.isFinite(deletionMtimeMs) || deletionMtimeMs < 0 || deletionMtimeMs > DELETION_REVISION_MAX_MTIME_MS)) {
443
- throw new Error("Deletion revision timestamp is invalid.");
444
- }
445
- return this.withDeletionRevisionLock(async (lock) => {
446
- const safeTarget = await this.resolveConfiguredStoragePath(target);
447
- try {
448
- await lstat(safeTarget);
449
- } catch (error) {
450
- if (isErrnoCode(error, "ENOENT")) return false;
451
- throw error;
452
- }
453
- if (!isValidManagedStoragePath(relativePath)) {
454
- throw new Error("Deletion revision path is invalid.");
455
- }
456
- const revision = deletionMtimeMs === null ? void 0 : deletionMtimeMs ?? Date.now();
457
- const before = await this.readDeletionRevisionMetadata();
458
- const identity = deletionRevisionPathIdentity(relativePath);
459
- let existingPath;
460
- for (const candidatePath of before.keys()) {
461
- if (deletionRevisionPathIdentity(candidatePath) === identity) {
462
- existingPath = candidatePath;
463
- break;
464
- }
465
- }
466
- const existing = existingPath === void 0 ? void 0 : before.get(existingPath);
467
- const changed = existingPath !== void 0 && existingPath !== relativePath || (revision === void 0 ? existing !== void 0 : existing !== revision);
468
- if (changed) {
469
- const updated = new Map(before);
470
- if (existingPath !== void 0) updated.delete(existingPath);
471
- if (revision !== void 0) updated.set(relativePath, revision);
472
- await this.writeDeletionRevisionMetadata(updated, lock);
473
- }
474
- try {
475
- const unlinkTarget = await this.resolveConfiguredStoragePath(target);
476
- await unlink(unlinkTarget);
477
- return true;
478
- } catch (error) {
479
- if (changed) await this.writeDeletionRevisionMetadata(before, lock);
480
- if (isErrnoCode(error, "ENOENT")) return false;
481
- throw error;
482
- }
483
- });
484
- }
485
- };
486
-
487
- // src/storage/cas-revision-store.ts
488
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
489
- import { mkdir as mkdir2, open as open2, readFile as readFile2, rename as rename2, unlink as unlink2 } from "fs/promises";
490
- import path2 from "path";
491
- var ACCESS_TELEMETRY_FRONTMATTER_KEY = /^(?:accessCount|lastAccessed):/;
492
- function canonicalSemanticFileText(raw) {
493
- const lines = raw.split("\n");
494
- if (lines[0] !== "---") return raw;
495
- const closing = lines.indexOf("---", 1);
496
- if (closing === -1) return raw;
497
- const frontmatter = lines.slice(1, closing).filter((line) => !ACCESS_TELEMETRY_FRONTMATTER_KEY.test(line));
498
- return [...lines.slice(0, 1), ...frontmatter, ...lines.slice(closing)].join("\n");
499
- }
500
- var CAS_REVISION_LOCK_STALE_MS = 6e4;
501
- var CAS_REVISION_LOCK_MAX_WAIT_MS = 12e4;
502
- var SHA256_HEX = /^[0-9a-f]{64}$/;
503
- function isDigestOrNullOrUndefined(value) {
504
- if (value === void 0 || value === null) return true;
505
- return typeof value === "string" && SHA256_HEX.test(value);
506
- }
507
- function pendingWriteHasLanded(view) {
508
- if (view.writeLanded === true) return true;
509
- if (view.writeLanded === false) return false;
510
- return view.expectedDigest !== void 0;
511
- }
512
- var CasRevisionStore = class {
513
- baseDir;
514
- legacyMetadataPath;
515
- constructor(baseDir) {
516
- this.baseDir = baseDir;
517
- this.legacyMetadataPath = path2.join(baseDir, ".offline-sync", "cas-revisions.v1.json");
518
- }
519
- parseCasRevisionMetadata(raw) {
520
- let parsed;
521
- try {
522
- parsed = JSON.parse(raw);
523
- } catch {
524
- throw new Error("CAS revision metadata is invalid.");
525
- }
526
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
527
- throw new Error("CAS revision metadata is invalid.");
528
- }
529
- const root = parsed;
530
- if (Object.keys(root).sort().join(",") !== "revisions,version" || root.version !== 1 || !Array.isArray(root.revisions)) {
531
- throw new Error("CAS revision metadata is invalid.");
532
- }
533
- const revisions = /* @__PURE__ */ new Map();
534
- for (const rawEntry of root.revisions) {
535
- if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
536
- throw new Error("CAS revision metadata is invalid.");
537
- }
538
- const entry = rawEntry;
539
- if (Object.keys(entry).sort().join(",") !== "path,revision" || typeof entry.path !== "string" || !isValidManagedStoragePath(entry.path) || typeof entry.revision !== "string" || entry.revision.length === 0 || revisions.has(entry.path)) {
540
- throw new Error("CAS revision metadata is invalid.");
541
- }
542
- revisions.set(entry.path, entry.revision);
543
- }
544
- return revisions;
545
- }
546
- async readLegacyMetadata() {
547
- const baseDir = path2.resolve(this.baseDir);
548
- const legacyPath = await resolveSafeStoragePath(baseDir, path2.relative(baseDir, this.legacyMetadataPath));
549
- let raw;
550
- try {
551
- raw = await readFile2(legacyPath, "utf8");
552
- } catch (error) {
553
- if (isErrnoCode(error, "ENOENT")) return /* @__PURE__ */ new Map();
554
- throw new Error("CAS revision metadata is unavailable.");
555
- }
556
- return this.parseCasRevisionMetadata(raw);
557
- }
558
- /** #2813 (P1 A): every sidecar path is resolved (and symlink-audited)
559
- * inside the memory root before it is used for a lock, read, rename, or
560
- * unlink. A symlinked `.offline-sync`, `.offline-sync/cas-revisions`, or
561
- * shard file makes this throw, so no sidecar traffic can follow a link
562
- * out of the root. */
563
- async getSafeShardInfo(relativePath) {
564
- const baseDir = path2.resolve(this.baseDir);
565
- const hash = createHash2("sha256").update(relativePath).digest("hex");
566
- const shardPath = await resolveSafeStoragePath(baseDir, ".offline-sync", "cas-revisions", `${hash}.json`);
567
- const lockPath = await resolveSafeStoragePath(baseDir, ".offline-sync", "cas-revisions", `${hash}.json.lock`);
568
- return { shardPath, lockPath };
569
- }
570
- async readShardView(shardPath, relativePath) {
571
- let raw;
572
- try {
573
- raw = await readFile2(shardPath, "utf8");
574
- } catch (error) {
575
- if (isErrnoCode(error, "ENOENT")) return { kind: "missing" };
576
- throw error;
577
- }
578
- let parsed;
579
- try {
580
- parsed = JSON.parse(raw);
581
- } catch {
582
- throw new Error("CAS revision shard is unreadable.");
583
- }
584
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
585
- return { kind: "foreign" };
586
- }
587
- const root = parsed;
588
- if (root.state !== void 0) {
589
- if (root.version !== 1 || typeof root.path !== "string" || typeof root.revision !== "string" || root.revision.length === 0 || root.state !== "pending" && root.state !== "committed" || root.previous !== void 0 && (typeof root.previous !== "string" || root.previous.length === 0) || !isDigestOrNullOrUndefined(root.baselineDigest) || !isDigestOrNullOrUndefined(root.expectedDigest) || root.writeLanded !== void 0 && typeof root.writeLanded !== "boolean") {
590
- throw new Error("CAS revision shard is unreadable.");
591
- }
592
- const foreign = root.path !== relativePath;
593
- const committedDigest = typeof root.committedDigest === "string" ? root.committedDigest : void 0;
594
- const committedSemanticFingerprint = typeof root.committedSemanticFingerprint === "string" ? root.committedSemanticFingerprint : void 0;
595
- return root.state === "pending" ? {
596
- kind: "pending",
597
- revision: root.revision,
598
- previous: root.previous,
599
- foreign,
600
- baselineDigest: root.baselineDigest,
601
- expectedDigest: root.expectedDigest,
602
- ...typeof root.writeLanded === "boolean" ? { writeLanded: root.writeLanded } : {}
603
- } : {
604
- kind: "committed",
605
- revision: root.revision,
606
- committedDigest,
607
- committedSemanticFingerprint,
608
- foreign
609
- };
610
- }
611
- if (root.version === 1 && root.path === relativePath && typeof root.revision === "string" && root.revision.length > 0) {
612
- const committedDigest = typeof root.committedDigest === "string" ? root.committedDigest : void 0;
613
- const committedSemanticFingerprint = typeof root.committedSemanticFingerprint === "string" ? root.committedSemanticFingerprint : void 0;
614
- return {
615
- kind: "committed",
616
- revision: root.revision,
617
- committedDigest,
618
- committedSemanticFingerprint,
619
- foreign: false
620
- };
621
- }
622
- return { kind: "foreign" };
623
- }
624
- async readStandingRevision(relativePath) {
625
- try {
626
- return await this.readStandingRevisionStrict(relativePath);
627
- } catch (error) {
628
- log.warn(`CasRevisionStore failed to read standing revision for ${relativePath}: ${error}`);
629
- return void 0;
630
- }
631
- }
632
- /** #2813 (P1 A): strict core of {@link readStandingRevision}. Returns
633
- * undefined ONLY when no receipt exists for the target — the shard is
634
- * absent (or names another target) and the legacy metadata holds no
635
- * entry. THROWS when the standing receipt cannot be determined: an
636
- * unreadable or corrupt shard, a PENDING reservation awaiting
637
- * finalization or recovery, or unreadable or invalid legacy metadata.
638
- * Fail-open callers collapse the throw into undefined; truthful callers
639
- * ({@link readRevisionStatus}) report `unavailable` so a transaction can
640
- * refuse instead of mistaking the failure for absence. */
641
- async readStandingRevisionStrict(relativePath) {
642
- const { shardPath } = await this.getSafeShardInfo(relativePath);
643
- const view = await this.readShardView(shardPath, relativePath);
644
- if (view.kind === "pending") {
645
- throw new Error(
646
- `CAS revision receipt for ${relativePath} is pending finalization (recovery needed).`
647
- );
648
- }
649
- if (view.kind === "committed" && !view.foreign) {
650
- return {
651
- revision: view.revision,
652
- committedDigest: view.committedDigest,
653
- committedSemanticFingerprint: view.committedSemanticFingerprint
654
- };
655
- }
656
- const legacyMap = await this.readLegacyMetadata();
657
- const legacyRev = legacyMap.get(relativePath);
658
- return legacyRev === void 0 ? void 0 : { revision: legacyRev };
659
- }
660
- async writeShard(shardPath, relativePath, revision, lock, state, previous, evidence) {
661
- let payload;
662
- if (state === "committed") {
663
- payload = { version: 1, path: relativePath, revision, state };
664
- if (evidence?.committedDigest !== void 0) {
665
- payload.committedDigest = evidence.committedDigest;
666
- }
667
- if (evidence?.committedSemanticFingerprint !== void 0) {
668
- payload.committedSemanticFingerprint = evidence.committedSemanticFingerprint;
669
- }
670
- } else {
671
- payload = { version: 1, path: relativePath, revision, state };
672
- if (previous !== void 0) payload.previous = previous;
673
- if (evidence !== void 0) {
674
- if (evidence.baselineDigest !== void 0) payload.baselineDigest = evidence.baselineDigest;
675
- if (evidence.expectedDigest !== void 0) payload.expectedDigest = evidence.expectedDigest;
676
- if (evidence.writeLanded !== void 0) payload.writeLanded = evidence.writeLanded;
677
- }
678
- }
679
- const baseDir = path2.resolve(this.baseDir);
680
- const temporaryPath = await resolveSafeStoragePath(
681
- baseDir,
682
- path2.relative(baseDir, `${shardPath}.${process.pid}.${randomUUID2()}.tmp`)
683
- );
684
- let handle = null;
685
- try {
686
- await mkdir2(path2.dirname(shardPath), { recursive: true });
687
- handle = await open2(temporaryPath, "wx", 384);
688
- await handle.writeFile(`${JSON.stringify(payload)}
689
- `, "utf8");
690
- await handle.sync();
691
- await handle.close();
692
- handle = null;
693
- if (!await lock.refresh()) {
694
- throw new Error("CAS revision shard lock was lost.");
695
- }
696
- await rename2(temporaryPath, shardPath);
697
- } finally {
698
- if (handle !== null) {
699
- await handle.close().catch((error) => {
700
- log.warn("failed to close CAS revision shard temporary file", error);
701
- });
702
- }
703
- await unlink2(temporaryPath).catch((error) => {
704
- if (!isErrnoCode(error, "ENOENT")) {
705
- log.warn("failed to clean up CAS revision shard temporary file", error);
706
- }
707
- });
708
- }
709
- }
710
- async resolveRelativePath(filePath) {
711
- const baseDir = path2.resolve(this.baseDir);
712
- const target = await resolveSafeStoragePath(baseDir, path2.relative(baseDir, path2.resolve(baseDir, filePath)));
713
- return path2.relative(baseDir, target).split(path2.sep).join("/");
714
- }
715
- async withShardLock(relativePath, task) {
716
- const { lockPath } = await this.getSafeShardInfo(relativePath);
717
- return await withHeldFileLock(
718
- lockPath,
719
- { staleMs: CAS_REVISION_LOCK_STALE_MS, maxWaitMs: CAS_REVISION_LOCK_MAX_WAIT_MS },
720
- async (acquired, lock) => {
721
- if (!acquired) throw new Error("CAS revision lock is unavailable.");
722
- return await task(lock);
723
- }
724
- );
725
- }
726
- /** The target's standing revision token, or undefined when no receipt was
727
- * ever minted for it. Fail-open on storage/read errors. */
728
- async readRevision(filePath) {
729
- try {
730
- const relativePath = await this.resolveRelativePath(filePath);
731
- return (await this.readStandingRevision(relativePath))?.revision;
732
- } catch (error) {
733
- log.warn(`CasRevisionStore.readRevision failed for ${filePath}: ${error}`);
734
- return void 0;
735
- }
736
- }
737
- /** #2813 (P1 A): the truthful standing-receipt read. `absent` means no
738
- * receipt was ever minted for the target — undefined semantics stay
739
- * correct for those records. `unavailable` means the sidecar could not
740
- * be read OR a reservation is pending finalization; callers that
741
- * transact on receipt identity MUST refuse rather than treat the
742
- * failure as absence. */
743
- async readRevisionStatus(filePath) {
744
- try {
745
- const relativePath = await this.resolveRelativePath(filePath);
746
- const standing = await this.readStandingRevisionStrict(relativePath);
747
- return standing === void 0 ? { status: "absent" } : {
748
- status: "present",
749
- revision: standing.revision,
750
- ...standing.committedDigest !== void 0 ? { committedDigest: standing.committedDigest } : {},
751
- ...standing.committedSemanticFingerprint !== void 0 ? { committedSemanticFingerprint: standing.committedSemanticFingerprint } : {}
752
- };
753
- } catch (error) {
754
- return { status: "unavailable", reason: error instanceof Error ? error.message : String(error) };
755
- }
756
- }
757
- /** #2813 (P1 C): reserve the NEXT revision token as a durable PENDING
758
- * marker under the target's shard lock — the write-ahead record of a
759
- * receipt transaction. The token is strictly greater than the standing
760
- * token (max(clock, prev + 1ms)), so two commits can never share a
761
- * receipt — not within one millisecond, not across a backward clock
762
- * step. Refuses while another reservation is pending (recovery needed)
763
- * or the standing receipt cannot be determined. The caller MUST either
764
- * `commit` after its durable write lands or `abort` when it fails.
765
- * #2807: the marker records the reserve-time fingerprint (`baselineDigest`)
766
- * and optional expected post-write fingerprint (`expectedDigest`) with
767
- * `writeLanded: false`. {@link recoverPendingRevision} decides crashes
768
- * before or after the memory write from that evidence. */
769
- async beginRevisionTransaction(filePath, expectedContent) {
770
- const relativePath = await this.resolveRelativePath(filePath);
771
- return await this.withShardLock(relativePath, async (lock) => {
772
- const standingInfo = await this.readStandingRevisionStrict(relativePath);
773
- const standing = standingInfo?.revision;
774
- const next = nextCasRevisionIso(standing);
775
- const baselineDigest = await this.digestDurableFile(relativePath);
776
- const expectedDigest = expectedContent != null ? this.digestContent(expectedContent) : void 0;
777
- const { shardPath } = await this.getSafeShardInfo(relativePath);
778
- await this.writeShard(shardPath, relativePath, next, lock, "pending", standing, {
779
- baselineDigest,
780
- ...expectedDigest !== void 0 ? { expectedDigest } : {},
781
- writeLanded: false
782
- });
783
- return {
784
- pendingRevision: next,
785
- writeLanded: () => this.recordPendingWriteLanded(relativePath, next),
786
- commit: () => this.finalizeRevisionTransaction(relativePath, next, "commit"),
787
- abort: () => this.finalizeRevisionTransaction(relativePath, next, "abort")
788
- };
789
- });
790
- }
791
- digestContent(content) {
792
- const bytes = typeof content === "string" ? Buffer.from(content, "utf8") : content;
793
- return createHash2("sha256").update(bytes).digest("hex");
794
- }
795
- async digestDurableFile(filePath) {
796
- const bytes = await this.readDurableBytes(filePath);
797
- return bytes === null ? null : createHash2("sha256").update(bytes).digest("hex");
798
- }
799
- /** #2870: canonical SEMANTIC fingerprint of the durable file — its text
800
- * with the access-telemetry frontmatter keys stripped
801
- * ({@link canonicalSemanticFileText}), hashed. Null means the file was
802
- * absent. A fingerprint, never memory content. */
803
- async semanticFingerprintDurableFile(filePath) {
804
- const bytes = await this.readDurableBytes(filePath);
805
- return bytes === null ? null : createHash2("sha256").update(canonicalSemanticFileText(bytes.toString("utf8"))).digest("hex");
806
- }
807
- async readDurableBytes(filePath) {
808
- const relativePath = await this.resolveRelativePath(filePath);
809
- const baseDir = path2.resolve(this.baseDir);
810
- const target = await resolveSafeStoragePath(baseDir, relativePath);
811
- try {
812
- return await readFile2(target);
813
- } catch (error) {
814
- if (isErrnoCode(error, "ENOENT")) return null;
815
- throw new Error(
816
- `CAS revision evidence: durable memory file ${relativePath} is unreadable: ${error instanceof Error ? error.message : String(error)}`
817
- );
818
- }
819
- }
820
- /** #2807: mark the reserved write as landed. Verifies a pre-recorded
821
- * expected digest against the durable file, or records the file digest
822
- * when none was reserved. Sets `writeLanded: true`. Idempotent. */
823
- async recordPendingWriteLanded(relativePath, token) {
824
- await this.withShardLock(relativePath, async (lock) => {
825
- const { shardPath } = await this.getSafeShardInfo(relativePath);
826
- const view = await this.readShardView(shardPath, relativePath);
827
- if (view.kind !== "pending" || view.foreign || view.revision !== token) {
828
- throw new Error(
829
- `CAS revision evidence recording refused for ${relativePath}: the pending marker does not match the reserved token.`
830
- );
831
- }
832
- if (pendingWriteHasLanded(view) && view.writeLanded !== false) return;
833
- const currentDigest = await this.digestDurableFile(relativePath);
834
- if (view.expectedDigest !== void 0 && currentDigest !== view.expectedDigest) {
835
- throw new Error(
836
- `CAS revision evidence recording refused for ${relativePath}: the durable file does not match the reserved expected digest.`
837
- );
838
- }
839
- await this.writeShard(shardPath, relativePath, token, lock, "pending", view.previous, {
840
- baselineDigest: view.baselineDigest === void 0 ? null : view.baselineDigest,
841
- expectedDigest: view.expectedDigest !== void 0 ? view.expectedDigest : currentDigest,
842
- writeLanded: true
843
- });
844
- });
845
- }
846
- async finalizeRevisionTransaction(relativePath, token, outcome) {
847
- await this.withShardLock(relativePath, async (lock) => {
848
- const { shardPath } = await this.getSafeShardInfo(relativePath);
849
- const view = await this.readShardView(shardPath, relativePath);
850
- if (view.kind === "pending" && !view.foreign && view.revision === token) {
851
- if (outcome === "commit") {
852
- const committedDigest = view.expectedDigest ?? await this.digestDurableFile(relativePath);
853
- const committedSemanticFingerprint = await this.semanticFingerprintDurableFile(relativePath);
854
- await this.writeShard(shardPath, relativePath, token, lock, "committed", void 0, {
855
- committedDigest,
856
- committedSemanticFingerprint
857
- });
858
- } else if (view.previous !== void 0) {
859
- await this.writeShard(shardPath, relativePath, view.previous, lock, "committed");
860
- } else {
861
- await unlink2(shardPath);
862
- }
863
- return;
864
- }
865
- if (outcome === "commit" && view.kind === "committed" && !view.foreign && view.revision === token) {
866
- return;
867
- }
868
- if (outcome === "abort" && view.kind === "missing") {
869
- return;
870
- }
871
- throw new Error(
872
- `CAS revision transaction ${outcome} refused for ${relativePath}: the pending marker does not match the reserved token.`
873
- );
874
- });
875
- }
876
- /** #2837: remove the target's receipt shard once its durable memory file
877
- * has been deleted, so orphan shards cannot accumulate. Under the shard
878
- * lock: a PENDING marker is crash-recovery evidence for
879
- * {@link recoverPendingRevision} and is KEPT ("pending"); a shard naming
880
- * another target is KEPT ("foreign") — never destroy a receipt you cannot
881
- * attribute. Never touches the durable memory file or the legacy global
882
- * metadata: a stale legacy entry keeps per-path monotonicity if the path
883
- * is ever reused. */
884
- async removeRevisionShard(filePath) {
885
- const relativePath = await this.resolveRelativePath(filePath);
886
- return await this.withShardLock(relativePath, async () => {
887
- const { shardPath } = await this.getSafeShardInfo(relativePath);
888
- const view = await this.readShardView(shardPath, relativePath);
889
- if (view.kind === "missing") return "absent";
890
- if (view.kind === "pending") return view.foreign ? "foreign" : "pending";
891
- if (view.kind === "foreign" || view.foreign) return "foreign";
892
- await unlink2(shardPath);
893
- return "removed";
894
- });
895
- }
896
- /** #2813 (P1 C): recovery for a reservation left PENDING by a crash or a
897
- * failed publication. The caller supplies the KNOWN outcome of the
898
- * durable memory write: `fileWriteLanded` publishes the pending token as
899
- * the COMMITTED receipt; otherwise the reservation is discarded and the
900
- * previous standing token restored. Idempotent — an already-committed or
901
- * absent shard is reported, not mutated. */
902
- async reconcilePendingRevision(filePath, fileWriteLanded) {
903
- const relativePath = await this.resolveRelativePath(filePath);
904
- return await this.withShardLock(relativePath, async (lock) => {
905
- const { shardPath } = await this.getSafeShardInfo(relativePath);
906
- const view = await this.readShardView(shardPath, relativePath);
907
- if (view.kind === "committed") return "committed";
908
- if (view.kind !== "pending") return "absent";
909
- if (view.foreign) {
910
- throw new Error(
911
- `CAS revision pending marker for ${relativePath} names another target; manual recovery is required.`
912
- );
913
- }
914
- if (fileWriteLanded) {
915
- const committedDigest = view.expectedDigest ?? await this.digestDurableFile(relativePath);
916
- const committedSemanticFingerprint = await this.semanticFingerprintDurableFile(relativePath);
917
- await this.writeShard(shardPath, relativePath, view.revision, lock, "committed", void 0, {
918
- committedDigest,
919
- committedSemanticFingerprint
920
- });
921
- } else if (view.previous !== void 0) {
922
- await this.writeShard(shardPath, relativePath, view.previous, lock, "committed");
923
- } else {
924
- await unlink2(shardPath);
925
- }
926
- return "reconciled";
927
- });
928
- }
929
- /** #2807 (P1): evidence-based recovery for a reservation left PENDING by
930
- * a crash between reserve and finalize. Under the shard lock, the durable
931
- * file's current fingerprint is compared against the marker:
932
- * - matches `expectedDigest` → publish the pending token as COMMITTED;
933
- * - matches `baselineDigest` (including both absent) → abort and restore;
934
- * - anything else THROWS. Recovery never guesses.
935
- *
936
- * `onlyWithWriteEvidence` is the unlocked read path. A marker whose write
937
- * has not landed (`writeLanded` false, or legacy with no expectedDigest)
938
- * may belong to a live writer, so it returns `reserved`. A landed marker
939
- * whose file no longer matches `expectedDigest` throws ambiguity. */
940
- async recoverPendingRevision(filePath, options) {
941
- const relativePath = await this.resolveRelativePath(filePath);
942
- return await this.withShardLock(relativePath, async (lock) => {
943
- const { shardPath } = await this.getSafeShardInfo(relativePath);
944
- const view = await this.readShardView(shardPath, relativePath);
945
- if (view.kind === "missing" || view.kind === "foreign") return "absent";
946
- if (view.kind === "committed") return view.foreign ? "absent" : "committed";
947
- if (view.foreign) {
948
- throw new Error(
949
- `CAS revision pending marker for ${relativePath} names another target; manual recovery is required.`
950
- );
951
- }
952
- const currentDigest = await this.digestDurableFile(relativePath);
953
- const landed = pendingWriteHasLanded(view);
954
- if (options?.onlyWithWriteEvidence && !landed) {
955
- return "reserved";
956
- }
957
- if (view.expectedDigest !== void 0 && currentDigest === view.expectedDigest) {
958
- await this.writeShard(shardPath, relativePath, view.revision, lock, "committed", void 0, {
959
- committedDigest: view.expectedDigest,
960
- committedSemanticFingerprint: await this.semanticFingerprintDurableFile(relativePath)
961
- });
962
- return "committed";
963
- }
964
- if (options?.onlyWithWriteEvidence) {
965
- const evidence2 = view.expectedDigest === void 0 ? "the pending marker carries no crash evidence (written by an older version)" : "the durable file matches neither the expected commit fingerprint nor the reserve-time baseline";
966
- throw new Error(
967
- `CAS revision recovery for ${relativePath} is ambiguous: ${evidence2} (pending token ${view.revision}${view.previous !== void 0 ? `, previous ${view.previous}` : ""}). Inspect the durable file and the shard at ${shardPath}, then run reconcilePendingRevision with the known write outcome. Refusing to guess.`
968
- );
969
- }
970
- if (view.baselineDigest !== void 0 && currentDigest === view.baselineDigest) {
971
- if (view.previous !== void 0) {
972
- await this.writeShard(shardPath, relativePath, view.previous, lock, "committed");
973
- } else {
974
- await unlink2(shardPath);
975
- }
976
- return "aborted";
977
- }
978
- const evidence = view.baselineDigest === void 0 && view.expectedDigest === void 0 ? "the pending marker carries no crash evidence (written by an older version)" : "the durable file matches neither the expected commit fingerprint nor the reserve-time baseline";
979
- throw new Error(
980
- `CAS revision recovery for ${relativePath} is ambiguous: ${evidence} (pending token ${view.revision}${view.previous !== void 0 ? `, previous ${view.previous}` : ""}). Inspect the durable file and the shard at ${shardPath}, then run reconcilePendingRevision with the known write outcome. Refusing to guess.`
981
- );
982
- });
983
- }
984
- };
985
-
986
- // src/storage/cas-revision-host.ts
987
- var CasRevisionHost = class extends CasRevisionStore {
988
- /** Standing CAS revision token — receipt identity, never public `updated` (#2807). Fail-open on read errors. */
989
- async readCasRevision(filePath) {
990
- try {
991
- return await this.readRevision(filePath);
992
- } catch (err) {
993
- log.warn(`storage.readCasRevision failed for ${filePath}: ${err}`);
994
- return void 0;
995
- }
996
- }
997
- /** #2813 (P1 A): truthful CAS receipt probe — distinguishes a genuinely
998
- * ABSENT receipt (target predates the sidecar; `undefined` semantics stay
999
- * correct) from an UNAVAILABLE one (unreadable shard: receipt identity is
1000
- * unknown). Transactional callers that must refuse on unknown identity
1001
- * route here; `readCasRevision` stays fail-open for advisory reads.
1002
- * #2807 (P1): a PENDING marker left by a crash is lazily recovered on
1003
- * this first read when its evidence is decisive; a reserve-only marker
1004
- * may belong to a live writer and defers to the path-locked write path,
1005
- * and ambiguity stays `unavailable` with an actionable reason. */
1006
- async readCasRevisionStatus(filePath) {
1007
- const status = await this.readRevisionStatus(filePath);
1008
- if (status.status !== "unavailable" || !/pending/i.test(status.reason)) return status;
1009
- try {
1010
- const outcome = await this.recoverPendingRevision(filePath, {
1011
- onlyWithWriteEvidence: true
1012
- });
1013
- if (outcome === "reserved") return status;
1014
- } catch (error) {
1015
- return {
1016
- status: "unavailable",
1017
- reason: error instanceof Error ? error.message : String(error)
1018
- };
1019
- }
1020
- return await this.readRevisionStatus(filePath);
1021
- }
1022
- async readDurableFileDigest(filePath) {
1023
- try {
1024
- return await this.digestDurableFile(filePath);
1025
- } catch (err) {
1026
- log.warn(`storage.readDurableFileDigest failed for ${filePath}: ${err}`);
1027
- return null;
1028
- }
1029
- }
1030
- /** #2807 (P1): reconcile any crash-orphaned PENDING marker for this
1031
- * target BEFORE the next semantic mutation reserves a new token —
1032
- * otherwise a crashed reservation would make the memory permanently
1033
- * unwritable. Decisive evidence heals; ambiguity throws so the
1034
- * mutation fails closed with the actionable recovery error. */
1035
- async beginRevision(pathname, expectedContent) {
1036
- await this.recoverPendingRevision(pathname);
1037
- return await this.beginRevisionTransaction(pathname, expectedContent);
1038
- }
1039
- /** #2837: best-effort post-deletion sweep of the target's receipt shard.
1040
- * The durable memory file is already gone, so a failed sweep must never
1041
- * fail the deletion itself — it logs instead. A PENDING shard (crash
1042
- * recovery evidence) and a foreign shard (another target's receipt) are
1043
- * kept, with a warning naming the recovery path. */
1044
- async sweepShardAfterDeletion(filePath) {
1045
- try {
1046
- const outcome = await this.removeRevisionShard(filePath);
1047
- if (outcome === "pending") {
1048
- log.warn(
1049
- `CAS revision shard for ${filePath} kept after memory deletion: a pending reservation stands \u2014 run recovery before the path is reused`
1050
- );
1051
- } else if (outcome === "foreign") {
1052
- log.warn(
1053
- `CAS revision shard for ${filePath} kept after memory deletion: the shard names another target`
1054
- );
1055
- }
1056
- } catch (err) {
1057
- log.warn(`failed to sweep CAS revision shard for deleted memory ${filePath}: ${err}`);
1058
- }
1059
- }
1060
- };
1061
-
1062
- // src/storage/tombstone-blocked-capture-sync.ts
1063
- import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
1064
- import { unlink as unlink4 } from "fs/promises";
1065
- import path4 from "path";
1066
- import { isDeepStrictEqual } from "util";
1067
-
1068
- // src/storage/tombstone-blocked-capture-index.ts
1069
- import { execFile } from "child_process";
1070
- import { randomUUID as randomUUID3 } from "crypto";
1071
- import { mkdir as mkdir3, readFile as readFile3, readdir, rename as rename3, stat, unlink as unlink3, writeFile } from "fs/promises";
1072
- import path3 from "path";
1073
- import { promisify } from "util";
1074
- var REBUILD_MAX_ATTEMPTS = 3;
1075
- var execFileAsync = promisify(execFile);
1076
- var REBUILD_RETRY_BASE_MS = 50;
1077
- var ABANDONED_MARKER_MIN_AGE_MS = 6e4;
1078
- var MARKER_PROCESS_START_TOLERANCE_MS = 2e3;
1079
- var DOTNET_UNIX_EPOCH_TICKS = 621355968e9;
1080
- var MARKER_PROCESS_STARTED_AT_MS = Date.now() - process.uptime() * 1e3;
1081
- var TombstoneBlockedCaptureIndex = class {
1082
- options;
1083
- index = null;
1084
- loadPromise = null;
1085
- refreshPromise = null;
1086
- authoritative = false;
1087
- captureWriteLock;
1088
- constructor(options) {
1089
- this.options = options;
1090
- this.captureWriteLock = new TombstoneBlockedCaptureWriteLock({
1091
- stateDir: options.stateDir,
1092
- withHeldFileLock: options.withHeldFileLock,
1093
- lockOptions: options.captureLockLimitSource
1094
- });
1095
- }
1096
- createIndex() {
1097
- return new ContentHashIndex(
1098
- path3.join(this.options.stateDir, "tombstone-blocked-capture"),
1099
- this.options.secureStoreKeyProvider,
1100
- this.options.secureStoreWriteKeyProvider,
1101
- this.options.memoryDir,
1102
- this.options.lockOptions()
1103
- );
1104
- }
1105
- indexPath() {
1106
- return path3.join(this.options.stateDir, "tombstone-blocked-capture", "fact-hashes.txt");
1107
- }
1108
- rebuildMarkerPath() {
1109
- return path3.join(this.options.stateDir, "tombstone-blocked-capture", "rebuild-required");
1110
- }
1111
- async readRebuildMarkers() {
1112
- try {
1113
- const entries = (await readdir(this.rebuildMarkerPath())).filter((entry) => !entry.startsWith("."));
1114
- return (await Promise.all(
1115
- entries.map(async (entry) => {
1116
- const markerPath = path3.join(this.rebuildMarkerPath(), entry);
1117
- try {
1118
- const raw = (await readFile3(markerPath, "utf8")).trim();
1119
- if (raw === "committed") return { path: markerPath, committed: true };
1120
- let value;
1121
- let malformed = false;
1122
- try {
1123
- const parsed = JSON.parse(raw);
1124
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1125
- malformed = true;
1126
- value = {};
1127
- } else {
1128
- value = parsed;
1129
- }
1130
- } catch {
1131
- malformed = true;
1132
- value = {};
1133
- }
1134
- const state = value.state;
1135
- if (state !== "pending" && state !== "committed") malformed = true;
1136
- const markerStat = await stat(markerPath).catch(() => null);
1137
- const createdAt = typeof value.createdAt === "number" && Number.isFinite(value.createdAt) ? value.createdAt : markerStat?.mtimeMs;
1138
- if (typeof value.pid !== "number" || !Number.isInteger(value.pid)) malformed = true;
1139
- const ownerId = typeof value.ownerId === "string" && value.ownerId.length > 0 ? value.ownerId : path3.basename(markerPath);
1140
- if (typeof value.ownerId !== "string" || value.ownerId.length === 0) malformed = true;
1141
- const processStartedAtMs = typeof value.processStartedAtMs === "number" && Number.isFinite(value.processStartedAtMs) && value.processStartedAtMs > 0 ? value.processStartedAtMs : void 0;
1142
- return {
1143
- path: markerPath,
1144
- committed: state === "committed",
1145
- ...typeof value.pid === "number" && Number.isInteger(value.pid) ? { pid: value.pid } : {},
1146
- ...ownerId.length > 0 ? { ownerId } : {},
1147
- ...typeof createdAt === "number" && Number.isFinite(createdAt) ? { createdAt } : {},
1148
- ...processStartedAtMs === void 0 ? {} : { processStartedAtMs },
1149
- ...malformed ? { malformed: true } : {}
1150
- };
1151
- } catch (err) {
1152
- if (isErrnoCode(err, "ENOENT")) return null;
1153
- throw err;
1154
- }
1155
- })
1156
- )).filter((marker) => marker !== null);
1157
- } catch (err) {
1158
- if (isErrnoCode(err, "ENOENT")) return [];
1159
- throw err;
1160
- }
1161
- }
1162
- async isAbandonedMarker(marker) {
1163
- if (marker.committed || marker.createdAt === void 0) return false;
1164
- if (Date.now() - marker.createdAt < ABANDONED_MARKER_MIN_AGE_MS) return false;
1165
- if (marker.malformed) return true;
1166
- return marker.pid !== void 0 && !await this.isProcessAlive(marker.pid, marker.processStartedAtMs);
1167
- }
1168
- async isProcessAlive(pid, processStartedAtMs) {
1169
- try {
1170
- process.kill(pid, 0);
1171
- } catch (err) {
1172
- return !isErrnoCode(err, "ESRCH");
1173
- }
1174
- if (processStartedAtMs === void 0) return true;
1175
- const runningStartedAtMs = await this.readProcessStartedAtMs(pid);
1176
- if (runningStartedAtMs === null) return true;
1177
- return runningStartedAtMs <= processStartedAtMs + MARKER_PROCESS_START_TOLERANCE_MS;
1178
- }
1179
- async readProcessStartedAtMs(pid) {
1180
- try {
1181
- if (process.platform === "win32") {
1182
- const { stdout: stdout2 } = await execFileAsync(
1183
- "powershell.exe",
1184
- [
1185
- "-NoLogo",
1186
- "-NoProfile",
1187
- "-NonInteractive",
1188
- "-Command",
1189
- `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks`
1190
- ],
1191
- { encoding: "utf8", timeout: 1e3 }
1192
- );
1193
- const ticks = Number(stdout2.trim());
1194
- return Number.isFinite(ticks) ? (ticks - DOTNET_UNIX_EPOCH_TICKS) / 1e4 : null;
1195
- }
1196
- const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "lstart="], {
1197
- encoding: "utf8",
1198
- timeout: 1e3
1199
- });
1200
- const output = stdout.trim();
1201
- if (!output) return null;
1202
- const startedAtMs = Date.parse(output);
1203
- return Number.isFinite(startedAtMs) ? startedAtMs : null;
1204
- } catch {
1205
- return null;
1206
- }
1207
- }
1208
- async getClearableRebuildMarkerPaths(markers) {
1209
- const paths = await Promise.all(
1210
- markers.map(async (marker) => marker.committed || await this.isAbandonedMarker(marker) ? marker.path : null)
1211
- );
1212
- return paths.filter((markerPath) => markerPath !== null);
1213
- }
1214
- async getRebuildMarkers() {
1215
- return await this.readRebuildMarkers();
1216
- }
1217
- // A pending marker protects the pre-commit gap; only committed markers are safe for another writer to clear.
1218
- async writeMarkerAtomically(markerPath, payload) {
1219
- const temporaryPath = `${markerPath}.tmp-${process.pid}-${randomUUID3()}`;
1220
- try {
1221
- await writeFile(temporaryPath, payload, "utf8");
1222
- await rename3(temporaryPath, markerPath);
1223
- } finally {
1224
- await unlink3(temporaryPath).catch((err) => {
1225
- if (!isErrnoCode(err, "ENOENT")) throw err;
1226
- });
1227
- }
1228
- }
1229
- async markRebuildRequired(committed = false) {
1230
- const markerDir = this.rebuildMarkerPath();
1231
- await mkdir3(markerDir, { recursive: true });
1232
- const ownerId = randomUUID3();
1233
- const markerPath = path3.join(markerDir, ownerId);
1234
- await this.writeMarkerAtomically(
1235
- markerPath,
1236
- `${JSON.stringify({
1237
- state: committed ? "committed" : "pending",
1238
- pid: process.pid,
1239
- ownerId,
1240
- createdAt: Date.now(),
1241
- processStartedAtMs: MARKER_PROCESS_STARTED_AT_MS
1242
- })}
1243
- `
1244
- );
1245
- return markerPath;
1246
- }
1247
- async markRebuildCommitted(markerPath) {
1248
- await this.writeMarkerAtomically(
1249
- markerPath,
1250
- `${JSON.stringify({
1251
- state: "committed",
1252
- pid: process.pid,
1253
- ownerId: path3.basename(markerPath),
1254
- createdAt: Date.now(),
1255
- processStartedAtMs: MARKER_PROCESS_STARTED_AT_MS
1256
- })}
1257
- `
1258
- );
1259
- }
1260
- async clearRebuildRequired(markerPaths) {
1261
- await Promise.all(
1262
- markerPaths.map(async (markerPath) => {
1263
- try {
1264
- await unlink3(markerPath);
1265
- } catch (err) {
1266
- if (!isErrnoCode(err, "ENOENT")) throw err;
1267
- }
1268
- })
1269
- );
1270
- }
1271
- async hasRebuildRequired(excludedMarkers = []) {
1272
- const excluded = new Set(excludedMarkers);
1273
- return (await this.getRebuildMarkers()).some((marker) => !excluded.has(marker.path));
1274
- }
1275
- async setAuthoritative(rebuilt) {
1276
- this.authoritative = rebuilt && !(await this.getRebuildMarkers()).some((marker) => !marker.committed);
1277
- }
1278
- isBlocked(memory) {
1279
- return memory.frontmatter.status === "pending_review" && Boolean(memory.frontmatter.blockedBy);
1280
- }
1281
- async rebuild(index) {
1282
- return await index.rebuildUnderLock(async () => {
1283
- index.clear();
1284
- const existing = [...await this.options.readAllMemories(), ...await this.options.readAllColdMemories()];
1285
- for (const memory of existing) {
1286
- if (!this.isBlocked(memory)) continue;
1287
- index.add(
1288
- buildExplicitCaptureDedupKey(memory.content, memory.frontmatter.category, memory.frontmatter.sourceConnector)
1289
- );
1290
- }
1291
- });
1292
- }
1293
- async hasPersistedIndexOrMarkers() {
1294
- try {
1295
- await stat(this.indexPath());
1296
- return true;
1297
- } catch (err) {
1298
- if (!isErrnoCode(err, "ENOENT")) throw err;
1299
- return (await this.readRebuildMarkers()).length > 0;
1300
- }
1301
- }
1302
- async getIndex() {
1303
- if (this.index) return this.index;
1304
- if (!this.loadPromise) {
1305
- const index = this.createIndex();
1306
- this.loadPromise = (async () => {
1307
- await index.load();
1308
- const rebuildMarkers = await this.getRebuildMarkers();
1309
- const clearableRebuildMarkers = await this.getClearableRebuildMarkerPaths(rebuildMarkers);
1310
- let persisted = !index.requiresFormatMigration;
1311
- try {
1312
- await stat(this.indexPath());
1313
- if (rebuildMarkers.length > 0) persisted = false;
1314
- } catch (err) {
1315
- if (isErrnoCode(err, "ENOENT")) persisted = false;
1316
- else throw err;
1317
- }
1318
- if (!persisted) {
1319
- const lockOptions = this.options.lockOptions();
1320
- const maxAttempts = lockOptions.retryMaxAttempts ?? REBUILD_MAX_ATTEMPTS;
1321
- const baseMs = lockOptions.retryBaseMs ?? REBUILD_RETRY_BASE_MS;
1322
- let rebuilt = false;
1323
- for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
1324
- rebuilt = await this.rebuild(index);
1325
- if (rebuilt || attempt === maxAttempts - 1) break;
1326
- const wait = Math.min(baseMs * 2 ** attempt, CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS);
1327
- const delayState = Promise.withResolvers();
1328
- setTimeout(delayState.resolve, wait);
1329
- await delayState.promise;
1330
- }
1331
- if (!rebuilt) {
1332
- throw new Error("tombstone-blocked capture index rebuild lock unavailable");
1333
- }
1334
- await this.clearRebuildRequired(clearableRebuildMarkers);
1335
- }
1336
- await this.setAuthoritative(true);
1337
- this.index = index;
1338
- return index;
1339
- })().catch((err) => {
1340
- this.loadPromise = null;
1341
- throw err;
1342
- });
1343
- }
1344
- return this.loadPromise;
1345
- }
1346
- async withCaptureWriteLock(task, identity) {
1347
- return await this.captureWriteLock.withCaptureWriteLock(task, identity);
1348
- }
1349
- async withCaptureWriteLockHashes(task, identityHashes) {
1350
- return await this.captureWriteLock.withCaptureWriteLockHashes(task, identityHashes);
1351
- }
1352
- async withCaptureWriteLockAndHashes(task, identity, identityHashes) {
1353
- return await this.captureWriteLock.withCaptureWriteLockAndHashes(task, identity, identityHashes);
1354
- }
1355
- async reload() {
1356
- if (!this.refreshPromise) {
1357
- this.refreshPromise = (async () => {
1358
- this.index = null;
1359
- this.loadPromise = null;
1360
- return await this.getIndex();
1361
- })().finally(() => {
1362
- this.refreshPromise = null;
1363
- });
1364
- }
1365
- return this.refreshPromise;
1366
- }
1367
- /** Return blocked-row membership and authority from one index snapshot. */
1368
- async check(content, category, sourceConnector) {
1369
- const key = buildExplicitCaptureDedupKey(content, category, sourceConnector);
1370
- let index = await this.getIndex();
1371
- if (await this.hasRebuildRequired() || !await index.isDiskFingerprintCurrent()) {
1372
- index = await this.reload();
1373
- }
1374
- return { has: index.has(key), authoritative: this.authoritative };
1375
- }
1376
- /** Return whether a blocked row matches this explicit-capture identity. */
1377
- async has(content, category, sourceConnector) {
1378
- return (await this.check(content, category, sourceConnector)).has;
1379
- }
1380
- /** Return whether the in-memory index is safe to answer an authoritative miss. */
1381
- async isAuthoritative() {
1382
- const index = await this.getIndex();
1383
- if (await this.hasRebuildRequired() || !await index.isDiskFingerprintCurrent()) {
1384
- await this.reload();
1385
- }
1386
- return this.authoritative;
1387
- }
1388
- /** Reserve durable rebuild intent before a blocked memory file is committed. */
1389
- async prepareWrite() {
1390
- return this.markRebuildRequired();
1391
- }
1392
- /** Mark a prepared write committed once its memory file is durable. */
1393
- async commitWrite(rebuildMarker) {
1394
- await this.markRebuildCommitted(rebuildMarker);
1395
- }
1396
- /** Drop a pending marker when the protected memory write never commits. */
1397
- async discardWrite(rebuildMarker) {
1398
- await this.clearRebuildRequired([rebuildMarker]);
1399
- }
1400
- /** Add a newly persisted blocked row to the durable targeted index. */
1401
- async add(memory, rebuildMarker) {
1402
- if (!this.isBlocked(memory)) return;
1403
- let index = await this.getIndex();
1404
- if (await this.hasRebuildRequired(rebuildMarker ? [rebuildMarker] : []) || !await index.isDiskFingerprintCurrent()) {
1405
- index = await this.reload();
1406
- }
1407
- index.add(
1408
- buildExplicitCaptureDedupKey(memory.content, memory.frontmatter.category, memory.frontmatter.sourceConnector)
1409
- );
1410
- const marker = rebuildMarker ?? await this.markRebuildRequired(true);
1411
- if (rebuildMarker) await this.markRebuildCommitted(rebuildMarker);
1412
- await index.saveMergingWithDisk();
1413
- await index.flushReconcileRetry();
1414
- if (index.hasPendingChanges) {
1415
- this.markUntrusted();
1416
- return;
1417
- }
1418
- await this.clearRebuildRequired([marker]);
1419
- await this.setAuthoritative(true);
1420
- }
1421
- /** Rebuild the index after a blocked row changes or is removed. */
1422
- async rebuildIfLoaded(ownedMarker) {
1423
- const index = this.index ?? (await this.hasPersistedIndexOrMarkers() ? this.getIndex() : null);
1424
- if (!index) return;
1425
- const rebuildMarkers = await this.getRebuildMarkers();
1426
- const rebuildMarker = await this.markRebuildRequired(true);
1427
- const clearableRebuildMarkers = await this.getClearableRebuildMarkerPaths(rebuildMarkers);
1428
- const rebuilt = await this.rebuild(await index);
1429
- if (rebuilt) {
1430
- await this.clearRebuildRequired([
1431
- ...clearableRebuildMarkers,
1432
- ...ownedMarker === void 0 || clearableRebuildMarkers.includes(ownedMarker) ? [] : [ownedMarker],
1433
- rebuildMarker
1434
- ]);
1435
- }
1436
- await this.setAuthoritative(rebuilt);
1437
- }
1438
- /** Rebuild when either side of a write is blocked and its identity changed. */
1439
- async sync(before, after, rebuildMarker) {
1440
- const beforeBlocked = this.isBlocked(before);
1441
- const afterBlocked = this.isBlocked(after);
1442
- if (!beforeBlocked && !afterBlocked) return;
1443
- const beforeKey = buildExplicitCaptureDedupKey(
1444
- before.content,
1445
- before.frontmatter.category,
1446
- before.frontmatter.sourceConnector
1447
- );
1448
- const afterKey = buildExplicitCaptureDedupKey(
1449
- after.content,
1450
- after.frontmatter.category,
1451
- after.frontmatter.sourceConnector
1452
- );
1453
- if (beforeBlocked && afterBlocked && beforeKey === afterKey) {
1454
- if (rebuildMarker) {
1455
- await this.markRebuildCommitted(rebuildMarker);
1456
- await this.clearRebuildRequired([rebuildMarker]);
1457
- }
1458
- return;
1459
- }
1460
- const existingMarkers = await this.getRebuildMarkers();
1461
- const marker = rebuildMarker ?? await this.markRebuildRequired(true);
1462
- const clearableExistingMarkers = await this.getClearableRebuildMarkerPaths(existingMarkers);
1463
- if (rebuildMarker) await this.markRebuildCommitted(rebuildMarker);
1464
- const rebuilt = await this.rebuild(await this.getIndex());
1465
- if (rebuilt) {
1466
- await this.clearRebuildRequired([...clearableExistingMarkers, marker]);
1467
- }
1468
- await this.setAuthoritative(rebuilt);
1469
- }
1470
- markUntrusted() {
1471
- this.authoritative = false;
1472
- }
1473
- async failOpen(operation, action) {
1474
- try {
1475
- await action();
1476
- } catch (err) {
1477
- this.markUntrusted();
1478
- log.warn(`storage.${operation} completed but failed to update tombstone-blocked capture index: ${err}`);
1479
- }
1480
- }
1481
- async addWrittenMemory(pathname, frontmatter, content, rebuildMarker) {
1482
- await this.failOpen("writeMemory", () => this.add({ path: pathname, frontmatter, content }, rebuildMarker));
1483
- }
1484
- async rebuildAfterInvalidation(ownedMarker) {
1485
- await this.failOpen("invalidateMemory", () => this.rebuildIfLoaded(ownedMarker));
1486
- }
1487
- /**
1488
- * Drop an already-committed write marker without re-reading the corpus. The
1489
- * offline-sync mutation path commits its marker around the file write, then
1490
- * must clear it even when the write cannot have changed the blocked key set
1491
- * (neither side tombstone-blocked) — otherwise the marker lingers until some
1492
- * later rebuild clears it. Mirrors the tail of `add`/`sync`.
1493
- */
1494
- async clearCommittedWriteMarker(rebuildMarker) {
1495
- await this.clearRebuildRequired([rebuildMarker]);
1496
- await this.setAuthoritative(true);
1497
- }
1498
- async syncUpdatedMemory(before, frontmatter, content, rebuildMarker) {
1499
- await this.failOpen("updateMemory", () => this.sync(before, { ...before, frontmatter, content }, rebuildMarker));
1500
- }
1501
- async syncUpdatedFrontmatter(before, frontmatter, rebuildMarker) {
1502
- await this.failOpen("writeMemoryFrontmatter", () => this.sync(before, { ...before, frontmatter }, rebuildMarker));
1503
- }
1504
- shouldRebuildAfterInvalidationForPath(filePath) {
1505
- const relative = path3.relative(path3.resolve(this.options.memoryDir), path3.resolve(filePath));
1506
- const category = relative.split(path3.sep)[0];
1507
- return category === "cold" || RECALL_FALLBACK_DIRS.includes(category);
1508
- }
1509
- };
1510
-
1511
- // src/storage/tombstone-blocked-capture-sync.ts
1512
- var OFFLINE_SYNC_BUFFER_LIMIT_BYTES = 1048576;
1513
- var OFFLINE_SYNC_STAGE_READ_BYTES = 64 * 1024;
1514
- var OFFLINE_SYNC_FRONTMATTER_DELIMITER = Buffer.from("\n---\n", "utf8");
1515
- var CASED_CHARACTER = /\p{Cased}/u;
1516
- var CASE_IGNORABLE_CHARACTER = /\p{Case_Ignorable}/u;
1517
- function createExplicitContentNormalizer(sink) {
1518
- let started = false;
1519
- let pendingSpace = false;
1520
- let output = "";
1521
- let precededByCased = false;
1522
- let pendingSigma = false;
1523
- const flush = () => {
1524
- if (output.length > 0) {
1525
- sink.write(output);
1526
- output = "";
1527
- }
1528
- };
1529
- const emit = (text) => {
1530
- for (const character of text) {
1531
- if (/\s/u.test(character)) {
1532
- if (started) pendingSpace = true;
1533
- continue;
1534
- }
1535
- if (pendingSpace) {
1536
- output += " ";
1537
- pendingSpace = false;
1538
- }
1539
- output += character;
1540
- started = true;
1541
- if (output.length >= OFFLINE_SYNC_STAGE_READ_BYTES) flush();
1542
- }
1543
- };
1544
- const resolveSigma = (followedByCased) => {
1545
- flush();
1546
- sink.resolveConditionalSigma(!followedByCased);
1547
- pendingSigma = false;
1548
- };
1549
- const push = (text) => {
1550
- for (const character of text) {
1551
- const caseIgnorable = CASE_IGNORABLE_CHARACTER.test(character);
1552
- const cased = CASED_CHARACTER.test(character);
1553
- if (pendingSigma) {
1554
- if (caseIgnorable) {
1555
- emit(character.toLowerCase());
1556
- continue;
1557
- }
1558
- resolveSigma(cased);
1559
- }
1560
- if (character === "\u03A3" && precededByCased) {
1561
- flush();
1562
- sink.beginConditionalSigma();
1563
- pendingSigma = true;
1564
- precededByCased = true;
1565
- continue;
1566
- }
1567
- emit(character.toLowerCase());
1568
- if (!caseIgnorable) precededByCased = cased;
1569
- }
1570
- };
1571
- return {
1572
- push,
1573
- finish() {
1574
- if (pendingSigma) resolveSigma(false);
1575
- flush();
1576
- }
1577
- };
1578
- }
1579
- async function forEachStagedTextChunk(stagePath, offset, onText, key, memoryDir) {
1580
- const decoder = new TextDecoder("utf-8");
1581
- let remaining = offset;
1582
- for await (const chunk of readMaybeEncryptedFileFromChunks(stagePath, key, memoryDir)) {
1583
- const textChunk = remaining >= chunk.length ? null : chunk.subarray(remaining);
1584
- remaining = Math.max(0, remaining - chunk.length);
1585
- if (textChunk === null || textChunk.length === 0) continue;
1586
- const text = decoder.decode(textChunk, { stream: true });
1587
- if (text.length > 0) onText(text);
1588
- }
1589
- const finalText = decoder.decode();
1590
- if (finalText.length > 0) onText(finalText);
1591
- }
1592
- async function findStagedBodyOffset(stagePath, key, memoryDir) {
1593
- let scannedBytes = 0;
1594
- let tail = Buffer.alloc(0);
1595
- for await (const chunk of readMaybeEncryptedFileFromChunks(stagePath, key, memoryDir)) {
1596
- const window = tail.length > 0 ? Buffer.concat([tail, chunk]) : chunk;
1597
- const windowStart = scannedBytes - tail.length;
1598
- for (let searchFrom = 0; ; ) {
1599
- const delimiterStart = window.indexOf(OFFLINE_SYNC_FRONTMATTER_DELIMITER, searchFrom);
1600
- if (delimiterStart < 0) break;
1601
- if (windowStart + delimiterStart >= 4) {
1602
- return windowStart + delimiterStart + OFFLINE_SYNC_FRONTMATTER_DELIMITER.length;
1603
- }
1604
- searchFrom = delimiterStart + 1;
1605
- }
1606
- scannedBytes += chunk.length;
1607
- const tailLength = Math.min(OFFLINE_SYNC_FRONTMATTER_DELIMITER.length - 1, window.length);
1608
- tail = Buffer.from(window.subarray(window.length - tailLength));
1609
- }
1610
- return null;
1611
- }
1612
- async function readStagedPrefix(stagePath, byteLimit, key, memoryDir) {
1613
- const chunks = [];
1614
- let bytesRead = 0;
1615
- for await (const chunk of readMaybeEncryptedFileFromChunks(stagePath, key, memoryDir)) {
1616
- const length = Math.min(chunk.length, byteLimit - bytesRead);
1617
- if (length > 0) chunks.push(chunk.subarray(0, length));
1618
- bytesRead += length;
1619
- if (bytesRead >= byteLimit) break;
1620
- }
1621
- return Buffer.concat(chunks, bytesRead);
1622
- }
1623
- function parseTombstoneBlockedOfflineSyncMemory(filePath, content, memoryDir, parseFrontmatter, normalizeFrontmatterForPath, toMemoryPathRel) {
1624
- const parsed = parseFrontmatter(content.toString("utf8"));
1625
- if (!parsed) return null;
1626
- return {
1627
- path: filePath,
1628
- frontmatter: normalizeFrontmatterForPath(parsed.frontmatter, toMemoryPathRel(memoryDir, filePath), parsed.content),
1629
- content: parsed.content
1630
- };
1631
- }
1632
- var TombstoneBlockedCaptureIndexHost = class {
1633
- tombstoneBlockedCaptureIndex = null;
1634
- /** #2813 (P1 C, #2807): reserve the target's next durable CAS revision
1635
- * token as a two-phase receipt transaction — a PENDING write-ahead
1636
- * marker under the mutation's path lock, published COMMITTED only after
1637
- * the durable file write lands. Receipt identity lives in the per-target
1638
- * sidecar, never in public `frontmatter.updated`. The default host keeps
1639
- * no receipt identity; StorageManager records it. */
1640
- async beginDurableMemoryRevision(_pathname, _expectedContent) {
1641
- return void 0;
1642
- }
1643
- /** #2837 (shard cleanup): after a managed memory file is durably deleted,
1644
- * sweep its per-target CAS receipt shard so orphan shards cannot grow
1645
- * forever. The sweep runs under the shard's own lock and is best-effort —
1646
- * the memory file is already gone, so a sweep failure logs and never
1647
- * fails the delete. The sweeper is a second CasRevisionHost over the same
1648
- * memory dir; the host is stateless and every mutation serializes through
1649
- * the sidecar's file locks, so it shares the same shard files
1650
- * StorageManager's own host operates on. */
1651
- deletedMemoryShardSweeper = null;
1652
- async deleteManagedStorageFile(filePath, deletionMtimeMs) {
1653
- const deleted = await this.deleteManagedStorageFileDurable(filePath, deletionMtimeMs);
1654
- if (!deleted) return false;
1655
- if (this.deletedMemoryShardSweeper === null) {
1656
- this.deletedMemoryShardSweeper = new CasRevisionHost(this.tombstoneBlockedCaptureIndexOptions().memoryDir);
1657
- }
1658
- await this.deletedMemoryShardSweeper.sweepShardAfterDeletion(filePath);
1659
- return true;
1660
- }
1661
- async writeStorageSecureFileChunks(filePath, chunks) {
1662
- const options = this.tombstoneBlockedCaptureIndexOptions();
1663
- await writeMaybeEncryptedFileFromChunks(
1664
- filePath,
1665
- chunks,
1666
- options.secureStoreWriteKeyProvider(),
1667
- {},
1668
- options.memoryDir
1669
- );
1670
- this.notifyCatalogWriteForPath(filePath);
1671
- }
1672
- getTombstoneBlockedCaptureIndex() {
1673
- if (this.tombstoneBlockedCaptureIndex === null) {
1674
- this.tombstoneBlockedCaptureIndex = new TombstoneBlockedCaptureIndex(this.tombstoneBlockedCaptureIndexOptions());
1675
- }
1676
- return this.tombstoneBlockedCaptureIndex;
1677
- }
1678
- async withTombstoneBlockedCaptureWriteLock(task, identity) {
1679
- return await this.getTombstoneBlockedCaptureIndex().withCaptureWriteLock(task, identity);
1680
- }
1681
- /** Read selected memories while their mutation locks remain held. */
1682
- async readMemorySnapshotsIfUnchanged(expected) {
1683
- return await this.withMemorySnapshotsIfUnchanged(expected, async (memories) => memories);
1684
- }
1685
- async withMemorySnapshotsIfUnchanged(expected, task) {
1686
- if (expected.length === 0) return await task([]);
1687
- const pathnames = [...new Set(expected.map((memory) => memory.path))];
1688
- if (pathnames.length !== expected.length) return null;
1689
- return await this.withTombstoneBlockedCaptureWriteLock(async () => {
1690
- const current = await Promise.all(pathnames.map((pathname) => this.readMemoryByPath(pathname)));
1691
- for (let index = 0; index < expected.length; index += 1) {
1692
- const actual = current[index];
1693
- const snapshot = expected[index];
1694
- if (!actual || !snapshot || actual.frontmatter.id !== snapshot.frontmatter.id || invalidationCommitFingerprint(actual) !== invalidationCommitFingerprint(snapshot)) {
1695
- return null;
1696
- }
1697
- }
1698
- return await task(current);
1699
- }, pathnames.map(buildCapturePathLockIdentity));
1700
- }
1701
- async withTombstoneBlockedCaptureWriteLockAndHashes(task, identity, identityHashes) {
1702
- return await this.getTombstoneBlockedCaptureIndex().withCaptureWriteLockAndHashes(task, identity, identityHashes);
1703
- }
1704
- async writeTombstoneBlockedMutation(blocked, pathname, fileContent, identity, updateIndex, beforeIndexUpdate, coordinate = false, shouldMintRevision) {
1705
- const host = {
1706
- readCurrent: () => this.readMemoryByPath(pathname),
1707
- isBlocked: (memory) => this.isTombstoneBlockedMemory(memory),
1708
- isQueuedReview: (memory) => isQueuedReviewMemory(memory),
1709
- memoryIdentity: (memory) => this.offlineSyncMemoryIdentity(memory),
1710
- prepareWrite: () => this.getTombstoneBlockedCaptureIndex().prepareWrite(),
1711
- commitWrite: (marker) => this.getTombstoneBlockedCaptureIndex().commitWrite(marker),
1712
- discardWrite: (marker) => this.getTombstoneBlockedCaptureIndex().discardWrite(marker),
1713
- markUntrusted: () => this.getTombstoneBlockedCaptureIndex().markUntrusted(),
1714
- writeStorageSecureFile: (target, content) => this.writeStorageSecureFile(target, content),
1715
- withCaptureWriteLock: (task, lockIdentity) => this.withTombstoneBlockedCaptureWriteLock(task, lockIdentity),
1716
- logWarning: (message) => log.warn(message),
1717
- beginDurableMemoryRevision: (targetPath, expectedContent) => this.beginDurableMemoryRevision(targetPath, expectedContent)
1718
- };
1719
- return await runTombstoneBlockedMutation(host, {
1720
- blocked,
1721
- pathname,
1722
- fileContent,
1723
- identity,
1724
- updateIndex,
1725
- beforeIndexUpdate,
1726
- coordinate,
1727
- shouldMintRevision
1728
- });
1729
- }
1730
- async writeTombstoneBlockedMemory(pathname, fileContent, frontmatter, content, beforeIndexUpdate) {
1731
- await this.writeTombstoneBlockedMutation(
1732
- tombstoneBlocked(frontmatter),
1733
- pathname,
1734
- fileContent,
1735
- [
1736
- buildCapturePathLockIdentity(pathname),
1737
- buildExplicitCaptureDedupKey(content, frontmatter.category, frontmatter.sourceConnector)
1738
- ],
1739
- (rebuildMarker) => this.getTombstoneBlockedCaptureIndex().addWrittenMemory(pathname, frontmatter, content, rebuildMarker),
1740
- beforeIndexUpdate
1741
- );
1742
- }
1743
- async withTombstoneBlockedMemoryPathLock(pathname, task, additionalPathnames = []) {
1744
- let lockIdentities = [pathname, ...additionalPathnames].map(buildCapturePathLockIdentity);
1745
- for (; ; ) {
1746
- let retryIdentity;
1747
- let result;
1748
- await this.withTombstoneBlockedCaptureWriteLock(async () => {
1749
- const current = await this.readMemoryByPath(pathname);
1750
- if (current !== null) {
1751
- const currentIdentity = this.offlineSyncMemoryIdentity(current);
1752
- if (!lockIdentities.includes(currentIdentity)) {
1753
- retryIdentity = currentIdentity;
1754
- return;
1755
- }
1756
- }
1757
- result = await task(current);
1758
- }, lockIdentities);
1759
- if (retryIdentity !== void 0) {
1760
- lockIdentities = [.../* @__PURE__ */ new Set([...lockIdentities, retryIdentity])];
1761
- continue;
1762
- }
1763
- return result;
1764
- }
1765
- }
1766
- async writeTombstoneBlockedChunk(pathname, fileContent, frontmatter, content, findDuplicate, afterWrite) {
1767
- const incomingIdentity = buildExplicitCaptureDedupKey(content, frontmatter.category, frontmatter.sourceConnector);
1768
- let lockIdentities = [buildCapturePathLockIdentity(pathname), incomingIdentity];
1769
- for (; ; ) {
1770
- let retryIdentity;
1771
- let result = frontmatter.id;
1772
- await this.withTombstoneBlockedCaptureWriteLock(async () => {
1773
- const current = await this.readMemoryByPath(pathname);
1774
- if (current !== null) {
1775
- const currentIdentity = this.offlineSyncMemoryIdentity(current);
1776
- if (!lockIdentities.includes(currentIdentity)) {
1777
- retryIdentity = currentIdentity;
1778
- return;
1779
- }
1780
- }
1781
- if (tombstoneBlocked(frontmatter)) {
1782
- const duplicate = await findDuplicate();
1783
- if (duplicate) {
1784
- result = duplicate.frontmatter.id;
1785
- return;
1786
- }
1787
- }
1788
- await this.writeTombstoneBlockedMemory(pathname, fileContent, frontmatter, content);
1789
- await afterWrite(current);
1790
- }, lockIdentities);
1791
- if (retryIdentity === void 0) return result;
1792
- lockIdentities = [.../* @__PURE__ */ new Set([...lockIdentities, retryIdentity])];
1793
- }
1794
- }
1795
- async writeTombstoneBlockedUpdate(before, fileContent, frontmatter, content, beforeIndexUpdate) {
1796
- return await this.writeTombstoneBlockedMutation(
1797
- tombstoneBlocked(before.frontmatter) || tombstoneBlocked(frontmatter),
1798
- before.path,
1799
- fileContent,
1800
- [
1801
- buildCapturePathLockIdentity(before.path),
1802
- this.offlineSyncMemoryIdentity(before),
1803
- buildExplicitCaptureDedupKey(content, frontmatter.category, frontmatter.sourceConnector)
1804
- ],
1805
- (rebuildMarker, current) => this.getTombstoneBlockedCaptureIndex().syncUpdatedMemory(
1806
- current ?? before,
1807
- frontmatter,
1808
- content,
1809
- rebuildMarker
1810
- ),
1811
- beforeIndexUpdate,
1812
- true,
1813
- () => true
1814
- );
1815
- }
1816
- async writeTombstoneBlockedFrontmatter(before, fileContent, frontmatter, beforeIndexUpdate) {
1817
- return await this.writeTombstoneBlockedMutation(
1818
- tombstoneBlocked(before.frontmatter) || tombstoneBlocked(frontmatter),
1819
- before.path,
1820
- fileContent,
1821
- [
1822
- buildCapturePathLockIdentity(before.path),
1823
- this.offlineSyncMemoryIdentity(before),
1824
- buildExplicitCaptureDedupKey(before.content, frontmatter.category, frontmatter.sourceConnector)
1825
- ],
1826
- (rebuildMarker, current) => this.getTombstoneBlockedCaptureIndex().syncUpdatedFrontmatter(current ?? before, frontmatter, rebuildMarker),
1827
- beforeIndexUpdate,
1828
- true,
1829
- (current) => isSemanticFrontmatterChange((current ?? before).frontmatter, frontmatter)
1830
- );
1831
- }
1832
- isTombstoneBlockedMemory(memory) {
1833
- return memory !== null && memory.frontmatter.status === "pending_review" && Boolean(memory.frontmatter.blockedBy);
1834
- }
1835
- offlineSyncMemoryIdentity(memory) {
1836
- return buildExplicitCaptureDedupKey(
1837
- memory.content,
1838
- memory.frontmatter.category,
1839
- memory.frontmatter.sourceConnector
1840
- );
1841
- }
1842
- async isDuplicateTombstoneBlockedOfflineSync(target, incoming, streamedIdentity) {
1843
- const before = await this.readMemoryByPath(target);
1844
- const duplicate = streamedIdentity ? true : await this.getTombstoneBlockedCaptureIndex().has(
1845
- incoming.content,
1846
- incoming.frontmatter.category,
1847
- incoming.frontmatter.sourceConnector
1848
- );
1849
- if (!duplicate) return false;
1850
- const incomingIdentity = streamedIdentity?.identityHash ?? this.offlineSyncMemoryIdentity(incoming);
1851
- const options = this.tombstoneBlockedCaptureIndexOptions();
1852
- const existing = [...await options.readAllMemories(), ...await options.readAllColdMemories()];
1853
- const exactMatch = existing.some(
1854
- (memory) => this.isTombstoneBlockedMemory(memory) && (streamedIdentity ? captureIdentityHash(memory) : this.offlineSyncMemoryIdentity(memory)) === incomingIdentity
1855
- );
1856
- if (!exactMatch) return false;
1857
- if (before === null) return true;
1858
- if (!this.isTombstoneBlockedMemory(before)) return false;
1859
- const beforeIdentity = streamedIdentity ? captureIdentityHash(before) : this.offlineSyncMemoryIdentity(before);
1860
- if (beforeIdentity !== incomingIdentity) return false;
1861
- return streamedIdentity ? isDeepStrictEqual(before.frontmatter, incoming.frontmatter) : before.content === incoming.content && isDeepStrictEqual(before.frontmatter, incoming.frontmatter);
1862
- }
1863
- async invalidateAfterOfflineSyncMutation(filePath, ownedMarker, archiveChanged = true, blockedKeySetMayHaveChanged = true) {
1864
- this.invalidateAllMemoriesCache();
1865
- this.invalidateKnowledgeIndexCache();
1866
- this.markFactHashIndexNotAuthoritative();
1867
- if (filePath.includes(`${path4.sep}cold${path4.sep}`)) {
1868
- this.invalidateColdMemoriesCache();
1869
- }
1870
- if (blockedKeySetMayHaveChanged) {
1871
- await this.rebuildTombstoneBlockedCaptureAfterInvalidationForPath(filePath, ownedMarker);
1872
- } else if (ownedMarker !== void 0) {
1873
- try {
1874
- await this.getTombstoneBlockedCaptureIndex().clearCommittedWriteMarker(ownedMarker);
1875
- } catch (err) {
1876
- this.getTombstoneBlockedCaptureIndex().markUntrusted();
1877
- log.warn(`storage.offlineSyncFile committed write succeeded; marker cleanup failed: ${err}`);
1878
- }
1879
- }
1880
- if (filePath.includes(`${path4.sep}artifacts${path4.sep}`)) {
1881
- this.bumpArtifactWriteVersion();
1882
- }
1883
- const memoryDir = this.tombstoneBlockedCaptureIndexOptions().memoryDir;
1884
- bumpArchiveMutationForPath(memoryDir, archiveChanged, filePath);
1885
- this.bumpMemoryStatusVersion();
1886
- }
1887
- async runTombstoneBlockedArchive(memory, task) {
1888
- let archivedPath = null;
1889
- const archived = await this.runTombstoneBlockedInvalidation(memory, async (current, rebuildMarker, markDurable) => {
1890
- let archiveDurable = false;
1891
- const markArchiveDurable = () => {
1892
- archiveDurable = true;
1893
- markDurable();
1894
- };
1895
- archivedPath = await task(current, markArchiveDurable);
1896
- if (rebuildMarker !== void 0 && (archivedPath !== null || archiveDurable)) {
1897
- await this.rebuildTombstoneBlockedCaptureAfterInvalidation(rebuildMarker);
1898
- } else if (rebuildMarker !== void 0) {
1899
- const index = this.getTombstoneBlockedCaptureIndex();
1900
- try {
1901
- await index.discardWrite(rebuildMarker);
1902
- } catch {
1903
- index.markUntrusted();
1904
- }
1905
- }
1906
- return archivedPath !== null;
1907
- });
1908
- return archived ? archivedPath : null;
1909
- }
1910
- async runTombstoneBlockedInvalidation(memory, task, propagateErrors = false, coordinate = false) {
1911
- let lockIdentities = [
1912
- buildCapturePathLockIdentity(memory.path),
1913
- ...coordinate || this.isTombstoneBlockedMemory(memory) || isQueuedReviewMemory(memory) ? [this.offlineSyncMemoryIdentity(memory)] : []
1914
- ];
1915
- for (; ; ) {
1916
- const attempt = async () => {
1917
- const options = this.tombstoneBlockedCaptureIndexOptions();
1918
- const current = await this.readMemoryByPath(memory.path) ?? (await options.readAllMemories()).find((candidate) => candidate.frontmatter.id === memory.frontmatter.id) ?? (await options.readAllColdMemories()).find((candidate) => candidate.frontmatter.id === memory.frontmatter.id);
1919
- if (!current) return { result: false };
1920
- const blocked = this.isTombstoneBlockedMemory(current);
1921
- const queuedReview = isQueuedReviewMemory(current);
1922
- if (blocked || queuedReview) {
1923
- const currentIdentity = this.offlineSyncMemoryIdentity(current);
1924
- if (!lockIdentities.includes(currentIdentity)) return { retryIdentity: currentIdentity };
1925
- }
1926
- const blockedIndex = blocked ? this.getTombstoneBlockedCaptureIndex() : null;
1927
- let rebuildMarker;
1928
- let durable = false;
1929
- try {
1930
- if (blockedIndex) rebuildMarker = await blockedIndex.prepareWrite();
1931
- const result2 = await task(current, rebuildMarker, () => {
1932
- durable = true;
1933
- });
1934
- if (!result2 && rebuildMarker && !durable && blockedIndex) {
1935
- try {
1936
- await blockedIndex.discardWrite(rebuildMarker);
1937
- } catch {
1938
- blockedIndex.markUntrusted();
1939
- }
1940
- }
1941
- return { result: result2 };
1942
- } catch (error) {
1943
- if (rebuildMarker && !durable && blockedIndex) {
1944
- try {
1945
- await blockedIndex.discardWrite(rebuildMarker);
1946
- } catch {
1947
- blockedIndex.markUntrusted();
1948
- }
1949
- }
1950
- if (propagateErrors) throw error;
1951
- return { result: false };
1952
- }
1953
- };
1954
- const result = await this.withTombstoneBlockedCaptureWriteLock(attempt, lockIdentities);
1955
- if (result.retryIdentity !== void 0) {
1956
- lockIdentities = [.../* @__PURE__ */ new Set([...lockIdentities, result.retryIdentity])];
1957
- continue;
1958
- }
1959
- return result.result;
1960
- }
1961
- }
1962
- async deleteMemoryForMaintenance(memory, shouldDelete = () => true) {
1963
- let deleted = null;
1964
- const removed = await this.runTombstoneBlockedInvalidation(
1965
- memory,
1966
- async (current, rebuildMarker, markDurable) => {
1967
- if (!shouldDelete(current)) return false;
1968
- if (!await this.deleteManagedStorageFile(current.path)) return false;
1969
- markDurable();
1970
- deleted = current;
1971
- const memoryDir = this.tombstoneBlockedCaptureIndexOptions().memoryDir;
1972
- bumpArchiveMutationForPath(memoryDir, true, current.path);
1973
- markProjectedMemoryPathInvalid(memoryDir, current.frontmatter.id);
1974
- this.invalidateAllMemoriesCache();
1975
- if (current.path.includes(`${path4.sep}cold${path4.sep}`)) {
1976
- this.invalidateColdMemoriesCache();
1977
- }
1978
- await this.rebuildTombstoneBlockedCaptureAfterInvalidation(rebuildMarker);
1979
- this.bumpMemoryCorpusVersion();
1980
- this.bumpMemoryStatusVersion();
1981
- return true;
1982
- },
1983
- true,
1984
- true
1985
- );
1986
- return removed ? deleted : null;
1987
- }
1988
- async runTombstoneBlockedOfflineSyncMutation(target, after, write, coordinate = false, streamedIdentity) {
1989
- for (; ; ) {
1990
- const before = await this.readMemoryByPath(target);
1991
- const targetIdentity = before === null ? void 0 : this.offlineSyncMemoryIdentity(before);
1992
- const afterIdentity = after === null || streamedIdentity ? void 0 : this.offlineSyncMemoryIdentity(after);
1993
- const mustCoordinate = coordinate || targetIdentity !== void 0 || afterIdentity !== void 0 || streamedIdentity !== void 0;
1994
- const identities = [
1995
- ...mustCoordinate ? [buildCapturePathLockIdentity(target)] : [],
1996
- ...targetIdentity === void 0 ? [] : [targetIdentity],
1997
- ...afterIdentity === void 0 ? [] : [afterIdentity]
1998
- ];
1999
- let retryIdentity;
2000
- const mutate = async () => {
2001
- const current = await this.readMemoryByPath(target);
2002
- if (current !== null) {
2003
- const currentIdentity = this.offlineSyncMemoryIdentity(current);
2004
- if (currentIdentity !== targetIdentity) {
2005
- retryIdentity = currentIdentity;
2006
- return;
2007
- }
2008
- }
2009
- if (this.isTombstoneBlockedMemory(after) && await this.isDuplicateTombstoneBlockedOfflineSync(target, after, streamedIdentity)) {
2010
- return;
2011
- }
2012
- const affectsBlockedIndex = coordinate || this.isTombstoneBlockedMemory(current) || this.isTombstoneBlockedMemory(after);
2013
- const marker = affectsBlockedIndex ? await this.getTombstoneBlockedCaptureIndex().prepareWrite() : void 0;
2014
- let durable = false;
2015
- try {
2016
- const changed = await write();
2017
- durable = true;
2018
- if (marker) {
2019
- try {
2020
- await this.getTombstoneBlockedCaptureIndex().commitWrite(marker);
2021
- } catch (err) {
2022
- this.getTombstoneBlockedCaptureIndex().markUntrusted();
2023
- log.warn(`storage.offlineSyncFile committed write marker failed: ${err}`);
2024
- }
2025
- }
2026
- await this.invalidateAfterOfflineSyncMutation(
2027
- target,
2028
- marker,
2029
- changed !== false,
2030
- this.isTombstoneBlockedMemory(current) || this.isTombstoneBlockedMemory(after)
2031
- );
2032
- } catch (err) {
2033
- if (marker && !durable) {
2034
- try {
2035
- await this.getTombstoneBlockedCaptureIndex().discardWrite(marker);
2036
- } catch (cleanupError) {
2037
- this.getTombstoneBlockedCaptureIndex().markUntrusted();
2038
- log.warn(`storage.offlineSyncFile failed to clear write marker: ${cleanupError}`);
2039
- }
2040
- }
2041
- throw err;
2042
- }
2043
- };
2044
- if (!mustCoordinate) {
2045
- await mutate();
2046
- } else if (streamedIdentity) {
2047
- await this.withTombstoneBlockedCaptureWriteLockAndHashes(mutate, identities, [streamedIdentity.identityHash]);
2048
- } else {
2049
- await this.withTombstoneBlockedCaptureWriteLock(mutate, identities);
2050
- }
2051
- if (retryIdentity !== void 0) continue;
2052
- return;
2053
- }
2054
- }
2055
- async writeTombstoneBlockedOfflineSyncFile(target, content) {
2056
- const options = this.tombstoneBlockedCaptureIndexOptions();
2057
- const after = options.parseMemory?.(target, content) ?? null;
2058
- await withRawEntityPageMutation(path4.dirname(options.stateDir), target, async () => {
2059
- await this.runTombstoneBlockedOfflineSyncMutation(
2060
- target,
2061
- after,
2062
- () => this.writeManagedStorageFile(target, () => this.writeStorageSecureFile(target, content))
2063
- );
2064
- });
2065
- }
2066
- async prepareTombstoneBlockedOfflineSyncChunks(target, chunks) {
2067
- const parseMemory = this.tombstoneBlockedCaptureIndexOptions().parseMemory;
2068
- if (!parseMemory) return { after: null, chunks };
2069
- const iterator = chunks[Symbol.asyncIterator]();
2070
- const buffered = [];
2071
- let bufferedBytes = 0;
2072
- let firstOverflow;
2073
- for (; ; ) {
2074
- const next = await iterator.next();
2075
- if (next.done) break;
2076
- if (bufferedBytes + next.value.length > OFFLINE_SYNC_BUFFER_LIMIT_BYTES) {
2077
- firstOverflow = next.value;
2078
- break;
2079
- }
2080
- bufferedBytes += next.value.length;
2081
- buffered.push(next.value);
2082
- }
2083
- const overflow = firstOverflow;
2084
- if (overflow === void 0) {
2085
- let after = null;
2086
- try {
2087
- after = parseMemory(target, Buffer.concat(buffered));
2088
- } catch {
2089
- after = null;
2090
- }
2091
- const replay = (async function* () {
2092
- for (const chunk of buffered) yield chunk;
2093
- })();
2094
- return { after, chunks: replay };
2095
- }
2096
- const options = this.tombstoneBlockedCaptureIndexOptions();
2097
- const stagePath = path4.join(
2098
- options.stateDir,
2099
- "tombstone-blocked-capture",
2100
- "offline-sync-staging",
2101
- `${randomUUID4()}.stage`
2102
- );
2103
- const stagedChunks = (async function* () {
2104
- for (const chunk of buffered) yield chunk;
2105
- yield overflow;
2106
- for (; ; ) {
2107
- const next = await iterator.next();
2108
- if (next.done) break;
2109
- yield next.value;
2110
- }
2111
- })();
2112
- try {
2113
- await writeMaybeEncryptedFileFromChunks(
2114
- stagePath,
2115
- stagedChunks,
2116
- options.secureStoreWriteKeyProvider(),
2117
- { atomic: false },
2118
- options.memoryDir
2119
- );
2120
- return await this.describeStagedOfflineSyncChunks(target, stagePath, parseMemory);
2121
- } catch (err) {
2122
- await unlink4(stagePath).catch(() => {
2123
- });
2124
- throw err;
2125
- }
2126
- }
2127
- async describeStagedOfflineSyncChunks(target, stagePath, parseMemory) {
2128
- const options = this.tombstoneBlockedCaptureIndexOptions();
2129
- const key = options.secureStoreKeyProvider();
2130
- const bodyOffset = await findStagedBodyOffset(stagePath, key, options.memoryDir);
2131
- if (bodyOffset === null) {
2132
- return { after: null, chunks: this.readStagedOfflineSyncChunks(stagePath), stagePath };
2133
- }
2134
- const prefix = await readStagedPrefix(stagePath, bodyOffset, key, options.memoryDir);
2135
- let after = null;
2136
- try {
2137
- after = parseMemory(target, prefix.subarray(0, bodyOffset));
2138
- } catch {
2139
- after = null;
2140
- }
2141
- if (after === null) {
2142
- return { after: null, chunks: this.readStagedOfflineSyncChunks(stagePath), stagePath };
2143
- }
2144
- const streamedIdentity = await this.computeStagedOfflineSyncIdentity(stagePath, bodyOffset, after);
2145
- return {
2146
- after,
2147
- chunks: this.readStagedOfflineSyncChunks(stagePath),
2148
- stagePath,
2149
- streamedIdentity
2150
- };
2151
- }
2152
- async computeStagedOfflineSyncIdentity(stagePath, bodyOffset, after) {
2153
- let normalizedLength = 0;
2154
- const countNormalizer = createExplicitContentNormalizer({
2155
- write: (chunk) => {
2156
- normalizedLength += chunk.length;
2157
- },
2158
- beginConditionalSigma: () => {
2159
- normalizedLength += 1;
2160
- },
2161
- resolveConditionalSigma: () => {
2162
- }
2163
- });
2164
- const options = this.tombstoneBlockedCaptureIndexOptions();
2165
- const key = options.secureStoreKeyProvider();
2166
- await forEachStagedTextChunk(stagePath, bodyOffset, (text) => countNormalizer.push(text), key, options.memoryDir);
2167
- countNormalizer.finish();
2168
- const category = after.frontmatter.category;
2169
- const connector = after.frontmatter.sourceConnector?.trim() ?? "";
2170
- const encode = (label, value) => `${label} ${value.length} ${value}`;
2171
- const identityPrefix = [
2172
- encode("category", category),
2173
- encode("connector", connector),
2174
- `content ${normalizedLength} `
2175
- ].join(" ");
2176
- let identityHash = createHash3("sha256");
2177
- let normalSigmaIdentityHash;
2178
- identityHash.update(identityPrefix);
2179
- const identityNormalizer = createExplicitContentNormalizer({
2180
- write: (chunk) => {
2181
- identityHash.update(chunk);
2182
- normalSigmaIdentityHash?.update(chunk);
2183
- },
2184
- beginConditionalSigma: () => {
2185
- normalSigmaIdentityHash = identityHash.copy().update("\u03C3");
2186
- identityHash.update("\u03C2");
2187
- },
2188
- resolveConditionalSigma: (useFinalSigma) => {
2189
- if (!useFinalSigma) {
2190
- if (normalSigmaIdentityHash === void 0) throw new Error("conditional sigma hash state is missing");
2191
- identityHash = normalSigmaIdentityHash;
2192
- }
2193
- normalSigmaIdentityHash = void 0;
2194
- }
2195
- });
2196
- await forEachStagedTextChunk(
2197
- stagePath,
2198
- bodyOffset,
2199
- (text) => identityNormalizer.push(text),
2200
- key,
2201
- options.memoryDir
2202
- );
2203
- identityNormalizer.finish();
2204
- return {
2205
- identityHash: identityHash.digest("hex")
2206
- };
2207
- }
2208
- async *readStagedOfflineSyncChunks(stagePath) {
2209
- const options = this.tombstoneBlockedCaptureIndexOptions();
2210
- for await (const chunk of readMaybeEncryptedFileFromChunks(
2211
- stagePath,
2212
- options.secureStoreKeyProvider(),
2213
- options.memoryDir
2214
- )) {
2215
- yield chunk;
2216
- }
2217
- }
2218
- async writeTombstoneBlockedOfflineSyncFileChunks(target, chunks) {
2219
- const coordinate = this.getTombstoneBlockedCaptureIndex().shouldRebuildAfterInvalidationForPath(target);
2220
- const prepared = coordinate ? await this.prepareTombstoneBlockedOfflineSyncChunks(target, chunks) : { after: null, chunks };
2221
- const streamedIdentity = prepared.streamedIdentity;
2222
- const baseDir = path4.dirname(this.tombstoneBlockedCaptureIndexOptions().stateDir);
2223
- try {
2224
- await withRawEntityPageMutation(baseDir, target, async () => {
2225
- await this.runTombstoneBlockedOfflineSyncMutation(
2226
- target,
2227
- prepared.after,
2228
- () => this.writeManagedStorageFile(target, () => this.writeStorageSecureFileChunks(target, prepared.chunks)),
2229
- coordinate,
2230
- streamedIdentity
2231
- );
2232
- });
2233
- } finally {
2234
- if (prepared.stagePath !== void 0) await unlink4(prepared.stagePath).catch(() => {
2235
- });
2236
- }
2237
- }
2238
- async writeOfflineSyncFile(filePath, content) {
2239
- const target = this.assertManagedStoragePath(filePath, "storage.writeOfflineSyncFile");
2240
- await this.writeTombstoneBlockedOfflineSyncFile(target, content);
2241
- await this.requestSyncReconcileIfMigrationPath(target);
2242
- }
2243
- async writeOfflineSyncFileChunks(filePath, chunks) {
2244
- const target = this.assertManagedStoragePath(filePath, "storage.writeOfflineSyncFileChunks");
2245
- await this.writeTombstoneBlockedOfflineSyncFileChunks(target, chunks);
2246
- await this.requestSyncReconcileIfMigrationPath(target);
2247
- }
2248
- /**
2249
- * Opaque replicated bytes can restore legacy memory references or entity
2250
- * relationship targets. Other sync traffic must not request reconciliation.
2251
- */
2252
- async requestSyncReconcileIfMigrationPath(target) {
2253
- const stateDir = this.tombstoneBlockedCaptureIndexOptions().stateDir;
2254
- if (!pathMayCarryEntityRefs(path4.dirname(stateDir), target)) return;
2255
- await requestEntityCanonicalIdReconcile(stateDir);
2256
- }
2257
- async deleteOfflineSyncFile(filePath, deletionMtimeMs) {
2258
- const target = this.assertManagedStoragePath(filePath, "storage.deleteOfflineSyncFile");
2259
- await this.deleteTombstoneBlockedOfflineSyncFile(target, deletionMtimeMs);
2260
- await this.requestSyncReconcileIfMigrationPath(target);
2261
- }
2262
- async deleteTombstoneBlockedOfflineSyncFile(target, deletionMtimeMs) {
2263
- const stateDir = this.tombstoneBlockedCaptureIndexOptions().stateDir;
2264
- await withRawEntityPageMutation(path4.dirname(stateDir), target, async () => {
2265
- await this.runTombstoneBlockedOfflineSyncMutation(target, null, async () => {
2266
- return await this.deleteManagedStorageFile(target, deletionMtimeMs);
2267
- });
2268
- });
2269
- }
2270
- async checkTombstoneBlockedExplicitCapture(...args) {
2271
- return this.getTombstoneBlockedCaptureIndex().check(...args);
2272
- }
2273
- async hasTombstoneBlockedExplicitCapture(...args) {
2274
- return this.getTombstoneBlockedCaptureIndex().has(...args);
2275
- }
2276
- async isTombstoneBlockedExplicitCaptureIndexAuthoritative() {
2277
- return this.getTombstoneBlockedCaptureIndex().isAuthoritative();
2278
- }
2279
- async rebuildTombstoneBlockedCaptureAfterInvalidationForPath(filePath, ownedMarker) {
2280
- const index = this.getTombstoneBlockedCaptureIndex();
2281
- if (index.shouldRebuildAfterInvalidationForPath(filePath)) {
2282
- await this.rebuildTombstoneBlockedCaptureAfterInvalidation(ownedMarker);
2283
- }
2284
- }
2285
- async rebuildTombstoneBlockedCaptureAfterInvalidation(ownedMarker) {
2286
- await this.getTombstoneBlockedCaptureIndex().rebuildAfterInvalidation(ownedMarker);
2287
- }
2288
- };
2289
-
2290
- export {
2291
- invalidationCommitFingerprint,
2292
- casCommittedRevisionOf,
2293
- withCasCommitReceipt,
2294
- DeletionRevisionStore,
2295
- canonicalSemanticFileText,
2296
- CasRevisionHost,
2297
- parseTombstoneBlockedOfflineSyncMemory,
2298
- TombstoneBlockedCaptureIndexHost
2299
- };
2300
- //# sourceMappingURL=chunk-FHGWKODN.js.map