@remnic/core 10.0.83 → 10.0.85

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 (413) hide show
  1. package/dist/access-admin-ops-surface.js +67 -66
  2. package/dist/access-authorization-probe.js +68 -67
  3. package/dist/access-boundary.js +67 -66
  4. package/dist/access-cli.js +148 -146
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +67 -66
  7. package/dist/access-http-briefing.js +68 -67
  8. package/dist/access-http-query.js +69 -68
  9. package/dist/access-http.js +91 -89
  10. package/dist/access-identity-continuity-surface.js +67 -66
  11. package/dist/access-lcm-surface.js +67 -66
  12. package/dist/access-mcp-frames.d.ts +29 -0
  13. package/dist/access-mcp-frames.d.ts.map +1 -0
  14. package/dist/access-mcp-frames.js +10 -0
  15. package/dist/access-mcp.d.ts +1 -1
  16. package/dist/access-mcp.d.ts.map +1 -1
  17. package/dist/access-mcp.js +82 -80
  18. package/dist/access-namespace-preflight.js +67 -66
  19. package/dist/access-observe-write-category.js +9 -9
  20. package/dist/access-observe-write-surface.d.ts.map +1 -1
  21. package/dist/access-observe-write-surface.js +67 -66
  22. package/dist/access-offline-manifest.js +3 -3
  23. package/dist/access-operations-batch.js +71 -70
  24. package/dist/access-operations.d.ts +1 -1
  25. package/dist/access-operations.js +78 -77
  26. package/dist/access-recall-concurrency.js +67 -66
  27. package/dist/access-recall-response.js +67 -66
  28. package/dist/access-recall-surface.js +67 -66
  29. package/dist/access-schema.js +4 -4
  30. package/dist/access-service.js +67 -66
  31. package/dist/access-surface-catalog.js +69 -68
  32. package/dist/access-surface-catalog.js.map +1 -1
  33. package/dist/active-recall.js +31 -31
  34. package/dist/{auto-sync-C7E6G7Y2.js → auto-sync-3AZW7MXI.js} +6 -6
  35. package/dist/briefing.js +25 -25
  36. package/dist/buffer-persistence.d.ts +8 -4
  37. package/dist/buffer-persistence.d.ts.map +1 -1
  38. package/dist/buffer-persistence.js +1 -1
  39. package/dist/buffer-turn-helpers.d.ts +5 -0
  40. package/dist/buffer-turn-helpers.d.ts.map +1 -1
  41. package/dist/buffer-turn-helpers.js +1 -1
  42. package/dist/buffer.d.ts +4 -1
  43. package/dist/buffer.d.ts.map +1 -1
  44. package/dist/buffer.js +9 -9
  45. package/dist/calibration.js +2 -2
  46. package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-GWVG7LGC.js} +2 -2
  47. package/dist/capsule-gunzip-KGBDGF7P.js +11 -0
  48. package/dist/{capsule-merge-XVTQJJ6R.js → capsule-merge-2IIXFXN5.js} +5 -11
  49. package/dist/capsule-merge-2IIXFXN5.js.map +1 -0
  50. package/dist/causal-behavior.js +2 -2
  51. package/dist/causal-chain.js +2 -2
  52. package/dist/causal-consolidation.js +32 -32
  53. package/dist/causal-retrieval.js +2 -2
  54. package/dist/causal-trajectory-graph.js +2 -2
  55. package/dist/causal-trajectory.js +1 -1
  56. package/dist/{chunk-T6T2RMWW.js → chunk-23B4HLWG.js} +5 -5
  57. package/dist/{chunk-LECSS7EE.js → chunk-2ASEDFOA.js} +2 -2
  58. package/dist/{chunk-TPX7XXY4.js → chunk-2JAT4S7N.js} +2 -2
  59. package/dist/{chunk-XRBHIR7Q.js → chunk-2URKYLMK.js} +2 -2
  60. package/dist/{chunk-IN4OFAHX.js → chunk-2XYDL2FG.js} +2 -2
  61. package/dist/{chunk-HQGATKG2.js → chunk-3KGCHTTK.js} +2 -2
  62. package/dist/{chunk-YAUHSNPB.js → chunk-3LHPTBWG.js} +2 -2
  63. package/dist/{chunk-7PMOCQOL.js → chunk-3ODZLCHC.js} +192 -63
  64. package/dist/chunk-3ODZLCHC.js.map +1 -0
  65. package/dist/{chunk-XT45WCKX.js → chunk-3VU2FDTU.js} +8 -8
  66. package/dist/{chunk-CIIHIAJ2.js → chunk-433EXTC4.js} +3 -3
  67. package/dist/{chunk-QZSIDSB2.js → chunk-44FQCC5D.js} +2 -2
  68. package/dist/{chunk-YUTT4ZTI.js → chunk-45VUHDV2.js} +2 -2
  69. package/dist/{chunk-54FDG7K7.js → chunk-4DPLCYEK.js} +2 -2
  70. package/dist/{chunk-KO3YBHOV.js → chunk-4IWEPQEV.js} +2 -2
  71. package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
  72. package/dist/{chunk-K6RTQZA4.js → chunk-6DANGCMO.js} +2 -2
  73. package/dist/{chunk-2IW5PT7U.js → chunk-6KFFRMPQ.js} +2 -2
  74. package/dist/{chunk-MSIRVXQ3.js → chunk-73YVMZ7D.js} +4 -4
  75. package/dist/{chunk-A7BM2JTH.js → chunk-AJHNXIQU.js} +2 -2
  76. package/dist/{chunk-2D7BTSDW.js → chunk-ANZZLRUX.js} +2 -2
  77. package/dist/{chunk-YOEYL6CH.js → chunk-AP3TSV6S.js} +2 -2
  78. package/dist/{chunk-GQZSGXR6.js → chunk-BATPQNRG.js} +2 -2
  79. package/dist/{chunk-D5SSBCYY.js → chunk-BLGQUZFB.js} +2 -2
  80. package/dist/{chunk-BDN3TDGV.js → chunk-BLMTOIBK.js} +2 -2
  81. package/dist/{chunk-B74NPUHT.js → chunk-BMIM6ESC.js} +43 -43
  82. package/dist/chunk-BMIM6ESC.js.map +1 -0
  83. package/dist/{chunk-R45L4TV6.js → chunk-BUKK5SWA.js} +6 -6
  84. package/dist/{chunk-TFLPDNQV.js → chunk-CCU7URRP.js} +2 -2
  85. package/dist/{chunk-2XJZLSXE.js → chunk-CMRHXPLQ.js} +20 -20
  86. package/dist/{chunk-BK4FBHZ3.js → chunk-D6THW7PA.js} +3 -3
  87. package/dist/{chunk-BFBS7OFQ.js → chunk-DC55RIEZ.js} +4 -4
  88. package/dist/{chunk-VKDAJJQP.js → chunk-DI5ET2IE.js} +5 -5
  89. package/dist/{chunk-QZBTIYKD.js → chunk-DM4RRTN7.js} +3 -3
  90. package/dist/{chunk-S57QBPTA.js → chunk-DPPLNZ3A.js} +4 -6
  91. package/dist/chunk-DPPLNZ3A.js.map +1 -0
  92. package/dist/{chunk-EBOHFZI4.js → chunk-E5QEF3JO.js} +2 -2
  93. package/dist/{chunk-DD6OIFDG.js → chunk-E7V5AK7I.js} +3 -3
  94. package/dist/{chunk-WAD32CZG.js → chunk-ELWI2R6H.js} +26 -13
  95. package/dist/chunk-ELWI2R6H.js.map +1 -0
  96. package/dist/{chunk-BV2BRHD4.js → chunk-FHGWKODN.js} +2 -2
  97. package/dist/{chunk-HPFVZPRH.js → chunk-FK4WC6VK.js} +3 -3
  98. package/dist/{chunk-VLMBSGQF.js → chunk-FRM4UTDR.js} +6 -6
  99. package/dist/{chunk-LTDLJCMQ.js → chunk-FZPDRCOB.js} +2 -2
  100. package/dist/{chunk-ZJJFUEPO.js → chunk-GBOYOL5L.js} +2 -2
  101. package/dist/{chunk-YUTG35M4.js → chunk-GJVGFKOU.js} +2 -2
  102. package/dist/{chunk-77R7BQZ2.js → chunk-GKYIRTNX.js} +33 -11
  103. package/dist/{chunk-77R7BQZ2.js.map → chunk-GKYIRTNX.js.map} +1 -1
  104. package/dist/{chunk-K36RMINQ.js → chunk-GOZXQNIM.js} +3 -3
  105. package/dist/{chunk-HWHNQVRV.js → chunk-GYNRNKMW.js} +2 -2
  106. package/dist/{chunk-LU66I56Q.js → chunk-H34INI3P.js} +29 -3
  107. package/dist/chunk-H34INI3P.js.map +1 -0
  108. package/dist/{chunk-AYTDCMEL.js → chunk-H4D5YURJ.js} +80 -94
  109. package/dist/chunk-H4D5YURJ.js.map +1 -0
  110. package/dist/{chunk-6LCWKS62.js → chunk-HVFX2N2G.js} +60 -59
  111. package/dist/chunk-HVFX2N2G.js.map +1 -0
  112. package/dist/chunk-IGLJNHOX.js +62 -0
  113. package/dist/chunk-IGLJNHOX.js.map +1 -0
  114. package/dist/{chunk-KFLD7ZZG.js → chunk-IUKYFTGW.js} +3 -3
  115. package/dist/{chunk-YK7GOPIE.js → chunk-JFH6JAGY.js} +6 -4
  116. package/dist/chunk-JFH6JAGY.js.map +1 -0
  117. package/dist/{chunk-BNQJKWWZ.js → chunk-K3FH3LAQ.js} +4 -4
  118. package/dist/{chunk-FJ2VJTGA.js → chunk-KHZVAISH.js} +2 -2
  119. package/dist/{chunk-WHQXWFH7.js → chunk-LT3CMO65.js} +2 -2
  120. package/dist/{chunk-NUSAAN75.js → chunk-MHSBOEH7.js} +2 -2
  121. package/dist/{chunk-KJCL2FZO.js → chunk-MNYQVYJ4.js} +2 -2
  122. package/dist/{chunk-RZH5RVJC.js → chunk-MTTYIXSU.js} +2 -2
  123. package/dist/{chunk-FVRTCYZ3.js → chunk-MVOJDLXS.js} +2 -2
  124. package/dist/{chunk-KESJ5XX3.js → chunk-MYCCO6AA.js} +2 -2
  125. package/dist/{chunk-66JOLMWS.js → chunk-MZCGHPIJ.js} +2 -2
  126. package/dist/{chunk-B4ZU34JQ.js → chunk-N6II66E5.js} +3 -3
  127. package/dist/{chunk-BK7AJUWB.js → chunk-NIV7RPWQ.js} +6 -6
  128. package/dist/{chunk-TLHF75OR.js → chunk-O55AGDTI.js} +10 -10
  129. package/dist/{chunk-GGJFRVRD.js → chunk-OMYRPMQC.js} +2 -2
  130. package/dist/{chunk-SVCHO337.js → chunk-ONN2E5QD.js} +32 -23
  131. package/dist/chunk-ONN2E5QD.js.map +1 -0
  132. package/dist/{chunk-XHGYZRVW.js → chunk-OSTFDWZJ.js} +4 -4
  133. package/dist/{chunk-BXLOS5AJ.js → chunk-OWHERGF2.js} +2 -2
  134. package/dist/{chunk-WC475JOG.js → chunk-P225W4RQ.js} +6 -6
  135. package/dist/{chunk-TFA6HPOA.js → chunk-QCP75JGO.js} +5 -5
  136. package/dist/{chunk-JRVMCZE3.js → chunk-QEYTRFR3.js} +2 -2
  137. package/dist/chunk-QV2VZKD5.js +31 -0
  138. package/dist/chunk-QV2VZKD5.js.map +1 -0
  139. package/dist/{chunk-IZJQNDGI.js → chunk-R6HMAIKM.js} +2 -2
  140. package/dist/{chunk-VUROWXFL.js → chunk-RNGDKFY2.js} +2 -2
  141. package/dist/{chunk-FF6N7BXC.js → chunk-S45INSV3.js} +4 -4
  142. package/dist/{chunk-2RUTN7PR.js → chunk-S7GJNA3K.js} +2 -2
  143. package/dist/{chunk-XYP5WBDO.js → chunk-SGHV6LZ7.js} +13 -13
  144. package/dist/chunk-SGHV6LZ7.js.map +1 -0
  145. package/dist/{chunk-RJWL3RHB.js → chunk-TFEHSPGQ.js} +2 -2
  146. package/dist/{chunk-T77AX6DG.js → chunk-TQVECGUQ.js} +4 -4
  147. package/dist/{chunk-OQSSQOKQ.js → chunk-TV65NR7L.js} +2 -2
  148. package/dist/{chunk-KQUSP6LB.js → chunk-UDKWFDV2.js} +1 -1
  149. package/dist/chunk-UDKWFDV2.js.map +1 -0
  150. package/dist/{chunk-HQPFACXO.js → chunk-UH3FPX3L.js} +2 -2
  151. package/dist/{chunk-4EACCIPO.js → chunk-UHPHH74F.js} +2 -2
  152. package/dist/{chunk-DQEMWVMT.js → chunk-UVYI6VIX.js} +1 -1
  153. package/dist/{chunk-6QYPL6NI.js → chunk-V45TEVDA.js} +3 -3
  154. package/dist/{chunk-GCAQ3VUJ.js → chunk-VCVEMQJK.js} +2 -2
  155. package/dist/{chunk-6L63IN3M.js → chunk-VEFGHRKV.js} +1 -1
  156. package/dist/{chunk-E33UJHY3.js → chunk-WFDWLWDK.js} +2 -2
  157. package/dist/{chunk-4KLQI3L2.js → chunk-WLNWFHX3.js} +1 -1
  158. package/dist/chunk-WLNWFHX3.js.map +1 -0
  159. package/dist/{chunk-KQLBXUUQ.js → chunk-WYG5UNDY.js} +2 -2
  160. package/dist/{chunk-MPGLM2VU.js → chunk-X7REO4CH.js} +2 -2
  161. package/dist/{chunk-MPDLLLOG.js → chunk-XO23OKMK.js} +9 -5
  162. package/dist/chunk-XO23OKMK.js.map +1 -0
  163. package/dist/{chunk-Z3DDQXM4.js → chunk-XSFQN7TP.js} +35 -36
  164. package/dist/chunk-XSFQN7TP.js.map +1 -0
  165. package/dist/{chunk-FZVROZKC.js → chunk-XVZA6VXC.js} +4 -4
  166. package/dist/{chunk-WV254W4V.js → chunk-Y3IYKNBY.js} +2 -2
  167. package/dist/{chunk-FUOAIJKQ.js → chunk-Y43X3E24.js} +2 -2
  168. package/dist/{chunk-3T3VHXUH.js → chunk-YAAKVTZA.js} +2 -2
  169. package/dist/{chunk-VPEIWMUT.js → chunk-YLTU2DC4.js} +2 -2
  170. package/dist/{chunk-65SKM4BF.js → chunk-YNBMBL4C.js} +2 -2
  171. package/dist/{chunk-PJYK7HH2.js → chunk-ZWTHVUT5.js} +2 -2
  172. package/dist/cli.js +110 -108
  173. package/dist/coding/export-okf-codegraph.js +25 -25
  174. package/dist/coding/pre-action-gate.js +2 -2
  175. package/dist/compounding/engine.js +27 -27
  176. package/dist/config.js +28 -28
  177. package/dist/connectors/codex-materialize-runner.js +25 -25
  178. package/dist/connectors/index.js +25 -25
  179. package/dist/contradiction/index.js +4 -4
  180. package/dist/convergence-refresh.js +27 -27
  181. package/dist/conversation-index/backend.js +2 -2
  182. package/dist/corpus-watermark.js +27 -27
  183. package/dist/deep-recall-http-glue.js +5 -5
  184. package/dist/entity-mention-index-cache.d.ts.map +1 -1
  185. package/dist/entity-mention-index-cache.js +2 -1
  186. package/dist/entity-retrieval.js +27 -26
  187. package/dist/event-order-recall.js +2 -2
  188. package/dist/explicit-capture.js +5 -5
  189. package/dist/external-wiki-access.js +68 -67
  190. package/dist/extraction-evidence-class.js +2 -2
  191. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  192. package/dist/extraction-quote-grounding.js +1 -1
  193. package/dist/extraction-source-grounding-rules.js +2 -2
  194. package/dist/extraction-source-grounding.js +5 -5
  195. package/dist/extraction-span-materialize.js +2 -2
  196. package/dist/extraction.js +40 -40
  197. package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
  198. package/dist/{graph-edge-decay-CXTNJ7IQ.js → graph-edge-decay-EKWXRBWF.js} +5 -5
  199. package/dist/graph-edge-decay-EKWXRBWF.js.map +1 -0
  200. package/dist/graph-jsonl.js +3 -3
  201. package/dist/graph-snapshot.js +3 -3
  202. package/dist/graph.js +2 -2
  203. package/dist/harmonic-retrieval.js +25 -25
  204. package/dist/index.js +205 -203
  205. package/dist/lcm/engine.js +2 -2
  206. package/dist/lcm/index.js +2 -2
  207. package/dist/local-llm.js +2 -2
  208. package/dist/location/index.js +28 -28
  209. package/dist/maintenance/memory-governance.js +25 -25
  210. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +25 -25
  211. package/dist/maintenance/rebuild-memory-projection-runner.js +27 -27
  212. package/dist/maintenance/rebuild-memory-projection.js +26 -26
  213. package/dist/memory-browse-operations.js +68 -67
  214. package/dist/memory-subject.js +2 -2
  215. package/dist/namespaces/migrate.js +26 -26
  216. package/dist/namespaces/search.d.ts +8 -0
  217. package/dist/namespaces/search.d.ts.map +1 -1
  218. package/dist/namespaces/search.js +10 -6
  219. package/dist/namespaces/storage.js +25 -25
  220. package/dist/operator-doctor-corpus.js +28 -28
  221. package/dist/operator-toolkit.js +39 -39
  222. package/dist/orchestration/backlog-scheduler.d.ts +3 -2
  223. package/dist/orchestration/backlog-scheduler.d.ts.map +1 -1
  224. package/dist/orchestration/backlog-scheduler.js +159 -157
  225. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  226. package/dist/orchestration/buffer-drain-runner.d.ts +6 -1
  227. package/dist/orchestration/buffer-drain-runner.d.ts.map +1 -1
  228. package/dist/orchestration/buffer-drain-runner.js +149 -147
  229. package/dist/orchestration/extraction-persist.d.ts.map +1 -1
  230. package/dist/orchestration/fact-dedup-window.d.ts +1 -4
  231. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
  232. package/dist/orchestration/maintenance.js +39 -39
  233. package/dist/orchestrator.js +148 -146
  234. package/dist/provenance.js +2 -2
  235. package/dist/recall-navigation-operations.js +68 -67
  236. package/dist/recall-navigation.js +67 -66
  237. package/dist/reconcile/manifest.js +2 -2
  238. package/dist/{resolution-BNWE5Y25.js → resolution-23ZBDM6A.js} +5 -5
  239. package/dist/resolution-23ZBDM6A.js.map +1 -0
  240. package/dist/resume-bundles.js +29 -29
  241. package/dist/salvage-envelope.js +4 -4
  242. package/dist/search/factory.js +5 -5
  243. package/dist/search/index.js +9 -9
  244. package/dist/semantic-consolidation.js +26 -26
  245. package/dist/semantic-rule-promotion.js +25 -25
  246. package/dist/semantic-rule-verifier.js +25 -25
  247. package/dist/source-agent-qualifier.js +31 -31
  248. package/dist/standup/build.js +25 -25
  249. package/dist/storage/buffer-drain-claims.d.ts +18 -6
  250. package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
  251. package/dist/storage/buffer-drain-claims.js +2 -2
  252. package/dist/storage/buffer-session-files.js +2 -2
  253. package/dist/storage/content-hash-index.d.ts +8 -0
  254. package/dist/storage/content-hash-index.d.ts.map +1 -1
  255. package/dist/storage.d.ts +10 -1
  256. package/dist/storage.d.ts.map +1 -1
  257. package/dist/storage.js +24 -24
  258. package/dist/subject-config.js +3 -3
  259. package/dist/summarizer.js +3 -3
  260. package/dist/support-passport/index.js +70 -69
  261. package/dist/transfer/autodetect.js +1 -1
  262. package/dist/transfer/backup.js +2 -2
  263. package/dist/transfer/capsule-export.js +3 -3
  264. package/dist/transfer/capsule-gunzip.d.ts +14 -0
  265. package/dist/transfer/capsule-gunzip.d.ts.map +1 -0
  266. package/dist/transfer/capsule-import.d.ts.map +1 -1
  267. package/dist/transfer/capsule-import.js +3 -2
  268. package/dist/transfer/capsule-merge.d.ts.map +1 -1
  269. package/dist/transfer/export-okf.js +25 -25
  270. package/dist/verified-recall.js +25 -25
  271. package/dist/who-knows-cli.js +26 -26
  272. package/dist/who-knows.js +25 -25
  273. package/dist/write-envelope.js +3 -3
  274. package/package.json +2 -2
  275. package/src/access-mcp-correction-tools.test.ts +45 -0
  276. package/src/access-mcp-frames.test.ts +47 -0
  277. package/src/access-mcp-frames.ts +74 -0
  278. package/src/access-mcp.test.ts +146 -0
  279. package/src/access-mcp.ts +30 -30
  280. package/src/access-observe-write-surface.ts +4 -4
  281. package/src/backlog-scheduler.test.ts +443 -3
  282. package/src/buffer-persistence.ts +15 -6
  283. package/src/buffer-turn-helpers.ts +6 -0
  284. package/src/buffer.ts +6 -6
  285. package/src/cli.ts +4 -4
  286. package/src/compounding/weekly-evidence.test.ts +237 -0
  287. package/src/entity-mention-index-cache.ts +1 -6
  288. package/src/extraction-quote-grounding.test.ts +49 -0
  289. package/src/extraction-quote-grounding.ts +50 -12
  290. package/src/fact-dedup-window.test.ts +103 -20
  291. package/src/maintenance/rebuild-memory-projection-runner.test.ts +90 -0
  292. package/src/namespaces/search.test.ts +339 -0
  293. package/src/namespaces/search.ts +54 -1
  294. package/src/orchestration/backlog-scheduler.ts +13 -12
  295. package/src/orchestration/buffer-drain-runner.ts +40 -16
  296. package/src/orchestration/extraction-persist.ts +2 -10
  297. package/src/orchestration/fact-dedup-window.ts +5 -12
  298. package/src/orchestration/full-corpus-heap-guard.test.ts +52 -0
  299. package/src/projection-rebuild-config.test.ts +79 -0
  300. package/src/storage/buffer-drain-claims.test.ts +330 -6
  301. package/src/storage/buffer-drain-claims.ts +226 -80
  302. package/src/storage/buffer-session-files.ts +2 -2
  303. package/src/storage/content-hash-index.ts +9 -0
  304. package/src/storage/entity-store-symlink.test.ts +44 -0
  305. package/src/storage.ts +15 -0
  306. package/src/transfer/capsule-gunzip.ts +39 -0
  307. package/src/transfer/capsule-import.test.ts +73 -0
  308. package/src/transfer/capsule-import.ts +2 -2
  309. package/src/transfer/capsule-merge.test.ts +34 -0
  310. package/src/transfer/capsule-merge.ts +2 -10
  311. package/src/utils/malformed-entity-record.test.ts +69 -0
  312. package/dist/capsule-merge-XVTQJJ6R.js.map +0 -1
  313. package/dist/chunk-4KLQI3L2.js.map +0 -1
  314. package/dist/chunk-6LCWKS62.js.map +0 -1
  315. package/dist/chunk-7PMOCQOL.js.map +0 -1
  316. package/dist/chunk-AYTDCMEL.js.map +0 -1
  317. package/dist/chunk-B74NPUHT.js.map +0 -1
  318. package/dist/chunk-KQUSP6LB.js.map +0 -1
  319. package/dist/chunk-LU66I56Q.js.map +0 -1
  320. package/dist/chunk-MPDLLLOG.js.map +0 -1
  321. package/dist/chunk-S57QBPTA.js.map +0 -1
  322. package/dist/chunk-SVCHO337.js.map +0 -1
  323. package/dist/chunk-WAD32CZG.js.map +0 -1
  324. package/dist/chunk-XYP5WBDO.js.map +0 -1
  325. package/dist/chunk-YK7GOPIE.js.map +0 -1
  326. package/dist/chunk-Z3DDQXM4.js.map +0 -1
  327. /package/dist/{capsule-crypto-YO5QJ6L3.js.map → access-mcp-frames.js.map} +0 -0
  328. /package/dist/{auto-sync-C7E6G7Y2.js.map → auto-sync-3AZW7MXI.js.map} +0 -0
  329. /package/dist/{graph-edge-decay-CXTNJ7IQ.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
  330. /package/dist/{resolution-BNWE5Y25.js.map → capsule-gunzip-KGBDGF7P.js.map} +0 -0
  331. /package/dist/{chunk-T6T2RMWW.js.map → chunk-23B4HLWG.js.map} +0 -0
  332. /package/dist/{chunk-LECSS7EE.js.map → chunk-2ASEDFOA.js.map} +0 -0
  333. /package/dist/{chunk-TPX7XXY4.js.map → chunk-2JAT4S7N.js.map} +0 -0
  334. /package/dist/{chunk-XRBHIR7Q.js.map → chunk-2URKYLMK.js.map} +0 -0
  335. /package/dist/{chunk-IN4OFAHX.js.map → chunk-2XYDL2FG.js.map} +0 -0
  336. /package/dist/{chunk-HQGATKG2.js.map → chunk-3KGCHTTK.js.map} +0 -0
  337. /package/dist/{chunk-YAUHSNPB.js.map → chunk-3LHPTBWG.js.map} +0 -0
  338. /package/dist/{chunk-XT45WCKX.js.map → chunk-3VU2FDTU.js.map} +0 -0
  339. /package/dist/{chunk-CIIHIAJ2.js.map → chunk-433EXTC4.js.map} +0 -0
  340. /package/dist/{chunk-QZSIDSB2.js.map → chunk-44FQCC5D.js.map} +0 -0
  341. /package/dist/{chunk-YUTT4ZTI.js.map → chunk-45VUHDV2.js.map} +0 -0
  342. /package/dist/{chunk-54FDG7K7.js.map → chunk-4DPLCYEK.js.map} +0 -0
  343. /package/dist/{chunk-KO3YBHOV.js.map → chunk-4IWEPQEV.js.map} +0 -0
  344. /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
  345. /package/dist/{chunk-K6RTQZA4.js.map → chunk-6DANGCMO.js.map} +0 -0
  346. /package/dist/{chunk-2IW5PT7U.js.map → chunk-6KFFRMPQ.js.map} +0 -0
  347. /package/dist/{chunk-MSIRVXQ3.js.map → chunk-73YVMZ7D.js.map} +0 -0
  348. /package/dist/{chunk-A7BM2JTH.js.map → chunk-AJHNXIQU.js.map} +0 -0
  349. /package/dist/{chunk-2D7BTSDW.js.map → chunk-ANZZLRUX.js.map} +0 -0
  350. /package/dist/{chunk-YOEYL6CH.js.map → chunk-AP3TSV6S.js.map} +0 -0
  351. /package/dist/{chunk-GQZSGXR6.js.map → chunk-BATPQNRG.js.map} +0 -0
  352. /package/dist/{chunk-D5SSBCYY.js.map → chunk-BLGQUZFB.js.map} +0 -0
  353. /package/dist/{chunk-BDN3TDGV.js.map → chunk-BLMTOIBK.js.map} +0 -0
  354. /package/dist/{chunk-R45L4TV6.js.map → chunk-BUKK5SWA.js.map} +0 -0
  355. /package/dist/{chunk-TFLPDNQV.js.map → chunk-CCU7URRP.js.map} +0 -0
  356. /package/dist/{chunk-2XJZLSXE.js.map → chunk-CMRHXPLQ.js.map} +0 -0
  357. /package/dist/{chunk-BK4FBHZ3.js.map → chunk-D6THW7PA.js.map} +0 -0
  358. /package/dist/{chunk-BFBS7OFQ.js.map → chunk-DC55RIEZ.js.map} +0 -0
  359. /package/dist/{chunk-VKDAJJQP.js.map → chunk-DI5ET2IE.js.map} +0 -0
  360. /package/dist/{chunk-QZBTIYKD.js.map → chunk-DM4RRTN7.js.map} +0 -0
  361. /package/dist/{chunk-EBOHFZI4.js.map → chunk-E5QEF3JO.js.map} +0 -0
  362. /package/dist/{chunk-DD6OIFDG.js.map → chunk-E7V5AK7I.js.map} +0 -0
  363. /package/dist/{chunk-BV2BRHD4.js.map → chunk-FHGWKODN.js.map} +0 -0
  364. /package/dist/{chunk-HPFVZPRH.js.map → chunk-FK4WC6VK.js.map} +0 -0
  365. /package/dist/{chunk-VLMBSGQF.js.map → chunk-FRM4UTDR.js.map} +0 -0
  366. /package/dist/{chunk-LTDLJCMQ.js.map → chunk-FZPDRCOB.js.map} +0 -0
  367. /package/dist/{chunk-ZJJFUEPO.js.map → chunk-GBOYOL5L.js.map} +0 -0
  368. /package/dist/{chunk-YUTG35M4.js.map → chunk-GJVGFKOU.js.map} +0 -0
  369. /package/dist/{chunk-K36RMINQ.js.map → chunk-GOZXQNIM.js.map} +0 -0
  370. /package/dist/{chunk-HWHNQVRV.js.map → chunk-GYNRNKMW.js.map} +0 -0
  371. /package/dist/{chunk-KFLD7ZZG.js.map → chunk-IUKYFTGW.js.map} +0 -0
  372. /package/dist/{chunk-BNQJKWWZ.js.map → chunk-K3FH3LAQ.js.map} +0 -0
  373. /package/dist/{chunk-FJ2VJTGA.js.map → chunk-KHZVAISH.js.map} +0 -0
  374. /package/dist/{chunk-WHQXWFH7.js.map → chunk-LT3CMO65.js.map} +0 -0
  375. /package/dist/{chunk-NUSAAN75.js.map → chunk-MHSBOEH7.js.map} +0 -0
  376. /package/dist/{chunk-KJCL2FZO.js.map → chunk-MNYQVYJ4.js.map} +0 -0
  377. /package/dist/{chunk-RZH5RVJC.js.map → chunk-MTTYIXSU.js.map} +0 -0
  378. /package/dist/{chunk-FVRTCYZ3.js.map → chunk-MVOJDLXS.js.map} +0 -0
  379. /package/dist/{chunk-KESJ5XX3.js.map → chunk-MYCCO6AA.js.map} +0 -0
  380. /package/dist/{chunk-66JOLMWS.js.map → chunk-MZCGHPIJ.js.map} +0 -0
  381. /package/dist/{chunk-B4ZU34JQ.js.map → chunk-N6II66E5.js.map} +0 -0
  382. /package/dist/{chunk-BK7AJUWB.js.map → chunk-NIV7RPWQ.js.map} +0 -0
  383. /package/dist/{chunk-TLHF75OR.js.map → chunk-O55AGDTI.js.map} +0 -0
  384. /package/dist/{chunk-GGJFRVRD.js.map → chunk-OMYRPMQC.js.map} +0 -0
  385. /package/dist/{chunk-XHGYZRVW.js.map → chunk-OSTFDWZJ.js.map} +0 -0
  386. /package/dist/{chunk-BXLOS5AJ.js.map → chunk-OWHERGF2.js.map} +0 -0
  387. /package/dist/{chunk-WC475JOG.js.map → chunk-P225W4RQ.js.map} +0 -0
  388. /package/dist/{chunk-TFA6HPOA.js.map → chunk-QCP75JGO.js.map} +0 -0
  389. /package/dist/{chunk-JRVMCZE3.js.map → chunk-QEYTRFR3.js.map} +0 -0
  390. /package/dist/{chunk-IZJQNDGI.js.map → chunk-R6HMAIKM.js.map} +0 -0
  391. /package/dist/{chunk-VUROWXFL.js.map → chunk-RNGDKFY2.js.map} +0 -0
  392. /package/dist/{chunk-FF6N7BXC.js.map → chunk-S45INSV3.js.map} +0 -0
  393. /package/dist/{chunk-2RUTN7PR.js.map → chunk-S7GJNA3K.js.map} +0 -0
  394. /package/dist/{chunk-RJWL3RHB.js.map → chunk-TFEHSPGQ.js.map} +0 -0
  395. /package/dist/{chunk-T77AX6DG.js.map → chunk-TQVECGUQ.js.map} +0 -0
  396. /package/dist/{chunk-OQSSQOKQ.js.map → chunk-TV65NR7L.js.map} +0 -0
  397. /package/dist/{chunk-HQPFACXO.js.map → chunk-UH3FPX3L.js.map} +0 -0
  398. /package/dist/{chunk-4EACCIPO.js.map → chunk-UHPHH74F.js.map} +0 -0
  399. /package/dist/{chunk-DQEMWVMT.js.map → chunk-UVYI6VIX.js.map} +0 -0
  400. /package/dist/{chunk-6QYPL6NI.js.map → chunk-V45TEVDA.js.map} +0 -0
  401. /package/dist/{chunk-GCAQ3VUJ.js.map → chunk-VCVEMQJK.js.map} +0 -0
  402. /package/dist/{chunk-6L63IN3M.js.map → chunk-VEFGHRKV.js.map} +0 -0
  403. /package/dist/{chunk-E33UJHY3.js.map → chunk-WFDWLWDK.js.map} +0 -0
  404. /package/dist/{chunk-KQLBXUUQ.js.map → chunk-WYG5UNDY.js.map} +0 -0
  405. /package/dist/{chunk-MPGLM2VU.js.map → chunk-X7REO4CH.js.map} +0 -0
  406. /package/dist/{chunk-FZVROZKC.js.map → chunk-XVZA6VXC.js.map} +0 -0
  407. /package/dist/{chunk-WV254W4V.js.map → chunk-Y3IYKNBY.js.map} +0 -0
  408. /package/dist/{chunk-FUOAIJKQ.js.map → chunk-Y43X3E24.js.map} +0 -0
  409. /package/dist/{chunk-3T3VHXUH.js.map → chunk-YAAKVTZA.js.map} +0 -0
  410. /package/dist/{chunk-VPEIWMUT.js.map → chunk-YLTU2DC4.js.map} +0 -0
  411. /package/dist/{chunk-65SKM4BF.js.map → chunk-YNBMBL4C.js.map} +0 -0
  412. /package/dist/{chunk-PJYK7HH2.js.map → chunk-ZWTHVUT5.js.map} +0 -0
  413. /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/storage/content-hash-index.ts"],"sourcesContent":["// Content-hash dedup index for facts, extracted from storage.ts (issue #1909 /\n// PR #2016 write-hot-paths). Self-contained: cross-process locked reconcile /\n// rebuild saves plus the deferred durable-retry machinery. storage.ts imports\n// these back and re-exports the class + companion types that were previously\n// part of its public API.\nimport { mkdir, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { computeContentHash, normalizeContent } from \"../content-hash.js\";\nimport { log } from \"../logger.js\";\nimport { SecureStoreLockedError, readMaybeEncryptedFile, writeMaybeEncryptedFile } from \"../secure-store/secure-fs.js\";\nimport { isErrnoCode } from \"../utils/errno.js\";\nimport { withHeldFileLock } from \"../utils/serialize-mutations.js\";\n\n/**\n * Stale threshold (ms) for the per-file advisory lock guarding a fact-hash\n * index merge-save (issue #1909 review round 7). The critical section is a\n * single read+atomic-rewrite of one index file (≤ a few MB), so a holder older\n * than this is treated as crashed. Generous to tolerate slow/encrypted disks\n * without breaking a legitimately in-progress publish.\n */\nconst CONTENT_HASH_INDEX_LOCK_STALE_MS = 30_000;\n/** Version marker for the normalized Unicode hash index format. */\nconst CONTENT_HASH_INDEX_FORMAT_HEADER = \"# remnic-content-hash-index:v2\";\n\nfunction parseHashIndex(raw: string): { versioned: boolean; hashes: Set<string> } {\n const lines = raw\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n if (lines[0] !== CONTENT_HASH_INDEX_FORMAT_HEADER) {\n return { versioned: false, hashes: new Set() };\n }\n return { versioned: true, hashes: new Set(lines.slice(1)) };\n}\n\nfunction serializeHashIndex(hashes: Set<string>): string {\n return `${CONTENT_HASH_INDEX_FORMAT_HEADER}\\n${[...hashes].join(\"\\n\")}\\n`;\n}\n\n/**\n * Durable-retry policy for a deferred content-hash reconcile save whose\n * cross-process lock acquire timed out (issue #1909 / PR #2016). When the lock\n * is contended past `withHeldFileLock`'s bounded wait, the added hashes are held\n * only in this process's memory; relying on \"some later batch save\" is not\n * durable (the run may be the last before idle/shutdown), and a long-lived peer\n * that already built its authoritative in-memory index will not see the write.\n * So we schedule a bounded, exponentially-backed-off background retry that keeps\n * re-attempting the locked publish until it lands. Bounded because the durable\n * fact `.md` is already on disk and a process restart rebuilds the index from\n * the corpus regardless — exhausting retries degrades to that rebuild safety\n * net, never to data loss or a fatal append failure.\n */\nconst CONTENT_HASH_INDEX_RETRY_MAX_ATTEMPTS = 5;\n/** Base backoff (ms) before the first deferred reconcile-save retry; doubles each attempt. */\nconst CONTENT_HASH_INDEX_RETRY_BASE_MS = 500;\n/** Ceiling (ms) for the exponential reconcile-save retry backoff. */\nexport const CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS = 5_000;\n\n/**\n * Tuning for {@link ContentHashIndex} cross-process lock waits and the\n * deferred reconcile-save durable retry (issue #1909 / PR #2016). All optional;\n * production uses the module defaults. Tests inject small values so the\n * lock-timeout + retry path is exercised deterministically without real\n * multi-second waits.\n */\nexport interface ContentHashIndexLockOptions {\n /** Bounded wait to acquire the per-file advisory lock; passed to `withHeldFileLock` (default 5000ms). */\n readonly maxWaitMs?: number;\n /** Poll interval while waiting for a busy lock (default 50ms). */\n readonly pollMs?: number;\n /** Base backoff before the first deferred-save retry (default 500ms). */\n readonly retryBaseMs?: number;\n /** Max deferred-save retries before falling back to the corpus-rebuild safety net (default 5). */\n readonly retryMaxAttempts?: number;\n /**\n * Tombstone-blocked capture write-lock backoff (COS-392). These do not\n * change the fact-hash index lock. Omitted fields use the 50ms / 2s / 60s\n * defaults. Converge copies its config here before replicating a file.\n */\n readonly captureWriteLockInitialDelayMs?: number;\n readonly captureWriteLockMaxDelayMs?: number;\n readonly captureWriteLockDeadlineMs?: number;\n /** Apply abort signal. Checked before each probe, during backoff, and before the write. */\n readonly captureWriteLockSignal?: AbortSignal;\n}\n\n/**\n * Thrown when the fact-hash index cannot be made authoritative — the\n * cross-process rebuild lock could not be acquired within the bounded retry\n * budget (PR #2016). Callers that require an authoritative dedup answer surface\n * this instead of silently trusting a stale loaded snapshot.\n */\nexport class FactHashIndexNotAuthoritativeError extends Error {\n constructor(stateDir: string) {\n super(\n `fact-hash index is not authoritative: the cross-process rebuild lock for ${stateDir} could not be acquired within the bounded retry budget`\n );\n this.name = \"FactHashIndexNotAuthoritativeError\";\n }\n}\n\ntype DiskFingerprint = {\n mtimeMs: number;\n size: number;\n dev: number;\n ino: number;\n ctimeMs: number;\n birthtimeMs: number;\n};\nexport interface ContentHashPathEntry {\n readonly path: string;\n readonly contentHash: string;\n}\n\n/**\n * Content-hash dedup index for facts.\n * Normalizes content (lowercase, strip punctuation, collapse whitespace),\n * computes SHA-256, and stores hashes in a line-delimited file.\n * Prevents writing semantically identical facts.\n */\nexport class ContentHashIndex {\n private hashes: Set<string> = new Set();\n /**\n * Fact-ONLY hash partition (issue #2474). `hashes` above is category-blind\n * (every write category registers there); this subset carries only\n * `category === \"fact\"` hashes so fact-only membership checks\n * (`hasFact*`) can never be satisfied by an unrelated decision/preference\n * sharing the same normalized body. In-memory only — never serialized to\n * fact-hashes.txt; `clear()` drops it and every authoritative rebuild\n * repopulates it via `addFactByHash`. Subset invariant: `factHashes ⊆\n * hashes` — `addFact*` registers in both, and the removal sites that drop\n * a shared hash also drop the partition entry (removal stays caller-paired\n * because shared-index removal is category-blind: archiving a non-fact\n * must not evict an active fact's fact-only membership).\n */\n private factHashes: Set<string> = new Set();\n private dirty = false;\n /** True when load() found the pre-Unicode, markerless on-disk format. */\n private formatMigrationRequired = false;\n /**\n * Hashes explicitly removed since the last successful save (issue #1909 review\n * round 8 thread 3). Tracked separately so `saveMergingWithDisk` can be\n * removal-AWARE: under the file lock it reads the latest on-disk set, DROPS\n * these, then unions our additions — so a removal batch cannot resurrect a\n * hash (union alone would) and cannot clobber a concurrent extraction's\n * appended hash (blind overwrite would). Consumed + cleared by every save.\n */\n private removed: Set<string> = new Set();\n /**\n * Hashes added by THIS instance since the last successful save (issue #1909\n * review round 9 finding 3). Tracked separately from the loaded snapshot so a\n * reconciling save publishes `(on-disk \\ removed) ∪ added` — it re-applies only\n * OUR additions, never the stale loaded entries. Without this, seeding the\n * merge from `this.hashes` (loaded ∪ added) would resurrect a hash a peer\n * removed on disk while this instance held a stale snapshot.\n */\n private added: Set<string> = new Set();\n private readonly filePath: string;\n private readonly secureStoreKeyProvider: () => Buffer | null;\n private readonly secureStoreWriteKeyProvider: () => Buffer | null;\n private readonly memoryDir: string;\n private readonly lockOptions: ContentHashIndexLockOptions;\n /**\n * Deferred reconcile-save durable-retry state (issue #1909 / PR #2016). At\n * most ONE retry is ever armed (`reconcileRetryTimer` is the reentrancy guard)\n * so successive timed-out saves cannot stack duplicate/parallel retries.\n * `reconcileRetryBarrier` resolves when the chain settles (published or gave\n * up) — awaited by tests and any caller wanting the eventual result.\n */\n private reconcileRetryTimer: NodeJS.Timeout | null = null;\n private reconcileRetryAttempts = 0;\n private reconcileRetryBarrier: Promise<void> | null = null;\n private reconcileRetryResolve: (() => void) | null = null;\n /**\n * On-disk fingerprint captured at the last point THIS instance's in-memory\n * set matched disk — after `load()`, `save()`, or a reconcile publish. A peer\n * process that advances the durable index changes its file generation even\n * when byte size and mtime collide, so the freshness check remains safe\n * without an O(file-size) read on the hot path.\n */\n private lastSyncedFingerprint: DiskFingerprint | null = null;\n constructor(\n stateDir: string,\n secureStoreKeyProvider: () => Buffer | null = () => null,\n secureStoreWriteKeyProvider: () => Buffer | null = secureStoreKeyProvider,\n memoryDir: string = path.dirname(stateDir),\n lockOptions: ContentHashIndexLockOptions = {}\n ) {\n this.filePath = path.join(stateDir, \"fact-hashes.txt\");\n this.secureStoreKeyProvider = secureStoreKeyProvider;\n this.secureStoreWriteKeyProvider = secureStoreWriteKeyProvider;\n this.memoryDir = memoryDir;\n this.lockOptions = lockOptions;\n }\n /** Whether a caller with an authoritative corpus must rebuild this legacy index. */\n get requiresFormatMigration(): boolean {\n return this.formatMigrationRequired;\n }\n\n /** Load existing hashes from disk. Safe to call multiple times. */\n async load(): Promise<void> {\n try {\n const raw = await readMaybeEncryptedFile(this.filePath, this.secureStoreKeyProvider(), this.memoryDir);\n const parsed = parseHashIndex(raw);\n this.formatMigrationRequired = !parsed.versioned;\n if (!parsed.versioned) {\n // Hashes from the pre-NFC format are unsafe after normalization changes.\n // Ignore them until an authoritative corpus rebuild publishes v2.\n this.hashes.clear();\n log.warn(`content-hash index: ignored legacy unversioned index at ${this.filePath}`);\n } else {\n for (const hash of parsed.hashes) this.hashes.add(hash);\n }\n log.debug(`content-hash index: loaded ${this.hashes.size} hashes`);\n } catch (err) {\n if (err instanceof SecureStoreLockedError) throw err;\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n log.debug(\"content-hash index: no existing index — starting fresh\");\n }\n // The loaded set now reflects the on-disk file — baseline the fingerprint so\n // a later peer write is detectable (PR #2016 review).\n await this.captureSyncedFingerprint();\n }\n\n /** Cheap `stat` of the durable index file; null when it does not exist yet. */\n private async statIndexFile(): Promise<DiskFingerprint | null> {\n try {\n const s = await stat(this.filePath);\n return {\n mtimeMs: s.mtimeMs,\n size: s.size,\n dev: s.dev,\n ino: s.ino,\n ctimeMs: s.ctimeMs,\n birthtimeMs: s.birthtimeMs,\n };\n } catch (err) {\n if (isErrnoCode(err, \"ENOENT\")) return null;\n throw err;\n }\n }\n\n /** Record the current on-disk fingerprint as this instance's synced baseline. */\n private async captureSyncedFingerprint(): Promise<void> {\n try {\n this.lastSyncedFingerprint = await this.statIndexFile();\n } catch (err) {\n // A stat failure other than ENOENT leaves no baseline; the next freshness\n // check reports non-current and the caller confirms against the corpus.\n this.lastSyncedFingerprint = null;\n log.debug(`content-hash index: could not fingerprint ${this.filePath}: ${err}`);\n }\n }\n\n /**\n * True when the durable index file is unchanged since this instance last\n * synced it (load / save / reconcile). False when a peer advanced it, when\n * the file appeared or vanished, or when freshness cannot be established\n * (stat error) — the caller then treats the in-memory snapshot as\n * non-authoritative and confirms a dedup miss against the durable corpus.\n * The stat includes file identity and generation metadata so same-size,\n * same-mtime atomic replacements are detected without reading the file body.\n */\n async isDiskFingerprintCurrent(): Promise<boolean> {\n let current: DiskFingerprint | null;\n try {\n current = await this.statIndexFile();\n } catch {\n return false;\n }\n const last = this.lastSyncedFingerprint;\n if (last === null || current === null) {\n return last === null && current === null;\n }\n return (\n current.mtimeMs === last.mtimeMs &&\n current.size === last.size &&\n current.dev === last.dev &&\n current.ino === last.ino &&\n current.ctimeMs === last.ctimeMs &&\n current.birthtimeMs === last.birthtimeMs\n );\n }\n\n /** Check if content already exists in the index. */\n has(content: string): boolean {\n return this.hashes.has(ContentHashIndex.computeHash(content));\n }\n\n /** Add content hash to the index. */\n add(content: string): void {\n const hash = ContentHashIndex.computeHash(content);\n // A re-add supersedes a pending removal of the same hash (round 8 thread 3).\n this.removed.delete(hash);\n // Record OUR durable delta even when the local snapshot already holds the\n // hash (PR #2016 thread SD-nG): local membership can be STALE — a peer\n // removed this hash on disk while this instance kept it in memory. Skipping\n // `added`/`dirty` on that path let the reconciling save compute\n // (on-disk \\ removed) ∪ added and silently drop the reintroduced hash, so\n // the peer never saw it again. Set semantics keep this idempotent (no\n // duplicate entries) and the `removed.delete` above preserves remove/add\n // ordering.\n this.hashes.add(hash);\n this.added.add(hash);\n this.dirty = true;\n }\n\n get size(): number {\n return this.hashes.size;\n }\n\n /** Clear all loaded hashes so the next save rewrites the index from scratch. */\n clear(): void {\n if (this.hashes.size > 0) {\n this.hashes.clear();\n }\n // The fact-only partition is rebuilt with the shared set on every\n // authoritative corpus rebuild, so it clears here too (issue #2474).\n this.factHashes.clear();\n // A full clear is always paired with a plain overwrite save() (rebuild);\n // pending per-hash add/remove deltas are subsumed by writing the rebuilt set.\n // Cancel any armed deferred-reconcile retry (PR #2016) so it cannot fire in\n // the clear→rebuild→save window and union the pre-clear on-disk rows back.\n this.added.clear();\n this.removed.clear();\n this.cancelReconcileRetry();\n this.dirty = true;\n }\n\n /** Persist index to disk if changed (plain whole-file overwrite). */\n async save(): Promise<void> {\n if (!this.dirty) return;\n await mkdir(path.dirname(this.filePath), { recursive: true });\n // Snapshot the deltas + overwrite body THIS save publishes BEFORE the write\n // yields the event loop. `writeMaybeEncryptedFile` materializes `serialized`\n // eagerly, then the disk await lets a concurrent same-process add()/remove()\n // land a late delta (PR #2016 thread PRRT_kwDORJXyws6SEBri). Clearing the\n // LIVE added/removed sets afterward — as the previous unconditional\n // clear()+`dirty = false` did — would drop that late hash from the index AND\n // leave no retry, so peers never see it until a corpus rebuild. Consume only\n // what we published; late deltas stay pending. Mirrors saveMergingWithDisk.\n const publishedAdded = new Set<string>(this.added);\n const publishedRemoved = new Set<string>(this.removed);\n const serialized = serializeHashIndex(this.hashes);\n await writeMaybeEncryptedFile(this.filePath, serialized, this.secureStoreWriteKeyProvider(), {}, this.memoryDir);\n this.formatMigrationRequired = false;\n // Consume ONLY the deltas this overwrite published; anything that arrived\n // during the awaits above remains pending for the next save.\n for (const h of publishedAdded) this.added.delete(h);\n for (const h of publishedRemoved) this.removed.delete(h);\n // Honest dirty state: true iff late deltas remain unpersisted.\n this.dirty = this.added.size > 0 || this.removed.size > 0;\n // This overwrite is now the on-disk state — re-baseline the freshness\n // fingerprint so our own write is not later mistaken for a peer's (PR #2016).\n await this.captureSyncedFingerprint();\n if (this.dirty) {\n // Late deltas landed mid-save: arm a bounded durable retry so they reach\n // disk even without a further batch save — never a silent in-memory-only\n // hash. The locked reconcile republishes only OUR delta onto the overwrite.\n this.scheduleReconcileRetry();\n } else {\n // Clean overwrite supersedes any pending deferred reconcile work.\n this.cancelReconcileRetry();\n }\n log.debug(`content-hash index: saved ${this.hashes.size} hashes`);\n }\n\n /**\n * Persist the index by RECONCILING with the latest on-disk state under a\n * cross-process lock (issue #1909). Used by the extraction-persist batch save\n * for BOTH append and removal batches, so appends and removals to the same\n * index serialize against each other:\n * final = (on-disk \\ this.removed) ∪ this.added\n * Note it re-applies only OUR local additions (`added`), NOT the loaded\n * snapshot (review round 9 finding 3): seeding from `this.hashes` would\n * resurrect a hash a peer removed on disk while we held a stale snapshot. This\n * preserves a concurrent writer's appended hashes (a blind overwrite would\n * drop them), never resurrects a removed hash, and never loses our additions.\n *\n * Hardening:\n * - Dirty short-circuit (round 7 finding 3): when this instance neither added\n * nor removed anything this run, skip the O(file) read+rewrite entirely.\n * - Cross-process lock (round 7 finding 2): serialize the read→reconcile→write\n * window on a per-file advisory lock. If the lock is NOT acquired (timeout),\n * do NOT publish (round 9 finding 2): keep `dirty` set AND schedule a bounded\n * durable background retry (PR #2016) that keeps re-attempting the locked\n * publish until the deferred additions land — never an unlocked write that\n * could clobber a peer, and never a silent \"batch save complete\" that leaves\n * the addition in this process's memory only.\n */\n async saveMergingWithDisk(): Promise<void> {\n if (!this.dirty) return;\n // The advisory lock file lives beside the index — ensure the dir exists\n // before acquiring it (the index dir may not exist on a first write).\n await mkdir(path.dirname(this.filePath), { recursive: true });\n const publish = async (): Promise<void> => {\n // Snapshot the deltas this publish is responsible for. The read+rewrite\n // below yields the event loop, so a concurrent add()/remove() can land\n // new deltas while we await disk (PR #2016 thread SD7Tj). Consuming the\n // LIVE sets afterward would drop those late deltas AND clear `dirty`,\n // stranding them in no save at all. We consume only what we published;\n // anything that arrived meanwhile stays pending for the next save.\n const publishedAdded = new Set<string>(this.added);\n const publishedRemoved = new Set<string>(this.removed);\n const merged = new Set<string>(publishedAdded);\n try {\n const raw = await readMaybeEncryptedFile(this.filePath, this.secureStoreKeyProvider(), this.memoryDir);\n const parsed = parseHashIndex(raw);\n if (parsed.versioned) {\n for (const hash of parsed.hashes) {\n // Keep prior/concurrent on-disk hashes EXCEPT the ones we removed.\n if (!publishedRemoved.has(hash)) merged.add(hash);\n }\n } else {\n // Do not carry hashes from the pre-NFC format into a v2 write.\n log.warn(`content-hash index: discarded legacy unversioned entries from ${this.filePath}`);\n }\n } catch (err) {\n if (err instanceof SecureStoreLockedError) throw err;\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n // ENOENT → no on-disk index yet; write our additions as-is.\n }\n await mkdir(path.dirname(this.filePath), { recursive: true });\n await writeMaybeEncryptedFile(\n this.filePath,\n serializeHashIndex(merged),\n this.secureStoreWriteKeyProvider(),\n {},\n this.memoryDir\n );\n this.formatMigrationRequired = false;\n // Consume ONLY the deltas we just published; deltas that arrived during\n // the awaits above remain pending.\n for (const h of publishedAdded) this.added.delete(h);\n for (const h of publishedRemoved) this.removed.delete(h);\n // Fold any still-pending deltas onto the reconciled set so the in-memory\n // view stays consistent with what the next save will publish.\n for (const h of this.added) merged.add(h);\n for (const h of this.removed) merged.delete(h);\n this.hashes = merged;\n // Honest dirty state: true iff late deltas remain to be persisted. A\n // subsequent batch save or the shutdown flush drains them.\n this.dirty = this.added.size > 0 || this.removed.size > 0;\n // The reconciled set is now the on-disk state — re-baseline the freshness\n // fingerprint (captured under the held lock) so our own publish is not\n // later mistaken for a peer's advance (PR #2016 review).\n await this.captureSyncedFingerprint();\n };\n await withHeldFileLock(\n `${this.filePath}.lock`,\n {\n staleMs: CONTENT_HASH_INDEX_LOCK_STALE_MS,\n ...(this.lockOptions.maxWaitMs !== undefined ? { maxWaitMs: this.lockOptions.maxWaitMs } : {}),\n ...(this.lockOptions.pollMs !== undefined ? { pollMs: this.lockOptions.pollMs } : {}),\n },\n async (acquired) => {\n if (!acquired) {\n // Never publish unlocked (round 9 finding 2): keep dirty AND schedule a\n // durable background retry (PR #2016). Relying on an unguaranteed later\n // batch save silently left the addition in this process's memory only;\n // the retry keeps re-attempting the locked publish until the addition\n // lands on disk.\n log.warn(\n `content-hash index: lock not acquired for ${this.filePath}; deferring reconcile save (dirty retained, scheduling durable retry)`\n );\n this.scheduleReconcileRetry();\n return;\n }\n await publish();\n if (this.dirty) {\n // A concurrent add()/remove() landed during publish's disk awaits, so\n // deltas remain unpersisted (PR #2016 thread SD7Tj). Arm a bounded\n // durable retry so they reach disk even if no further batch save comes\n // — never leave a settled barrier over pending work.\n this.scheduleReconcileRetry();\n } else {\n // Published cleanly: any pending deferred retry is now satisfied.\n this.cancelReconcileRetry();\n }\n }\n );\n log.debug(`content-hash index: reconcile-saved ${this.hashes.size} hashes`);\n }\n\n /**\n * Rebuild-and-publish the index from an authoritative source (the durable\n * corpus) under the SAME per-file cross-process lock the reconciling\n * append/removal saves (`saveMergingWithDisk`) use (issue #1909 / PR #2016).\n * The `populate` callback runs WHILE the lock is held and MUST repopulate this\n * index from the corpus (`clear()` then `addByHash(...)` for every corpus\n * hash); the rebuilt set is then published with a plain overwrite that is now\n * serialized against every locked writer.\n *\n * This closes the finding where the previous unlocked clear→scan→save() could\n * overwrite a peer's newer lock-merged or deferred additions: a locked writer\n * that committed BEFORE the rebuild acquired the lock has its memory `.md` on\n * disk and is re-scanned; one that commits AFTER the rebuild releases\n * reconciles its additions on top of the freshly-published set. No union with\n * the raw on-disk file is performed, so the rebuild keeps its\n * garbage-collection property (orphaned hashes for deleted memories drop).\n *\n * Lock-acquisition failure is explicit and non-destructive: `populate` is NOT\n * run and nothing is written (never an unlocked overwrite). Returns true when\n * the rebuild was published under the lock, false when the lock timed out —\n * the caller keeps the index non-authoritative and retries on next use. Uses\n * the same non-reentrant file lock as the reconcile path, so it can never\n * deadlock with the deferred reconcile-retry: a concurrent locked writer (or a\n * fired retry) simply yields `acquired=false` after the bounded wait.\n */\n async rebuildUnderLock(populate: () => Promise<void>): Promise<boolean> {\n // The advisory lock file lives beside the index — ensure the dir exists\n // before acquiring it (the index dir may not exist on a first rebuild).\n await mkdir(path.dirname(this.filePath), { recursive: true });\n let published = false;\n await withHeldFileLock(\n `${this.filePath}.lock`,\n {\n staleMs: CONTENT_HASH_INDEX_LOCK_STALE_MS,\n ...(this.lockOptions.maxWaitMs !== undefined ? { maxWaitMs: this.lockOptions.maxWaitMs } : {}),\n ...(this.lockOptions.pollMs !== undefined ? { pollMs: this.lockOptions.pollMs } : {}),\n },\n async (acquired) => {\n if (!acquired) {\n // Never publish unlocked: a concurrent locked writer holds the lock,\n // so overwriting now could clobber its addition. Leave the index as-is\n // and let the caller retry the authoritative rebuild on next use.\n log.warn(\n `content-hash index: lock not acquired for ${this.filePath}; deferring authoritative rebuild (index left non-authoritative, retried on next use)`\n );\n return;\n }\n await populate();\n // Publish under the held lock. save() takes no lock (no re-entrancy) and\n // is a full overwrite that supersedes any pending add/remove deltas.\n await this.save();\n published = true;\n }\n );\n return published;\n }\n\n /**\n * True while a background durable reconcile-save retry is armed (PR #2016).\n * Test/introspection hook.\n */\n get hasPendingReconcileRetry(): boolean {\n return this.reconcileRetryTimer !== null;\n }\n\n /** True while this index has in-memory changes not yet published to disk. */\n get hasPendingChanges(): boolean {\n return this.dirty;\n }\n\n /**\n * Resolve once any armed background reconcile-save retry chain has settled —\n * either it published the deferred additions, or it exhausted its bounded\n * attempts and fell back to the corpus-rebuild safety net. Resolves\n * immediately when nothing is armed. Lets a caller (or a test) await the\n * eventual durability outcome of a lock-timed-out save.\n */\n async whenReconcileRetrySettled(): Promise<void> {\n await (this.reconcileRetryBarrier ?? Promise.resolve());\n }\n\n /**\n * Drive any deferred lock-timeout reconcile save to completion INLINE at a\n * lifecycle boundary (PR #2016 finding 3). The background retry timer is\n * `unref`'d so it never keeps a long-lived daemon alive — but a short-lived\n * writer (a one-shot CLI) can exit before it fires, leaving a durable fact\n * `.md` whose hash never reached `fact-hashes.txt` for peers that already\n * built their in-memory index. `orchestrator.destroy()` calls this so the\n * addition publishes before the process exits; long-lived hosts only reach it\n * at their own shutdown, so their in-flight retries keep running in the\n * background until then. Bounded by the same attempt ceiling as the background\n * retry; a permanently contended lock falls back to the corpus-rebuild-on-\n * restart safety net (the fact `.md` is already durable). No deadlock: each\n * attempt is the same non-reentrant, bounded-wait file lock.\n */\n async flushReconcileRetry(): Promise<void> {\n if (!this.dirty) return;\n const maxAttempts = this.lockOptions.retryMaxAttempts ?? CONTENT_HASH_INDEX_RETRY_MAX_ATTEMPTS;\n const baseMs = this.lockOptions.retryBaseMs ?? CONTENT_HASH_INDEX_RETRY_BASE_MS;\n for (let attempt = 0; this.dirty && attempt < maxAttempts; attempt += 1) {\n // Cancel the unref'd background timer — we drive the locked publish inline.\n if (this.reconcileRetryTimer) {\n clearTimeout(this.reconcileRetryTimer);\n this.reconcileRetryTimer = null;\n }\n await this.saveMergingWithDisk();\n if (!this.dirty) break;\n if (attempt < maxAttempts - 1) {\n const wait = Math.min(baseMs * 2 ** attempt, CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS);\n await new Promise<void>((resolve) => setTimeout(resolve, wait));\n }\n }\n // Clear any timer the final saveMergingWithDisk armed and settle the barrier\n // so awaiters unblock — the bounded inline drain has done its best.\n if (this.reconcileRetryTimer) {\n clearTimeout(this.reconcileRetryTimer);\n this.reconcileRetryTimer = null;\n }\n this.reconcileRetryAttempts = 0;\n this.settleReconcileRetry();\n }\n\n /**\n * Schedule ONE bounded, exponentially-backed-off background retry of the\n * deferred reconcile save (PR #2016). Reentrant-safe: an already-armed timer\n * short-circuits, so successive timed-out saves never stack duplicate retries.\n * When attempts are exhausted the chain settles without publishing — the\n * durable fact `.md` is on disk and a process restart rebuilds the index from\n * the corpus, so this degrades to that safety net, never a fatal failure.\n */\n private scheduleReconcileRetry(): void {\n if (this.reconcileRetryTimer) return; // one retry in flight — no duplicate/reentrant retries\n const maxAttempts = this.lockOptions.retryMaxAttempts ?? CONTENT_HASH_INDEX_RETRY_MAX_ATTEMPTS;\n if (this.reconcileRetryAttempts >= maxAttempts) {\n log.warn(\n `content-hash index: exhausted ${maxAttempts} deferred reconcile-save retries for ${this.filePath}; ` +\n `${this.added.size} addition(s) remain dirty in-memory (a process restart rebuilds the index from the durable corpus)`\n );\n this.reconcileRetryAttempts = 0;\n this.settleReconcileRetry();\n return;\n }\n if (!this.reconcileRetryBarrier) {\n this.reconcileRetryBarrier = new Promise<void>((resolve) => {\n this.reconcileRetryResolve = resolve;\n });\n }\n const attempt = this.reconcileRetryAttempts + 1;\n const baseMs = this.lockOptions.retryBaseMs ?? CONTENT_HASH_INDEX_RETRY_BASE_MS;\n const delay = Math.min(baseMs * 2 ** (attempt - 1), CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS);\n this.reconcileRetryTimer = setTimeout(() => {\n this.reconcileRetryTimer = null;\n this.reconcileRetryAttempts = attempt;\n void this.saveMergingWithDisk()\n .catch((err) => {\n // A publish-time I/O/encryption error (not a lock timeout) is best-effort:\n // log and let the corpus-rebuild safety net cover it rather than loop.\n log.warn(`content-hash index: deferred reconcile-save retry failed for ${this.filePath}: ${err}`);\n })\n .finally(() => {\n // If the attempt neither published (dirty cleared → cancelReconcileRetry)\n // nor re-armed a further retry, the chain has ended — settle the barrier.\n if (this.dirty && !this.reconcileRetryTimer) this.settleReconcileRetry();\n });\n }, delay);\n this.reconcileRetryTimer.unref?.();\n }\n\n /** Cancel any armed reconcile-save retry and settle the barrier (a publish landed). */\n private cancelReconcileRetry(): void {\n if (this.reconcileRetryTimer) {\n clearTimeout(this.reconcileRetryTimer);\n this.reconcileRetryTimer = null;\n }\n this.reconcileRetryAttempts = 0;\n this.settleReconcileRetry();\n }\n\n /** Resolve and clear the retry barrier if one is outstanding. */\n private settleReconcileRetry(): void {\n const resolve = this.reconcileRetryResolve;\n this.reconcileRetryBarrier = null;\n this.reconcileRetryResolve = null;\n resolve?.();\n }\n\n /** Remove a hash from the index (used when archiving/deleting). */\n remove(content: string): void {\n const hash = ContentHashIndex.computeHash(content);\n // Record the removal (round 8 thread 3) even when the hash is not in our\n // in-memory set — a reconciling save must still drop it from the latest\n // on-disk state. A removal supersedes a pending add. Always mark dirty.\n this.added.delete(hash);\n this.removed.add(hash);\n this.hashes.delete(hash);\n this.dirty = true;\n }\n\n /**\n * Remove a pre-computed SHA-256 hash directly from the index without\n * re-hashing. Use this when the caller already holds the stored hash\n * (e.g. `memory.frontmatter.contentHash`) to avoid the double-hash bug\n * where `remove(hash)` would compute `hash(hash)` and never match the\n * entry.\n */\n removeByHash(hash: string): void {\n this.added.delete(hash);\n this.removed.add(hash);\n this.hashes.delete(hash);\n this.dirty = true;\n }\n\n /**\n * Add a pre-computed SHA-256 hash directly to the index without re-hashing.\n * Use this when the caller already holds the stored hash\n * (e.g. `memory.frontmatter.contentHash`) so that the index records the raw\n * content hash rather than re-hashing the citation-annotated body.\n *\n * @internal Only called from `StorageManager.ensureFactHashIndexAuthoritative`.\n * Not part of the public API — prefer `add(content)` for external callers.\n */\n addByHash(hash: string): void {\n // A re-add supersedes a pending removal of the same hash.\n this.removed.delete(hash);\n // Record OUR durable delta even when the local snapshot already holds the\n // hash (PR #2016 thread PRRT_kwDORJXyws6SEHve). Outside a rebuild\n // (StorageManager.addActiveFactContentHash on reactivation) local\n // membership can be STALE: a peer removed this hash on disk while this\n // instance kept it in memory. Skipping added/dirty on that path let the\n // reconciling save compute (on-disk \\ removed) ∪ added and silently drop\n // the reintroduced hash, so the peer never saw it again. Set semantics keep\n // this idempotent (no duplicate entries) and the removed.delete above\n // preserves remove/add ordering. Mirrors add().\n this.hashes.add(hash);\n this.added.add(hash);\n this.dirty = true;\n }\n\n // ── Fact-only partition (issue #2474) ─────────────────────────────────\n // Folded here from StorageManager's parallel `factOnlyHashes` set, which had\n // to be kept in lockstep by hand on every add/remove/rebuild site. The\n // partition lives on the index instance, so every path that mutates the\n // shared set and needs fact-only truth mutates it in the same breath.\n\n /** Register fact content in BOTH the shared index and the fact-only partition. */\n addFact(content: string): void {\n this.addFactByHash(ContentHashIndex.computeHash(content));\n }\n\n /** Register a pre-computed fact hash in both the shared index and the fact-only partition. */\n addFactByHash(hash: string): void {\n this.addByHash(hash);\n this.factHashes.add(hash);\n }\n\n /** Fact-ONLY membership: has this content been registered as a fact? */\n hasFact(content: string): boolean {\n return this.factHashes.has(ContentHashIndex.computeHash(content));\n }\n\n /** Fact-ONLY membership for a pre-computed SHA-256 hash. */\n hasFactByHash(hash: string): boolean {\n return this.factHashes.has(hash);\n }\n\n /**\n * Drop fact-only partition membership WITHOUT touching the shared index.\n * The shared removal stays caller-owned: a shared hash is dropped only when\n * no other active memory holds it, while fact-only membership follows the\n * fact's own lifecycle (supersede/archive/promotion).\n */\n removeFactByHash(hash: string): void {\n this.factHashes.delete(hash);\n }\n\n /**\n * Resolve a pre-computed semantic hash to one deterministic corpus path.\n *\n * This lookup is separate from durable hash membership: `this.hashes` stays\n * one hash per line and `has(content)` keeps its existing raw-content contract.\n * Reconciliation supplies short-lived manifest rows where hash and path coexist.\n */\n static resolvePathByHash(hash: string, entries: Iterable<ContentHashPathEntry>): string | undefined {\n const canonicalHash = hash.toLowerCase();\n if (!/^[a-f0-9]{64}$/.test(canonicalHash)) return undefined;\n let canonicalPath: string | undefined;\n for (const entry of entries) {\n if (entry.contentHash.toLowerCase() !== canonicalHash || entry.path.length === 0) continue;\n if (canonicalPath === undefined || entry.path < canonicalPath) canonicalPath = entry.path;\n }\n return canonicalPath;\n }\n\n /** Normalize content (delegates to content-hash.ts for a single source of truth). */\n static normalizeContent(content: string): string {\n return normalizeContent(content);\n }\n\n /** Compute SHA-256 hash (delegates to content-hash.ts for a single source of truth). */\n static computeHash(content: string): string {\n return computeContentHash(content);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/namespaces/search.ts"],"sourcesContent":["import { resolveNamespaceCapabilities } from \"../capabilities.js\";\nimport path from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport type { PluginConfig, QmdSearchResult } from \"../types.js\";\nimport type {\n SearchBackend,\n SearchExecutionOptions,\n SearchQueryOptions,\n} from \"../search/port.js\";\nimport { createSearchBackend } from \"../search/factory.js\";\nimport { namespaceIdentityToken, normalizeNamespaceIdentity } from \"./identity.js\";\nimport { ALL_CATEGORY_DIRS } from \"../utils/category-dir.js\";\n\nconst NESTED_NAMESPACE_FILTER_OVERFETCH_FACTOR = 4;\nconst NESTED_NAMESPACE_FILTER_OVERFETCH_MIN = 50;\n\nexport function namespaceCollectionName(\n baseCollection: string,\n namespace: string,\n options?: {\n defaultNamespace?: string;\n useLegacyDefaultCollection?: boolean;\n },\n): string {\n const trimmed = normalizeNamespaceIdentity(namespace);\n const defaultNamespace = normalizeNamespaceIdentity(options?.defaultNamespace ?? \"\") || \"default\";\n if (\n options?.useLegacyDefaultCollection === true &&\n trimmed === defaultNamespace\n ) {\n return baseCollection;\n }\n\n return `${baseCollection}--${namespaceIdentityToken(trimmed || defaultNamespace)}`;\n}\n\ntype StorageRouterLike = {\n storageFor(namespace: string): Promise<{ dir: string }>;\n};\n\ntype NamespaceBackendRecord = {\n backend: SearchBackend;\n collection: string;\n memoryDir: string;\n available: boolean;\n collectionState: CollectionState;\n filtersNestedNamespaces: boolean;\n disposed: boolean;\n};\n\nexport interface NamespaceUpdateResult {\n backendCount: number;\n eligibleNamespaces: string[];\n}\n\nexport type CollectionState = \"present\" | \"missing\" | \"unknown\" | \"skipped\";\n\nexport interface NamespaceSearchHealth {\n collection: string;\n memoryDir: string;\n available: boolean;\n collectionState: CollectionState;\n debugStatus: string;\n installedVersion: string | null;\n supportedVersion: string | null;\n supported: boolean | null;\n upgradeAvailable: boolean | null;\n doctorAvailable: boolean | null;\n daemonMode: boolean | null;\n pendingEmbeddings: number | null;\n oldestPendingAgeMs: number | null;\n embeddingBacklogThreshold: number;\n}\n\ntype NamespaceScopedSearchConfig = PluginConfig & {\n hostEmbeddingProviderScope?: string;\n};\n\ntype BackendRecordOptions = {\n autoCreateCollection: boolean;\n abortAsUnavailable: boolean;\n failOpenMissingGuardedCollection: boolean;\n};\n\nexport class NamespaceSearchRouter {\n private readonly cache = new Map<string, Promise<NamespaceBackendRecord>>();\n private readonly pendingDisposals = new Set<Promise<void>>();\n\n constructor(\n private readonly config: PluginConfig,\n private readonly storageRouter: StorageRouterLike,\n private readonly createBackend: (config: PluginConfig) => SearchBackend = createSearchBackend,\n ) {}\n\n async collectionForNamespace(namespace: string): Promise<string> {\n return (await this.backendRecordFor(namespace)).collection;\n }\n\n /**\n * Resolve the search backend for a namespace (#2019). Returns `unknown` so\n * callers (e.g. the prioritized-embed path) duck-type for capability like\n * `embedFiles` rather than depending on a concrete QMD shape. Reuses the\n * cached backend record, so this never re-creates a backend. Returns `null`\n * when the namespace backend is unavailable.\n */\n async backendForNamespace(namespace: string): Promise<unknown> {\n const record = await this.backendRecordFor(namespace);\n return record.available ? record.backend : null;\n }\n\n async searchAcrossNamespaces(options: {\n query: string;\n namespaces: string[];\n maxResults?: number;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n searchOptions?: SearchQueryOptions;\n execution?: SearchExecutionOptions;\n }): Promise<QmdSearchResult[]> {\n const query = options.query.trim();\n if (!query) return [];\n const maxResults = Math.max(0, Math.floor(options.maxResults ?? this.config.qmdMaxResults));\n if (maxResults === 0) return [];\n\n const method = options.mode ?? \"search\";\n const namespaces = Array.from(new Set(options.namespaces.map((value) => value.trim()).filter(Boolean)));\n if (namespaces.length === 0) return [];\n\n const resultsByNamespace = await Promise.all(\n namespaces.map(async (namespace) => {\n const record = await this.backendRecordFor(namespace);\n if (!record.available || record.collectionState === \"missing\") {\n // A per-namespace backend that is unavailable (or missing its\n // collection) must report like any other QMD skip (#1536): its\n // empty contribution is otherwise indistinguishable from a true\n // no-matches for that namespace (codex round-5 review on #1544).\n try {\n options.execution?.onDegradation?.({\n backend: \"qmd\",\n code: \"backend_unavailable\",\n detail:\n record.collectionState === \"missing\"\n ? `namespace collection missing: ${namespace}`\n : `namespace backend unavailable: ${namespace}`,\n });\n } catch {\n // Observers must never break search.\n }\n return { namespace, results: [] as QmdSearchResult[] };\n }\n const backendLimit = backendSearchLimit(record, maxResults);\n let results: QmdSearchResult[];\n switch (method) {\n case \"hybrid\":\n results = await record.backend.hybridSearch(\n query,\n record.collection,\n backendLimit,\n options.execution,\n );\n break;\n case \"bm25\":\n results = await record.backend.bm25Search(\n query,\n record.collection,\n backendLimit,\n options.execution,\n );\n break;\n case \"vector\":\n results = await record.backend.vectorSearch(\n query,\n record.collection,\n backendLimit,\n options.execution,\n );\n break;\n default:\n results = await record.backend.search(\n query,\n record.collection,\n backendLimit,\n options.searchOptions,\n options.execution,\n );\n break;\n }\n results = filterNamespaceSubtreeResults(record, results)\n .map((result) => ({\n ...result,\n namespace,\n // Resolve to an absolute path so the (namespace, path) identity is\n // globally unique — same-relative-path hits from different namespaces\n // stay distinct across every downstream consumer with no special\n // handling. Display/citation surfaces relativize for portability (#2020).\n path: resolveNamespaceResultPath(record.memoryDir, record.collection, result.path),\n }))\n // Reject hits whose resolved path escapes the queried namespace's\n // storage root (#2077). A stale/corrupt per-namespace collection can\n // return a path owned by another namespace; stamping it would let\n // downstream scope checks (which trust the stamped namespace) and\n // absolute-path resolution surface a foreign-namespace memory.\n .filter((result) => resolvedPathIsInsideNamespaceRoot(record.memoryDir, result.path));\n return { namespace, results };\n }),\n );\n\n return mergeNamespaceSearchResults(resultsByNamespace, maxResults);\n }\n\n /**\n * Update all namespace backends.\n * Returns the number of backends for which an update was attempted\n * (i.e., available and collection present). Callers can treat 0 as a\n * signal that no backend was eligible — useful for success-verification in\n * startup-sync when namespacesEnabled is true.\n */\n async updateNamespaces(\n namespaces: string[],\n execution?: SearchExecutionOptions,\n options?: { strict?: boolean; force?: boolean },\n ): Promise<number> {\n return (await this.updateNamespacesDetailed(namespaces, execution, options)).backendCount;\n }\n\n async updateNamespacesDetailed(\n namespaces: string[],\n execution?: SearchExecutionOptions,\n options?: { strict?: boolean; force?: boolean },\n ): Promise<NamespaceUpdateResult> {\n const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));\n const eligible = (await Promise.all(\n unique.map(async (namespace) => {\n const record = await this.backendRecordFor(namespace);\n return record.available && record.collectionState !== \"missing\"\n ? { namespace, record }\n : null;\n }),\n )).filter((entry): entry is { namespace: string; record: NamespaceBackendRecord } => entry !== null);\n\n const globalEntry = eligible.find(({ record }) => record.backend.updatesAllCollections?.() === true);\n const scopedEntries = globalEntry\n ? eligible.filter(({ record }) => record.backend.updatesAllCollections?.() !== true)\n : eligible;\n\n await Promise.all([\n globalEntry\n ? updateBackendRecord(globalEntry.record, execution, options)\n : Promise.resolve(),\n ...scopedEntries.map(({ record }) => updateBackendRecord(record, execution, options)),\n ]);\n\n return {\n backendCount: (globalEntry ? 1 : 0) + scopedEntries.length,\n eligibleNamespaces: eligible.map(({ namespace }) => namespace),\n };\n }\n\n async embedNamespaces(namespaces: string[], options?: { strict?: boolean }): Promise<void> {\n const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));\n await Promise.all(\n unique.map(async (namespace) => {\n const record = await this.backendRecordFor(namespace);\n if (!record.available || record.collectionState === \"missing\") return;\n await embedBackendRecord(record, options);\n }),\n );\n }\n\n async ensureNamespaceCollection(\n namespace: string,\n execution?: SearchExecutionOptions,\n ): Promise<\"present\" | \"missing\" | \"unknown\" | \"skipped\"> {\n const record = await this.backendRecordFor(namespace, execution);\n return record.collectionState;\n }\n\n async healthForNamespace(\n namespace: string,\n execution?: SearchExecutionOptions,\n ): Promise<NamespaceSearchHealth> {\n const key = namespace.trim() || this.config.defaultNamespace;\n const record = await this.createBackendRecordFor(\n key,\n execution,\n {\n autoCreateCollection: false,\n abortAsUnavailable: true,\n failOpenMissingGuardedCollection: false,\n },\n );\n try {\n const liveRecord = await this.liveCachedRecordForHealth(key, record, execution);\n const diagnosticBackend = liveRecord?.backend ?? record.backend;\n const versionStatus =\n \"getVersionStatus\" in diagnosticBackend &&\n typeof diagnosticBackend.getVersionStatus === \"function\"\n ? diagnosticBackend.getVersionStatus()\n : null;\n const daemonMode = daemonModeForBackend(diagnosticBackend);\n const backendStatus =\n \"status\" in diagnosticBackend &&\n typeof diagnosticBackend.status === \"function\"\n ? await Promise.race([\n diagnosticBackend.status().catch(() => null),\n new Promise<null>((resolve) => setTimeout(() => resolve(null), 5_000).unref?.()),\n ])\n : null;\n const collectionState =\n liveRecord?.collectionState === \"missing\"\n ? \"missing\"\n : record.collectionState;\n\n return {\n collection: record.collection,\n memoryDir: record.memoryDir,\n available: liveRecord?.available ?? record.available,\n collectionState,\n debugStatus: diagnosticBackend.debugStatus(),\n installedVersion: versionStatus?.installedVersion ?? null,\n supportedVersion: versionStatus?.supportedVersion ?? null,\n supported: versionStatus?.supported ?? null,\n upgradeAvailable: versionStatus?.upgradeAvailable ?? null,\n doctorAvailable: versionStatus?.capabilities?.doctor ?? null,\n daemonMode,\n pendingEmbeddings: backendStatus?.pendingEmbeddings ?? null,\n oldestPendingAgeMs: backendStatus?.oldestPendingAgeMs ?? null,\n embeddingBacklogThreshold: this.config.qmdEmbeddingBacklogThreshold,\n };\n } finally {\n const dispose = (record.backend as { dispose?: () => void | Promise<void> }).dispose;\n await dispose?.call(record.backend);\n }\n }\n\n private async liveCachedRecordForHealth(\n key: string,\n disposableRecord: NamespaceBackendRecord,\n execution?: SearchExecutionOptions,\n ): Promise<NamespaceBackendRecord | null> {\n const pending = this.cache.get(key);\n if (!pending) return null;\n const cachedRecord = await awaitWithAbort(pending, execution?.signal).catch(() => null);\n if (!cachedRecord) return null;\n if (cachedRecord.collection !== disposableRecord.collection) return null;\n if (cachedRecord.memoryDir !== disposableRecord.memoryDir) return null;\n return cachedRecord;\n }\n\n private trackDisposal(promise: Promise<void>): void {\n this.pendingDisposals.add(promise);\n void promise.finally(() => this.pendingDisposals.delete(promise));\n }\n\n private async drainPendingDisposals(): Promise<void> {\n if (this.pendingDisposals.size === 0) return;\n await Promise.allSettled(this.pendingDisposals);\n }\n\n /** Clear cached backend records so the next access re-probes availability. */\n clearCache(): void {\n const pendingRecords = Array.from(this.cache.values());\n this.cache.clear();\n const disposal = Promise.allSettled(pendingRecords).then(async (settled) => {\n await Promise.allSettled(\n settled.flatMap((entry) =>\n entry.status === \"fulfilled\" ? [this.disposeRecord(entry.value)] : []),\n );\n });\n this.trackDisposal(disposal);\n }\n\n private async disposeRecord(record: NamespaceBackendRecord): Promise<void> {\n if (record.disposed) return;\n record.disposed = true;\n const dispose = (record.backend as { dispose?: () => void | Promise<void> }).dispose;\n await dispose?.call(record.backend);\n }\n\n /** Release any per-namespace backend handles held by cached records. */\n async dispose(): Promise<void> {\n await this.drainPendingDisposals();\n const pendingRecords = Array.from(this.cache.values());\n this.cache.clear();\n const settled = await Promise.allSettled(pendingRecords);\n await Promise.allSettled(\n settled.flatMap((entry) =>\n entry.status === \"fulfilled\" ? [this.disposeRecord(entry.value)] : []),\n );\n }\n\n /**\n * Disable namespace search for this runtime while retaining the resolved\n * records as unavailable/missing. This releases concrete QMD/daemon handles\n * without letting later reads recreate a search backend after the root path\n * has been switched to NoopSearchBackend for a missing collection.\n */\n async disableSearchBackends(): Promise<void> {\n const settled = await Promise.allSettled(this.cache.values());\n await Promise.allSettled(\n settled.flatMap((entry) => {\n if (entry.status !== \"fulfilled\") return [];\n entry.value.available = false;\n entry.value.collectionState = \"missing\";\n return [this.disposeRecord(entry.value)];\n }),\n );\n }\n\n private async backendRecordFor(\n namespace: string,\n execution?: SearchExecutionOptions,\n ): Promise<NamespaceBackendRecord> {\n await this.drainPendingDisposals();\n const key = namespace.trim() || this.config.defaultNamespace;\n const existing = this.cache.get(key);\n if (existing) return await existing;\n\n const pending = this.createBackendRecordFor(key, execution, {\n autoCreateCollection: true,\n abortAsUnavailable: false,\n failOpenMissingGuardedCollection: true,\n }).catch((error) => {\n this.cache.delete(key);\n throw error;\n });\n\n this.cache.set(key, pending);\n return await pending;\n }\n\n private async createBackendRecordFor(\n namespace: string,\n execution: SearchExecutionOptions | undefined,\n options: BackendRecordOptions,\n ): Promise<NamespaceBackendRecord> {\n const key = namespace.trim() || this.config.defaultNamespace;\n const storage = await this.storageRouter.storageFor(key);\n const useLegacyDefaultCollection =\n key === this.config.defaultNamespace && storage.dir === this.config.memoryDir;\n const filtersNestedNamespaces =\n resolveNamespaceCapabilities(this.config).namespaces === true && useLegacyDefaultCollection;\n const rootHostEmbeddingScope =\n (this.config as NamespaceScopedSearchConfig).hostEmbeddingProviderScope ??\n this.config.memoryDir;\n const scopedConfig: NamespaceScopedSearchConfig = {\n ...this.config,\n memoryDir: storage.dir,\n hostEmbeddingProviderScope: rootHostEmbeddingScope,\n qmdCollection: namespaceCollectionName(this.config.qmdCollection, key, {\n defaultNamespace: this.config.defaultNamespace,\n useLegacyDefaultCollection,\n }),\n };\n\n const backend = this.createBackend(scopedConfig);\n try {\n const availabilityProbe =\n options.autoCreateCollection || typeof backend.checkAvailability !== \"function\"\n ? backend.probe(execution)\n : backend.checkAvailability({ signal: execution?.signal });\n const available = await awaitWithAbort(availabilityProbe, execution?.signal).catch((error) => {\n if (error instanceof NamespaceSearchAbortError && !options.abortAsUnavailable) {\n throw error;\n }\n return false;\n });\n const collectionState = available\n ? await awaitWithAbort(\n // The legacy default namespace at the flat root (`filtersNestedNamespaces`)\n // shares its `memoryDir` with the `namespaces/` container, so its base\n // collection is a \"broad root\" that also indexes nested-namespace files.\n // We STILL auto-create it (issue #1929): the search side already targets\n // this exact base collection and `filterNamespaceSubtreeResults` strips\n // the nested `namespaces/` subtree out of its results, so index and\n // search stay symmetric. Skipping creation left a configured default\n // namespace (e.g. `geek`) with maintenance \"ran\" but no collection and 0\n // recall results. `ensureCollection` is a no-op when the collection is\n // already present, so legacy installs that pre-created the broad root are\n // unaffected.\n this.collectionStateForBackend(backend, storage.dir, scopedConfig.qmdCollection, {\n autoCreate: options.autoCreateCollection,\n failOpenMissingGuardedCollection: options.failOpenMissingGuardedCollection,\n execution,\n }),\n execution?.signal,\n ).catch((error) => {\n if (error instanceof NamespaceSearchAbortError && !options.abortAsUnavailable) {\n throw error;\n }\n return \"unknown\" as const;\n })\n : \"unknown\";\n return {\n backend,\n collection: scopedConfig.qmdCollection,\n memoryDir: storage.dir,\n available,\n collectionState,\n filtersNestedNamespaces,\n disposed: false,\n };\n } catch (error) {\n const dispose = (backend as { dispose?: () => void | Promise<void> }).dispose;\n if (dispose) {\n await Promise.resolve(dispose.call(backend)).catch(() => {});\n }\n throw error;\n }\n }\n\n private async collectionStateForBackend(\n backend: SearchBackend,\n memoryDir: string,\n collection: string,\n options: {\n autoCreate: boolean;\n failOpenMissingGuardedCollection: boolean;\n execution?: SearchExecutionOptions;\n },\n ): Promise<CollectionState> {\n if (!options.autoCreate) {\n if (!backend.checkCollection) return \"unknown\";\n const collectionState = await backend\n .checkCollection(collection, options.execution)\n .catch(() => \"unknown\" as const);\n return options.failOpenMissingGuardedCollection && collectionState === \"missing\"\n ? \"unknown\"\n : collectionState;\n }\n return await backend.ensureCollection(memoryDir, collection, options.execution).catch(() => \"unknown\" as const);\n }\n}\n\nclass NamespaceSearchAbortError extends Error {\n constructor() {\n super(\"operation aborted\");\n }\n}\n\nfunction awaitWithAbort<T>(operation: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) return operation;\n if (signal.aborted) return Promise.reject(new NamespaceSearchAbortError());\n\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => {\n signal.removeEventListener(\"abort\", onAbort);\n reject(new NamespaceSearchAbortError());\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n operation.then(resolve, reject).finally(() => {\n signal.removeEventListener(\"abort\", onAbort);\n });\n });\n}\n\nfunction filterNamespaceSubtreeResults(\n record: NamespaceBackendRecord,\n results: QmdSearchResult[],\n): QmdSearchResult[] {\n if (!record.filtersNestedNamespaces) return results;\n return results.filter((result) =>\n !pathIsInsideNamespaceSubtree(record.memoryDir, record.collection, result.path)\n );\n}\n\nfunction backendSearchLimit(\n record: NamespaceBackendRecord,\n maxResults: number,\n): number {\n if (!record.filtersNestedNamespaces) return maxResults;\n return Math.max(\n maxResults,\n maxResults * NESTED_NAMESPACE_FILTER_OVERFETCH_FACTOR,\n NESTED_NAMESPACE_FILTER_OVERFETCH_MIN,\n );\n}\n\nasync function updateBackendRecord(\n record: NamespaceBackendRecord,\n execution?: SearchExecutionOptions,\n options?: { strict?: boolean; force?: boolean },\n): Promise<void> {\n if (options?.strict === true) {\n if (\n // Forced refreshes must skip the throttled all-collections update: its\n // success has to prove the current writes are indexed, which a throttle\n // skip cannot.\n options.force !== true &&\n record.backend.updatesAllCollections?.() === true &&\n typeof record.backend.updateStrict === \"function\"\n ) {\n await record.backend.updateStrict(execution);\n return;\n }\n if (typeof record.backend.updateCollectionStrict === \"function\") {\n await record.backend.updateCollectionStrict(record.collection, execution);\n return;\n }\n // Backends with no strict variant (Orama, LanceDB, Meilisearch, remote) have a fail-open\n // update(): it resolves when it returned early or failed. Their updateCompleted() says\n // whether the refresh ran, so strict callers get a failure instead of a fake success.\n const completed = await record.backend.updateCompleted?.(execution);\n if (completed !== undefined) {\n if (!completed) throw new Error(\"search backend update did not complete\");\n return;\n }\n }\n await record.backend.update(execution);\n}\n\nasync function embedBackendRecord(\n record: NamespaceBackendRecord,\n options?: { strict?: boolean },\n): Promise<void> {\n if (options?.strict === true) {\n if (typeof record.backend.embedCollectionStrict === \"function\") {\n await record.backend.embedCollectionStrict(record.collection);\n return;\n }\n if (typeof record.backend.embedStrict === \"function\") {\n await record.backend.embedStrict();\n return;\n }\n }\n if (typeof record.backend.embedCollection === \"function\") {\n await record.backend.embedCollection(record.collection);\n return;\n }\n await record.backend.embed();\n}\n\nfunction daemonModeForBackend(backend: SearchBackend): boolean | null {\n return \"isDaemonMode\" in backend && typeof backend.isDaemonMode === \"function\"\n ? backend.isDaemonMode() === true\n : null;\n}\n\nfunction isContainedPath(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return (\n relative === \"\" ||\n (relative !== \"..\" && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative))\n );\n}\n\nfunction pathIsInsideNamespaceSubtree(\n memoryDir: string,\n collection: string,\n resultPath: string | undefined,\n): boolean {\n if (!resultPath) return false;\n const normalizedResultPath = normalizeQmdResultPath(resultPath, collection);\n const memoryRoot = path.resolve(memoryDir);\n const namespacesRoot = path.join(memoryRoot, \"namespaces\");\n const candidate = path.isAbsolute(normalizedResultPath)\n ? path.normalize(normalizedResultPath)\n : path.resolve(memoryRoot, normalizedResultPath);\n return isContainedPath(namespacesRoot, candidate);\n}\n\n/**\n * Reject a QMD hit whose resolved path escapes the queried namespace's storage\n * root (#2077). `resolvedPath` is the already-resolved value produced by\n * `resolveNamespaceResultPath`; a stale/corrupt per-namespace collection can\n * yield an absolute path owned by another namespace, and stamping it with the\n * queried namespace would surface a foreign-namespace memory downstream.\n *\n * Lexical containment runs first — it is cheap and the only check possible when\n * the file is not yet on disk (a stale collection may cite a since-deleted\n * path). When both root and candidate exist, their filesystem realpaths are\n * canonicalized and re-checked so a symlink planted inside the namespace root\n * cannot resolve into another namespace or outside storage. An unresolved path\n * (ENOENT) keeps the lexical result rather than rejecting the stale-collection\n * case this guard targets.\n */\nfunction resolvedPathIsInsideNamespaceRoot(memoryDir: string, resolvedPath: string): boolean {\n const root = path.resolve(memoryDir);\n const candidate = path.isAbsolute(resolvedPath)\n ? path.normalize(resolvedPath)\n : path.resolve(root, resolvedPath);\n if (!isContainedPath(root, candidate)) return false;\n try {\n return isContainedPath(realpathSync.native(root), realpathSync.native(candidate));\n } catch {\n return true;\n }\n}\nexport function normalizeQmdResultPath(resultPath: string, collection: string): string {\n let value = resultPath.trim();\n if (value.startsWith(\"qmd://\")) {\n try {\n const parsed = new URL(value);\n if (parsed.protocol === \"qmd:\" && parsed.hostname === collection) {\n value = decodeURIComponent(parsed.pathname.replace(/^\\/+/, \"\"));\n }\n } catch {\n const remainder = value.slice(\"qmd://\".length);\n const slashIndex = remainder.indexOf(\"/\");\n if (slashIndex !== -1) {\n value = remainder.slice(slashIndex + 1);\n }\n }\n }\n\n // Only strip a genuine QMD collection prefix — never when the collection name\n // collides with a memory category dir (e.g. qmdCollection == \"facts\"), or a\n // bare \"facts/<id>.md\" hit would be corrupted to \"<id>.md\" and fail namespace\n // reads/access tracking (#2020).\n const collectionPrefix = `${collection}/`;\n if (value.startsWith(collectionPrefix) && !ALL_CATEGORY_DIRS.includes(collection)) {\n value = value.slice(collectionPrefix.length);\n }\n return value;\n}\nexport function resolveNamespaceResultPath(\n memoryDir: string,\n collection: string,\n resultPath: string,\n): string {\n const normalized = normalizeQmdResultPath(resultPath, collection);\n if (path.isAbsolute(normalized)) return normalized;\n const root = path.resolve(memoryDir);\n const resolved = path.resolve(root, normalized);\n const relative = path.relative(root, resolved);\n if (relative === \"\" || relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n return resultPath;\n }\n return resolved;\n}\nfunction mergeNamespaceSearchResults(\n lists: Array<{ namespace: string; results: QmdSearchResult[] }>,\n maxResults: number,\n): QmdSearchResult[] {\n const merged = new Map<string, QmdSearchResult>();\n\n for (const { namespace, results } of lists) {\n for (const result of results) {\n const key = `${namespace}\\0${result.path || result.docid}`;\n const existing = merged.get(key);\n if (!existing) {\n merged.set(key, { ...result, namespace });\n continue;\n }\n if (result.score > existing.score) {\n merged.set(key, {\n ...result,\n namespace,\n snippet: existing.snippet || result.snippet || \"\",\n });\n }\n }\n }\n\n return [...merged.values()]\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults);\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/buffer-persistence.ts"],"sourcesContent":["import type { BufferEntryState, BufferState, BufferSurpriseEvent, BufferTurn } from \"./types.js\";\nimport type { BufferSessionSaveOutcome } from \"./storage/buffer-drain-claims.js\";\n\n/**\n * The buffer persistence surface SmartBuffer depends on.\n *\n * `saveBufferSession` / `deleteBufferSession` are the per-session incremental\n * writes (one file per session); when a storage backend does not provide\n * them, the buffer falls back to the whole-state `saveBuffer`. Both are\n * optional in the type so a storage double only has to implement what it\n * actually supports.\n */\nexport interface BufferStorage {\n loadBuffer(): Promise<BufferState>;\n saveBuffer(state: BufferState): Promise<void>;\n /** Reports \"handed_over\" when the session is claimed by `remnic buffer drain` and the entry moved into the claim instead of a session file. Legacy doubles may return void (treated as \"saved\"). */\n saveBufferSession?(key: string, entry: BufferEntryState): Promise<BufferSessionSaveOutcome | void>;\n deleteBufferSession?(key: string): Promise<void>;\n /** Sync claim check for the extraction gate; omitted when the backend has no drain-claim store. */\n isBufferSessionClaimed?(key: string): boolean;\n /** Optional surprise-telemetry sink; older storage and test doubles omit it. */\n appendBufferSurpriseEvents?(events: BufferSurpriseEvent[]): Promise<number>;\n}\n\n/** What one persist pass changed beyond plain writes. */\nexport interface BufferPersistOutcome {\n /** Sessions whose in-memory entry moved into a drain claim: the caller must evict them (a claimed session no longer lives in this process). */\n handedOver: string[];\n /** Per session, the turns a drain already extracted: the caller subtracts them from its in-memory copy. */\n drainedTurns: Map<string, BufferTurn[]>;\n}\n\n/**\n * Tracks which sessions' persisted files are stale and writes only those.\n *\n * Marks are cleared only after a durable write of that session. All calls\n * happen under SmartBuffer's serialized mutation chain: one writer.\n */\nexport class BufferSessionPersistence {\n private readonly dirty = new Set<string>();\n private readonly deleted = new Set<string>();\n\n markDirty(key: string): void {\n this.dirty.add(key);\n this.deleted.delete(key);\n }\n\n markDeleted(key: string): void {\n this.deleted.add(key);\n this.dirty.delete(key);\n }\n\n /** Eviction removed `key` from memory: its session file must go, any pending write is moot. */\n markEvicted(keys: readonly string[]): void {\n for (const key of keys) this.markDeleted(key);\n }\n\n hasPending(): boolean {\n return this.dirty.size > 0 || this.deleted.size > 0;\n }\n\n /**\n * Persist buffer state. On session-capable storage this writes ONLY the\n * sessions whose state changed since the last successful persist and\n * deletes files for evicted/reset sessions, so the observe path never\n * re-serializes other sessions' turns. On legacy storage it falls back to\n * the whole-state saveBuffer. Files are deleted before dirty writes: a\n * crash mid-persist can then only resurrect already-evicted data (the same\n * exposure as a whole-file rewrite), never drop sessions the state says\n * exist.\n */\n async persist(\n storage: BufferStorage,\n state: BufferState,\n peekEntry: (key: string) => BufferEntryState | undefined,\n ): Promise<BufferPersistOutcome> {\n const saveSession = storage.saveBufferSession?.bind(storage);\n const deleteSession = storage.deleteBufferSession?.bind(storage);\n if (!saveSession || !deleteSession) {\n await storage.saveBuffer(state);\n this.dirty.clear();\n this.deleted.clear();\n return { handedOver: [], drainedTurns: new Map() };\n }\n const handedOver: string[] = [];\n const drainedTurns = new Map<string, BufferTurn[]>();\n for (const key of [...this.deleted]) {\n await deleteSession(key);\n this.deleted.delete(key);\n }\n for (const key of [...this.dirty]) {\n const entry = peekEntry(key);\n if (!entry) {\n // A dirty key without state must not be written from a fabricated\n // empty entry: treat it as a deletion instead.\n this.markDeleted(key);\n continue;\n }\n const outcome = await saveSession(key, entry);\n this.dirty.delete(key);\n // \"handed_over\": the entry moved into a drain claim. Content is durable\n // in the claim, so this key's pending state is settled — the caller\n // evicts the in-memory entry on its side.\n if (outcome?.status === \"handed_over\") handedOver.push(key);\n // A drain tombstone: the turns it extracted must leave this process's\n // copy too, or the owner would extract them a second time.\n if (outcome?.drainedTurns && outcome.drainedTurns.length > 0) drainedTurns.set(key, outcome.drainedTurns);\n }\n return { handedOver, drainedTurns };\n }\n}\n"],"mappings":";AAsCO,IAAM,2BAAN,MAA+B;AAAA,EACnB,QAAQ,oBAAI,IAAY;AAAA,EACxB,UAAU,oBAAI,IAAY;AAAA,EAE3C,UAAU,KAAmB;AAC3B,SAAK,MAAM,IAAI,GAAG;AAClB,SAAK,QAAQ,OAAO,GAAG;AAAA,EACzB;AAAA,EAEA,YAAY,KAAmB;AAC7B,SAAK,QAAQ,IAAI,GAAG;AACpB,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA;AAAA,EAGA,YAAY,MAA+B;AACzC,eAAW,OAAO,KAAM,MAAK,YAAY,GAAG;AAAA,EAC9C;AAAA,EAEA,aAAsB;AACpB,WAAO,KAAK,MAAM,OAAO,KAAK,KAAK,QAAQ,OAAO;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,QACJ,SACA,OACA,WAC+B;AAC/B,UAAM,cAAc,QAAQ,mBAAmB,KAAK,OAAO;AAC3D,UAAM,gBAAgB,QAAQ,qBAAqB,KAAK,OAAO;AAC/D,QAAI,CAAC,eAAe,CAAC,eAAe;AAClC,YAAM,QAAQ,WAAW,KAAK;AAC9B,WAAK,MAAM,MAAM;AACjB,WAAK,QAAQ,MAAM;AACnB,aAAO,EAAE,YAAY,CAAC,GAAG,cAAc,oBAAI,IAAI,EAAE;AAAA,IACnD;AACA,UAAM,aAAuB,CAAC;AAC9B,UAAM,eAAe,oBAAI,IAA0B;AACnD,eAAW,OAAO,CAAC,GAAG,KAAK,OAAO,GAAG;AACnC,YAAM,cAAc,GAAG;AACvB,WAAK,QAAQ,OAAO,GAAG;AAAA,IACzB;AACA,eAAW,OAAO,CAAC,GAAG,KAAK,KAAK,GAAG;AACjC,YAAM,QAAQ,UAAU,GAAG;AAC3B,UAAI,CAAC,OAAO;AAGV,aAAK,YAAY,GAAG;AACpB;AAAA,MACF;AACA,YAAM,UAAU,MAAM,YAAY,KAAK,KAAK;AAC5C,WAAK,MAAM,OAAO,GAAG;AAIrB,UAAI,SAAS,WAAW,cAAe,YAAW,KAAK,GAAG;AAG1D,UAAI,SAAS,gBAAgB,QAAQ,aAAa,SAAS,EAAG,cAAa,IAAI,KAAK,QAAQ,YAAY;AAAA,IAC1G;AACA,WAAO,EAAE,YAAY,aAAa;AAAA,EACpC;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/entity-mention-index-cache.ts"],"sourcesContent":["import path from \"node:path\";\nimport { mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { raceAbort } from \"./abort-error.js\";\nimport { entityMentionEpoch } from \"./entity-mention-epoch.js\";\nimport { log } from \"./logger.js\";\n\n/**\n * In-memory entity mention index.\n *\n * Scope (namespaces, dirs, secure-store key, alias map, native revision) is\n * the cache key. Mention epoch and entity-file mutation are identity inside\n * the slot, not part of the key:\n *\n * - metadata-only corpus bumps do not move the epoch, so the next recall hits\n * - entity-file changes rebuild from entity files before the recall returns,\n * reusing memory snippets (no fact scan). A new entity is visible on that\n * recall; its memory snippets arrive with the next full reconcile. A new\n * canonical id schedules that reconcile. Callers share one scan. One\n * caller's abort stops that caller waiting and leaves the scan running\n * for the others\n * - memory create/edit/delete moves the epoch. The recall serves the last\n * index immediately and one background reconcile runs, plus at most one\n * follow-up if that scan overlaps a later write. A recall during either\n * scan does not grant another follow-up. Further rescans wait for the\n * next recall. After writes quiesce and `settleEntityMentionIndex`\n * finishes, the index matches a full rebuild\n * - secure-store key, alias map, and native revision stay in the key, so a\n * change is a miss and awaits a full rebuild. A locked store never reads\n * another key's plaintext. Evicting that older scope also stops its scan\n * from publishing the persisted index\n * - a persisted index is readable per storage root. A process-wide cache\n * clear blocks every root until that root's own post-clear build publishes.\n * One root publishing does not reopen another root's file\n */\n\nconst MAX_SLOTS = 32;\nconst SETTLE_ROUNDS = 8;\n\nexport type EntityMentionIdentity = {\n mentionEpoch: string;\n entityMutation: string;\n};\n\ntype ScopedStorage = {\n dir: string;\n hotCacheKeyId?(): string;\n entityAliases?: Readonly<Record<string, string>> | null;\n getEntityMutationVersion?(): number;\n};\n\ntype Slot = {\n scopeKey: string;\n index: unknown;\n identity: EntityMentionIdentity;\n token: number;\n rebuild: Promise<void> | null;\n entityJob: Promise<void> | null;\n again: boolean;\n followUps: number;\n currentIdentity: () => EntityMentionIdentity;\n buildFull: (abortSignal?: AbortSignal) => Promise<unknown>;\n};\n\nconst slots = new Map<string, Slot>();\nconst rebuildsByScope = new Map<string, number>();\nconst GENERATION_STRIDE = 1_000_000_000;\nlet clearEpoch = 0;\nconst localGenerationByDir = new Map<string, number>();\nconst publishedGenerationByDir = new Map<string, number>();\nconst persistTailByDir = new Map<string, Promise<void>>();\n\nfunction resolveMentionDir(dir: string): string {\n return path.resolve(dir);\n}\n\nexport function entityMentionCacheGeneration(dir: string): number {\n const resolved = resolveMentionDir(dir);\n return clearEpoch * GENERATION_STRIDE + (localGenerationByDir.get(resolved) ?? 0);\n}\n\nexport function entityMentionPersistedIndexReadable(dir: string): boolean {\n const resolved = resolveMentionDir(dir);\n const local = localGenerationByDir.get(resolved) ?? 0;\n if (clearEpoch === 0 && local === 0) return true;\n return publishedGenerationByDir.get(resolved) === entityMentionCacheGeneration(resolved);\n}\n\nfunction dirsInScope(scopeKey: string): string[] {\n const nsEnd = scopeKey.indexOf(\"\\u001e\");\n const storageEnd = nsEnd === -1 ? -1 : scopeKey.indexOf(\"\\u001e\", nsEnd + 1);\n if (nsEnd === -1 || storageEnd === -1) return [];\n return scopeKey\n .slice(nsEnd + 1, storageEnd)\n .split(\"\\u001f\")\n .map((part) => part.split(\"\\u001d\")[0] ?? \"\")\n .filter((dir) => dir.length > 0)\n .map((dir) => resolveMentionDir(dir));\n}\n\nfunction persistedMentionIndexFiles(scopeKey: string): string[] {\n return dirsInScope(scopeKey).map((dir) => path.join(dir, \"state\", \"entity-mention-index.json\"));\n}\n\nfunction bumpScopeDirs(scopeKey: string): void {\n for (const dir of dirsInScope(scopeKey)) {\n localGenerationByDir.set(dir, (localGenerationByDir.get(dir) ?? 0) + 1);\n }\n}\n\nfunction captureDirGenerations(scopeKey: string): Map<string, number> {\n const captured = new Map<string, number>();\n for (const dir of dirsInScope(scopeKey)) captured.set(dir, entityMentionCacheGeneration(dir));\n return captured;\n}\n\nfunction generationsCurrent(captured: Map<string, number>): boolean {\n for (const [dir, generation] of captured) {\n if (entityMentionCacheGeneration(dir) !== generation) return false;\n }\n return true;\n}\n\nfunction publishDirGenerations(captured: Map<string, number>): void {\n for (const [dir, generation] of captured) {\n if (entityMentionCacheGeneration(dir) === generation) publishedGenerationByDir.set(dir, generation);\n }\n}\n\nexport async function writePersistedEntityMentionIndex(\n dir: string,\n generation: number,\n content: string,\n): Promise<void> {\n const resolved = resolveMentionDir(dir);\n const previous = persistTailByDir.get(resolved) ?? Promise.resolve();\n const run = previous.catch(() => undefined).then(async () => {\n if (entityMentionCacheGeneration(resolved) !== generation) return;\n const file = path.join(resolved, \"state\", \"entity-mention-index.json\");\n await mkdir(path.dirname(file), { recursive: true });\n const current = await readFile(file, \"utf-8\").catch(() => \"\");\n if (current === content) return;\n if (entityMentionCacheGeneration(resolved) !== generation) return;\n await writeFile(file, content, \"utf-8\");\n });\n persistTailByDir.set(resolved, run.then(() => undefined, () => undefined));\n await run;\n}\n\nfunction discardPersistedMentionIndex(scopeKey: string): void {\n for (const file of persistedMentionIndexFiles(scopeKey)) {\n void rm(file, { force: true }).catch(() => undefined);\n }\n}\n\nfunction throwIfMentionAborted(signal?: AbortSignal): void {\n if (!signal?.aborted) return;\n const reason = signal.reason;\n throw reason instanceof Error ? reason : new Error(\"entity mention rebuild aborted\");\n}\n\nfunction compareStrings(left: string, right: string): number {\n if (left < right) return -1;\n if (left > right) return 1;\n return 0;\n}\n\nfunction deduped(values: readonly string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const value of values) {\n const trimmed = value.trim();\n if (trimmed.length === 0 || seen.has(trimmed)) continue;\n seen.add(trimmed);\n out.push(trimmed);\n }\n return out;\n}\n\nfunction sameIdentity(left: EntityMentionIdentity, right: EntityMentionIdentity): boolean {\n return left.mentionEpoch === right.mentionEpoch && left.entityMutation === right.entityMutation;\n}\n\nfunction noteRebuild(scopeKey: string): void {\n rebuildsByScope.set(scopeKey, (rebuildsByScope.get(scopeKey) ?? 0) + 1);\n}\n\nfunction scopeFamily(scopeKey: string): string {\n const nsEnd = scopeKey.indexOf(\"\\u001e\");\n const storageEnd = nsEnd === -1 ? -1 : scopeKey.indexOf(\"\\u001e\", nsEnd + 1);\n const namespaceKey = nsEnd === -1 ? scopeKey : scopeKey.slice(0, nsEnd);\n const storageKey = nsEnd === -1 || storageEnd === -1 ? \"\" : scopeKey.slice(nsEnd + 1, storageEnd);\n const dirs = storageKey\n .split(\"\\u001f\")\n .map((part) => part.split(\"\\u001d\")[0] ?? \"\")\n .join(\"\\u001f\");\n return `${namespaceKey}\\u001e${dirs}`;\n}\n\nfunction forgetScope(scopeKey: string, bumpPersisted = false): void {\n const slot = slots.get(scopeKey);\n if (slot) {\n slot.token += 1;\n slot.again = false;\n }\n if (bumpPersisted) bumpScopeDirs(scopeKey);\n slots.delete(scopeKey);\n rebuildsByScope.delete(scopeKey);\n}\n\nfunction evictOldest(scopeKey: string): void {\n if (slots.size < MAX_SLOTS || slots.has(scopeKey)) return;\n const oldest = slots.keys().next().value;\n if (typeof oldest === \"string\") forgetScope(oldest);\n}\n\nfunction evictSameFamily(scopeKey: string): void {\n const family = scopeFamily(scopeKey);\n for (const key of [...slots.keys()]) {\n if (key !== scopeKey && scopeFamily(key) === family) forgetScope(key, true);\n }\n}\n\nfunction wantsSnippetReconcile(index: unknown): boolean {\n return (\n !!index &&\n typeof index === \"object\" &&\n (index as { pendingSnippetReconcile?: boolean }).pendingSnippetReconcile === true\n );\n}\n\nfunction armRebuild(slot: Slot): void {\n // A recall that arrives while a scan is already running must not grant\n // another follow-up. The chain stops after the one already armed.\n if (!slot.rebuild) slot.followUps = 1;\n startRebuild(slot);\n}\n\nfunction startRebuild(slot: Slot): void {\n // A recall that arrives during a scan is not a write. The follow-up is\n // armed only when the scan's end identity differs from the identity it\n // started with. Setting `again` here made every concurrent recall pay a\n // second full corpus scan after a stable rebuild.\n if (slot.rebuild) return;\n const token = slot.token;\n const captured = captureDirGenerations(slot.scopeKey);\n noteRebuild(slot.scopeKey);\n const started = slot.currentIdentity();\n slot.again = false;\n const handle: { pending: Promise<void> | null } = { pending: null };\n handle.pending = slot\n .buildFull(undefined)\n .then((index) => {\n if (slot.token !== token || !generationsCurrent(captured)) return;\n publishDirGenerations(captured);\n const ended = slot.currentIdentity();\n slot.index = index;\n if (sameIdentity(started, ended)) {\n slot.identity = ended;\n return;\n }\n // Scan overlapped later writes. Keep the start identity so the next\n // recall stays stale and exactly one follow-up reconcile runs.\n slot.identity = started;\n slot.again = true;\n })\n .catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n log.warn(`entity mention index rebuild failed: ${message}`);\n })\n .finally(() => {\n if (slot.rebuild === handle.pending) slot.rebuild = null;\n // One automatic follow-up per caller-started rebuild. A store that keeps\n // changing during every scan waits for the next recall (or settle) instead\n // of chaining full scans with nobody waiting.\n if (!slot.again || slot.followUps <= 0) return;\n slot.again = false;\n slot.followUps -= 1;\n startRebuild(slot);\n });\n slot.rebuild = handle.pending;\n}\n\nexport function entityMentionScopeKey(\n namespaces: readonly string[] | undefined,\n storages: readonly ScopedStorage[],\n nativeRevision: string,\n): string {\n const namespaceKey = deduped(namespaces ?? []).join(\"\\u001f\");\n const storageKey = storages\n .map((storage) => {\n const aliases = Object.entries(storage.entityAliases ?? {}).sort(([left], [right]) =>\n compareStrings(left, right),\n );\n return `${path.resolve(storage.dir)}\\u001d${storage.hotCacheKeyId?.() ?? \"\"}\\u001d${JSON.stringify(aliases)}`;\n })\n .join(\"\\u001f\");\n return `${namespaceKey}\\u001e${storageKey}\\u001e${nativeRevision}`;\n}\n\nexport function entityMentionIdentity(storages: readonly ScopedStorage[]): EntityMentionIdentity {\n return {\n mentionEpoch: storages.map((storage) => String(entityMentionEpoch.current(storage.dir))).join(\"\\u001f\"),\n entityMutation: storages.map((storage) => String(storage.getEntityMutationVersion?.() ?? 0)).join(\"\\u001f\"),\n };\n}\n\nexport function entityMentionFullRebuildsStarted(scopeKey: string): number {\n return rebuildsByScope.get(scopeKey) ?? 0;\n}\n\nexport function entityMentionIndexScopeKeys(): string[] {\n return [...slots.keys()];\n}\n\nexport function dropEntityMentionIndexCache(scopeKey?: string): void {\n if (scopeKey === undefined) {\n clearEpoch += 1;\n for (const key of [...slots.keys()]) {\n discardPersistedMentionIndex(key);\n forgetScope(key);\n }\n return;\n }\n discardPersistedMentionIndex(scopeKey);\n forgetScope(scopeKey, true);\n}\n\nexport async function settleEntityMentionIndex(scopeKey?: string): Promise<void> {\n const matches = (slot: Slot) => scopeKey === undefined || slot.scopeKey === scopeKey;\n for (let round = 0; round < SETTLE_ROUNDS; round += 1) {\n for (const slot of slots.values()) {\n if (!matches(slot)) continue;\n if (!slot.rebuild && slot.again) {\n slot.again = false;\n startRebuild(slot);\n }\n }\n const pending = [...slots.values()].filter((slot) => matches(slot) && slot.rebuild).map((slot) => slot.rebuild!);\n if (pending.length === 0) return;\n await Promise.all(pending);\n }\n}\n\nexport async function resolveEntityMentionIndex<T>(options: {\n scopeKey: string;\n currentIdentity: () => EntityMentionIdentity;\n buildFull: (abortSignal?: AbortSignal) => Promise<T>;\n rebuildEntities: (previous: T, abortSignal?: AbortSignal) => Promise<T>;\n abortSignal?: AbortSignal;\n}): Promise<T> {\n const live = options.currentIdentity();\n const existing = slots.get(options.scopeKey);\n if (!existing) {\n evictSameFamily(options.scopeKey);\n evictOldest(options.scopeKey);\n noteRebuild(options.scopeKey);\n const captured = captureDirGenerations(options.scopeKey);\n const started = options.currentIdentity();\n const index = await options.buildFull(options.abortSignal);\n // A clear or family eviction during this first scan must not publish it.\n if (!generationsCurrent(captured)) return index;\n publishDirGenerations(captured);\n const ended = options.currentIdentity();\n evictSameFamily(options.scopeKey);\n evictOldest(options.scopeKey);\n const slot: Slot = {\n scopeKey: options.scopeKey,\n index,\n identity: sameIdentity(started, ended) ? ended : started,\n token: 0,\n rebuild: null,\n entityJob: null,\n again: false,\n followUps: 0,\n currentIdentity: options.currentIdentity,\n buildFull: options.buildFull,\n };\n slots.set(options.scopeKey, slot);\n if (!sameIdentity(started, ended)) armRebuild(slot);\n return index;\n }\n existing.currentIdentity = options.currentIdentity;\n existing.buildFull = options.buildFull;\n while (existing.identity.entityMutation !== options.currentIdentity().entityMutation) {\n throwIfMentionAborted(options.abortSignal);\n const pending = existing.entityJob;\n if (pending) {\n await raceAbort(pending, options.abortSignal, \"entity mention rebuild aborted\");\n continue;\n }\n const liveNow = options.currentIdentity();\n const epochBefore = existing.identity.mentionEpoch;\n const mutationBefore = liveNow.entityMutation;\n let release = (): void => {};\n const gate = new Promise<void>((resolve) => {\n release = resolve;\n });\n existing.entityJob = gate;\n existing.token += 1;\n existing.again = false;\n const token = existing.token;\n const captured = captureDirGenerations(existing.scopeKey);\n // Shared scan. This caller's abort stops its wait and leaves the scan\n // running. Publish when the scan settles, even if this caller has left.\n // A family eviction bumps the token and the root generation; this scan\n // must not install its result or start a follow-up that would republish.\n const scan = options.rebuildEntities(existing.index as T, undefined);\n void scan\n .then(\n (rebuilt) => {\n if (existing.token !== token || !generationsCurrent(captured)) return;\n const now = options.currentIdentity();\n existing.index = rebuilt;\n const epochStable = epochBefore === liveNow.mentionEpoch && epochBefore === now.mentionEpoch;\n const mutationStable = mutationBefore === now.entityMutation;\n existing.identity = {\n entityMutation: mutationStable ? now.entityMutation : mutationBefore,\n mentionEpoch: epochStable ? now.mentionEpoch : epochBefore,\n };\n if (existing.identity.mentionEpoch !== now.mentionEpoch || wantsSnippetReconcile(rebuilt)) {\n armRebuild(existing);\n }\n },\n () => {},\n )\n .finally(() => {\n if (existing.entityJob === gate) existing.entityJob = null;\n release();\n });\n const rebuilt = await raceAbort(scan, options.abortSignal, \"entity mention rebuild aborted\");\n throwIfMentionAborted(options.abortSignal);\n return rebuilt;\n }\n if (existing.identity.mentionEpoch !== live.mentionEpoch) {\n armRebuild(existing);\n return existing.index as T;\n }\n return existing.index as T;\n}\n\nentityMentionEpoch.registerReset(() => {\n dropEntityMentionIndexCache();\n});\n"],"mappings":"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","names":["rebuilt"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/orchestration/buffer-drain-runner.ts"],"sourcesContent":["/** Claim-backed extraction-buffer drain (`remnic buffer drain` worker AND the */\n\nimport { hostname } from \"node:os\";\nimport type { BufferStorage } from \"../buffer-persistence.js\";\nimport { type ExtractionScopeGroup, describeError, groupTurnsByExtractionScope } from \"../buffer-turn-helpers.js\";\nimport { SmartBuffer } from \"../buffer.js\";\nimport { resolveMemoryLifecycleCapabilities } from \"../capabilities.js\";\nimport { shouldSkipImplicitExtraction } from \"../explicit-capture.js\";\nimport { log } from \"../logger.js\";\nimport type { ResolvedScopeProfilePlan } from \"../namespaces/scope-profiles.js\";\nimport type { Orchestrator } from \"../orchestrator.js\";\nimport { headerConnector, renderExtractionConversation, resolveSourceConnector } from \"../source-agent-qualifier.js\";\nimport type { BufferDrainClaims, DrainLease } from \"../storage/buffer-drain-claims.js\";\nimport type { BufferEntryState, BufferState, BufferTurn, PluginConfig } from \"../types.js\";\nimport { ExtractionDrain } from \"./extraction-drain.js\";\nimport type { ExtractionRunResult } from \"./extraction-run.js\";\nimport { selfDeps } from \"./self-deps.js\";\nimport { TurnIngestionCoordinator, type TurnIngestionDeps } from \"./turn-ingestion.js\";\n\nexport type DrainSessionStatus = \"drained\" | \"deferred\" | \"failed\" | \"skipped\";\n\nexport interface DrainSessionOutcome {\n key: string;\n status: DrainSessionStatus;\n /** Turns the claim held when the drain started. */\n turnsAtStart: number;\n /** Turns still in the claim when the drain stopped. */\n turnsRemaining: number;\n /** Facts the extraction persisted during this run. */\n persistedCount: number;\n /** Durable outputs (memories + entities) the extraction wrote. */\n durableOutputCount: number;\n /** Why the session was skipped or deferred (machine-readable). */\n reason?: string;\n /** Current-policy denials: scope groups whose authorization no longer holds. */\n denied?: { reason: string; turns: number }[];\n}\n\n/** Extraction results that mean \"current policy refuses this scope\": the\n * turns are valid backlog whose captured scope is no longer writable. */\nconst DENIED_SCOPE_REASONS: ReadonlySet<string> = new Set([\n \"pinned_namespace_unavailable\",\n \"namespace_write_revoked\",\n \"scope_profile_revoked\",\n // The resolved profile has no writable layer: the same current-policy\n // refusal family, surfaced from inside the shared runExtraction gate.\n \"scope_profile_no_writable_layer\",\n // captureMode=explicit: ordinary buffered conversation turns never create\n // memories; only deliberately accepted captures drain.\n \"capture_mode_explicit\",\n]);\n\nexport interface BufferDrainRunnerOptions {\n /** Identity of this drainer; owns the lease it takes. */\n ownerId: string;\n /** Lease lifetime in ms. The heartbeat keeps it alive while draining. */\n leaseMs?: number;\n /** Cancels the run at the next chunk boundary. */\n signal?: AbortSignal;\n /** Progress callback (per session, and per claim transition). */\n onEvent?: (event: DrainRunnerEvent) => void;\n}\n\n/** A failed drain run: the reason a chunk or the session could not finish. */\nexport type DrainExtractionError = Error;\n\nexport type DrainRunnerEvent =\n | { type: \"session_started\"; key: string; turns: number }\n | { type: \"session_finished\"; key: string; outcome: DrainSessionOutcome };\n\nconst DEFAULT_LEASE_MS = 120_000;\n\n/**\n * A `BufferStorage` view of one drain claim: the claim file IS the buffer.\n * Writes are the drain's progress (what is left to extract), so every\n * successfully extracted chunk is durably removed before the next one runs.\n */\nexport class ClaimBufferStorage implements BufferStorage {\n constructor(\n private readonly claims: BufferDrainClaims,\n private readonly key: string,\n private readonly ownerId: string,\n private readonly entry: BufferEntryState\n ) {}\n\n async loadBuffer(): Promise<BufferState> {\n const entries: NonNullable<BufferState[\"entries\"]> = Object.create(null);\n entries[this.key] = {\n turns: this.entry.turns,\n retainedTurns: this.entry.retainedTurns,\n lastExtractionAt: this.entry.lastExtractionAt,\n extractionCount: this.entry.extractionCount,\n };\n return {\n turns: this.entry.turns,\n lastExtractionAt: this.entry.lastExtractionAt,\n extractionCount: this.entry.extractionCount,\n entries,\n };\n }\n\n async saveBuffer(state: BufferState): Promise<void> {\n // Whole-state fallback (the claim covers one session): persist the entry.\n const entry = state.entries?.[this.key];\n await this.claims.writeEntry(\n this.key,\n this.ownerId,\n entry ?? { turns: [], lastExtractionAt: null, extractionCount: 0 },\n this.entry\n );\n }\n\n async saveBufferSession(key: string, entry: BufferEntryState): Promise<void> {\n await this.claims.writeEntry(key, this.ownerId, entry, key === this.key ? this.entry : undefined);\n }\n\n async deleteBufferSession(): Promise<void> {\n // The claim is removed by the ack path, never by a buffer eviction: the\n // drain decides when the session is finished.\n }\n}\n\n/** Per-invocation isolated execution context for one claim drain. */\ninterface ClaimDrainExecution {\n claimBuffer: SmartBuffer;\n coordinator: TurnIngestionCoordinator;\n executionDrain: ExtractionDrain;\n}\n\n/** The scopeProfileWritePlan option a scope group forwards to the queue. */\ninterface GroupPlanOption {\n scopeProfileWritePlan?: ResolvedScopeProfilePlan | null;\n}\n\n/** Durable counters a drain pass contributed. */\ninterface GroupCounts {\n persistedCount: number;\n durableOutputCount: number;\n}\n\n/** Pending turns across canonical + retained copies (the honest backlog count). */\nfunction pendingTurnCount(entry: BufferEntryState | undefined): number {\n if (!entry) return 0;\n return entry.turns.length + (entry.retainedTurns?.length ?? 0);\n}\n\n/** Load one claimed session. An unclaimed key is nothing to drain. */\nasync function loadClaimedSession(\n claims: BufferDrainClaims,\n key: string\n): Promise<\n | { kind: \"notClaimed\"; notClaimed: DrainSessionOutcome }\n | { kind: \"claimed\"; claim: { entry: BufferEntryState }; turnsAtStart: number }\n> {\n const claim = await claims.load(key);\n if (!claim) {\n return {\n kind: \"notClaimed\",\n notClaimed: {\n key,\n status: \"skipped\",\n turnsAtStart: 0,\n turnsRemaining: 0,\n persistedCount: 0,\n durableOutputCount: 0,\n reason: \"not_claimed\",\n },\n };\n }\n return { kind: \"claimed\", claim, turnsAtStart: pendingTurnCount(claim.entry) };\n}\n\nfunction busyLeaseOutcome(key: string, turnsAtStart: number): DrainSessionOutcome {\n return {\n key,\n status: \"skipped\",\n turnsAtStart,\n turnsRemaining: turnsAtStart,\n persistedCount: 0,\n durableOutputCount: 0,\n reason: \"claimed_by_another_drain\",\n };\n}\n\n/** Take the lease unless this drainer already holds it. A busy lease means\n * another drainer owns the claim — skip without touching content. */\nasync function adoptLease(\n claims: BufferDrainClaims,\n key: string,\n ownerId: string,\n leaseMs: number\n): Promise<DrainSessionOutcome | null> {\n const held = await claims.adopt(key, {\n ownerId,\n pid: process.pid,\n host: hostname(),\n acquiredAt: new Date().toISOString(),\n heartbeatAt: new Date().toISOString(),\n leaseMs,\n });\n return held === \"busy\" ? busyLeaseOutcome(key, pendingTurnCount((await claims.load(key))?.entry)) : null;\n}\n\nfunction assembleOutcome(\n key: string,\n turnsAtStart: number,\n verdict: { status: DrainSessionStatus; reason?: string; turnsRemaining: number },\n counts: { persistedCount: number; durableOutputCount: number },\n denied: Map<string, number>\n): DrainSessionOutcome {\n const outcome: DrainSessionOutcome = {\n key,\n status: verdict.status,\n turnsAtStart,\n turnsRemaining: verdict.turnsRemaining,\n persistedCount: counts.persistedCount,\n durableOutputCount: counts.durableOutputCount,\n };\n if (denied.size > 0) outcome.denied = [...denied].map(([deniedReason, turns]) => ({ reason: deniedReason, turns }));\n if (verdict.reason !== undefined) outcome.reason = verdict.reason;\n return outcome;\n}\n\n/** Ack an EMPTY claim. The ack verdict is the truth: live ingress can merge\n * between the empty read and the ack — a not_empty ack means pending work\n * exists; reload it, release OUR lease, report deferred, never a false\n * drained. */\nasync function ackEmptyClaim(\n claims: BufferDrainClaims,\n key: string,\n ownerId: string,\n turnsAtStart: number\n): Promise<DrainSessionOutcome> {\n const ackOutcome = await claims.ack(key, ownerId);\n if (ackOutcome !== \"acked\") {\n const reloaded = await claims.load(key);\n const turnsRemaining = pendingTurnCount(reloaded?.entry);\n await claims.releaseLease(key, ownerId);\n return {\n key,\n status: \"deferred\",\n turnsAtStart,\n turnsRemaining,\n persistedCount: 0,\n durableOutputCount: 0,\n reason: ackOutcome === \"not_empty\" ? \"ack_not_empty\" : \"ack_not_owner\",\n };\n }\n return { key, status: \"drained\", turnsAtStart, turnsRemaining: 0, persistedCount: 0, durableOutputCount: 0 };\n}\n\n/** another drainer took the claim, so this run must stop instead of writing */\nfunction startLeaseHeartbeat(\n claims: BufferDrainClaims,\n key: string,\n ownerId: string,\n leaseMs: number,\n abort: AbortController\n): NodeJS.Timeout {\n const heartbeat = setInterval(\n () => {\n void claims\n .heartbeat(key, ownerId, leaseMs)\n .then((state) => {\n if (state === \"lost\") abort.abort(new Error(`buffer drain: lease lost for session ${key}`));\n })\n .catch((err: Error) => {\n // A heartbeat that cannot be renewed is a lease that cannot be held:\n // stop this run cleanly at the next chunk boundary (the next run\n // resumes it).\n abort.abort(new Error(`buffer drain: lease heartbeat failed for session ${key}: ${describeError(err)}`));\n });\n },\n Math.max(1_000, Math.floor(leaseMs / 3))\n );\n heartbeat.unref?.();\n return heartbeat;\n}\n\n/** coordinator bound to it, and the no-drop chunked drain. Built per */\nfunction buildClaimExecution(\n orchestrator: Orchestrator,\n claims: BufferDrainClaims,\n key: string,\n ownerId: string,\n claim: { entry: BufferEntryState }\n): ClaimDrainExecution {\n const claimBuffer = new SmartBuffer(orchestrator.config, new ClaimBufferStorage(claims, key, ownerId, claim.entry));\n const isolatedRun = orchestrator.extractionRunCoordinatorForBuffer(claimBuffer);\n const coordinator = new TurnIngestionCoordinator(selfDeps<TurnIngestionDeps>(orchestrator));\n const config = orchestrator.config;\n const ingestionDeps = selfDeps<TurnIngestionDeps>(orchestrator);\n // No-drop: a repeatedly failing chunk stays in the claim as durable\n // retryable backlog — never side-filed, never counted as processed.\n const executionDrain = new ExtractionDrain(\n {\n config: {\n extractionMinUserTurns: config.extractionMinUserTurns,\n extractionMinChars: config.extractionMinChars,\n extractionMaxTurnChars: config.extractionMaxTurnChars,\n extractionChunkDropAfterFailures: config.extractionChunkDropAfterFailures,\n },\n extractionWindowBudgetChars: () => ingestionDeps.extractionWindowBudgetChars?.() ?? Number.POSITIVE_INFINITY,\n // Render-exact chunk sizing: the model clamp bounds the CONVERSATION\n // (truncateLocalExtractionConversation), so measure exactly that —\n // with the SAME source-classification gate extract() applies when it\n // prepends the connector header (header margin included when the\n // engine would render it).\n measureRenderedTurns: (turns) => {\n const sourceConnector = resolveMemoryLifecycleCapabilities(config).extractionScopeClassification\n ? resolveSourceConnector(turns)\n : undefined;\n return renderExtractionConversation(\n turns.map((turn) => ({ ...turn, content: turn.content.trim().slice(0, config.extractionMaxTurnChars) })),\n headerConnector(sourceConnector)\n ).conversation.length;\n },\n runExtraction: (turns, runOptions) => isolatedRun.runExtraction(turns, runOptions),\n getStorage: () => ingestionDeps.getStorage(),\n buffer: claimBuffer,\n },\n { dropFailedChunks: false }\n );\n return { claimBuffer, coordinator, executionDrain };\n}\n\n/** Plan semantics for one scope group: a pinned plan forwards as-is; a\n * planless ACCEPTED group pins null (existing flush behavior); unpinned\n * legacy groups pass NO option so runExtraction resolves the CURRENT\n * profile. */\nfunction planOptionForGroup(group: ExtractionScopeGroup): GroupPlanOption {\n if (group.scopeProfileWritePlan !== undefined) return { scopeProfileWritePlan: group.scopeProfileWritePlan };\n if (group.turns.some((turn) => turn.acceptedCapture === true)) return { scopeProfileWritePlan: null };\n return {};\n}\n\n/** captureMode=explicit: ordinary buffered conversation turns never create\n * memories — only deliberately accepted captures drain. */\nfunction explicitModeDenied(group: ExtractionScopeGroup, config: PluginConfig): boolean {\n return shouldSkipImplicitExtraction(config) && !group.turns.some((turn) => turn.acceptedCapture === true);\n}\n\ninterface GroupOutcome {\n turns: number;\n result?: ExtractionRunResult;\n}\n\ninterface ScopeDrainContext {\n config: PluginConfig;\n key: string;\n signal: AbortSignal;\n claimBuffer: SmartBuffer;\n coordinator: TurnIngestionCoordinator;\n executionDrain: ExtractionDrain;\n turns: BufferTurn[];\n}\n\n/** Drain a claimed session's turns ONE captured-scope group at a time: each\n * group keeps its own namespace/principal/plan pins, and the shared\n * extraction boundary re-applies CURRENT authorization from the turn's\n * ORIGINAL coding binding before any write. Groups run sequentially (FIFO\n * per key — the queue's same-key exclusion would serialize them anyway); a\n * denied group never blocks or clears the other groups. */\nasync function drainScopeGroups(\n ctx: ScopeDrainContext\n): Promise<{ groupOutcomes: GroupOutcome[]; groupError?: DrainExtractionError }> {\n const groupOutcomes: GroupOutcome[] = [];\n let groupError: DrainExtractionError | undefined;\n for (const group of groupTurnsByExtractionScope(ctx.turns)) {\n if (ctx.signal.aborted) break;\n if (explicitModeDenied(group, ctx.config)) {\n log.warn(\n `buffer drain: session ${ctx.key} denied capture_mode_explicit (${group.turns.length} ordinary turn(s) retained)`\n );\n groupOutcomes.push({\n turns: group.turns.length,\n result: { status: \"skipped\", reason: \"capture_mode_explicit\", persistedCount: 0, durableOutputCount: 0 },\n });\n continue;\n }\n const settleGate = Promise.withResolvers<{ cause?: unknown; result?: ExtractionRunResult }>();\n let done = false;\n const onSettle = (cause: unknown, result?: ExtractionRunResult): void => {\n if (done) return;\n done = true;\n settleGate.resolve({ cause, result });\n };\n // Settlement and callback EXIT are tracked separately: the lease is\n // released only after BOTH the queued closure settled and fully exited\n // (an abort settles through its listener while the closure may still be\n // persisting).\n const exited = Promise.withResolvers<void>();\n void ctx.coordinator\n .queueBufferedExtraction(group.turns, \"trigger_mode\", {\n bufferKey: ctx.key,\n clearBufferAfterExtraction: true,\n clearMatchingTurns: true,\n skipDedupeCheck: true,\n skipCharThreshold: true,\n skipUserTurnThreshold: true,\n // Automatic background work yields to fail-closed foreground flushes\n // (preemptRunning), same as the live trigger path.\n preemptable: true,\n abortSignal: ctx.signal,\n writeNamespaceOverride: group.writeNamespaceOverride,\n principalOverride: group.principalOverride,\n ...planOptionForGroup(group),\n execution: { buffer: ctx.claimBuffer, drain: ctx.executionDrain },\n onTaskSettled: (error, result) => settleGate.resolve({ cause: error, result }),\n onTaskExit: () => exited.resolve(),\n })\n .catch((cause: DrainExtractionError) => onSettle(cause));\n const [verdict] = await Promise.all([settleGate.promise, exited.promise]);\n const settled = verdict;\n if (settled.cause !== undefined) {\n // SAFETY: settle captures whatever the queued closure rejected with;\n // every drain rejection site throws Error values, and a rejection is\n // always a DrainExtractionError in this path.\n groupError = settled.cause as Error;\n break;\n }\n groupOutcomes.push({ turns: group.turns.length, result: settled.result });\n }\n return { groupOutcomes, groupError };\n}\n\n/** Denied scope groups: policy refusals leave their turns pending (the group\n * result's turns were NOT cleared), aggregated per reason so a drain is\n * never reported complete while denied backlog remains. */\nfunction deniedByReason(groupOutcomes: readonly GroupOutcome[]): Map<string, number> {\n const denied = new Map<string, number>();\n for (const entry of groupOutcomes) {\n if (entry.result?.status !== \"skipped\" || !DENIED_SCOPE_REASONS.has(entry.result.reason ?? \"\")) continue;\n denied.set(entry.result.reason!, (denied.get(entry.result.reason!) ?? 0) + entry.turns);\n }\n return denied;\n}\n\nfunction lastGroupReason(groupOutcomes: readonly GroupOutcome[]): string | undefined {\n for (let index = groupOutcomes.length - 1; index >= 0; index -= 1) {\n const reason = groupOutcomes[index]?.result?.reason;\n if (reason !== undefined) return reason;\n }\n return undefined;\n}\n\n/** the session file is gone for good — but the ack verdict is the truth (live */\nasync function settleDrainVerdict(\n claims: BufferDrainClaims,\n key: string,\n ownerId: string,\n groupOutcomes: readonly GroupOutcome[],\n groupError: DrainExtractionError | undefined,\n turnsRemaining: number\n): Promise<{ status: DrainSessionStatus; reason?: string; turnsRemaining: number }> {\n if (turnsRemaining === 0) return await ackOrReload(claims, key, ownerId);\n if (groupError) {\n await claims.releaseLease(key, ownerId);\n return { status: \"failed\", reason: describeError(groupError), turnsRemaining };\n }\n await claims.releaseLease(key, ownerId);\n const reason =\n groupOutcomes.find((entry) => entry.result?.reason !== undefined && DENIED_SCOPE_REASONS.has(entry.result.reason!))\n ?.result?.reason ??\n lastGroupReason(groupOutcomes) ??\n \"deferred\";\n return { status: \"deferred\", reason, turnsRemaining };\n}\n\n/** Ack an emptied claim; a not-acked verdict means live ingress merged (or\n * the lease moved) — reload the honest pending count and release OUR lease,\n * keeping the source. */\nasync function ackOrReload(\n claims: BufferDrainClaims,\n key: string,\n ownerId: string\n): Promise<{ status: DrainSessionStatus; reason?: string; turnsRemaining: number }> {\n const ackOutcome = await claims.ack(key, ownerId);\n if (ackOutcome === \"acked\") return { status: \"drained\", turnsRemaining: 0 };\n const reloaded = await claims.load(key);\n const turnsRemaining = pendingTurnCount(reloaded?.entry);\n await claims.releaseLease(key, ownerId);\n return { status: \"deferred\", reason: ackOutcome === \"not_empty\" ? \"ack_not_empty\" : \"ack_not_owner\", turnsRemaining };\n}\n\nfunction groupCounts(groupOutcomes: readonly GroupOutcome[]): GroupCounts {\n return {\n persistedCount: groupOutcomes.reduce((sum, entry) => sum + (entry.result?.persistedCount ?? 0), 0),\n durableOutputCount: groupOutcomes.reduce((sum, entry) => sum + (entry.result?.durableOutputCount ?? 0), 0),\n };\n}\n\nexport interface DrainScanEntry {\n key: string;\n turns: number;\n claimed: boolean;\n}\n\n/** Session-buffer listings: a claimed session reports its CLAIM's pending\n * count (the live buffer copy may lag the claim), an unclaimed one its\n * buffer entry. */\nasync function bufferSessionListings(\n claims: BufferDrainClaims,\n entries: Record<string, BufferEntryState>\n): Promise<{ listings: DrainScanEntry[]; seen: Set<string> }> {\n const listings: DrainScanEntry[] = [];\n const seen = new Set<string>();\n for (const [key, entry] of Object.entries(entries)) {\n seen.add(key);\n const claim = claims.isClaimed(key) ? await claims.load(key) : null;\n listings.push({\n key,\n turns: claim ? pendingTurnCount(claim.entry) : pendingTurnCount(entry),\n claimed: claim !== null,\n });\n }\n return { listings, seen };\n}\n\n/** Claims the buffer state no longer lists (drained by an earlier run whose\n * claim file outlived its session file). */\nasync function orphanClaimListings(claims: BufferDrainClaims, seen: Set<string>): Promise<DrainScanEntry[]> {\n const out: DrainScanEntry[] = [];\n for (const listed of await claims.list()) {\n if (seen.has(listed.key)) continue;\n out.push({ key: listed.key, turns: listed.turnCount, claimed: true });\n }\n return out;\n}\n\n/** The drain runs on the caller's abort signal combined with this run's own\n * lease-loss controller. */\nfunction drainAbortSignal(callerSignal: AbortSignal | undefined, abort: AbortController): AbortSignal {\n return callerSignal ? AbortSignal.any([callerSignal, abort.signal]) : abort.signal;\n}\n\n/** One heartbeat-protected claim drain: run every scope group, settle the\n * verdict against the claim store, and assemble the honest outcome. The\n * heartbeat is cleared by the caller on every exit path. */\nasync function runClaimDrain(\n ctx: {\n orchestrator: Orchestrator;\n claims: BufferDrainClaims;\n key: string;\n ownerId: string;\n claimBuffer: SmartBuffer;\n coordinator: TurnIngestionCoordinator;\n executionDrain: ExtractionDrain;\n turns: BufferTurn[];\n turnsAtStart: number;\n config: PluginConfig;\n signal: AbortSignal;\n },\n options: BufferDrainRunnerOptions,\n heartbeat: NodeJS.Timeout\n): Promise<DrainSessionOutcome> {\n // An emptied claim acks here: the ack verdict is the truth (drained), or\n // live ingress merged between the read and the ack — deferred, source kept.\n if (ctx.turns.length === 0) {\n const outcome = await ackEmptyClaim(ctx.claims, ctx.key, ctx.ownerId, ctx.turnsAtStart);\n options.onEvent?.({ type: \"session_finished\", key: ctx.key, outcome });\n return outcome;\n }\n const { groupOutcomes, groupError } = await drainScopeGroups(ctx);\n // Heartbeat cleared on EVERY exit path: a rejected drain must not keep\n // renewing the lease forever (cleared in the caller's finally).\n const remaining = await ctx.claims.load(ctx.key);\n const { status, reason, turnsRemaining } = await settleDrainVerdict(\n ctx.claims,\n ctx.key,\n ctx.ownerId,\n groupOutcomes,\n groupError,\n pendingTurnCount(remaining?.entry)\n );\n const outcome = assembleOutcome(\n ctx.key,\n ctx.turnsAtStart,\n { status, reason, turnsRemaining },\n groupCounts(groupOutcomes),\n deniedByReason(groupOutcomes)\n );\n log.info(`buffer drain: session ${ctx.key} ${status} (${outcome.turnsRemaining} turn(s) remaining)`);\n options.onEvent?.({ type: \"session_finished\", key: ctx.key, outcome });\n return outcome;\n}\n\nexport class BufferDrainRunner {\n constructor(private readonly orchestrator: Orchestrator) {}\n\n /** The claim store of this orchestrator's memory root. */\n get claims(): BufferDrainClaims {\n return this.orchestrator.storage.bufferDrainClaims();\n }\n\n /**\n * Drain one claimed session. The caller owns the lease (it may have taken\n * it already); this method verifies ownership before touching content.\n */\n async drainSession(key: string, options: BufferDrainRunnerOptions): Promise<DrainSessionOutcome> {\n const claims = this.claims;\n const leaseMs = options.leaseMs ?? DEFAULT_LEASE_MS;\n const claimed = await loadClaimedSession(claims, key);\n if (claimed.kind === \"notClaimed\") return claimed.notClaimed;\n const { claim, turnsAtStart } = claimed;\n options.onEvent?.({ type: \"session_started\", key, turns: turnsAtStart });\n\n // Take the lease unless this drainer already holds it.\n const busy = await adoptLease(claims, key, options.ownerId, leaseMs);\n if (busy !== null) {\n const outcome = busy;\n options.onEvent?.({ type: \"session_finished\", key, outcome });\n return outcome;\n }\n\n // The claim is the buffer for this run: every successful chunk is removed\n // from it, so a kill leaves exactly the un-extracted turns.\n const { claimBuffer, coordinator, executionDrain } = buildClaimExecution(\n this.orchestrator,\n claims,\n key,\n options.ownerId,\n claim\n );\n await claimBuffer.load();\n\n // The claim's turns (retained first, like the live buffer's order).\n const turns = [...(claim.entry.retainedTurns ?? []), ...claim.entry.turns];\n\n // A lost lease means another drainer took the claim: stop instead of\n // writing progress into a claim this run no longer owns.\n const abort = new AbortController();\n const signal = drainAbortSignal(options.signal, abort);\n const heartbeat = startLeaseHeartbeat(claims, key, options.ownerId, leaseMs, abort);\n try {\n return await runClaimDrain(\n {\n orchestrator: this.orchestrator,\n claims,\n key,\n ownerId: options.ownerId,\n claimBuffer,\n coordinator,\n executionDrain,\n turns,\n turnsAtStart,\n config: this.orchestrator.config,\n signal,\n },\n options,\n heartbeat\n );\n } finally {\n clearInterval(heartbeat);\n }\n }\n\n /** Claim a session file for the drain. Returns false when it is not claimable. */\n async claim(key: string, ownerId: string, leaseMs = DEFAULT_LEASE_MS): Promise<boolean> {\n const lease: DrainLease = {\n ownerId,\n pid: process.pid,\n host: hostname(),\n acquiredAt: new Date().toISOString(),\n heartbeatAt: new Date().toISOString(),\n leaseMs,\n };\n return (await this.claims.claimFromSessionFile(key, lease)) === \"claimed\";\n }\n\n /** Sessions with buffered turns, claimed or not (CLI scan). */\n async scan(): Promise<DrainScanEntry[]> {\n const sessions = await this.orchestrator.storage.loadBuffer();\n const { listings, seen } = await bufferSessionListings(this.claims, sessions.entries ?? {});\n return [...listings, ...(await orphanClaimListings(this.claims, seen))];\n }\n}\n"],"mappings":"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","names":["outcome"]}