@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
@@ -831,6 +831,345 @@ test("healthForNamespace stops waiting when namespace availability probe aborts"
831
831
  assert.equal(backend.disposed, 1);
832
832
  });
833
833
 
834
+ test("healthForNamespace reuses a completed result within the TTL without re-probing", async (t) => {
835
+ t.mock.timers.enable({ apis: ["Date"], now: 1_700_000_000_000 });
836
+ try {
837
+ const created: FakeBackend[] = [];
838
+ const router = new NamespaceSearchRouter(
839
+ config(),
840
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
841
+ () => {
842
+ const backend = new FakeBackend(false, [], { check: "present" }, false, {
843
+ debugStatus: "cached-health",
844
+ });
845
+ backend.statusResult = {
846
+ pendingEmbeddings: 3,
847
+ oldestPendingAgeMs: 1_000,
848
+ totalFiles: 10,
849
+ embeddedFiles: 7,
850
+ };
851
+ created.push(backend);
852
+ return backend;
853
+ },
854
+ );
855
+
856
+ const first = await router.healthForNamespace("shared");
857
+ const second = await router.healthForNamespace("shared");
858
+
859
+ assert.equal(created.length, 1, "second call must not create another backend probe");
860
+ assert.equal(created[0]?.availabilitySignals.length, 1);
861
+ assert.deepEqual(created[0]?.checkCollections, ["openclaw-engram--ns-736861726564"]);
862
+ assert.deepEqual(first, second);
863
+ assert.equal(first.debugStatus, "cached-health");
864
+ assert.equal(first.pendingEmbeddings, 3);
865
+
866
+ first.available = false;
867
+ first.debugStatus = "mutated";
868
+ const third = await router.healthForNamespace("shared");
869
+ assert.equal(third.available, true, "returned health objects must be copies");
870
+ assert.equal(third.debugStatus, "cached-health");
871
+ assert.equal(created.length, 1);
872
+ } finally {
873
+ t.mock.timers.reset();
874
+ }
875
+ });
876
+
877
+ test("healthForNamespace re-probes after the health result TTL elapses", async (t) => {
878
+ t.mock.timers.enable({ apis: ["Date"], now: 1_700_000_000_000 });
879
+ try {
880
+ const created: FakeBackend[] = [];
881
+ const router = new NamespaceSearchRouter(
882
+ config(),
883
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
884
+ () => {
885
+ const backend = new FakeBackend(false, [], { check: "present" });
886
+ backend.statusResult = {
887
+ pendingEmbeddings: created.length,
888
+ oldestPendingAgeMs: 0,
889
+ totalFiles: 1,
890
+ embeddedFiles: 1,
891
+ };
892
+ created.push(backend);
893
+ return backend;
894
+ },
895
+ );
896
+
897
+ const first = await router.healthForNamespace("shared");
898
+ assert.equal(created.length, 1);
899
+ assert.equal(first.pendingEmbeddings, 0);
900
+
901
+ t.mock.timers.tick(10_000);
902
+ const second = await router.healthForNamespace("shared");
903
+
904
+ assert.equal(created.length, 2, "TTL expiry must force a fresh backend probe");
905
+ assert.equal(second.pendingEmbeddings, 1);
906
+ assert.equal(created[0]?.disposed, 1);
907
+ assert.equal(created[1]?.disposed, 1);
908
+ } finally {
909
+ t.mock.timers.reset();
910
+ }
911
+ });
912
+
913
+ test("healthForNamespace clearCache forces a re-probe before the TTL", async () => {
914
+ const created: FakeBackend[] = [];
915
+ const router = new NamespaceSearchRouter(
916
+ config(),
917
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
918
+ () => {
919
+ const backend = new FakeBackend(false, [], { check: "present" });
920
+ backend.statusResult = {
921
+ pendingEmbeddings: created.length + 1,
922
+ oldestPendingAgeMs: 0,
923
+ totalFiles: 1,
924
+ embeddedFiles: 1,
925
+ };
926
+ created.push(backend);
927
+ return backend;
928
+ },
929
+ );
930
+
931
+ const first = await router.healthForNamespace("shared");
932
+ assert.equal(created.length, 1);
933
+ assert.equal(first.pendingEmbeddings, 1);
934
+
935
+ router.clearCache();
936
+ const second = await router.healthForNamespace("shared");
937
+
938
+ assert.equal(created.length, 2, "clearCache must drop the health result cache");
939
+ assert.equal(second.pendingEmbeddings, 2);
940
+ });
941
+
942
+ test("healthForNamespace does not cache a thrown probe", async () => {
943
+ let attempts = 0;
944
+ const created: FakeBackend[] = [];
945
+ const router = new NamespaceSearchRouter(
946
+ config(),
947
+ {
948
+ storageFor: async (namespace: string) => {
949
+ attempts += 1;
950
+ if (attempts === 1) {
951
+ throw new Error("storage unavailable");
952
+ }
953
+ return { dir: `/tmp/remnic/${namespace}` };
954
+ },
955
+ },
956
+ () => {
957
+ const backend = new FakeBackend(false, [], { check: "present" });
958
+ backend.statusResult = {
959
+ pendingEmbeddings: 9,
960
+ oldestPendingAgeMs: 0,
961
+ totalFiles: 1,
962
+ embeddedFiles: 1,
963
+ };
964
+ created.push(backend);
965
+ return backend;
966
+ },
967
+ );
968
+
969
+ await assert.rejects(
970
+ () => router.healthForNamespace("shared"),
971
+ /storage unavailable/,
972
+ );
973
+ assert.equal(created.length, 0, "failed call must not create a backend");
974
+
975
+ const health = await router.healthForNamespace("shared");
976
+ assert.equal(health.pendingEmbeddings, 9);
977
+ assert.equal(created.length, 1);
978
+
979
+ const cached = await router.healthForNamespace("shared");
980
+ assert.equal(created.length, 1, "only the successful result is cached");
981
+ assert.deepEqual(cached, health);
982
+ });
983
+
984
+ test("healthForNamespace does not cache an unavailable answer", async () => {
985
+ const created: FakeBackend[] = [];
986
+ const router = new NamespaceSearchRouter(
987
+ config(),
988
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
989
+ () => {
990
+ const backend = new FakeBackend(false, [], { check: "present" });
991
+ if (created.length === 0) {
992
+ backend.available = false;
993
+ }
994
+ backend.statusResult = {
995
+ pendingEmbeddings: created.length,
996
+ oldestPendingAgeMs: 0,
997
+ totalFiles: 1,
998
+ embeddedFiles: 1,
999
+ };
1000
+ created.push(backend);
1001
+ return backend;
1002
+ },
1003
+ );
1004
+
1005
+ const first = await router.healthForNamespace("shared");
1006
+ assert.equal(first.available, false);
1007
+ assert.equal(created.length, 1);
1008
+
1009
+ const second = await router.healthForNamespace("shared");
1010
+ assert.equal(second.available, true, "recovery must be visible on the next probe");
1011
+ assert.equal(created.length, 2, "unavailable answers must not be cached");
1012
+ });
1013
+
1014
+ test("healthForNamespace does not cache a missing-collection answer", async () => {
1015
+ const created: FakeBackend[] = [];
1016
+ const router = new NamespaceSearchRouter(
1017
+ config(),
1018
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
1019
+ () => {
1020
+ const backend = new FakeBackend(
1021
+ false,
1022
+ [],
1023
+ created.length === 0 ? { check: "missing" } : { check: "present" },
1024
+ );
1025
+ backend.statusResult = {
1026
+ pendingEmbeddings: created.length,
1027
+ oldestPendingAgeMs: 0,
1028
+ totalFiles: 1,
1029
+ embeddedFiles: 1,
1030
+ };
1031
+ created.push(backend);
1032
+ return backend;
1033
+ },
1034
+ );
1035
+
1036
+ const first = await router.healthForNamespace("shared");
1037
+ assert.equal(first.collectionState, "missing");
1038
+ assert.equal(created.length, 1);
1039
+
1040
+ const second = await router.healthForNamespace("shared");
1041
+ assert.equal(second.collectionState, "present", "collection recovery must re-probe");
1042
+ assert.equal(created.length, 2, "missing-collection answers must not be cached");
1043
+ });
1044
+
1045
+ function routerWithDegradedFirstProbe(
1046
+ created: FakeBackend[],
1047
+ firstCheck: CollectionState,
1048
+ degradeFirst: (backend: FakeBackend) => void,
1049
+ ): NamespaceSearchRouter {
1050
+ return new NamespaceSearchRouter(
1051
+ config(),
1052
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
1053
+ () => {
1054
+ const isFirst = created.length === 0;
1055
+ const backend = new FakeBackend(false, [], { check: isFirst ? firstCheck : "present" });
1056
+ backend.statusResult = {
1057
+ pendingEmbeddings: 10,
1058
+ oldestPendingAgeMs: 0,
1059
+ totalFiles: 1,
1060
+ embeddedFiles: 1,
1061
+ };
1062
+ if (isFirst) degradeFirst(backend);
1063
+ created.push(backend);
1064
+ return backend;
1065
+ },
1066
+ );
1067
+ }
1068
+
1069
+ test("healthForNamespace does not cache an unknown-collection answer", async () => {
1070
+ const created: FakeBackend[] = [];
1071
+ const router = routerWithDegradedFirstProbe(created, "unknown", () => {});
1072
+
1073
+ const first = await router.healthForNamespace("shared");
1074
+ assert.equal(first.collectionState, "unknown");
1075
+
1076
+ const second = await router.healthForNamespace("shared");
1077
+ assert.equal(second.collectionState, "present", "recovery must be visible on the next probe");
1078
+ assert.equal(created.length, 2, "unknown-collection answers must not be cached");
1079
+ });
1080
+
1081
+ test("healthForNamespace does not cache an answer whose backend status failed", async () => {
1082
+ const created: FakeBackend[] = [];
1083
+ const router = routerWithDegradedFirstProbe(created, "present", (backend) => {
1084
+ backend.statusResult = null;
1085
+ });
1086
+
1087
+ const first = await router.healthForNamespace("shared");
1088
+ assert.equal(first.pendingEmbeddings, null);
1089
+
1090
+ const second = await router.healthForNamespace("shared");
1091
+ assert.equal(second.pendingEmbeddings, 10, "backlog data must be visible on the next probe");
1092
+ assert.equal(created.length, 2, "answers without backlog data must not be cached");
1093
+ });
1094
+
1095
+ test("healthForNamespace does not cache a backlog over the threshold", async () => {
1096
+ const created: FakeBackend[] = [];
1097
+ const router = routerWithDegradedFirstProbe(created, "present", (backend) => {
1098
+ backend.statusResult = {
1099
+ pendingEmbeddings: config().qmdEmbeddingBacklogThreshold + 1,
1100
+ oldestPendingAgeMs: 0,
1101
+ totalFiles: 1,
1102
+ embeddedFiles: 1,
1103
+ };
1104
+ });
1105
+
1106
+ await router.healthForNamespace("shared");
1107
+
1108
+ const second = await router.healthForNamespace("shared");
1109
+ assert.equal(second.pendingEmbeddings, 10, "a drained backlog must be visible on the next probe");
1110
+ assert.equal(created.length, 2, "over-threshold answers must not be cached");
1111
+ });
1112
+
1113
+ test("healthForNamespace dispose drops the health result cache", async () => {
1114
+ const created: FakeBackend[] = [];
1115
+ const router = new NamespaceSearchRouter(
1116
+ config(),
1117
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
1118
+ () => {
1119
+ const backend = new FakeBackend(false, [], { check: "present" });
1120
+ backend.statusResult = {
1121
+ pendingEmbeddings: created.length + 1,
1122
+ oldestPendingAgeMs: 0,
1123
+ totalFiles: 1,
1124
+ embeddedFiles: 1,
1125
+ };
1126
+ created.push(backend);
1127
+ return backend;
1128
+ },
1129
+ );
1130
+
1131
+ const first = await router.healthForNamespace("shared");
1132
+ assert.equal(created.length, 1);
1133
+ assert.equal(first.pendingEmbeddings, 1);
1134
+
1135
+ await router.dispose();
1136
+ const second = await router.healthForNamespace("shared");
1137
+
1138
+ assert.equal(created.length, 2, "dispose must drop the health result cache");
1139
+ assert.equal(second.pendingEmbeddings, 2);
1140
+ });
1141
+
1142
+ test("healthForNamespace disableSearchBackends drops the health result cache", async () => {
1143
+ const created: FakeBackend[] = [];
1144
+ const router = new NamespaceSearchRouter(
1145
+ config(),
1146
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
1147
+ () => {
1148
+ const backend = new FakeBackend(false, [], { check: "present" });
1149
+ backend.statusResult = {
1150
+ pendingEmbeddings: created.length + 1,
1151
+ oldestPendingAgeMs: 0,
1152
+ totalFiles: 1,
1153
+ embeddedFiles: 1,
1154
+ };
1155
+ created.push(backend);
1156
+ return backend;
1157
+ },
1158
+ );
1159
+
1160
+ const first = await router.healthForNamespace("shared");
1161
+ assert.equal(created.length, 1);
1162
+ assert.equal(first.pendingEmbeddings, 1);
1163
+
1164
+ await router.disableSearchBackends();
1165
+ const second = await router.healthForNamespace("shared");
1166
+
1167
+ assert.equal(created.length, 2, "disableSearchBackends must drop the health result cache");
1168
+ assert.equal(second.pendingEmbeddings, 2);
1169
+ });
1170
+
1171
+
1172
+
834
1173
  test("ensureNamespaceCollection does not cache aborted namespace backend probes", async () => {
835
1174
  const created: FakeBackend[] = [];
836
1175
  const router = new NamespaceSearchRouter(
@@ -14,6 +14,9 @@ import { ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
14
14
  const NESTED_NAMESPACE_FILTER_OVERFETCH_FACTOR = 4;
15
15
  const NESTED_NAMESPACE_FILTER_OVERFETCH_MIN = 50;
16
16
 
17
+ // Short TTL so repeated health polls skip a full QMD re-probe (~1.5s).
18
+ const HEALTH_RESULT_CACHE_TTL_MS = 10_000;
19
+
17
20
  export function namespaceCollectionName(
18
21
  baseCollection: string,
19
22
  namespace: string,
@@ -72,6 +75,27 @@ export interface NamespaceSearchHealth {
72
75
  embeddingBacklogThreshold: number;
73
76
  }
74
77
 
78
+ /** True when the pending-embedding count is above a positive backlog threshold. */
79
+ export function embeddingBacklogExceeds(pending: number | null, threshold: number): boolean {
80
+ return threshold > 0 && pending !== null && pending > threshold;
81
+ }
82
+
83
+ /**
84
+ * The one definition of a degraded search answer. The readiness consumer
85
+ * reports it, and `healthForNamespace` refuses to cache it.
86
+ */
87
+ export function isSearchHealthDegraded(
88
+ health: Pick<NamespaceSearchHealth, "available" | "collectionState" | "pendingEmbeddings">,
89
+ backlogThreshold: number,
90
+ ): boolean {
91
+ const active = health.available && health.collectionState !== "missing";
92
+ return (
93
+ !active ||
94
+ health.collectionState === "unknown" ||
95
+ embeddingBacklogExceeds(health.pendingEmbeddings, backlogThreshold)
96
+ );
97
+ }
98
+
75
99
  type NamespaceScopedSearchConfig = PluginConfig & {
76
100
  hostEmbeddingProviderScope?: string;
77
101
  };
@@ -84,6 +108,11 @@ type BackendRecordOptions = {
84
108
 
85
109
  export class NamespaceSearchRouter {
86
110
  private readonly cache = new Map<string, Promise<NamespaceBackendRecord>>();
111
+ private readonly healthResultCache = new Map<
112
+ string,
113
+ { expiresAt: number; value: NamespaceSearchHealth }
114
+ >();
115
+
87
116
  private readonly pendingDisposals = new Set<Promise<void>>();
88
117
 
89
118
  constructor(
@@ -279,6 +308,11 @@ export class NamespaceSearchRouter {
279
308
  execution?: SearchExecutionOptions,
280
309
  ): Promise<NamespaceSearchHealth> {
281
310
  const key = namespace.trim() || this.config.defaultNamespace;
311
+ const cached = this.healthResultCache.get(key);
312
+ if (cached && cached.expiresAt > Date.now()) {
313
+ return { ...cached.value };
314
+ }
315
+
282
316
  const record = await this.createBackendRecordFor(
283
317
  key,
284
318
  execution,
@@ -310,7 +344,7 @@ export class NamespaceSearchRouter {
310
344
  ? "missing"
311
345
  : record.collectionState;
312
346
 
313
- return {
347
+ const health: NamespaceSearchHealth = {
314
348
  collection: record.collection,
315
349
  memoryDir: record.memoryDir,
316
350
  available: liveRecord?.available ?? record.available,
@@ -326,6 +360,22 @@ export class NamespaceSearchRouter {
326
360
  oldestPendingAgeMs: backendStatus?.oldestPendingAgeMs ?? null,
327
361
  embeddingBacklogThreshold: this.config.qmdEmbeddingBacklogThreshold,
328
362
  };
363
+
364
+ // Cache only a healthy answer that carries backlog data when the backend
365
+ // reports status. A degraded or incomplete answer must re-probe so a
366
+ // readiness poll sees recovery on the next call, not one TTL late.
367
+ const statusMissing = backendStatus === null && "status" in diagnosticBackend;
368
+ if (
369
+ execution?.signal?.aborted !== true &&
370
+ !statusMissing &&
371
+ !isSearchHealthDegraded(health, health.embeddingBacklogThreshold)
372
+ ) {
373
+ this.healthResultCache.set(key, {
374
+ expiresAt: Date.now() + HEALTH_RESULT_CACHE_TTL_MS,
375
+ value: { ...health },
376
+ });
377
+ }
378
+ return { ...health };
329
379
  } finally {
330
380
  const dispose = (record.backend as { dispose?: () => void | Promise<void> }).dispose;
331
381
  await dispose?.call(record.backend);
@@ -358,6 +408,7 @@ export class NamespaceSearchRouter {
358
408
 
359
409
  /** Clear cached backend records so the next access re-probes availability. */
360
410
  clearCache(): void {
411
+ this.healthResultCache.clear();
361
412
  const pendingRecords = Array.from(this.cache.values());
362
413
  this.cache.clear();
363
414
  const disposal = Promise.allSettled(pendingRecords).then(async (settled) => {
@@ -379,6 +430,7 @@ export class NamespaceSearchRouter {
379
430
  /** Release any per-namespace backend handles held by cached records. */
380
431
  async dispose(): Promise<void> {
381
432
  await this.drainPendingDisposals();
433
+ this.healthResultCache.clear();
382
434
  const pendingRecords = Array.from(this.cache.values());
383
435
  this.cache.clear();
384
436
  const settled = await Promise.allSettled(pendingRecords);
@@ -395,6 +447,7 @@ export class NamespaceSearchRouter {
395
447
  * has been switched to NoopSearchBackend for a missing collection.
396
448
  */
397
449
  async disableSearchBackends(): Promise<void> {
450
+ this.healthResultCache.clear();
398
451
  const settled = await Promise.allSettled(this.cache.values());
399
452
  await Promise.allSettled(
400
453
  settled.flatMap((entry) => {
@@ -25,6 +25,7 @@
25
25
  * every claim retryable; a restarted daemon's scheduler adopts them.
26
26
  */
27
27
 
28
+ import { randomUUID } from "node:crypto";
28
29
  import { hostname } from "node:os";
29
30
  import { log } from "../logger.js";
30
31
  import type { Orchestrator } from "../orchestrator.js";
@@ -44,8 +45,8 @@ export interface BacklogSchedulerDeps {
44
45
  liveBufferCounts():
45
46
  | Map<string, { turns: number; retainedTurns: number }>
46
47
  | Promise<Map<string, { turns: number; retainedTurns: number }>>;
47
- /** A fresh lease for claims this scheduler takes. */
48
- lease(): DrainLease;
48
+ /** Create a fresh lease for a unique owner token. */
49
+ lease(ownerId: string): DrainLease;
49
50
  /** Drain one key to quiescence (BufferDrainRunner.drainSession). */
50
51
  drainKey(
51
52
  key: string,
@@ -63,6 +64,7 @@ export interface BacklogSchedulerDeps {
63
64
  }
64
65
 
65
66
  export interface BacklogSchedulerOptions {
67
+ /** Optional label prefix; each scheduler instance adds a unique token. */
66
68
  ownerId?: string;
67
69
  /** Scan cadence in ms (startup + new-work + retry checks). */
68
70
  pollMs?: number;
@@ -135,7 +137,7 @@ export class BacklogScheduler {
135
137
  constructor(deps: BacklogSchedulerDeps, options: BacklogSchedulerOptions = {}) {
136
138
  this.deps = deps;
137
139
  this.options = {
138
- ownerId: options.ownerId ?? `daemon-drain-${process.pid}`,
140
+ ownerId: `${options.ownerId ?? "daemon-drain"}-${randomUUID()}`,
139
141
  pollMs: options.pollMs ?? DEFAULT_POLL_MS,
140
142
  retryBaseMs: options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS,
141
143
  retryMaxMs: options.retryMaxMs ?? DEFAULT_RETRY_MAX_MS,
@@ -333,11 +335,11 @@ export class BacklogScheduler {
333
335
  const { promise: settled, resolve: settle } = Promise.withResolvers<void>();
334
336
  this.inFlight.set(key, { controller, settled });
335
337
  try {
336
- if (!(await this.claimForSubmit(key, needsClaim))) return;
338
+ if (!(await this.claimForSubmit(key, needsClaim, this.options.ownerId))) return;
337
339
  // Stopped between the claim decision and the task: leave the claim
338
340
  // with its lease (it expires; the next start adopts) — never drain.
339
341
  if (this.stopped) return;
340
- await this.drainClaimedKey(key, controller.signal);
342
+ await this.drainClaimedKey(key, controller.signal, this.options.ownerId);
341
343
  } catch (err) {
342
344
  // Claim/drain failures are scheduler-level: the claim content is
343
345
  // untouched — count failed and back off (never an unhandled rejection
@@ -357,10 +359,10 @@ export class BacklogScheduler {
357
359
  }
358
360
 
359
361
  /** after all — the key goes quiet without a task. "claimed": ours. */
360
- private async claimForSubmit(key: string, needsClaim: boolean): Promise<boolean> {
362
+ private async claimForSubmit(key: string, needsClaim: boolean, ownerId: string): Promise<boolean> {
361
363
  if (!needsClaim) return true;
362
364
  try {
363
- const outcome = await this.deps.claims.claimFromSessionFile(key, this.deps.lease());
365
+ const outcome = await this.deps.claims.claimFromSessionFile(key, this.deps.lease(ownerId));
364
366
  if (outcome === "empty") {
365
367
  this.keys.delete(key);
366
368
  return false;
@@ -375,11 +377,11 @@ export class BacklogScheduler {
375
377
  /** Drain one claimed key. drainKey reports failures as outcomes; a thrown
376
378
  * error still means the claim content is intact — count it failed and
377
379
  * back off. */
378
- private async drainClaimedKey(key: string, signal: AbortSignal): Promise<void> {
380
+ private async drainClaimedKey(key: string, signal: AbortSignal, ownerId: string): Promise<void> {
379
381
  this.counters.claimed += 1;
380
382
  try {
381
383
  const outcome = await this.deps.drainKey(key, {
382
- ownerId: this.options.ownerId,
384
+ ownerId,
383
385
  signal,
384
386
  });
385
387
  this.applyOutcome(key, outcome);
@@ -480,7 +482,6 @@ export function wireBacklogScheduler(
480
482
  orchestrator: Orchestrator,
481
483
  options: BacklogSchedulerOptions = {}
482
484
  ): BacklogScheduler {
483
- const ownerId = options.ownerId ?? `daemon-drain-${process.pid}`;
484
485
  const runner = new BufferDrainRunner(orchestrator);
485
486
  return new BacklogScheduler(
486
487
  {
@@ -492,7 +493,7 @@ export function wireBacklogScheduler(
492
493
  await orchestrator.buffer.load();
493
494
  return orchestrator.buffer.entryCounts();
494
495
  },
495
- lease: () => {
496
+ lease: (ownerId) => {
496
497
  const now = new Date().toISOString();
497
498
  return {
498
499
  ownerId,
@@ -508,6 +509,6 @@ export function wireBacklogScheduler(
508
509
  // claim-backed drain accumulate toward the same threshold).
509
510
  breakerSnapshot: () => orchestrator.getExtractionResilienceStatus().breaker,
510
511
  },
511
- { ...options, ownerId }
512
+ options
512
513
  );
513
514
  }
@@ -74,27 +74,35 @@ const DEFAULT_LEASE_MS = 120_000;
74
74
  * A `BufferStorage` view of one drain claim: the claim file IS the buffer.
75
75
  * Writes are the drain's progress (what is left to extract), so every
76
76
  * successfully extracted chunk is durably removed before the next one runs.
77
+ *
78
+ * `seen` is the drainer's own view of the claim: the entry it loaded, then the
79
+ * entry it last wrote. Each progress write is diffed against it, so a write
80
+ * records only the turns extracted since the previous one.
77
81
  */
78
82
  export class ClaimBufferStorage implements BufferStorage {
83
+ private seen: BufferEntryState;
84
+
79
85
  constructor(
80
86
  private readonly claims: BufferDrainClaims,
81
87
  private readonly key: string,
82
88
  private readonly ownerId: string,
83
- private readonly entry: BufferEntryState
84
- ) {}
89
+ entry: BufferEntryState
90
+ ) {
91
+ this.seen = entry;
92
+ }
85
93
 
86
94
  async loadBuffer(): Promise<BufferState> {
87
95
  const entries: NonNullable<BufferState["entries"]> = Object.create(null);
88
96
  entries[this.key] = {
89
- turns: this.entry.turns,
90
- retainedTurns: this.entry.retainedTurns,
91
- lastExtractionAt: this.entry.lastExtractionAt,
92
- extractionCount: this.entry.extractionCount,
97
+ turns: this.seen.turns,
98
+ retainedTurns: this.seen.retainedTurns,
99
+ lastExtractionAt: this.seen.lastExtractionAt,
100
+ extractionCount: this.seen.extractionCount,
93
101
  };
94
102
  return {
95
- turns: this.entry.turns,
96
- lastExtractionAt: this.entry.lastExtractionAt,
97
- extractionCount: this.entry.extractionCount,
103
+ turns: this.seen.turns,
104
+ lastExtractionAt: this.seen.lastExtractionAt,
105
+ extractionCount: this.seen.extractionCount,
98
106
  entries,
99
107
  };
100
108
  }
@@ -102,16 +110,21 @@ export class ClaimBufferStorage implements BufferStorage {
102
110
  async saveBuffer(state: BufferState): Promise<void> {
103
111
  // Whole-state fallback (the claim covers one session): persist the entry.
104
112
  const entry = state.entries?.[this.key];
105
- await this.claims.writeEntry(
106
- this.key,
107
- this.ownerId,
108
- entry ?? { turns: [], lastExtractionAt: null, extractionCount: 0 },
109
- this.entry
110
- );
113
+ await this.writeProgress(entry ?? { turns: [], lastExtractionAt: null, extractionCount: 0 });
111
114
  }
112
115
 
113
116
  async saveBufferSession(key: string, entry: BufferEntryState): Promise<void> {
114
- await this.claims.writeEntry(key, this.ownerId, entry, key === this.key ? this.entry : undefined);
117
+ if (key !== this.key) {
118
+ await this.claims.writeEntry(key, this.ownerId, entry, undefined);
119
+ return;
120
+ }
121
+ await this.writeProgress(entry);
122
+ }
123
+
124
+ private async writeProgress(entry: BufferEntryState): Promise<void> {
125
+ await this.claims.writeEntry(this.key, this.ownerId, entry, this.seen);
126
+ // Copy the lists: the buffer keeps mutating its own arrays after a save.
127
+ this.seen = { ...entry, turns: [...entry.turns], retainedTurns: entry.retainedTurns ? [...entry.retainedTurns] : undefined };
115
128
  }
116
129
 
117
130
  async deleteBufferSession(): Promise<void> {
@@ -554,14 +567,25 @@ async function runClaimDrain(
554
567
  options: BufferDrainRunnerOptions,
555
568
  heartbeat: NodeJS.Timeout
556
569
  ): Promise<DrainSessionOutcome> {
570
+ // Reconcile only identities recorded as durably extracted under this
571
+ // owner's claim lease. The claim lock is released before entering the live
572
+ // buffer mutation queue, preserving the buffer→claim lock order used by
573
+ // concurrent ingress saves.
574
+ const reconcileDrainedTurns = async (): Promise<void> => {
575
+ const drained = await ctx.claims.drainedTurnsForOwner(ctx.key, ctx.ownerId);
576
+ if (drained !== null) await ctx.orchestrator.buffer.save({ key: ctx.key, drained });
577
+ };
578
+
557
579
  // An emptied claim acks here: the ack verdict is the truth (drained), or
558
580
  // live ingress merged between the read and the ack — deferred, source kept.
559
581
  if (ctx.turns.length === 0) {
582
+ await reconcileDrainedTurns();
560
583
  const outcome = await ackEmptyClaim(ctx.claims, ctx.key, ctx.ownerId, ctx.turnsAtStart);
561
584
  options.onEvent?.({ type: "session_finished", key: ctx.key, outcome });
562
585
  return outcome;
563
586
  }
564
587
  const { groupOutcomes, groupError } = await drainScopeGroups(ctx);
588
+ await reconcileDrainedTurns();
565
589
  // Heartbeat cleared on EVERY exit path: a rejected drain must not keep
566
590
  // renewing the lease forever (cleared in the caller's finally).
567
591
  const remaining = await ctx.claims.load(ctx.key);
@@ -1694,11 +1694,7 @@ export class ExtractionPersistCoordinator {
1694
1694
  // confirmation, so a disabled deployment never suppresses a write via
1695
1695
  // dedup (PR #2016 thread SD-nH).
1696
1696
  if (factDedupEnabled) {
1697
- exactDuplicate = await confirmActiveFactDuplicate(
1698
- { hasContentHashDedup: (storage, key) => this.deps.hasContentHashDedup(storage, key) },
1699
- targetStorage,
1700
- raceDedupOpts(contentHashDedupKey),
1701
- );
1697
+ exactDuplicate = await confirmActiveFactDuplicate(targetStorage, raceDedupOpts(contentHashDedupKey));
1702
1698
  }
1703
1699
  // PR #2016/#3231: finalize committed writes before a fail-closed
1704
1700
  // window throw propagates (PRRT_kwDORJXyws6zxG6H).
@@ -1711,11 +1707,7 @@ export class ExtractionPersistCoordinator {
1711
1707
  const raceRecheck = (storage: StorageManager, key: string) => () => {
1712
1708
  // Gates mirror the persist-time suppression gates (PR #2016 SD-nH; #1645 quarantine).
1713
1709
  if (!factDedupEnabled || injectionScreenStatus === "pending_review") return Promise.resolve(false);
1714
- return confirmActiveFactDuplicate(
1715
- { hasContentHashDedup: (s, k) => this.deps.hasContentHashDedup(s, k) },
1716
- storage,
1717
- raceDedupOpts(key),
1718
- );
1710
+ return confirmActiveFactDuplicate(storage, raceDedupOpts(key));
1719
1711
  };
1720
1712
  // #1671/#1707 — backfill bi-temporal bounds onto the surviving copy
1721
1713
  // when a duplicate short-circuits: persist-time hit OR raced duplicate