@remnic/core 10.0.51 → 10.0.53

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 (266) hide show
  1. package/dist/access-admin-ops-surface.js +24 -24
  2. package/dist/access-authorization-probe.js +25 -25
  3. package/dist/access-boundary.js +24 -24
  4. package/dist/access-cli.js +65 -65
  5. package/dist/access-extraction-force-flush.js +24 -24
  6. package/dist/access-http-briefing.js +25 -25
  7. package/dist/access-http-query.js +26 -26
  8. package/dist/access-http.js +40 -40
  9. package/dist/access-identity-continuity-surface.js +24 -24
  10. package/dist/access-lcm-surface.js +24 -24
  11. package/dist/access-mcp.js +33 -33
  12. package/dist/access-namespace-preflight.js +24 -24
  13. package/dist/access-observe-write-surface.js +24 -24
  14. package/dist/access-operations-batch.js +25 -25
  15. package/dist/access-operations.js +32 -32
  16. package/dist/access-recall-concurrency.js +24 -24
  17. package/dist/access-recall-response.js +24 -24
  18. package/dist/access-recall-surface.js +24 -24
  19. package/dist/access-service.js +24 -24
  20. package/dist/access-surface-catalog.js +26 -26
  21. package/dist/active-recall.js +9 -9
  22. package/dist/briefing.js +10 -10
  23. package/dist/buffer.js +5 -5
  24. package/dist/causal-consolidation.js +11 -11
  25. package/dist/{chunk-3NL4ALUP.js → chunk-24SIB3EW.js} +3 -3
  26. package/dist/{chunk-Y3T5KQCR.js → chunk-2EYCW2ET.js} +2 -2
  27. package/dist/{chunk-AHGOOZUC.js → chunk-34UVJNSG.js} +2 -2
  28. package/dist/{chunk-2EXF6IIB.js → chunk-3632XJMR.js} +2 -2
  29. package/dist/{chunk-KETS5A62.js → chunk-3H3PNQEF.js} +2 -2
  30. package/dist/{chunk-SFRBUBUZ.js → chunk-3LNAQLW4.js} +3 -3
  31. package/dist/{chunk-75BK4XGV.js → chunk-4BUJICMI.js} +10 -10
  32. package/dist/{chunk-KIWO4327.js → chunk-4W5B5Z6P.js} +2 -2
  33. package/dist/{chunk-B35EQQ7L.js → chunk-53OFDW3P.js} +3 -3
  34. package/dist/{chunk-2ONKXEC7.js → chunk-5LBBXI2A.js} +2 -2
  35. package/dist/{chunk-UCLJTGP3.js → chunk-65B5RFKQ.js} +2 -2
  36. package/dist/{chunk-KAJYSO3U.js → chunk-6CDNOREO.js} +2 -2
  37. package/dist/{chunk-POJRFSJM.js → chunk-7XGNGLAN.js} +2 -2
  38. package/dist/{chunk-A6JDT6QF.js → chunk-AO5ZOD32.js} +2 -2
  39. package/dist/{chunk-RA7YRL6H.js → chunk-AOSCGNIC.js} +3 -3
  40. package/dist/{chunk-LWMLBVGB.js → chunk-B43P3DTC.js} +2 -2
  41. package/dist/{chunk-SJD334AG.js → chunk-CA6X37OB.js} +217 -173
  42. package/dist/chunk-CA6X37OB.js.map +1 -0
  43. package/dist/{chunk-VUMWVYMR.js → chunk-CIMD2MUR.js} +3 -3
  44. package/dist/{chunk-XRFLR3XF.js → chunk-DLZGSZVC.js} +22 -9
  45. package/dist/chunk-DLZGSZVC.js.map +1 -0
  46. package/dist/{chunk-5CWH2LKL.js → chunk-DOJBV6WR.js} +29 -14
  47. package/dist/chunk-DOJBV6WR.js.map +1 -0
  48. package/dist/{chunk-44EUCSG4.js → chunk-FFQNNFHX.js} +2 -2
  49. package/dist/{chunk-HEF4BRMT.js → chunk-FZLZUDJJ.js} +2 -2
  50. package/dist/{chunk-L3EFYRYK.js → chunk-GGFQAGOT.js} +7 -7
  51. package/dist/{chunk-ZLCBLDJO.js → chunk-GIRWNTMV.js} +2 -2
  52. package/dist/{chunk-EC2KVA27.js → chunk-H4DWSOBQ.js} +169 -105
  53. package/dist/chunk-H4DWSOBQ.js.map +1 -0
  54. package/dist/{chunk-SHNM7RIT.js → chunk-HJ4X4BGS.js} +2 -2
  55. package/dist/{chunk-MUNYUSBU.js → chunk-HJQ4NKKO.js} +2 -2
  56. package/dist/{chunk-V6Z4NAJZ.js → chunk-HM3ZTDAX.js} +2 -2
  57. package/dist/{chunk-EZS6QJNC.js → chunk-IFOCK3OJ.js} +2 -2
  58. package/dist/{chunk-LXXIV7Y2.js → chunk-JEX2JL3U.js} +7 -2
  59. package/dist/{chunk-LXXIV7Y2.js.map → chunk-JEX2JL3U.js.map} +1 -1
  60. package/dist/{chunk-DF6ZF2BN.js → chunk-LIVNX7WK.js} +2 -2
  61. package/dist/{chunk-WY66J4PI.js → chunk-LRVFS4KA.js} +2 -2
  62. package/dist/{chunk-LMB47UM6.js → chunk-LTDK6HBV.js} +2 -2
  63. package/dist/{chunk-HCIRR3BA.js → chunk-M4U25HLR.js} +2 -2
  64. package/dist/{chunk-YK7ZJPQM.js → chunk-MGN2RQRG.js} +2 -2
  65. package/dist/{chunk-G26YNEOC.js → chunk-NWA6IXR3.js} +1 -1
  66. package/dist/{chunk-G26YNEOC.js.map → chunk-NWA6IXR3.js.map} +1 -1
  67. package/dist/{chunk-JIJAWTRK.js → chunk-OCXDZJL4.js} +264 -194
  68. package/dist/chunk-OCXDZJL4.js.map +1 -0
  69. package/dist/{chunk-IUQXRRJB.js → chunk-OMWQO7H6.js} +2 -2
  70. package/dist/{chunk-7VO6E5UD.js → chunk-OVZIYLBZ.js} +6 -2
  71. package/dist/chunk-OVZIYLBZ.js.map +1 -0
  72. package/dist/{chunk-SPTPBKZX.js → chunk-OWNAWY6D.js} +2 -2
  73. package/dist/{chunk-XKDQTDXN.js → chunk-PFUXPTBT.js} +36 -15
  74. package/dist/chunk-PFUXPTBT.js.map +1 -0
  75. package/dist/{chunk-DQOGZZJA.js → chunk-QAHP5SBG.js} +3 -3
  76. package/dist/{chunk-GBGU5OZP.js → chunk-QF5EJ5VP.js} +13 -13
  77. package/dist/{chunk-FT75AQ74.js → chunk-QS5DUSBM.js} +2 -2
  78. package/dist/{chunk-72CBJ5VP.js → chunk-R3WZ74B2.js} +3 -3
  79. package/dist/{chunk-PDTNQLPC.js → chunk-RG3I2663.js} +2 -2
  80. package/dist/{chunk-CK6GCGMX.js → chunk-RTJJPW3C.js} +2 -2
  81. package/dist/{chunk-RJDYDF5K.js → chunk-TRAOZ2WP.js} +2 -2
  82. package/dist/{chunk-6ZAU7LAR.js → chunk-TTTPTQ6S.js} +2 -2
  83. package/dist/{chunk-JS44PBCM.js → chunk-U2A56LLF.js} +6 -6
  84. package/dist/{chunk-MC7PXJDP.js → chunk-UBOWDOL5.js} +5 -5
  85. package/dist/{chunk-4FM35YIA.js → chunk-UQYBPPVC.js} +2 -2
  86. package/dist/{chunk-OCRLJJOV.js → chunk-V4VCBFAL.js} +3 -3
  87. package/dist/{chunk-FNNGWN3O.js → chunk-VGE4PUY6.js} +2 -2
  88. package/dist/{chunk-5DVOBBTV.js → chunk-VV73XT3A.js} +2 -2
  89. package/dist/{chunk-GE3ZX6YK.js → chunk-W7NKMRAF.js} +2 -2
  90. package/dist/{chunk-AJJPXUE6.js → chunk-WBN66HPI.js} +2 -2
  91. package/dist/{chunk-XWJUFER3.js → chunk-WGFEGBFB.js} +2 -2
  92. package/dist/{chunk-43TUWQNR.js → chunk-XDKEHZUC.js} +2 -2
  93. package/dist/{chunk-ROAKLYOL.js → chunk-XHEKS4UN.js} +2 -2
  94. package/dist/{chunk-ZKK3VMWU.js → chunk-XVTB6BZS.js} +2 -2
  95. package/dist/{chunk-AZLD4CKG.js → chunk-Y56D5FSD.js} +17 -17
  96. package/dist/{chunk-MPWOAIIH.js → chunk-YDT3OEYF.js} +2 -2
  97. package/dist/{chunk-IN6U2BS5.js → chunk-YJHOVA42.js} +2 -2
  98. package/dist/{chunk-TC55M5PO.js → chunk-YLN4ZQV4.js} +2 -2
  99. package/dist/cli.js +50 -50
  100. package/dist/coding/export-okf-codegraph.js +9 -9
  101. package/dist/compounding/engine.js +11 -11
  102. package/dist/config.js +9 -9
  103. package/dist/connectors/codex-materialize-runner.js +10 -10
  104. package/dist/connectors/index.js +10 -10
  105. package/dist/convergence-refresh.js +12 -12
  106. package/dist/corpus-watermark.js +12 -12
  107. package/dist/entity-retrieval.js +10 -10
  108. package/dist/external-wiki-access.js +25 -25
  109. package/dist/extraction.js +14 -14
  110. package/dist/harmonic-retrieval.js +10 -10
  111. package/dist/index.js +65 -65
  112. package/dist/location/index.js +12 -12
  113. package/dist/maintenance/memory-governance.js +10 -10
  114. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  115. package/dist/maintenance/rebuild-memory-projection-runner.js +12 -12
  116. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  117. package/dist/memory-browse-operations.js +25 -25
  118. package/dist/memory-browse.js +5 -5
  119. package/dist/namespaces/migrate.js +11 -11
  120. package/dist/namespaces/search.d.ts.map +1 -1
  121. package/dist/namespaces/search.js +8 -8
  122. package/dist/namespaces/storage.js +10 -10
  123. package/dist/operator-doctor-corpus.js +13 -13
  124. package/dist/operator-toolkit.js +18 -18
  125. package/dist/orchestration/extraction-persist-deps.d.ts +2 -0
  126. package/dist/orchestration/extraction-persist-deps.d.ts.map +1 -1
  127. package/dist/orchestration/extraction-persist-promotion.d.ts +40 -10
  128. package/dist/orchestration/extraction-persist-promotion.d.ts.map +1 -1
  129. package/dist/orchestration/extraction-persist.d.ts.map +1 -1
  130. package/dist/orchestration/lifecycle-pending-fallback-drain.d.ts +18 -0
  131. package/dist/orchestration/lifecycle-pending-fallback-drain.d.ts.map +1 -0
  132. package/dist/orchestration/maintenance.d.ts +11 -13
  133. package/dist/orchestration/maintenance.d.ts.map +1 -1
  134. package/dist/orchestration/maintenance.js +20 -20
  135. package/dist/orchestration/qmd-maintenance-completion.d.ts +20 -0
  136. package/dist/orchestration/qmd-maintenance-completion.d.ts.map +1 -0
  137. package/dist/orchestrator.d.ts.map +1 -1
  138. package/dist/orchestrator.js +65 -65
  139. package/dist/qmd.js +2 -2
  140. package/dist/recall-composition-decision.js +2 -2
  141. package/dist/recall-navigation-operations.js +25 -25
  142. package/dist/recall-navigation.js +24 -24
  143. package/dist/recall-state-view-admission.js +5 -5
  144. package/dist/recall-why-service.js +5 -5
  145. package/dist/resume-bundles.js +9 -9
  146. package/dist/search/factory.js +7 -7
  147. package/dist/search/index.js +7 -7
  148. package/dist/search/lancedb-backend.d.ts +3 -0
  149. package/dist/search/lancedb-backend.d.ts.map +1 -1
  150. package/dist/search/lancedb-backend.js +2 -2
  151. package/dist/search/meilisearch-backend.d.ts +3 -0
  152. package/dist/search/meilisearch-backend.d.ts.map +1 -1
  153. package/dist/search/meilisearch-backend.js +2 -2
  154. package/dist/search/orama-backend.d.ts +3 -0
  155. package/dist/search/orama-backend.d.ts.map +1 -1
  156. package/dist/search/orama-backend.js +2 -2
  157. package/dist/search/port.d.ts +7 -0
  158. package/dist/search/port.d.ts.map +1 -1
  159. package/dist/search/port.js +1 -1
  160. package/dist/search/remote-backend.d.ts +2 -0
  161. package/dist/search/remote-backend.d.ts.map +1 -1
  162. package/dist/search/remote-backend.js +2 -2
  163. package/dist/semantic-consolidation.js +11 -11
  164. package/dist/semantic-rule-promotion.js +10 -10
  165. package/dist/semantic-rule-verifier.js +10 -10
  166. package/dist/source-agent-qualifier.js +13 -13
  167. package/dist/standup/build.js +10 -10
  168. package/dist/storage/entity-canonical-id-migration-adapter.d.ts +7 -1
  169. package/dist/storage/entity-canonical-id-migration-adapter.d.ts.map +1 -1
  170. package/dist/storage/entity-migration-maintenance-pending.d.ts +31 -0
  171. package/dist/storage/entity-migration-maintenance-pending.d.ts.map +1 -0
  172. package/dist/storage.d.ts +2 -2
  173. package/dist/storage.d.ts.map +1 -1
  174. package/dist/storage.js +9 -9
  175. package/dist/support-passport/index.js +27 -27
  176. package/dist/transfer/export-okf.js +10 -10
  177. package/dist/verified-recall.js +10 -10
  178. package/dist/who-knows-cli.js +11 -11
  179. package/dist/who-knows.js +10 -10
  180. package/package.json +2 -2
  181. package/src/namespaces/search.test.ts +121 -0
  182. package/src/namespaces/search.ts +8 -0
  183. package/src/orchestration/extraction-persist-deps.ts +3 -0
  184. package/src/orchestration/extraction-persist-promotion.ts +104 -16
  185. package/src/orchestration/extraction-persist.ts +66 -77
  186. package/src/orchestration/lifecycle-pending-fallback-drain.ts +97 -0
  187. package/src/orchestration/maintenance.test.ts +443 -1
  188. package/src/orchestration/maintenance.ts +73 -95
  189. package/src/orchestration/qmd-maintenance-completion.ts +32 -0
  190. package/src/orchestrator-entity-write-failure.test.ts +370 -0
  191. package/src/orchestrator.ts +9 -1
  192. package/src/search/lancedb-backend.ts +29 -12
  193. package/src/search/meilisearch-backend.test.ts +148 -0
  194. package/src/search/meilisearch-backend.ts +41 -14
  195. package/src/search/orama-backend.test.ts +75 -1
  196. package/src/search/orama-backend.ts +22 -7
  197. package/src/search/port.ts +7 -0
  198. package/src/search/remote-backend.ts +4 -0
  199. package/src/storage/entity-canonical-id-migration-adapter.ts +44 -9
  200. package/src/storage/entity-migration-maintenance-pending-helpers.test.ts +87 -0
  201. package/src/storage/entity-migration-maintenance-pending.test.ts +242 -0
  202. package/src/storage/entity-migration-maintenance-pending.ts +89 -0
  203. package/src/storage.ts +2 -2
  204. package/dist/chunk-5CWH2LKL.js.map +0 -1
  205. package/dist/chunk-7VO6E5UD.js.map +0 -1
  206. package/dist/chunk-EC2KVA27.js.map +0 -1
  207. package/dist/chunk-JIJAWTRK.js.map +0 -1
  208. package/dist/chunk-SJD334AG.js.map +0 -1
  209. package/dist/chunk-XKDQTDXN.js.map +0 -1
  210. package/dist/chunk-XRFLR3XF.js.map +0 -1
  211. /package/dist/{chunk-3NL4ALUP.js.map → chunk-24SIB3EW.js.map} +0 -0
  212. /package/dist/{chunk-Y3T5KQCR.js.map → chunk-2EYCW2ET.js.map} +0 -0
  213. /package/dist/{chunk-AHGOOZUC.js.map → chunk-34UVJNSG.js.map} +0 -0
  214. /package/dist/{chunk-2EXF6IIB.js.map → chunk-3632XJMR.js.map} +0 -0
  215. /package/dist/{chunk-KETS5A62.js.map → chunk-3H3PNQEF.js.map} +0 -0
  216. /package/dist/{chunk-SFRBUBUZ.js.map → chunk-3LNAQLW4.js.map} +0 -0
  217. /package/dist/{chunk-75BK4XGV.js.map → chunk-4BUJICMI.js.map} +0 -0
  218. /package/dist/{chunk-KIWO4327.js.map → chunk-4W5B5Z6P.js.map} +0 -0
  219. /package/dist/{chunk-B35EQQ7L.js.map → chunk-53OFDW3P.js.map} +0 -0
  220. /package/dist/{chunk-2ONKXEC7.js.map → chunk-5LBBXI2A.js.map} +0 -0
  221. /package/dist/{chunk-UCLJTGP3.js.map → chunk-65B5RFKQ.js.map} +0 -0
  222. /package/dist/{chunk-KAJYSO3U.js.map → chunk-6CDNOREO.js.map} +0 -0
  223. /package/dist/{chunk-POJRFSJM.js.map → chunk-7XGNGLAN.js.map} +0 -0
  224. /package/dist/{chunk-A6JDT6QF.js.map → chunk-AO5ZOD32.js.map} +0 -0
  225. /package/dist/{chunk-RA7YRL6H.js.map → chunk-AOSCGNIC.js.map} +0 -0
  226. /package/dist/{chunk-LWMLBVGB.js.map → chunk-B43P3DTC.js.map} +0 -0
  227. /package/dist/{chunk-VUMWVYMR.js.map → chunk-CIMD2MUR.js.map} +0 -0
  228. /package/dist/{chunk-44EUCSG4.js.map → chunk-FFQNNFHX.js.map} +0 -0
  229. /package/dist/{chunk-HEF4BRMT.js.map → chunk-FZLZUDJJ.js.map} +0 -0
  230. /package/dist/{chunk-L3EFYRYK.js.map → chunk-GGFQAGOT.js.map} +0 -0
  231. /package/dist/{chunk-ZLCBLDJO.js.map → chunk-GIRWNTMV.js.map} +0 -0
  232. /package/dist/{chunk-SHNM7RIT.js.map → chunk-HJ4X4BGS.js.map} +0 -0
  233. /package/dist/{chunk-MUNYUSBU.js.map → chunk-HJQ4NKKO.js.map} +0 -0
  234. /package/dist/{chunk-V6Z4NAJZ.js.map → chunk-HM3ZTDAX.js.map} +0 -0
  235. /package/dist/{chunk-EZS6QJNC.js.map → chunk-IFOCK3OJ.js.map} +0 -0
  236. /package/dist/{chunk-DF6ZF2BN.js.map → chunk-LIVNX7WK.js.map} +0 -0
  237. /package/dist/{chunk-WY66J4PI.js.map → chunk-LRVFS4KA.js.map} +0 -0
  238. /package/dist/{chunk-LMB47UM6.js.map → chunk-LTDK6HBV.js.map} +0 -0
  239. /package/dist/{chunk-HCIRR3BA.js.map → chunk-M4U25HLR.js.map} +0 -0
  240. /package/dist/{chunk-YK7ZJPQM.js.map → chunk-MGN2RQRG.js.map} +0 -0
  241. /package/dist/{chunk-IUQXRRJB.js.map → chunk-OMWQO7H6.js.map} +0 -0
  242. /package/dist/{chunk-SPTPBKZX.js.map → chunk-OWNAWY6D.js.map} +0 -0
  243. /package/dist/{chunk-DQOGZZJA.js.map → chunk-QAHP5SBG.js.map} +0 -0
  244. /package/dist/{chunk-GBGU5OZP.js.map → chunk-QF5EJ5VP.js.map} +0 -0
  245. /package/dist/{chunk-FT75AQ74.js.map → chunk-QS5DUSBM.js.map} +0 -0
  246. /package/dist/{chunk-72CBJ5VP.js.map → chunk-R3WZ74B2.js.map} +0 -0
  247. /package/dist/{chunk-PDTNQLPC.js.map → chunk-RG3I2663.js.map} +0 -0
  248. /package/dist/{chunk-CK6GCGMX.js.map → chunk-RTJJPW3C.js.map} +0 -0
  249. /package/dist/{chunk-RJDYDF5K.js.map → chunk-TRAOZ2WP.js.map} +0 -0
  250. /package/dist/{chunk-6ZAU7LAR.js.map → chunk-TTTPTQ6S.js.map} +0 -0
  251. /package/dist/{chunk-JS44PBCM.js.map → chunk-U2A56LLF.js.map} +0 -0
  252. /package/dist/{chunk-MC7PXJDP.js.map → chunk-UBOWDOL5.js.map} +0 -0
  253. /package/dist/{chunk-4FM35YIA.js.map → chunk-UQYBPPVC.js.map} +0 -0
  254. /package/dist/{chunk-OCRLJJOV.js.map → chunk-V4VCBFAL.js.map} +0 -0
  255. /package/dist/{chunk-FNNGWN3O.js.map → chunk-VGE4PUY6.js.map} +0 -0
  256. /package/dist/{chunk-5DVOBBTV.js.map → chunk-VV73XT3A.js.map} +0 -0
  257. /package/dist/{chunk-GE3ZX6YK.js.map → chunk-W7NKMRAF.js.map} +0 -0
  258. /package/dist/{chunk-AJJPXUE6.js.map → chunk-WBN66HPI.js.map} +0 -0
  259. /package/dist/{chunk-XWJUFER3.js.map → chunk-WGFEGBFB.js.map} +0 -0
  260. /package/dist/{chunk-43TUWQNR.js.map → chunk-XDKEHZUC.js.map} +0 -0
  261. /package/dist/{chunk-ROAKLYOL.js.map → chunk-XHEKS4UN.js.map} +0 -0
  262. /package/dist/{chunk-ZKK3VMWU.js.map → chunk-XVTB6BZS.js.map} +0 -0
  263. /package/dist/{chunk-AZLD4CKG.js.map → chunk-Y56D5FSD.js.map} +0 -0
  264. /package/dist/{chunk-MPWOAIIH.js.map → chunk-YDT3OEYF.js.map} +0 -0
  265. /package/dist/{chunk-IN6U2BS5.js.map → chunk-YJHOVA42.js.map} +0 -0
  266. /package/dist/{chunk-TC55M5PO.js.map → chunk-YLN4ZQV4.js.map} +0 -0
@@ -0,0 +1,87 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import {
8
+ clearMaintenancePending,
9
+ drainMaintenanceSettlements,
10
+ ENTITY_MIGRATION_MAINTENANCE_PENDING_PREFIX,
11
+ listMaintenancePending,
12
+ settleMaintenancePending,
13
+ trackMaintenanceSettlement,
14
+ writeMaintenancePending,
15
+ } from "./entity-migration-maintenance-pending.js";
16
+
17
+ async function withStateDir(run: (stateDir: string) => Promise<void>): Promise<void> {
18
+ const stateDir = await mkdtemp(path.join(os.tmpdir(), "remnic-3286-marker-unit-"));
19
+ try {
20
+ await run(stateDir);
21
+ } finally {
22
+ await rm(stateDir, { recursive: true, force: true });
23
+ }
24
+ }
25
+
26
+ test("each arm writes its own marker, and listing ignores in-progress temp files", async () => {
27
+ await withStateDir(async (stateDir) => {
28
+ assert.deepEqual(await listMaintenancePending(path.join(stateDir, "missing")), [], "an absent state dir owes nothing");
29
+ const first = await writeMaintenancePending(stateDir);
30
+ const second = await writeMaintenancePending(stateDir);
31
+ assert.notEqual(first, second, "concurrent arms never share a file");
32
+ await writeFile(path.join(stateDir, `${ENTITY_MIGRATION_MAINTENANCE_PENDING_PREFIX}.crashed.tmp`), "partial");
33
+ await writeFile(path.join(stateDir, "entity-canonical-id-migration-v1.json"), "{}");
34
+ assert.deepEqual((await listMaintenancePending(stateDir)).sort(), [first, second].sort());
35
+ });
36
+ });
37
+
38
+ test("clearing named markers never deletes one armed by another run, and tolerates an already-gone name", async () => {
39
+ await withStateDir(async (stateDir) => {
40
+ const mine = await writeMaintenancePending(stateDir);
41
+ const concurrent = await writeMaintenancePending(stateDir);
42
+ await clearMaintenancePending(stateDir, [mine, "entity-canonical-id-maintenance.pending.already-gone"]);
43
+ assert.deepEqual(await listMaintenancePending(stateDir), [concurrent], "the concurrent run's marker survives");
44
+ await clearMaintenancePending(stateDir, [concurrent]);
45
+ assert.deepEqual(await readdir(stateDir), []);
46
+ });
47
+ });
48
+
49
+ test("settling clears on served or no answer, keeps on a refusal or a not-served promise, and propagates a rejection", async () => {
50
+ await withStateDir(async (stateDir) => {
51
+ const cases: Array<[string, boolean | Promise<boolean> | void, boolean]> = [
52
+ ["true", true, true],
53
+ ["nothing", undefined, true],
54
+ ["resolved true", Promise.resolve(true), true],
55
+ ["false", false, false],
56
+ ["resolved false", Promise.resolve(false), false],
57
+ ];
58
+ for (const [label, outcome, clears] of cases) {
59
+ const marker = await writeMaintenancePending(stateDir);
60
+ await settleMaintenancePending(stateDir, [marker], outcome);
61
+ assert.equal((await listMaintenancePending(stateDir)).length, clears ? 0 : 1, label);
62
+ await clearMaintenancePending(stateDir, await listMaintenancePending(stateDir));
63
+ }
64
+ const marker = await writeMaintenancePending(stateDir);
65
+ await assert.rejects(settleMaintenancePending(stateDir, [marker], Promise.reject(new Error("pass failed"))), /pass failed/);
66
+ assert.deepEqual(await listMaintenancePending(stateDir), [marker], "a rejected request keeps the marker");
67
+ });
68
+ });
69
+
70
+ test("tracked settlements can be drained and forgotten once done", async () => {
71
+ await withStateDir(async (stateDir) => {
72
+ const release = Promise.withResolvers<void>();
73
+ let finished = false;
74
+ trackMaintenanceSettlement(
75
+ stateDir,
76
+ release.promise.then(() => {
77
+ finished = true;
78
+ }),
79
+ );
80
+ const drained = drainMaintenanceSettlements(stateDir);
81
+ assert.equal(finished, false);
82
+ release.resolve();
83
+ await drained;
84
+ assert.equal(finished, true);
85
+ await drainMaintenanceSettlements(stateDir);
86
+ });
87
+ });
@@ -0,0 +1,242 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { StorageManager, normalizeEntityName } from "../storage.js";
8
+ import { drainMaintenanceSettlements, writeMaintenancePending } from "./entity-migration-maintenance-pending.js";
9
+
10
+ // Issue #3286: a crash between the reference rewrite and the completion hook
11
+ // must not leave the QMD index stale. A "maintenance pending" marker beside the
12
+ // migration journal makes the next startup's no-op replay fire the hook once.
13
+ const MAINTENANCE_PENDING_PREFIX = "entity-canonical-id-maintenance.pending";
14
+
15
+ /** Pending-marker files currently on disk (temp files excluded). */
16
+ async function pendingMarkers(dir: string): Promise<string[]> {
17
+ return (await readdir(path.join(dir, "state")))
18
+ .filter((name) => name.startsWith(MAINTENANCE_PENDING_PREFIX) && !name.endsWith(".tmp"));
19
+ }
20
+
21
+ interface SeededCorpus {
22
+ dir: string;
23
+ legacy: string;
24
+ canonical: string;
25
+ }
26
+
27
+ /**
28
+ * Seed a corpus with one legacy-typed entity file and one memory referencing
29
+ * it by the legacy id, so a later run has a real rewrite to perform. The
30
+ * legacy file keeps its old id as the filename while its `Type:` was retyped —
31
+ * the production legacy shape (see entity-canonical-id-collision.test.ts).
32
+ */
33
+ async function seedLegacyCorpus(): Promise<SeededCorpus> {
34
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-3286-pending-"));
35
+ // Create the directory skeleton (facts/ is date-partitioned on write, so
36
+ // the raw file below lands in a directory ensureDirectories has made).
37
+ await new StorageManager(dir).ensureDirectories();
38
+ const name = "Nightly Ingest";
39
+ const legacy = normalizeEntityName(name, "automation");
40
+ const canonical = normalizeEntityName(name, "automation-cron-job");
41
+ assert.notEqual(legacy, canonical, "fixture must actually straddle the rename");
42
+ await writeFile(
43
+ path.join(dir, "entities", `${legacy}.md`),
44
+ `---\nid: ${legacy}\ncreated: 2026-03-01T00:00:00.000Z\nupdated: 2026-03-01T00:00:00.000Z\n---\n\n`
45
+ + `# ${name}\n\n**Type:** automation-cron-job\n\nRuns at 02:00.\n`,
46
+ "utf8",
47
+ );
48
+ // Seed the memory file fully raw with the legacy entityRef: writeMemoryFrontmatter
49
+ // would canonicalize the ref through the journal this pass is about to build,
50
+ // and a raw edit after a cached read would serve the stale parse (the corpus
51
+ // version only moves on managed writes).
52
+ const memoryPath = path.join(dir, "facts", new Date().toISOString().slice(0, 10), "note-3286.md");
53
+ await writeFile(
54
+ memoryPath,
55
+ [
56
+ "---",
57
+ "id: fact-3286-note",
58
+ "category: fact",
59
+ "created: 2026-03-01T00:00:00.000Z",
60
+ "updated: 2026-03-01T00:00:00.000Z",
61
+ "source: extraction",
62
+ "confidence: 0.8",
63
+ "confidenceTier: implied",
64
+ "tags: []",
65
+ `entityRef: ${legacy}`,
66
+ "---",
67
+ "",
68
+ `References [[${legacy}]].`,
69
+ "",
70
+ ].join("\n"),
71
+ "utf8",
72
+ );
73
+ return { dir, legacy, canonical };
74
+ }
75
+
76
+ function countHook(storage: StorageManager, counter: { calls: number }, throwFirstTime = false): void {
77
+ storage.onEntityMigrationComplete = () => {
78
+ counter.calls += 1;
79
+ if (throwFirstTime && counter.calls === 1) throw new Error("simulated crash before maintenance was requested");
80
+ };
81
+ }
82
+
83
+ test("a crash after the rewrite but before the hook fires the hook once on the next no-op startup", async () => {
84
+ const { dir } = await seedLegacyCorpus();
85
+ const hook = { calls: 0 };
86
+ try {
87
+ // Pass 1: the migration rewrites; the hook throws to emulate the crash
88
+ // between the rewrite and maintenance being requested.
89
+ const first = new StorageManager(dir);
90
+ countHook(first, hook, true);
91
+ await first.ensureDirectories();
92
+ await first.drainEntityCanonicalIdMigration();
93
+ assert.equal(hook.calls, 1, "precondition: the rewriting pass reached the hook");
94
+
95
+ // Pass 2: a fresh manager replays a no-op journal; the pending marker
96
+ // must fire the hook exactly once, then clear itself.
97
+ const second = new StorageManager(dir);
98
+ countHook(second, hook);
99
+ await second.ensureDirectories();
100
+ await second.drainEntityCanonicalIdMigration();
101
+ assert.equal(hook.calls, 2, "the no-op replay must fire the hook once via the pending marker");
102
+ assert.equal((await pendingMarkers(dir)).length, 0, "the marker must clear after the hook");
103
+
104
+ // Pass 3: ordinary no-op startup stays quiet.
105
+ const third = new StorageManager(dir);
106
+ countHook(third, hook);
107
+ await third.ensureDirectories();
108
+ await third.drainEntityCanonicalIdMigration();
109
+ assert.equal(hook.calls, 2, "a no-op startup after the marker is cleared must not fire the hook");
110
+ } finally {
111
+ StorageManager.clearAllStaticCaches();
112
+ await rm(dir, { recursive: true, force: true });
113
+ }
114
+ });
115
+
116
+ test("a rewriting pass with a healthy hook leaves no pending marker", async () => {
117
+ const { dir, legacy, canonical } = await seedLegacyCorpus();
118
+ const hook = { calls: 0 };
119
+ try {
120
+ const storage = new StorageManager(dir);
121
+ countHook(storage, hook);
122
+ await storage.ensureDirectories();
123
+ await storage.drainEntityCanonicalIdMigration();
124
+ assert.equal(hook.calls, 1);
125
+ assert.equal((await pendingMarkers(dir)).length, 0);
126
+ const journal = JSON.parse(
127
+ await readFile(path.join(dir, "state", "entity-canonical-id-migration-v1.json"), "utf8"),
128
+ );
129
+ assert.equal(journal.complete, true, "precondition: the journal completed");
130
+ await assert.rejects(() => readFile(path.join(dir, "entities", `${legacy}.md`), "utf8"));
131
+ await readFile(path.join(dir, "entities", `${canonical}.md`), "utf8");
132
+ } finally {
133
+ StorageManager.clearAllStaticCaches();
134
+ await rm(dir, { recursive: true, force: true });
135
+ }
136
+ });
137
+ test("a process with no hook wired keeps the marker so a later wired start fires once", async () => {
138
+ const { dir } = await seedLegacyCorpus();
139
+ const hook = { calls: 0 };
140
+ try {
141
+ // A CLI-only process runs the rewrite with nothing to request maintenance.
142
+ const bare = new StorageManager(dir);
143
+ await bare.ensureDirectories();
144
+ await bare.drainEntityCanonicalIdMigration();
145
+ assert.equal((await pendingMarkers(dir)).length, 1, "no hook: the owed re-index must persist");
146
+
147
+ const wired = new StorageManager(dir);
148
+ countHook(wired, hook);
149
+ await wired.ensureDirectories();
150
+ await wired.drainEntityCanonicalIdMigration();
151
+ assert.equal(hook.calls, 1);
152
+ assert.equal((await pendingMarkers(dir)).length, 0);
153
+ } finally {
154
+ StorageManager.clearAllStaticCaches();
155
+ await rm(dir, { recursive: true, force: true });
156
+ }
157
+ });
158
+
159
+ test("a hook that refuses the request (shutdown) keeps the marker for the next start", async () => {
160
+ const { dir } = await seedLegacyCorpus();
161
+ const hook = { calls: 0 };
162
+ try {
163
+ const stopping = new StorageManager(dir);
164
+ stopping.onEntityMigrationComplete = () => {
165
+ hook.calls += 1;
166
+ return false;
167
+ };
168
+ await stopping.ensureDirectories();
169
+ await stopping.drainEntityCanonicalIdMigration();
170
+ assert.equal(hook.calls, 1);
171
+ assert.equal((await pendingMarkers(dir)).length, 1, "a refused request must stay owed");
172
+
173
+ const next = new StorageManager(dir);
174
+ countHook(next, hook);
175
+ await next.ensureDirectories();
176
+ await next.drainEntityCanonicalIdMigration();
177
+ assert.equal(hook.calls, 2);
178
+ assert.equal((await pendingMarkers(dir)).length, 0);
179
+ } finally {
180
+ StorageManager.clearAllStaticCaches();
181
+ await rm(dir, { recursive: true, force: true });
182
+ }
183
+ });
184
+
185
+ test("an async hook keeps the marker until its maintenance pass is served, and keeps it when that fails", async () => {
186
+ const { dir } = await seedLegacyCorpus();
187
+ try {
188
+ // The real hook only ARMS a debounced timer; its promise settles when the
189
+ // pass has run. Clearing at request time would lose the re-index to a crash
190
+ // or destroy() inside the debounce window (review finding on #3287).
191
+ const served = Promise.withResolvers<boolean>();
192
+ const first = new StorageManager(dir);
193
+ first.onEntityMigrationComplete = () => served.promise;
194
+ await first.ensureDirectories();
195
+ await first.drainEntityCanonicalIdMigration();
196
+ assert.equal((await pendingMarkers(dir)).length, 1, "the marker must outlive the unserved request");
197
+
198
+ served.resolve(false);
199
+ await drainMaintenanceSettlements(path.join(dir, "state"));
200
+ assert.equal((await pendingMarkers(dir)).length, 1, "a pass that did not serve the request keeps the marker");
201
+
202
+ // A later start still owes it; this time the pass is served.
203
+ const retry = Promise.withResolvers<boolean>();
204
+ const second = new StorageManager(dir);
205
+ second.onEntityMigrationComplete = () => retry.promise;
206
+ await second.ensureDirectories();
207
+ await second.drainEntityCanonicalIdMigration();
208
+ assert.equal((await pendingMarkers(dir)).length, 1);
209
+ retry.resolve(true);
210
+ await drainMaintenanceSettlements(path.join(dir, "state"));
211
+ assert.equal((await pendingMarkers(dir)).length, 0, "a served pass clears the marker");
212
+ } finally {
213
+ StorageManager.clearAllStaticCaches();
214
+ await rm(dir, { recursive: true, force: true });
215
+ }
216
+ });
217
+
218
+ test("clearing one run's marker never deletes a marker a concurrent run armed meanwhile", async () => {
219
+ const { dir } = await seedLegacyCorpus();
220
+ try {
221
+ const stateDir = path.join(dir, "state");
222
+ const first = new StorageManager(dir);
223
+ // Another process takes the migration lock after this run released it and
224
+ // arms its own marker before this run's hook returns and its clear starts.
225
+ let concurrentArm: Promise<string> | undefined;
226
+ first.onEntityMigrationComplete = () => {
227
+ concurrentArm = writeMaintenancePending(stateDir);
228
+ };
229
+ await first.ensureDirectories();
230
+ await first.drainEntityCanonicalIdMigration();
231
+ await drainMaintenanceSettlements(stateDir);
232
+ await concurrentArm;
233
+ assert.equal(
234
+ (await pendingMarkers(dir)).length,
235
+ 1,
236
+ "the concurrent run's marker must survive this run's clear (it is the only owed re-index if that run crashes)",
237
+ );
238
+ } finally {
239
+ StorageManager.clearAllStaticCaches();
240
+ await rm(dir, { recursive: true, force: true });
241
+ }
242
+ });
@@ -0,0 +1,89 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readdir, rename, unlink, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { isErrnoCode } from "../utils/errno.js";
5
+
6
+ /**
7
+ * Issue #3286: the entity migration asks search maintenance to re-index only
8
+ * after a pass that rewrote references. A crash between that rewrite and the
9
+ * request would replay as a quiet no-op and leave the QMD index and projections
10
+ * naming pre-rewrite entity ids. A marker, written BEFORE a pass rewrites
11
+ * anything and cleared only after the host reports the request SERVED, makes
12
+ * the owed request survive the crash. No marker means no request, so ordinary
13
+ * no-op startups stay quiet.
14
+ *
15
+ * One file per arming run (`<prefix>.<pid>-<uuid>`): a run clears only the
16
+ * files it observed at start or wrote itself, so a concurrent run's marker can
17
+ * never be overwritten or deleted by this run's clear (review finding on #3287).
18
+ */
19
+ export const ENTITY_MIGRATION_MAINTENANCE_PENDING_PREFIX = "entity-canonical-id-maintenance.pending";
20
+
21
+ /** Atomic temp-and-rename publish, the same pattern as the migration journal. Returns the marker's file name. */
22
+ export async function writeMaintenancePending(stateDir: string): Promise<string> {
23
+ const name = `${ENTITY_MIGRATION_MAINTENANCE_PENDING_PREFIX}.${process.pid}-${randomUUID()}`;
24
+ const destination = path.join(stateDir, name);
25
+ const temporary = `${destination}.tmp`;
26
+ try {
27
+ await writeFile(temporary, `${new Date().toISOString()}\n`, "utf-8");
28
+ await rename(temporary, destination);
29
+ } finally {
30
+ await unlink(temporary).catch(() => undefined);
31
+ }
32
+ return name;
33
+ }
34
+
35
+ /** Names of the markers currently on disk (in-progress temp files excluded). */
36
+ export async function listMaintenancePending(stateDir: string): Promise<string[]> {
37
+ try {
38
+ return (await readdir(stateDir)).filter(
39
+ (name) => name.startsWith(ENTITY_MIGRATION_MAINTENANCE_PENDING_PREFIX) && !name.endsWith(".tmp"),
40
+ );
41
+ } catch (error) {
42
+ if (isErrnoCode(error, "ENOENT")) return [];
43
+ throw error;
44
+ }
45
+ }
46
+
47
+ /** Remove exactly the named markers; one already gone (another run cleared it) is fine. */
48
+ export async function clearMaintenancePending(stateDir: string, names: readonly string[]): Promise<void> {
49
+ for (const name of names) {
50
+ try {
51
+ await unlink(path.join(stateDir, name));
52
+ } catch (error) {
53
+ if (!isErrnoCode(error, "ENOENT")) throw error;
54
+ }
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Clear the markers `held` by a run once the host's maintenance request was
60
+ * served. `false` (refused) or a promise settling to `false` keeps them; a
61
+ * rejection propagates so the caller can log it and the markers stay.
62
+ */
63
+ export async function settleMaintenancePending(
64
+ stateDir: string,
65
+ held: readonly string[],
66
+ outcome: boolean | Promise<boolean> | void,
67
+ ): Promise<void> {
68
+ if ((await outcome) !== false) await clearMaintenancePending(stateDir, held);
69
+ }
70
+
71
+ const settlements = new Map<string, Set<Promise<void>>>();
72
+
73
+ /** Track a settlement that outlives its migration run (an async hook is served after a debounce). */
74
+ export function trackMaintenanceSettlement(stateDir: string, settling: Promise<void>): void {
75
+ let tracked = settlements.get(stateDir);
76
+ if (!tracked) {
77
+ tracked = new Set<Promise<void>>();
78
+ settlements.set(stateDir, tracked);
79
+ }
80
+ const done = tracked;
81
+ done.add(settling);
82
+ void settling.finally(() => done.delete(settling));
83
+ }
84
+
85
+ /** Resolves once every tracked settlement for the directory has finished (never rejects: callers pre-handle errors). */
86
+ export async function drainMaintenanceSettlements(stateDir: string): Promise<void> {
87
+ const tracked = settlements.get(stateDir);
88
+ if (tracked) await Promise.all(tracked);
89
+ }
package/src/storage.ts CHANGED
@@ -1923,8 +1923,8 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
1923
1923
  onCatalogWrite?: () => void;
1924
1924
  /** Post-write embedding hook (#2019). Installed by the orchestrator; fire-and-forget. */
1925
1925
  onMemoryWrite?: (filePath: string) => void;
1926
- /** Request search maintenance after a completed entity replay. */
1927
- onEntityMigrationComplete?: () => void;
1926
+ /** Request search maintenance after an entity replay. false = refused; a Promise settles true once a pass served it. The pending marker stays until then. */
1927
+ onEntityMigrationComplete?: () => boolean | Promise<boolean> | void;
1928
1928
  private notifyMemoryWrite(filePath: string): void {
1929
1929
  try {
1930
1930
  this.onMemoryWrite?.(filePath);
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/search/lancedb-backend.ts"],"sourcesContent":["import { log } from \"../logger.js\";\nimport {\n reportSearchDegradation,\n resolveEnsureCollectionArgs,\n type SearchBackend,\n type SearchExecutionOptions,\n type SearchQueryOptions,\n type SearchResult,\n} from \"./port.js\";\nimport type { EmbedHelper, EmbedProviderIdentity, EmbedWithProviderResult } from \"./embed-helper.js\";\nimport { scanMemoryDir } from \"./document-scanner.js\";\nimport { isSearchAborted, throwIfSearchAborted } from \"./abort.js\";\n\nexport interface LanceDbBackendOptions {\n dbPath: string;\n collection: string;\n embedHelper: EmbedHelper;\n memoryDir: string;\n embeddingDimension: number;\n}\n\n/**\n * LanceDB search backend — embedded hybrid FTS+vector with RRF reranking.\n *\n * Uses @lancedb/lancedb for native Arrow-backed storage.\n * One table per collection. Supports full-text, vector, and hybrid search.\n */\nexport class LanceDbBackend implements SearchBackend {\n private readonly dbPath: string;\n private readonly collection: string;\n private readonly embedHelper: EmbedHelper;\n private readonly memoryDir: string;\n private readonly embeddingDimension: number;\n private available = false;\n private db: any = null;\n private lanceModule: any = null;\n private readonly vectorProviderCompatibility = new WeakMap<\n object,\n { providerIdentity: EmbedProviderIdentity; compatible: boolean }\n >();\n\n constructor(opts: LanceDbBackendOptions) {\n this.dbPath = opts.dbPath;\n this.collection = opts.collection;\n this.embedHelper = opts.embedHelper;\n this.memoryDir = opts.memoryDir;\n this.embeddingDimension = opts.embeddingDimension;\n }\n\n async probe(): Promise<boolean> {\n try {\n await this.ensureDb();\n this.available = true;\n return true;\n } catch (err) {\n log.debug(`LanceDbBackend probe failed: ${err}`);\n this.available = false;\n return false;\n }\n }\n\n isAvailable(): boolean {\n return this.available;\n }\n\n debugStatus(): string {\n return `backend=lancedb available=${this.available} dbPath=${this.dbPath}`;\n }\n\n async search(\n query: string,\n _collection?: string,\n maxResults?: number,\n _options?: SearchQueryOptions,\n execution?: SearchExecutionOptions,\n ): Promise<SearchResult[]> {\n return this.hybridSearch(query, _collection, maxResults, execution);\n }\n\n async searchGlobal(query: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n const limit = maxResults ?? 10;\n if (!this.available) return [];\n\n try {\n throwIfSearchAborted(execution, \"LanceDbBackend global search aborted\");\n const db = await this.ensureDb();\n const tableNames = await db.tableNames();\n const allResults: SearchResult[] = [];\n\n for (const name of tableNames) {\n throwIfSearchAborted(execution, \"LanceDbBackend global search aborted\");\n try {\n const table = await db.openTable(name);\n const results = await this.searchTable(table, query, \"hybrid\", limit, execution);\n allResults.push(...results);\n } catch {\n // Skip tables that fail\n }\n }\n\n allResults.sort((a, b) => b.score - a.score);\n return allResults.slice(0, limit);\n } catch (err) {\n log.debug(`LanceDbBackend searchGlobal failed: ${err}`);\n return [];\n }\n }\n\n async bm25Search(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n if (isSearchAborted(execution)) return [];\n const table = await this.ensureTableForCollection(collection ?? this.collection);\n if (isSearchAborted(execution)) return [];\n if (!table) return [];\n return this.searchTable(table, query, \"fts\", maxResults ?? 10, execution);\n }\n\n async vectorSearch(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n if (isSearchAborted(execution)) return [];\n const table = await this.ensureTableForCollection(collection ?? this.collection);\n if (isSearchAborted(execution)) return [];\n if (!table) return [];\n return this.searchTable(table, query, \"vector\", maxResults ?? 10, execution);\n }\n\n async hybridSearch(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n if (isSearchAborted(execution)) return [];\n const table = await this.ensureTableForCollection(collection ?? this.collection);\n if (isSearchAborted(execution)) return [];\n if (!table) return [];\n return this.searchTable(table, query, \"hybrid\", maxResults ?? 10, execution);\n }\n\n async update(execution?: SearchExecutionOptions): Promise<void> {\n await this.updateCollection(this.collection, execution);\n }\n\n async updateCollection(collection: string, execution?: SearchExecutionOptions): Promise<void> {\n await this.updateCollectionFromDir(collection, this.memoryDir, execution);\n }\n\n async updateCollectionFromDir(collection: string, memoryDir: string, execution?: SearchExecutionOptions): Promise<void> {\n if (isSearchAborted(execution)) return;\n let table = await this.ensureTableForCollection(collection);\n if (isSearchAborted(execution)) return;\n if (!table) return;\n\n const docs = await scanMemoryDir(memoryDir);\n if (isSearchAborted(execution)) return;\n if (docs.length === 0) {\n // Clear stale data when no docs remain\n try {\n const db = await this.ensureDb();\n await db.dropTable(collection).catch(() => {});\n if (collection === this.collection) this.table = null;\n } catch {\n // Best-effort cleanup\n }\n return;\n }\n\n const embeddingProviderIdentity = this.embedHelper.getProviderIdentity();\n const existingVectors = new Map<string, {\n vector: number[];\n providerIdentity?: string;\n }>();\n const vectorProviderColumnState = await this.tableVectorProviderColumnState(table);\n if (vectorProviderColumnState === \"missing\") {\n table = await this.recreateTableForCollection(collection);\n if (isSearchAborted(execution)) return;\n if (!table) return;\n } else if (vectorProviderColumnState === \"present\") {\n try {\n const existingRows = await table.query().select([\"docid\", \"vector\", \"vectorProvider\"]).toArray();\n for (const row of existingRows ?? []) {\n if (isSearchAborted(execution)) return;\n const docid = row.docid;\n if (typeof docid !== \"string\") continue;\n const vector = row.vector;\n if (!vector || typeof vector !== \"object\") continue;\n existingVectors.set(docid, {\n vector: Array.from(vector as ArrayLike<number>),\n providerIdentity: typeof row.vectorProvider === \"string\" ? row.vectorProvider : undefined,\n });\n }\n } catch {\n log.debug(\"LanceDbBackend skipped refresh after vector preservation failed\");\n return;\n }\n } else {\n log.debug(\"LanceDbBackend skipped vector preservation after vectorProvider probe failed\");\n return;\n }\n\n const rows = docs.map((d) => {\n const existing = existingVectors.get(d.docid);\n const canPreserveVector =\n existing &&\n this.isCompatibleStoredVector(existing.vector) &&\n (!embeddingProviderIdentity ||\n existing.providerIdentity === embeddingProviderIdentity);\n return {\n docid: d.docid,\n path: d.path,\n content: d.content,\n snippet: d.snippet,\n vector: canPreserveVector\n ? existing.vector\n : new Array(this.embeddingDimension).fill(0),\n vectorProvider: canPreserveVector\n ? existing.providerIdentity ?? \"\"\n : \"\",\n };\n });\n\n try {\n if (isSearchAborted(execution)) return;\n await table.add(rows, { mode: \"overwrite\" });\n this.rememberVectorProviderCompatibility(\n table,\n embeddingProviderIdentity,\n rows.length > 0 && rows.every((row) => row.vectorProvider === embeddingProviderIdentity),\n );\n if (isSearchAborted(execution)) return;\n // FTS index on the content column.\n await this.createFtsIndex(table);\n if (collection === this.collection) this.table = table;\n } catch (err) {\n log.debug(`LanceDbBackend update failed: ${err}`);\n }\n }\n\n async embed(): Promise<void> {\n await this.embedCollection(this.collection);\n }\n\n async embedCollection(collection: string): Promise<void> {\n if (!this.embedHelper.isAvailable()) return;\n\n const table = await this.ensureTableForCollection(collection);\n if (!table) return;\n\n try {\n const embeddingProviderIdentity = this.embedHelper.getProviderIdentity();\n const allRows = await table.query().select([\"docid\", \"content\", \"vector\", \"vectorProvider\"]).toArray();\n const needsEmbed = allRows.filter((row: any) => {\n if (embeddingProviderIdentity && row.vectorProvider !== embeddingProviderIdentity) {\n return true;\n }\n return !this.isCompatibleStoredVector(row.vector);\n });\n\n if (needsEmbed.length === 0) {\n this.rememberVectorProviderCompatibility(table, embeddingProviderIdentity, true);\n return;\n }\n\n let rowsToEmbed = needsEmbed;\n let embedResult = await this.embedHelper.embedBatchWithProvider(\n rowsToEmbed.map((row: any) => row.content as string),\n );\n if (!embedResult) return;\n if (\n embeddingProviderIdentity &&\n embedResult.providerIdentity !== embeddingProviderIdentity\n ) {\n const effectiveProviderIdentity = embedResult.providerIdentity;\n const originalDocids = new Set(rowsToEmbed.map((row: any) => row.docid));\n const effectiveNeedsEmbed = allRows.filter((row: any) => (\n row.vectorProvider !== effectiveProviderIdentity ||\n !this.isCompatibleStoredVector(row.vector)\n ));\n const sameRows =\n effectiveNeedsEmbed.length === rowsToEmbed.length &&\n effectiveNeedsEmbed.every((row: any) => originalDocids.has(row.docid));\n if (!sameRows) {\n const effectiveTexts = effectiveNeedsEmbed.map((row: any) => row.content as string);\n const effectiveEmbedResult = await this.embedHelper.embedBatchWithProvider(effectiveTexts);\n if (effectiveEmbedResult) {\n rowsToEmbed = effectiveNeedsEmbed;\n embedResult = effectiveEmbedResult;\n }\n }\n }\n const { vectors, providerIdentity } = embedResult;\n\n let allEmbedded = true;\n for (let i = 0; i < rowsToEmbed.length; i++) {\n const vec = vectors[i];\n if (!this.isExpectedDimensionVector(vec)) {\n allEmbedded = false;\n continue;\n }\n const docid = rowsToEmbed[i].docid;\n await table.update({\n where: `docid = '${docid.replace(/'/g, \"''\")}'`,\n values: { vector: vec, vectorProvider: providerIdentity },\n });\n }\n if (allEmbedded) {\n this.rememberVectorProviderCompatibility(table, providerIdentity, true);\n } else {\n this.rememberVectorProviderCompatibility(table, providerIdentity, false);\n }\n } catch (err) {\n log.debug(`LanceDbBackend embed failed: ${err}`);\n }\n }\n\n async ensureCollection(\n _memoryDir: string,\n collectionOrExecution?: string | SearchExecutionOptions,\n execution?: SearchExecutionOptions,\n ): Promise<\"present\" | \"missing\" | \"unknown\" | \"skipped\"> {\n const { collection, execution: effectiveExecution } = resolveEnsureCollectionArgs(\n collectionOrExecution,\n execution,\n );\n if (isSearchAborted(effectiveExecution)) return \"skipped\";\n try {\n await this.ensureTableForCollection(collection ?? this.collection);\n return \"present\";\n } catch {\n return \"missing\";\n }\n }\n\n private table: any = null;\n\n private get lanceIndex(): any {\n return this.lanceModule.Index ?? this.lanceModule.default?.Index;\n }\n\n private async ensureDb(): Promise<any> {\n if (this.db) return this.db;\n if (!this.lanceModule) {\n this.lanceModule = await import(\"@lancedb/lancedb\");\n }\n const connect = this.lanceModule.connect ?? this.lanceModule.default?.connect;\n this.db = await connect(this.dbPath);\n return this.db;\n }\n\n private async ensureTableForCollection(collection: string): Promise<any> {\n // For the default collection, use the cached instance\n if (collection === this.collection) return this.ensureTable();\n\n const db = await this.ensureDb();\n const tables = await db.tableNames();\n\n if (tables.includes(collection)) {\n return await db.openTable(collection);\n }\n\n // Create empty table with schema\n const emptyRow = {\n docid: \"__placeholder__\",\n path: \"\",\n content: \"\",\n snippet: \"\",\n vector: new Array(this.embeddingDimension).fill(0),\n vectorProvider: \"\",\n };\n const newTable = await db.createTable(collection, [emptyRow]);\n await this.createFtsIndex(newTable);\n try {\n await newTable.delete(\"docid = '__placeholder__'\");\n } catch {\n // May fail if delete isn't supported on empty-ish tables\n }\n return newTable;\n }\n\n private async recreateTableForCollection(collection: string): Promise<any> {\n const db = await this.ensureDb();\n try {\n await db.dropTable(collection).catch(() => {});\n } catch {\n // Best-effort legacy schema migration; table creation below may still recover.\n }\n if (collection === this.collection) this.table = null;\n return this.ensureTableForCollection(collection);\n }\n\n private async tableVectorProviderColumnState(table: any): Promise<\"present\" | \"missing\" | \"unknown\"> {\n try {\n await table.query().select([\"vectorProvider\"]).toArray();\n return \"present\";\n } catch (err) {\n if (isMissingVectorProviderColumnError(err)) {\n return \"missing\";\n }\n log.debug(`LanceDbBackend vectorProvider column probe failed: ${err}`);\n return \"unknown\";\n }\n }\n\n private async ensureTable(): Promise<any> {\n if (this.table) return this.table;\n\n const db = await this.ensureDb();\n const tables = await db.tableNames();\n\n if (tables.includes(this.collection)) {\n this.table = await db.openTable(this.collection);\n return this.table;\n }\n\n // Create empty table with schema\n const emptyRow = {\n docid: \"__placeholder__\",\n path: \"\",\n content: \"\",\n snippet: \"\",\n vector: new Array(this.embeddingDimension).fill(0),\n vectorProvider: \"\",\n };\n this.table = await db.createTable(this.collection, [emptyRow]);\n // Create FTS index on content column\n await this.createFtsIndex(this.table);\n // Remove placeholder row\n try {\n await this.table.delete(\"docid = '__placeholder__'\");\n } catch {\n // May fail if delete isn't supported on empty-ish tables\n }\n return this.table;\n }\n\n /**\n * Create the FTS index on the content column with the default tokenizer.\n *\n * Known limitation (issue #2187): tantivy's default tokenizer does not\n * segment space-free scripts (CJK/Thai), so lexical recall over those\n * scripts is near zero here. LanceDB's `baseTokenizer: \"ngram\"` option is\n * not script-aware — it would n-gram English content too, degrading\n * precision and bloating the index — so no CJK-capable configuration is\n * available in the current @lancedb/lancedb API. Non-English lexical recall\n * on this backend rides on the vector tier instead. See\n * docs/search-backends.md (\"Non-English content\").\n */\n private async createFtsIndex(table: any): Promise<void> {\n try {\n await table.createIndex(\"content\", { config: this.lanceIndex.fts() });\n } catch {\n // FTS index creation may fail on some platforms — degrade gracefully\n }\n }\n\n private async searchTable(\n table: any,\n query: string,\n mode: \"fts\" | \"vector\" | \"hybrid\",\n limit: number,\n execution?: SearchExecutionOptions,\n ): Promise<SearchResult[]> {\n try {\n throwIfSearchAborted(execution, `LanceDbBackend ${mode} search aborted`);\n if (mode === \"fts\") {\n const results = await table.search(query, \"fts\").limit(limit).toArray();\n throwIfSearchAborted(execution, `LanceDbBackend ${mode} search aborted`);\n return this.mapRows(results);\n }\n\n if (mode === \"vector\") {\n const embedResult = await this.resolveCompatibleQueryEmbedding(table, query, execution);\n throwIfSearchAborted(execution, `LanceDbBackend ${mode} search aborted`);\n if (!embedResult) {\n // Fall back to FTS\n const results = await table.search(query, \"fts\").limit(limit).toArray();\n throwIfSearchAborted(execution, `LanceDbBackend ${mode} search aborted`);\n return this.mapRows(results);\n }\n const results = await table.search(embedResult.vector).limit(limit).toArray();\n throwIfSearchAborted(execution, `LanceDbBackend ${mode} search aborted`);\n return this.mapRows(results);\n }\n\n // hybrid — try FTS+vector with RRF reranking\n const embedResult = await this.resolveCompatibleQueryEmbedding(table, query, execution);\n throwIfSearchAborted(execution, `LanceDbBackend ${mode} search aborted`);\n if (!embedResult) {\n const results = await table.search(query, \"fts\").limit(limit).toArray();\n throwIfSearchAborted(execution, `LanceDbBackend ${mode} search aborted`);\n return this.mapRows(results);\n }\n\n try {\n const results = await table\n .search(query, \"hybrid\")\n .vector(embedResult.vector)\n .limit(limit)\n .toArray();\n throwIfSearchAborted(execution, `LanceDbBackend ${mode} search aborted`);\n return this.mapRows(results);\n } catch {\n // Hybrid may not be supported in all LanceDB versions — fall back to vector\n const results = await table.search(embedResult.vector).limit(limit).toArray();\n throwIfSearchAborted(execution, `LanceDbBackend ${mode} search aborted`);\n return this.mapRows(results);\n }\n } catch (err) {\n log.debug(`LanceDbBackend search (${mode}) failed: ${err}`);\n if (!isSearchAborted(execution)) {\n reportSearchDegradation(execution, {\n backend: \"lancedb\",\n code: \"backend_error\",\n detail: err instanceof Error ? err.name : typeof err,\n });\n }\n return [];\n }\n }\n\n private async resolveCompatibleQueryEmbedding(\n table: any,\n query: string,\n execution?: SearchExecutionOptions,\n ): Promise<EmbedWithProviderResult | null> {\n const embedResult = await this.embedHelper.embedWithProvider(query, { signal: execution?.signal });\n throwIfSearchAborted(execution, \"LanceDbBackend query embedding aborted\");\n if (!embedResult || !this.isExpectedDimensionVector(embedResult.vector)) return null;\n\n const storedProviderIdentity = await this.findCompatibleStoredVectorProvider(table, execution);\n if (!storedProviderIdentity) {\n this.rememberVectorProviderCompatibility(table, embedResult.providerIdentity, false);\n return null;\n }\n if (storedProviderIdentity === embedResult.providerIdentity) return embedResult;\n\n const fallbackEmbed = await this.embedQueryWithStoredFallbackProvider(query, storedProviderIdentity, execution);\n throwIfSearchAborted(execution, \"LanceDbBackend fallback query embedding aborted\");\n if (\n fallbackEmbed &&\n fallbackEmbed.providerIdentity === storedProviderIdentity &&\n this.isExpectedDimensionVector(fallbackEmbed.vector)\n ) {\n return fallbackEmbed;\n }\n\n this.rememberVectorProviderCompatibility(table, embedResult.providerIdentity, false);\n return null;\n }\n\n private async embedQueryWithStoredFallbackProvider(\n query: string,\n providerIdentity: EmbedProviderIdentity,\n execution?: SearchExecutionOptions,\n ): Promise<EmbedWithProviderResult | null> {\n const embedWithIdentity = (this.embedHelper as unknown as {\n embedWithFallbackProviderIdentity?: (\n text: string,\n identity: EmbedProviderIdentity,\n options?: { signal?: AbortSignal },\n ) => Promise<EmbedWithProviderResult | null>;\n }).embedWithFallbackProviderIdentity;\n if (typeof embedWithIdentity !== \"function\") return null;\n return embedWithIdentity.call(this.embedHelper, query, providerIdentity, { signal: execution?.signal });\n }\n\n private async findCompatibleStoredVectorProvider(\n table: any,\n execution?: SearchExecutionOptions,\n ): Promise<EmbedProviderIdentity | null> {\n try {\n const cached = this.vectorProviderCompatibility.get(table);\n if (cached?.compatible) return cached.providerIdentity;\n const rows = await table.query().select([\"vector\", \"vectorProvider\"]).toArray();\n let providerIdentity: EmbedProviderIdentity | null = null;\n let compatible = rows.length > 0;\n for (const row of rows ?? []) {\n throwIfSearchAborted(execution, \"LanceDbBackend vector provider check aborted\");\n if (\n typeof row.vectorProvider !== \"string\" ||\n row.vectorProvider.length === 0 ||\n !this.isCompatibleStoredVector(row.vector)\n ) {\n compatible = false;\n break;\n }\n if (providerIdentity && row.vectorProvider !== providerIdentity) {\n compatible = false;\n break;\n }\n providerIdentity = row.vectorProvider as EmbedProviderIdentity;\n }\n if (compatible && providerIdentity) {\n this.vectorProviderCompatibility.set(table, {\n providerIdentity,\n compatible: true,\n });\n return providerIdentity;\n }\n return null;\n } catch (err) {\n if (isSearchAborted(execution)) throw err;\n log.debug(`LanceDbBackend stored vector provider check failed: ${err}`);\n return null;\n }\n }\n\n private mapRows(rows: any[]): SearchResult[] {\n return (rows ?? [])\n .filter((row) => row.docid && row.docid !== \"__placeholder__\")\n .map((row) => ({\n docid: row.docid ?? \"\",\n path: row.path ?? \"\",\n snippet: row.snippet ?? row.content?.slice(0, 200) ?? \"\",\n score: row._relevance_score ?? (row._distance != null ? 1 / (1 + (row._distance ?? 0)) : 0.5),\n }));\n }\n\n private async tableHasCompatibleVectors(\n table: any,\n providerIdentity: EmbedProviderIdentity,\n execution?: SearchExecutionOptions,\n ): Promise<boolean> {\n try {\n const cached = this.vectorProviderCompatibility.get(table);\n if (cached?.providerIdentity === providerIdentity) return cached.compatible;\n const rows = await table.query().select([\"vector\", \"vectorProvider\"]).toArray();\n let compatible = rows.length > 0;\n for (const row of rows ?? []) {\n throwIfSearchAborted(execution, \"LanceDbBackend vector provider check aborted\");\n if (\n row.vectorProvider !== providerIdentity ||\n !this.isCompatibleStoredVector(row.vector)\n ) {\n compatible = false;\n break;\n }\n }\n this.vectorProviderCompatibility.set(table, { providerIdentity, compatible });\n return compatible;\n } catch (err) {\n if (isSearchAborted(execution)) throw err;\n log.debug(`LanceDbBackend vector provider check failed: ${err}`);\n return false;\n }\n }\n\n private rememberVectorProviderCompatibility(\n table: unknown,\n providerIdentity: EmbedProviderIdentity | null,\n compatible: boolean,\n ): void {\n if (!table || typeof table !== \"object\") return;\n if (!providerIdentity) {\n this.vectorProviderCompatibility.delete(table);\n return;\n }\n this.vectorProviderCompatibility.set(table, { providerIdentity, compatible });\n }\n\n private isExpectedDimensionVector(vector: number[] | null | undefined): vector is number[] {\n return Array.isArray(vector) && vector.length === this.embeddingDimension;\n }\n\n private isCompatibleStoredVector(vector: unknown): boolean {\n if (!vector || typeof vector !== \"object\") return false;\n const arr = Array.from(vector as ArrayLike<number>);\n return (\n arr.length === this.embeddingDimension &&\n arr.every((value) => Number.isFinite(value)) &&\n arr.some((value) => value !== 0)\n );\n }\n}\n\nfunction isMissingVectorProviderColumnError(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return /\\bvectorProvider\\b/i.test(message) &&\n /\\b(column|field|schema|missing|not found|not exist|does not exist|unknown)\\b/i.test(message);\n}\n"],"mappings":"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","names":["embedResult","results"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/search/remote-backend.ts"],"sourcesContent":["import { log } from \"../logger.js\";\nimport {\n reportSearchDegradation,\n type SearchBackend,\n type SearchExecutionOptions,\n type SearchQueryOptions,\n type SearchResult,\n} from \"./port.js\";\n\nexport interface RemoteSearchBackendOptions {\n baseUrl: string;\n apiKey?: string;\n timeoutMs?: number;\n}\n\n/**\n * HTTP REST search backend adapter.\n *\n * Delegates search to a remote service. Maintenance methods are no-ops\n * (remote backends manage their own indexing).\n */\nexport class RemoteSearchBackend implements SearchBackend {\n private readonly baseUrl: string;\n private readonly apiKey?: string;\n private readonly timeoutMs: number;\n private available = false;\n\n constructor(opts: RemoteSearchBackendOptions) {\n let url = opts.baseUrl;\n while (url.endsWith(\"/\")) url = url.slice(0, -1);\n this.baseUrl = url;\n this.apiKey = opts.apiKey;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n }\n\n async probe(): Promise<boolean> {\n try {\n const res = await fetch(`${this.baseUrl}/health`, {\n method: \"GET\",\n headers: this.headers(),\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n this.available = res.ok;\n return this.available;\n } catch (err) {\n log.debug(`RemoteSearchBackend probe failed: ${err}`);\n this.available = false;\n return false;\n }\n }\n\n isAvailable(): boolean {\n return this.available;\n }\n\n debugStatus(): string {\n return `backend=remote available=${this.available} baseUrl=${this.baseUrl}`;\n }\n\n async search(\n query: string,\n collection?: string,\n maxResults?: number,\n _options?: SearchQueryOptions,\n execution?: SearchExecutionOptions,\n ): Promise<SearchResult[]> {\n return this.post(\"/search/deep\", { query, collection, maxResults }, execution);\n }\n\n async searchGlobal(query: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n return this.post(\"/search/deep\", { query, maxResults }, execution);\n }\n\n async bm25Search(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n return this.post(\"/search/bm25\", { query, collection, maxResults }, execution);\n }\n\n async vectorSearch(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n return this.post(\"/search/vector\", { query, collection, maxResults }, execution);\n }\n\n async hybridSearch(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {\n return this.post(\"/search/hybrid\", { query, collection, maxResults }, execution);\n }\n\n async update(_execution?: SearchExecutionOptions): Promise<void> {}\n async updateCollection(_collection: string, _execution?: SearchExecutionOptions): Promise<void> {}\n async embed(): Promise<void> {}\n async embedCollection(_collection: string): Promise<void> {}\n\n async ensureCollection(\n _memoryDir: string,\n _collectionOrExecution?: string | SearchExecutionOptions,\n _execution?: SearchExecutionOptions,\n ): Promise<\"skipped\"> {\n return \"skipped\";\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (this.apiKey) {\n h[\"Authorization\"] = `Bearer ${this.apiKey}`;\n }\n return h;\n }\n\n private async post(\n endpoint: string,\n body: Record<string, unknown>,\n execution?: SearchExecutionOptions,\n ): Promise<SearchResult[]> {\n if (!this.available) {\n reportSearchDegradation(execution, {\n backend: \"remote\",\n code: \"backend_unavailable\",\n });\n return [];\n }\n try {\n const res = await fetch(`${this.baseUrl}${endpoint}`, {\n method: \"POST\",\n headers: this.headers(),\n body: JSON.stringify(body),\n signal: execution?.signal\n ? AbortSignal.any([execution.signal, AbortSignal.timeout(this.timeoutMs)])\n : AbortSignal.timeout(this.timeoutMs),\n });\n if (!res.ok) {\n log.debug(`RemoteSearchBackend ${endpoint} returned ${res.status}`);\n reportSearchDegradation(execution, {\n backend: \"remote\",\n code: \"remote_error\",\n detail: `HTTP ${res.status}`,\n });\n return [];\n }\n const data = await res.json();\n if (!Array.isArray(data)) {\n reportSearchDegradation(execution, {\n backend: \"remote\",\n code: \"remote_error\",\n detail: \"invalid response body\",\n });\n return [];\n }\n return data as SearchResult[];\n } catch (err) {\n log.debug(`RemoteSearchBackend ${endpoint} failed: ${err}`);\n reportSearchDegradation(execution, {\n backend: \"remote\",\n code: \"remote_error\",\n detail: err instanceof Error ? err.name : typeof err,\n });\n return [];\n }\n }\n}\n"],"mappings":"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","names":[]}