@fgv/ts-agent-memory 5.1.0-47 → 5.1.0-48

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 (341) hide show
  1. package/dist/packlets/ingest/orchestrator.js +11 -1
  2. package/dist/packlets/ingest/orchestrator.js.map +1 -1
  3. package/dist/packlets/observe/memoryObservationStore.js +6 -0
  4. package/dist/packlets/observe/memoryObservationStore.js.map +1 -1
  5. package/dist/packlets/observe/observer.js.map +1 -1
  6. package/dist/packlets/store/fileTreeMemoryStore.js +52 -131
  7. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  8. package/dist/packlets/store/vectorMaintenance.js +251 -0
  9. package/dist/packlets/store/vectorMaintenance.js.map +1 -0
  10. package/dist/packlets/vector/inMemoryCosineIndex.js +81 -18
  11. package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  12. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +7 -0
  13. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  14. package/dist/packlets/vector/vectorIndex.js.map +1 -1
  15. package/dist/ts-agent-memory.d.ts +297 -66
  16. package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
  17. package/lib/packlets/ingest/orchestrator.js +11 -1
  18. package/lib/packlets/ingest/orchestrator.js.map +1 -1
  19. package/lib/packlets/observe/memoryObservationStore.d.ts.map +1 -1
  20. package/lib/packlets/observe/memoryObservationStore.js +6 -0
  21. package/lib/packlets/observe/memoryObservationStore.js.map +1 -1
  22. package/lib/packlets/observe/observer.d.ts +61 -0
  23. package/lib/packlets/observe/observer.d.ts.map +1 -1
  24. package/lib/packlets/observe/observer.js.map +1 -1
  25. package/lib/packlets/store/fileTreeMemoryStore.d.ts +66 -59
  26. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  27. package/lib/packlets/store/fileTreeMemoryStore.js +52 -131
  28. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  29. package/lib/packlets/store/vectorMaintenance.d.ts +149 -0
  30. package/lib/packlets/store/vectorMaintenance.d.ts.map +1 -0
  31. package/lib/packlets/store/vectorMaintenance.js +255 -0
  32. package/lib/packlets/store/vectorMaintenance.js.map +1 -0
  33. package/lib/packlets/vector/inMemoryCosineIndex.d.ts +30 -7
  34. package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
  35. package/lib/packlets/vector/inMemoryCosineIndex.js +80 -17
  36. package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  37. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +7 -0
  38. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
  39. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +7 -0
  40. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  41. package/lib/packlets/vector/vectorIndex.d.ts +129 -1
  42. package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
  43. package/lib/packlets/vector/vectorIndex.js.map +1 -1
  44. package/package.json +17 -7
  45. package/.rush/temp/5667e36f1c0cc503a5c20a3ebff76e7b9a702d41.tar.log +0 -468
  46. package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +0 -9
  47. package/.rush/temp/operation/build/all.log +0 -9
  48. package/.rush/temp/operation/build/log-chunks.jsonl +0 -9
  49. package/.rush/temp/operation/build/state.json +0 -3
  50. package/.rush/temp/shrinkwrap-deps.json +0 -689
  51. package/config/api-extractor.json +0 -343
  52. package/config/jest.config.json +0 -14
  53. package/config/rig.json +0 -4
  54. package/dist/test/unit/converters/antagonistRoundTrip.test.js +0 -95
  55. package/dist/test/unit/converters/antagonistRoundTrip.test.js.map +0 -1
  56. package/dist/test/unit/converters/bodyConverterRegistry.test.js +0 -72
  57. package/dist/test/unit/converters/bodyConverterRegistry.test.js.map +0 -1
  58. package/dist/test/unit/converters/envelopeConverter.test.js +0 -313
  59. package/dist/test/unit/converters/envelopeConverter.test.js.map +0 -1
  60. package/dist/test/unit/index/memoryIndex.test.js +0 -214
  61. package/dist/test/unit/index/memoryIndex.test.js.map +0 -1
  62. package/dist/test/unit/ingest/antagonistCycleAndParity.test.js +0 -277
  63. package/dist/test/unit/ingest/antagonistCycleAndParity.test.js.map +0 -1
  64. package/dist/test/unit/ingest/cycleGuard.test.js +0 -81
  65. package/dist/test/unit/ingest/cycleGuard.test.js.map +0 -1
  66. package/dist/test/unit/ingest/dedupScope.test.js +0 -434
  67. package/dist/test/unit/ingest/dedupScope.test.js.map +0 -1
  68. package/dist/test/unit/ingest/orchestrator.test.js +0 -1058
  69. package/dist/test/unit/ingest/orchestrator.test.js.map +0 -1
  70. package/dist/test/unit/observe/memoryObservationStore.test.js +0 -118
  71. package/dist/test/unit/observe/memoryObservationStore.test.js.map +0 -1
  72. package/dist/test/unit/retrieve/fragmentSemanticRetriever.test.js +0 -116
  73. package/dist/test/unit/retrieve/fragmentSemanticRetriever.test.js.map +0 -1
  74. package/dist/test/unit/retrieve/linkTraversalRetriever.test.js +0 -257
  75. package/dist/test/unit/retrieve/linkTraversalRetriever.test.js.map +0 -1
  76. package/dist/test/unit/retrieve/retrievers.test.js +0 -768
  77. package/dist/test/unit/retrieve/retrievers.test.js.map +0 -1
  78. package/dist/test/unit/retrieve/temporalRetrievers.test.js +0 -182
  79. package/dist/test/unit/retrieve/temporalRetrievers.test.js.map +0 -1
  80. package/dist/test/unit/store/antagonistTemporalBoundary.test.js +0 -120
  81. package/dist/test/unit/store/antagonistTemporalBoundary.test.js.map +0 -1
  82. package/dist/test/unit/store/embedOnWrite.test.js +0 -317
  83. package/dist/test/unit/store/embedOnWrite.test.js.map +0 -1
  84. package/dist/test/unit/store/fileTreeMemoryStore.test.js +0 -778
  85. package/dist/test/unit/store/fileTreeMemoryStore.test.js.map +0 -1
  86. package/dist/test/unit/store/fragmentEmbedOnWrite.test.js +0 -255
  87. package/dist/test/unit/store/fragmentEmbedOnWrite.test.js.map +0 -1
  88. package/dist/test/unit/store/indexInjection.test.js +0 -432
  89. package/dist/test/unit/store/indexInjection.test.js.map +0 -1
  90. package/dist/test/unit/store/lenientOpen.test.js +0 -248
  91. package/dist/test/unit/store/lenientOpen.test.js.map +0 -1
  92. package/dist/test/unit/store/listScoped.test.js +0 -109
  93. package/dist/test/unit/store/listScoped.test.js.map +0 -1
  94. package/dist/test/unit/store/observations.test.js +0 -239
  95. package/dist/test/unit/store/observations.test.js.map +0 -1
  96. package/dist/test/unit/store/rankAxis.test.js +0 -254
  97. package/dist/test/unit/store/rankAxis.test.js.map +0 -1
  98. package/dist/test/unit/store/scopeEncoding.test.js +0 -24
  99. package/dist/test/unit/store/scopeEncoding.test.js.map +0 -1
  100. package/dist/test/unit/store/temporalStore.test.js +0 -398
  101. package/dist/test/unit/store/temporalStore.test.js.map +0 -1
  102. package/dist/test/unit/tools/memoryTools.test.js +0 -842
  103. package/dist/test/unit/tools/memoryTools.test.js.map +0 -1
  104. package/dist/test/unit/types/identityCodec.test.js +0 -187
  105. package/dist/test/unit/types/identityCodec.test.js.map +0 -1
  106. package/dist/test/unit/types/ids.test.js +0 -84
  107. package/dist/test/unit/types/ids.test.js.map +0 -1
  108. package/dist/test/unit/types/temporalCodec.test.js +0 -203
  109. package/dist/test/unit/types/temporalCodec.test.js.map +0 -1
  110. package/dist/test/unit/types/temporalPolicy.test.js +0 -83
  111. package/dist/test/unit/types/temporalPolicy.test.js.map +0 -1
  112. package/dist/test/unit/types/writePolicy.test.js +0 -331
  113. package/dist/test/unit/types/writePolicy.test.js.map +0 -1
  114. package/dist/test/unit/vector/fragmentConverters.test.js +0 -58
  115. package/dist/test/unit/vector/fragmentConverters.test.js.map +0 -1
  116. package/dist/test/unit/vector/inMemoryCosineIndex.test.js +0 -252
  117. package/dist/test/unit/vector/inMemoryCosineIndex.test.js.map +0 -1
  118. package/dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js +0 -362
  119. package/dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map +0 -1
  120. package/dist/test/unit/vector/vectorIndex.test.js +0 -51
  121. package/dist/test/unit/vector/vectorIndex.test.js.map +0 -1
  122. package/eslint.config.js +0 -15
  123. package/etc/ts-agent-memory.api.md +0 -940
  124. package/lib/test/unit/converters/antagonistRoundTrip.test.d.ts +0 -10
  125. package/lib/test/unit/converters/antagonistRoundTrip.test.d.ts.map +0 -1
  126. package/lib/test/unit/converters/antagonistRoundTrip.test.js +0 -97
  127. package/lib/test/unit/converters/antagonistRoundTrip.test.js.map +0 -1
  128. package/lib/test/unit/converters/bodyConverterRegistry.test.d.ts +0 -2
  129. package/lib/test/unit/converters/bodyConverterRegistry.test.d.ts.map +0 -1
  130. package/lib/test/unit/converters/bodyConverterRegistry.test.js +0 -74
  131. package/lib/test/unit/converters/bodyConverterRegistry.test.js.map +0 -1
  132. package/lib/test/unit/converters/envelopeConverter.test.d.ts +0 -2
  133. package/lib/test/unit/converters/envelopeConverter.test.d.ts.map +0 -1
  134. package/lib/test/unit/converters/envelopeConverter.test.js +0 -315
  135. package/lib/test/unit/converters/envelopeConverter.test.js.map +0 -1
  136. package/lib/test/unit/index/memoryIndex.test.d.ts +0 -2
  137. package/lib/test/unit/index/memoryIndex.test.d.ts.map +0 -1
  138. package/lib/test/unit/index/memoryIndex.test.js +0 -216
  139. package/lib/test/unit/index/memoryIndex.test.js.map +0 -1
  140. package/lib/test/unit/ingest/antagonistCycleAndParity.test.d.ts +0 -9
  141. package/lib/test/unit/ingest/antagonistCycleAndParity.test.d.ts.map +0 -1
  142. package/lib/test/unit/ingest/antagonistCycleAndParity.test.js +0 -279
  143. package/lib/test/unit/ingest/antagonistCycleAndParity.test.js.map +0 -1
  144. package/lib/test/unit/ingest/cycleGuard.test.d.ts +0 -2
  145. package/lib/test/unit/ingest/cycleGuard.test.d.ts.map +0 -1
  146. package/lib/test/unit/ingest/cycleGuard.test.js +0 -83
  147. package/lib/test/unit/ingest/cycleGuard.test.js.map +0 -1
  148. package/lib/test/unit/ingest/dedupScope.test.d.ts +0 -2
  149. package/lib/test/unit/ingest/dedupScope.test.d.ts.map +0 -1
  150. package/lib/test/unit/ingest/dedupScope.test.js +0 -436
  151. package/lib/test/unit/ingest/dedupScope.test.js.map +0 -1
  152. package/lib/test/unit/ingest/orchestrator.test.d.ts +0 -2
  153. package/lib/test/unit/ingest/orchestrator.test.d.ts.map +0 -1
  154. package/lib/test/unit/ingest/orchestrator.test.js +0 -1060
  155. package/lib/test/unit/ingest/orchestrator.test.js.map +0 -1
  156. package/lib/test/unit/observe/memoryObservationStore.test.d.ts +0 -2
  157. package/lib/test/unit/observe/memoryObservationStore.test.d.ts.map +0 -1
  158. package/lib/test/unit/observe/memoryObservationStore.test.js +0 -120
  159. package/lib/test/unit/observe/memoryObservationStore.test.js.map +0 -1
  160. package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.d.ts +0 -2
  161. package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.d.ts.map +0 -1
  162. package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.js +0 -118
  163. package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.js.map +0 -1
  164. package/lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts +0 -2
  165. package/lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts.map +0 -1
  166. package/lib/test/unit/retrieve/linkTraversalRetriever.test.js +0 -259
  167. package/lib/test/unit/retrieve/linkTraversalRetriever.test.js.map +0 -1
  168. package/lib/test/unit/retrieve/retrievers.test.d.ts +0 -2
  169. package/lib/test/unit/retrieve/retrievers.test.d.ts.map +0 -1
  170. package/lib/test/unit/retrieve/retrievers.test.js +0 -770
  171. package/lib/test/unit/retrieve/retrievers.test.js.map +0 -1
  172. package/lib/test/unit/retrieve/temporalRetrievers.test.d.ts +0 -2
  173. package/lib/test/unit/retrieve/temporalRetrievers.test.d.ts.map +0 -1
  174. package/lib/test/unit/retrieve/temporalRetrievers.test.js +0 -184
  175. package/lib/test/unit/retrieve/temporalRetrievers.test.js.map +0 -1
  176. package/lib/test/unit/store/antagonistTemporalBoundary.test.d.ts +0 -9
  177. package/lib/test/unit/store/antagonistTemporalBoundary.test.d.ts.map +0 -1
  178. package/lib/test/unit/store/antagonistTemporalBoundary.test.js +0 -122
  179. package/lib/test/unit/store/antagonistTemporalBoundary.test.js.map +0 -1
  180. package/lib/test/unit/store/embedOnWrite.test.d.ts +0 -2
  181. package/lib/test/unit/store/embedOnWrite.test.d.ts.map +0 -1
  182. package/lib/test/unit/store/embedOnWrite.test.js +0 -319
  183. package/lib/test/unit/store/embedOnWrite.test.js.map +0 -1
  184. package/lib/test/unit/store/fileTreeMemoryStore.test.d.ts +0 -2
  185. package/lib/test/unit/store/fileTreeMemoryStore.test.d.ts.map +0 -1
  186. package/lib/test/unit/store/fileTreeMemoryStore.test.js +0 -780
  187. package/lib/test/unit/store/fileTreeMemoryStore.test.js.map +0 -1
  188. package/lib/test/unit/store/fragmentEmbedOnWrite.test.d.ts +0 -2
  189. package/lib/test/unit/store/fragmentEmbedOnWrite.test.d.ts.map +0 -1
  190. package/lib/test/unit/store/fragmentEmbedOnWrite.test.js +0 -257
  191. package/lib/test/unit/store/fragmentEmbedOnWrite.test.js.map +0 -1
  192. package/lib/test/unit/store/indexInjection.test.d.ts +0 -2
  193. package/lib/test/unit/store/indexInjection.test.d.ts.map +0 -1
  194. package/lib/test/unit/store/indexInjection.test.js +0 -434
  195. package/lib/test/unit/store/indexInjection.test.js.map +0 -1
  196. package/lib/test/unit/store/lenientOpen.test.d.ts +0 -2
  197. package/lib/test/unit/store/lenientOpen.test.d.ts.map +0 -1
  198. package/lib/test/unit/store/lenientOpen.test.js +0 -250
  199. package/lib/test/unit/store/lenientOpen.test.js.map +0 -1
  200. package/lib/test/unit/store/listScoped.test.d.ts +0 -2
  201. package/lib/test/unit/store/listScoped.test.d.ts.map +0 -1
  202. package/lib/test/unit/store/listScoped.test.js +0 -111
  203. package/lib/test/unit/store/listScoped.test.js.map +0 -1
  204. package/lib/test/unit/store/observations.test.d.ts +0 -2
  205. package/lib/test/unit/store/observations.test.d.ts.map +0 -1
  206. package/lib/test/unit/store/observations.test.js +0 -241
  207. package/lib/test/unit/store/observations.test.js.map +0 -1
  208. package/lib/test/unit/store/rankAxis.test.d.ts +0 -2
  209. package/lib/test/unit/store/rankAxis.test.d.ts.map +0 -1
  210. package/lib/test/unit/store/rankAxis.test.js +0 -256
  211. package/lib/test/unit/store/rankAxis.test.js.map +0 -1
  212. package/lib/test/unit/store/scopeEncoding.test.d.ts +0 -2
  213. package/lib/test/unit/store/scopeEncoding.test.d.ts.map +0 -1
  214. package/lib/test/unit/store/scopeEncoding.test.js +0 -26
  215. package/lib/test/unit/store/scopeEncoding.test.js.map +0 -1
  216. package/lib/test/unit/store/temporalStore.test.d.ts +0 -2
  217. package/lib/test/unit/store/temporalStore.test.d.ts.map +0 -1
  218. package/lib/test/unit/store/temporalStore.test.js +0 -400
  219. package/lib/test/unit/store/temporalStore.test.js.map +0 -1
  220. package/lib/test/unit/tools/memoryTools.test.d.ts +0 -2
  221. package/lib/test/unit/tools/memoryTools.test.d.ts.map +0 -1
  222. package/lib/test/unit/tools/memoryTools.test.js +0 -844
  223. package/lib/test/unit/tools/memoryTools.test.js.map +0 -1
  224. package/lib/test/unit/types/identityCodec.test.d.ts +0 -2
  225. package/lib/test/unit/types/identityCodec.test.d.ts.map +0 -1
  226. package/lib/test/unit/types/identityCodec.test.js +0 -189
  227. package/lib/test/unit/types/identityCodec.test.js.map +0 -1
  228. package/lib/test/unit/types/ids.test.d.ts +0 -2
  229. package/lib/test/unit/types/ids.test.d.ts.map +0 -1
  230. package/lib/test/unit/types/ids.test.js +0 -86
  231. package/lib/test/unit/types/ids.test.js.map +0 -1
  232. package/lib/test/unit/types/temporalCodec.test.d.ts +0 -2
  233. package/lib/test/unit/types/temporalCodec.test.d.ts.map +0 -1
  234. package/lib/test/unit/types/temporalCodec.test.js +0 -205
  235. package/lib/test/unit/types/temporalCodec.test.js.map +0 -1
  236. package/lib/test/unit/types/temporalPolicy.test.d.ts +0 -2
  237. package/lib/test/unit/types/temporalPolicy.test.d.ts.map +0 -1
  238. package/lib/test/unit/types/temporalPolicy.test.js +0 -85
  239. package/lib/test/unit/types/temporalPolicy.test.js.map +0 -1
  240. package/lib/test/unit/types/writePolicy.test.d.ts +0 -2
  241. package/lib/test/unit/types/writePolicy.test.d.ts.map +0 -1
  242. package/lib/test/unit/types/writePolicy.test.js +0 -333
  243. package/lib/test/unit/types/writePolicy.test.js.map +0 -1
  244. package/lib/test/unit/vector/fragmentConverters.test.d.ts +0 -2
  245. package/lib/test/unit/vector/fragmentConverters.test.d.ts.map +0 -1
  246. package/lib/test/unit/vector/fragmentConverters.test.js +0 -60
  247. package/lib/test/unit/vector/fragmentConverters.test.js.map +0 -1
  248. package/lib/test/unit/vector/inMemoryCosineIndex.test.d.ts +0 -2
  249. package/lib/test/unit/vector/inMemoryCosineIndex.test.d.ts.map +0 -1
  250. package/lib/test/unit/vector/inMemoryCosineIndex.test.js +0 -254
  251. package/lib/test/unit/vector/inMemoryCosineIndex.test.js.map +0 -1
  252. package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.d.ts +0 -2
  253. package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.d.ts.map +0 -1
  254. package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js +0 -364
  255. package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map +0 -1
  256. package/lib/test/unit/vector/vectorIndex.test.d.ts +0 -2
  257. package/lib/test/unit/vector/vectorIndex.test.d.ts.map +0 -1
  258. package/lib/test/unit/vector/vectorIndex.test.js +0 -53
  259. package/lib/test/unit/vector/vectorIndex.test.js.map +0 -1
  260. package/rush-logs/ts-agent-memory.build.cache.log +0 -3
  261. package/rush-logs/ts-agent-memory.build.log +0 -9
  262. package/src/index.ts +0 -14
  263. package/src/packlets/converters/bodyConverterRegistry.ts +0 -105
  264. package/src/packlets/converters/envelopeConverter.ts +0 -233
  265. package/src/packlets/converters/index.ts +0 -7
  266. package/src/packlets/index/index.ts +0 -6
  267. package/src/packlets/index/memoryIndex.ts +0 -332
  268. package/src/packlets/ingest/cycleGuard.ts +0 -153
  269. package/src/packlets/ingest/hostStages.ts +0 -111
  270. package/src/packlets/ingest/index.ts +0 -9
  271. package/src/packlets/ingest/model.ts +0 -195
  272. package/src/packlets/ingest/orchestrator.ts +0 -968
  273. package/src/packlets/observe/index.ts +0 -7
  274. package/src/packlets/observe/memoryObservationStore.ts +0 -153
  275. package/src/packlets/observe/observer.ts +0 -119
  276. package/src/packlets/retrieve/fragmentSemanticRetriever.ts +0 -138
  277. package/src/packlets/retrieve/hybridRetriever.ts +0 -194
  278. package/src/packlets/retrieve/index.ts +0 -14
  279. package/src/packlets/retrieve/linkTraversalRetriever.ts +0 -158
  280. package/src/packlets/retrieve/recencyRetriever.ts +0 -54
  281. package/src/packlets/retrieve/retriever.ts +0 -291
  282. package/src/packlets/retrieve/semanticRetriever.ts +0 -152
  283. package/src/packlets/retrieve/structuredFilterRetriever.ts +0 -58
  284. package/src/packlets/retrieve/tagRetriever.ts +0 -58
  285. package/src/packlets/retrieve/temporalRetrievers.ts +0 -210
  286. package/src/packlets/store/fileTreeMemoryStore.ts +0 -1872
  287. package/src/packlets/store/index.ts +0 -7
  288. package/src/packlets/store/scopeEncoding.ts +0 -36
  289. package/src/packlets/tools/index.ts +0 -6
  290. package/src/packlets/tools/memoryTools.ts +0 -706
  291. package/src/packlets/types/envelope.ts +0 -196
  292. package/src/packlets/types/filenameSafety.ts +0 -57
  293. package/src/packlets/types/identityCodec.ts +0 -447
  294. package/src/packlets/types/ids.ts +0 -124
  295. package/src/packlets/types/index.ts +0 -11
  296. package/src/packlets/types/temporal.ts +0 -96
  297. package/src/packlets/types/writePolicy.ts +0 -612
  298. package/src/packlets/vector/fragmentConverters.ts +0 -55
  299. package/src/packlets/vector/inMemoryCosineIndex.ts +0 -196
  300. package/src/packlets/vector/inMemoryFragmentCosineIndex.ts +0 -289
  301. package/src/packlets/vector/index.ts +0 -9
  302. package/src/packlets/vector/vectorIndex.ts +0 -273
  303. package/src/test/unit/converters/antagonistRoundTrip.test.ts +0 -110
  304. package/src/test/unit/converters/bodyConverterRegistry.test.ts +0 -89
  305. package/src/test/unit/converters/envelopeConverter.test.ts +0 -418
  306. package/src/test/unit/index/memoryIndex.test.ts +0 -272
  307. package/src/test/unit/ingest/antagonistCycleAndParity.test.ts +0 -367
  308. package/src/test/unit/ingest/cycleGuard.test.ts +0 -110
  309. package/src/test/unit/ingest/dedupScope.test.ts +0 -542
  310. package/src/test/unit/ingest/orchestrator.test.ts +0 -1355
  311. package/src/test/unit/observe/memoryObservationStore.test.ts +0 -158
  312. package/src/test/unit/retrieve/fragmentSemanticRetriever.test.ts +0 -163
  313. package/src/test/unit/retrieve/linkTraversalRetriever.test.ts +0 -329
  314. package/src/test/unit/retrieve/retrievers.test.ts +0 -1018
  315. package/src/test/unit/retrieve/temporalRetrievers.test.ts +0 -226
  316. package/src/test/unit/store/antagonistTemporalBoundary.test.ts +0 -158
  317. package/src/test/unit/store/embedOnWrite.test.ts +0 -418
  318. package/src/test/unit/store/fileTreeMemoryStore.test.ts +0 -1041
  319. package/src/test/unit/store/fragmentEmbedOnWrite.test.ts +0 -349
  320. package/src/test/unit/store/indexInjection.test.ts +0 -550
  321. package/src/test/unit/store/lenientOpen.test.ts +0 -292
  322. package/src/test/unit/store/listScoped.test.ts +0 -138
  323. package/src/test/unit/store/observations.test.ts +0 -290
  324. package/src/test/unit/store/rankAxis.test.ts +0 -349
  325. package/src/test/unit/store/scopeEncoding.test.ts +0 -37
  326. package/src/test/unit/store/temporalStore.test.ts +0 -469
  327. package/src/test/unit/tools/memoryTools.test.ts +0 -1121
  328. package/src/test/unit/types/identityCodec.test.ts +0 -266
  329. package/src/test/unit/types/ids.test.ts +0 -94
  330. package/src/test/unit/types/temporalCodec.test.ts +0 -259
  331. package/src/test/unit/types/temporalPolicy.test.ts +0 -124
  332. package/src/test/unit/types/writePolicy.test.ts +0 -446
  333. package/src/test/unit/vector/fragmentConverters.test.ts +0 -80
  334. package/src/test/unit/vector/inMemoryCosineIndex.test.ts +0 -318
  335. package/src/test/unit/vector/inMemoryFragmentCosineIndex.test.ts +0 -477
  336. package/src/test/unit/vector/vectorIndex.test.ts +0 -64
  337. package/temp/build/lint/_eslint-5eVG3S6w.json +0 -306
  338. package/temp/build/typescript/ts_8nwakTlr.json +0 -1
  339. package/temp/ts-agent-memory.api.json +0 -21030
  340. package/temp/ts-agent-memory.api.md +0 -940
  341. package/tsconfig.json +0 -8
@@ -0,0 +1,149 @@
1
+ import { Result } from '@fgv/ts-utils';
2
+ import { MemoryEmbedOutcome } from '../observe';
3
+ import { IEdgeTarget, IMemoryRecord, Kind, MemoryId, MemoryScopeKey } from '../types';
4
+ import { FragmentEmbedder, IFragmentVectorIndex, IVectorIndex, MemoryEmbedder } from '../vector';
5
+ /**
6
+ * The internal outcome of record-level embed-on-write: the record to persist,
7
+ * plus — only when the embedder declined a record that already carried an
8
+ * `embeddingRef` — the index entry that reference superseded.
9
+ *
10
+ * `stale` is carried out to the caller rather than acted on in place because the
11
+ * prune belongs on the far side of `_persist`: a persist that fails leaves the
12
+ * PREVIOUS content on disk, and the superseded vector is still an accurate
13
+ * embedding of that content.
14
+ * @internal
15
+ */
16
+ export interface IEmbedOnWriteOutcome {
17
+ readonly record: IMemoryRecord<string>;
18
+ readonly stale?: {
19
+ readonly index: IVectorIndex;
20
+ readonly target: IEdgeTarget;
21
+ };
22
+ /**
23
+ * What the record-granular index did, surfaced on the write observation.
24
+ * `undefined` when the question does not apply (nothing wired).
25
+ */
26
+ readonly embed?: MemoryEmbedOutcome;
27
+ }
28
+ /**
29
+ * What {@link VectorMaintenance} needs from the store it serves. Every field is
30
+ * the store's own, passed in rather than reached for, so this collaborator holds
31
+ * no reference back to the store and cannot quietly grow one.
32
+ * @internal
33
+ */
34
+ export interface IVectorMaintenanceParams {
35
+ readonly vectorIndex?: IVectorIndex;
36
+ readonly embed?: MemoryEmbedder;
37
+ readonly fragmentIndex?: IFragmentVectorIndex;
38
+ readonly fragmentEmbedder?: FragmentEmbedder;
39
+ /** The store's swallowed-failure logger; every vector fault is best-effort. */
40
+ readonly warn: (message: string) => void;
41
+ /**
42
+ * The store's per-kind record-index participation predicate
43
+ * (`IMemoryStore.embedsKind`). Passed in rather than re-derived so the store's
44
+ * declaration and the gate that enforces it cannot disagree.
45
+ */
46
+ readonly embedsKind: (kind: Kind) => boolean;
47
+ }
48
+ /**
49
+ * The store's record- and fragment-vector maintenance, extracted from
50
+ * {@link FileTreeMemoryStore} as a collaborator.
51
+ *
52
+ * @remarks
53
+ * Everything here is **best-effort by contract**: the durable record store is
54
+ * authoritative and both indexes are derived, rebuildable views, so a failed
55
+ * embed, add or remove is logged and the write still succeeds. Nothing in this
56
+ * class can turn a committed write into a `Failure` — which is exactly why it
57
+ * separates cleanly from the store's write path, where every step is fallible
58
+ * and fatal.
59
+ *
60
+ * The split is behavior-preserving: these are the same methods the store used to
61
+ * carry as privates, with the same call order and the same logging. What moved is
62
+ * where they live, not what they do.
63
+ * @internal
64
+ */
65
+ export declare class VectorMaintenance {
66
+ private readonly _vectorIndex;
67
+ private readonly _embed;
68
+ private readonly _fragmentIndex;
69
+ private readonly _fragmentEmbedder;
70
+ private readonly _warn;
71
+ private readonly _embedsKind;
72
+ constructor(params: IVectorMaintenanceParams);
73
+ /**
74
+ * Best-effort removal of everything the two indexes hold for one record. Used
75
+ * by the delete path and by cull-oldest eviction, both of which have already
76
+ * committed by the time they call it.
77
+ */
78
+ removeAll(target: IEdgeTarget): Promise<void>;
79
+ /**
80
+ * Best-effort embed-on-write. When a vector index AND an embedder are wired,
81
+ * embeds the built record, `add`s the vector (replace semantics handle a same-id
82
+ * re-embed — no explicit remove), and stamps the returned `embeddingRef`. A
83
+ * failure (returned `fail` OR a thrown/rejected hook) is logged and the
84
+ * unembedded record is returned unchanged — the put still persists, and the
85
+ * derived index is reconciled by a later `rebuild`. A pass-through no-op when
86
+ * unwired (byte-identical record).
87
+ *
88
+ * A **decline** is not a failure and is handled differently: see
89
+ * `declineEmbedding`.
90
+ *
91
+ * Always succeeds (`Result` is the chain's shape, never a vector-induced
92
+ * failure).
93
+ */
94
+ embedOnWrite(built: IMemoryRecord<string>, scope: MemoryScopeKey): Promise<Result<IEmbedOnWriteOutcome>>;
95
+ /**
96
+ * Prune the vector a decline superseded. Best-effort like the rest of the
97
+ * vector path: a failed `remove` is logged and the (already-persisted) record
98
+ * still carries no `embeddingRef`, because the record's own claim about itself
99
+ * should be true even when the derived index is momentarily stale — that is
100
+ * exactly what a later `rebuild` reconciles.
101
+ *
102
+ * The index travels with the target rather than being re-read from the instance
103
+ * so the prune lands on the same index the decline was made against, and so
104
+ * there is no second "is a vector index wired?" check whose false branch cannot
105
+ * be reached.
106
+ */
107
+ pruneStaleVector(stale: IEmbedOnWriteOutcome['stale']): Promise<void>;
108
+ /**
109
+ * Best-effort fragment-embed-on-write. When a fragment index AND a fragment
110
+ * embedder are wired, chunks + embeds the built record and replaces its
111
+ * fragments in the index (`addFragments` is whole-record-replace, so a re-authored
112
+ * document never leaves stale fragments behind — no explicit remove needed). A
113
+ * failure (returned `fail` OR a thrown/rejected hook) is logged and the record is
114
+ * returned unchanged — the put still persists, and the fragment index is a derived
115
+ * view a later `rebuild` reconciles. Unlike `embedOnWrite`
116
+ * it stamps nothing on the record (fragments have no per-record `embeddingRef`
117
+ * analog). A pass-through no-op when unwired (byte-identical record).
118
+ */
119
+ embedFragmentsOnWrite(built: IMemoryRecord<string>, scope: MemoryScopeKey): Promise<Result<IMemoryRecord<string>>>;
120
+ /**
121
+ * Best-effort fragment removal. A no-op unless the full fragment lifecycle is
122
+ * wired (both an index AND an embedder), so an unwired store does no fragment
123
+ * work and behaves byte-identically. Failures are logged, never surfaced — a
124
+ * committed delete/eviction must not fail because a derived fragment index could
125
+ * not be pruned.
126
+ */
127
+ private _removeFragmentsBestEffort;
128
+ /**
129
+ * Best-effort vector removal for each evicted record (never fails the put).
130
+ * Every evicted record is in the same `scope` as the incoming write (the
131
+ * cull-oldest cohort is the incoming record's `(scope, kind)` cohort), so that
132
+ * scope qualifies each removal target.
133
+ */
134
+ removeEvictedVectors(evicted: ReadonlyArray<MemoryId>, scope: MemoryScopeKey): Promise<void>;
135
+ /**
136
+ * Run a consumer-supplied vector hook, normalizing a thrown/rejected hook into a
137
+ * `Failure` and logging any failure at `warn`. Best-effort: the caller proceeds
138
+ * regardless, since the index is rebuildable.
139
+ */
140
+ private _tryVectorOp;
141
+ /**
142
+ * Best-effort vector removal. A no-op unless the full vector lifecycle is wired
143
+ * (both an index AND an embedder), so an unwired store does no vector work and
144
+ * behaves byte-identically. Failures are logged, never surfaced — a committed
145
+ * delete/eviction must not fail because a derived index could not be pruned.
146
+ */
147
+ private _removeVectorBestEffort;
148
+ }
149
+ //# sourceMappingURL=vectorMaintenance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vectorMaintenance.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/vectorMaintenance.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAiB,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EACL,gBAAgB,EAEhB,oBAAoB,EACpB,YAAY,EACZ,cAAc,EACf,MAAM,WAAW,CAAC;AAEnB;;;;;;;;;;GAUG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;KAAE,CAAC;IAChF;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAC;CACrC;AA4CD;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,oBAAoB,CAAC;IAC9C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;CAC9C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA2B;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAmC;IAClE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA+B;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4B;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0B;gBAEnC,MAAM,EAAE,wBAAwB;IASnD;;;;OAIG;IACU,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1D;;;;;;;;;;;;;;OAcG;IACU,YAAY,CACvB,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,EAC5B,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAuDxC;;;;;;;;;;;OAWG;IACU,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlF;;;;;;;;;;OAUG;IACU,qBAAqB,CAChC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,EAC5B,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;IAqBzC;;;;;;OAMG;YACW,0BAA0B;IAQxC;;;;;OAKG;IACU,oBAAoB,CAAC,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzG;;;;OAIG;YACW,YAAY;IAa1B;;;;;OAKG;YACW,uBAAuB;CAOtC"}
@@ -0,0 +1,255 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ var __rest = (this && this.__rest) || function (s, e) {
7
+ var t = {};
8
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
9
+ t[p] = s[p];
10
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
11
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
12
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
13
+ t[p[i]] = s[p[i]];
14
+ }
15
+ return t;
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.VectorMaintenance = void 0;
19
+ const ts_utils_1 = require("@fgv/ts-utils");
20
+ /**
21
+ * Project a record the embedder **declined** into its written form: the same
22
+ * record with no `embeddingRef`, plus the vector (if any) that reference
23
+ * superseded, for the caller to prune after the commit.
24
+ *
25
+ * @remarks
26
+ * A decline says "this record is intentionally not embedded". A re-put of a
27
+ * record that *was* embedded (or a caller who supplied an `embeddingRef` — the
28
+ * field is store-derived by contract but nothing strips it) arrives here
29
+ * carrying an inherited reference, so returning it unchanged would persist
30
+ * `embeddingRef` on a record the store just decided not to embed.
31
+ *
32
+ * Clearing the reference alone would be cosmetic and arguably worse: the index
33
+ * entry keyed on this target would survive, so a semantic query would keep
34
+ * returning the record — scored on its **previous** content — while the record
35
+ * itself claimed not to be indexed. So the vector goes too, via
36
+ * `pruneStaleVector` once the write has committed.
37
+ * `stale` is set only when a reference was actually inherited, which keeps the
38
+ * common decline (a record that was never embedded) free of an index round
39
+ * trip.
40
+ *
41
+ * Pure and static: the decision needs nothing from the instance, and deferring
42
+ * the index call to the caller is what lets it run on the far side of
43
+ * `_persist`.
44
+ */
45
+ function declineEmbedding(built, index, target, embed) {
46
+ if (built.envelope.embeddingRef === undefined) {
47
+ return { record: built, embed };
48
+ }
49
+ // Rest-spread rather than `embeddingRef: undefined`: the envelope is YAML-
50
+ // serialized, and an explicitly-undefined key is a serializer-dependent way
51
+ // to say "absent" where dropping the key is not.
52
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
53
+ const _a = built.envelope, { embeddingRef } = _a, envelope = __rest(_a, ["embeddingRef"]);
54
+ return { record: { envelope, body: built.body }, stale: { index, target }, embed };
55
+ }
56
+ /**
57
+ * The store's record- and fragment-vector maintenance, extracted from
58
+ * {@link FileTreeMemoryStore} as a collaborator.
59
+ *
60
+ * @remarks
61
+ * Everything here is **best-effort by contract**: the durable record store is
62
+ * authoritative and both indexes are derived, rebuildable views, so a failed
63
+ * embed, add or remove is logged and the write still succeeds. Nothing in this
64
+ * class can turn a committed write into a `Failure` — which is exactly why it
65
+ * separates cleanly from the store's write path, where every step is fallible
66
+ * and fatal.
67
+ *
68
+ * The split is behavior-preserving: these are the same methods the store used to
69
+ * carry as privates, with the same call order and the same logging. What moved is
70
+ * where they live, not what they do.
71
+ * @internal
72
+ */
73
+ class VectorMaintenance {
74
+ constructor(params) {
75
+ this._vectorIndex = params.vectorIndex;
76
+ this._embed = params.embed;
77
+ this._fragmentIndex = params.fragmentIndex;
78
+ this._fragmentEmbedder = params.fragmentEmbedder;
79
+ this._warn = params.warn;
80
+ this._embedsKind = params.embedsKind;
81
+ }
82
+ /**
83
+ * Best-effort removal of everything the two indexes hold for one record. Used
84
+ * by the delete path and by cull-oldest eviction, both of which have already
85
+ * committed by the time they call it.
86
+ */
87
+ async removeAll(target) {
88
+ await this._removeVectorBestEffort(target);
89
+ await this._removeFragmentsBestEffort(target);
90
+ }
91
+ /**
92
+ * Best-effort embed-on-write. When a vector index AND an embedder are wired,
93
+ * embeds the built record, `add`s the vector (replace semantics handle a same-id
94
+ * re-embed — no explicit remove), and stamps the returned `embeddingRef`. A
95
+ * failure (returned `fail` OR a thrown/rejected hook) is logged and the
96
+ * unembedded record is returned unchanged — the put still persists, and the
97
+ * derived index is reconciled by a later `rebuild`. A pass-through no-op when
98
+ * unwired (byte-identical record).
99
+ *
100
+ * A **decline** is not a failure and is handled differently: see
101
+ * `declineEmbedding`.
102
+ *
103
+ * Always succeeds (`Result` is the chain's shape, never a vector-induced
104
+ * failure).
105
+ */
106
+ async embedOnWrite(built, scope) {
107
+ if (this._vectorIndex === undefined || this._embed === undefined) {
108
+ // No outcome: with nothing wired there is no index for the record to be
109
+ // absent from, so reporting one would invent a coverage question the
110
+ // deployment has not asked.
111
+ return (0, ts_utils_1.succeed)({ record: built });
112
+ }
113
+ const vectorIndex = this._vectorIndex;
114
+ const embed = this._embed;
115
+ const target = { scope, id: built.envelope.id };
116
+ // Gate BEFORE the embedder call, which is the whole point: a `MemoryEmbedder`
117
+ // decline still pays the round trip, and on a locally-hosted model that round
118
+ // trip IS the cost. A kind excluded here is never handed to the embedder.
119
+ //
120
+ // An exclusion reaches the same conclusion as a decline — this record is
121
+ // intentionally not embedded — so it takes the same path: an inherited
122
+ // `embeddingRef` is dropped and the vector it named is pruned after the
123
+ // commit. Otherwise narrowing `embedKinds` on an existing vault would leave
124
+ // every previously-embedded record of the excluded kind claiming an
125
+ // embedding the store no longer maintains.
126
+ if (!this._embedsKind(built.envelope.kind)) {
127
+ return (0, ts_utils_1.succeed)(declineEmbedding(built, vectorIndex, target, 'excluded'));
128
+ }
129
+ const embedded = await this._tryVectorOp(() => embed(built), `embedding '${built.envelope.id}'`);
130
+ if (embedded.isFailure()) {
131
+ return (0, ts_utils_1.succeed)({ record: built, embed: 'failed' });
132
+ }
133
+ // A deliberate decline (`undefined`) stores the record with no embedding
134
+ // reference. Deliberately NOT logged, unlike the failure path above: a warning
135
+ // per write would make routine policy look like a recurring fault, which is
136
+ // the confusion this return value exists to end. It is still *reported* — as
137
+ // `embed: 'declined'` on the write observation — because saying nothing at all
138
+ // is what left `embeddingRef` absence three-ways ambiguous.
139
+ if (embedded.value === undefined) {
140
+ return (0, ts_utils_1.succeed)(declineEmbedding(built, vectorIndex, target, 'declined'));
141
+ }
142
+ // Hoisted: the `undefined` check above does not narrow across the callback
143
+ // boundary below, and a local keeps the non-null assertion out of the code.
144
+ const vector = embedded.value;
145
+ const added = await this._tryVectorOp(() => vectorIndex.add(target, vector), `vector add for '${built.envelope.id}'`);
146
+ if (added.isFailure()) {
147
+ return (0, ts_utils_1.succeed)({ record: built, embed: 'failed' });
148
+ }
149
+ return (0, ts_utils_1.succeed)({
150
+ record: { envelope: Object.assign(Object.assign({}, built.envelope), { embeddingRef: added.value }), body: built.body },
151
+ embed: 'embedded'
152
+ });
153
+ }
154
+ /**
155
+ * Prune the vector a decline superseded. Best-effort like the rest of the
156
+ * vector path: a failed `remove` is logged and the (already-persisted) record
157
+ * still carries no `embeddingRef`, because the record's own claim about itself
158
+ * should be true even when the derived index is momentarily stale — that is
159
+ * exactly what a later `rebuild` reconciles.
160
+ *
161
+ * The index travels with the target rather than being re-read from the instance
162
+ * so the prune lands on the same index the decline was made against, and so
163
+ * there is no second "is a vector index wired?" check whose false branch cannot
164
+ * be reached.
165
+ */
166
+ async pruneStaleVector(stale) {
167
+ if (stale === undefined) {
168
+ return;
169
+ }
170
+ await this._tryVectorOp(() => stale.index.remove(stale.target), `vector remove for declined '${stale.target.id}'`);
171
+ }
172
+ /**
173
+ * Best-effort fragment-embed-on-write. When a fragment index AND a fragment
174
+ * embedder are wired, chunks + embeds the built record and replaces its
175
+ * fragments in the index (`addFragments` is whole-record-replace, so a re-authored
176
+ * document never leaves stale fragments behind — no explicit remove needed). A
177
+ * failure (returned `fail` OR a thrown/rejected hook) is logged and the record is
178
+ * returned unchanged — the put still persists, and the fragment index is a derived
179
+ * view a later `rebuild` reconciles. Unlike `embedOnWrite`
180
+ * it stamps nothing on the record (fragments have no per-record `embeddingRef`
181
+ * analog). A pass-through no-op when unwired (byte-identical record).
182
+ */
183
+ async embedFragmentsOnWrite(built, scope) {
184
+ if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {
185
+ return (0, ts_utils_1.succeed)(built);
186
+ }
187
+ const fragmentIndex = this._fragmentIndex;
188
+ const fragmentEmbedder = this._fragmentEmbedder;
189
+ const target = { scope, id: built.envelope.id };
190
+ const embedded = await this._tryVectorOp(() => fragmentEmbedder(built), `fragment embedding '${built.envelope.id}'`);
191
+ if (embedded.isFailure()) {
192
+ return (0, ts_utils_1.succeed)(built);
193
+ }
194
+ await this._tryVectorOp(() => fragmentIndex.addFragments(target, embedded.value), `fragment add for '${built.envelope.id}'`);
195
+ return (0, ts_utils_1.succeed)(built);
196
+ }
197
+ /**
198
+ * Best-effort fragment removal. A no-op unless the full fragment lifecycle is
199
+ * wired (both an index AND an embedder), so an unwired store does no fragment
200
+ * work and behaves byte-identically. Failures are logged, never surfaced — a
201
+ * committed delete/eviction must not fail because a derived fragment index could
202
+ * not be pruned.
203
+ */
204
+ async _removeFragmentsBestEffort(target) {
205
+ if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {
206
+ return;
207
+ }
208
+ const fragmentIndex = this._fragmentIndex;
209
+ await this._tryVectorOp(() => fragmentIndex.remove(target), `fragment removal for '${target.id}'`);
210
+ }
211
+ /**
212
+ * Best-effort vector removal for each evicted record (never fails the put).
213
+ * Every evicted record is in the same `scope` as the incoming write (the
214
+ * cull-oldest cohort is the incoming record's `(scope, kind)` cohort), so that
215
+ * scope qualifies each removal target.
216
+ */
217
+ async removeEvictedVectors(evicted, scope) {
218
+ for (const id of evicted) {
219
+ await this.removeAll({ scope, id });
220
+ }
221
+ }
222
+ /**
223
+ * Run a consumer-supplied vector hook, normalizing a thrown/rejected hook into a
224
+ * `Failure` and logging any failure at `warn`. Best-effort: the caller proceeds
225
+ * regardless, since the index is rebuildable.
226
+ */
227
+ async _tryVectorOp(op, label) {
228
+ let result;
229
+ try {
230
+ result = await op();
231
+ }
232
+ catch (err) {
233
+ result = (0, ts_utils_1.fail)(`${label} threw: ${String(err)}`);
234
+ }
235
+ if (result.isFailure()) {
236
+ this._warn(`memory: ${label} failed (best-effort; derived index left for rebuild): ${result.message}`);
237
+ }
238
+ return result;
239
+ }
240
+ /**
241
+ * Best-effort vector removal. A no-op unless the full vector lifecycle is wired
242
+ * (both an index AND an embedder), so an unwired store does no vector work and
243
+ * behaves byte-identically. Failures are logged, never surfaced — a committed
244
+ * delete/eviction must not fail because a derived index could not be pruned.
245
+ */
246
+ async _removeVectorBestEffort(target) {
247
+ if (this._vectorIndex === undefined || this._embed === undefined) {
248
+ return;
249
+ }
250
+ const vectorIndex = this._vectorIndex;
251
+ await this._tryVectorOp(() => vectorIndex.remove(target), `vector removal for '${target.id}'`);
252
+ }
253
+ }
254
+ exports.VectorMaintenance = VectorMaintenance;
255
+ //# sourceMappingURL=vectorMaintenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vectorMaintenance.js","sourceRoot":"","sources":["../../../src/packlets/store/vectorMaintenance.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { MemoryEmbedOutcome } from '../observe';\nimport { IEdgeTarget, IMemoryRecord, Kind, MemoryId, MemoryScopeKey } from '../types';\nimport {\n FragmentEmbedder,\n IEmbeddedFragment,\n IFragmentVectorIndex,\n IVectorIndex,\n MemoryEmbedder\n} from '../vector';\n\n/**\n * The internal outcome of record-level embed-on-write: the record to persist,\n * plus — only when the embedder declined a record that already carried an\n * `embeddingRef` — the index entry that reference superseded.\n *\n * `stale` is carried out to the caller rather than acted on in place because the\n * prune belongs on the far side of `_persist`: a persist that fails leaves the\n * PREVIOUS content on disk, and the superseded vector is still an accurate\n * embedding of that content.\n * @internal\n */\nexport interface IEmbedOnWriteOutcome {\n readonly record: IMemoryRecord<string>;\n readonly stale?: { readonly index: IVectorIndex; readonly target: IEdgeTarget };\n /**\n * What the record-granular index did, surfaced on the write observation.\n * `undefined` when the question does not apply (nothing wired).\n */\n readonly embed?: MemoryEmbedOutcome;\n}\n\n/**\n * Project a record the embedder **declined** into its written form: the same\n * record with no `embeddingRef`, plus the vector (if any) that reference\n * superseded, for the caller to prune after the commit.\n *\n * @remarks\n * A decline says \"this record is intentionally not embedded\". A re-put of a\n * record that *was* embedded (or a caller who supplied an `embeddingRef` — the\n * field is store-derived by contract but nothing strips it) arrives here\n * carrying an inherited reference, so returning it unchanged would persist\n * `embeddingRef` on a record the store just decided not to embed.\n *\n * Clearing the reference alone would be cosmetic and arguably worse: the index\n * entry keyed on this target would survive, so a semantic query would keep\n * returning the record — scored on its **previous** content — while the record\n * itself claimed not to be indexed. So the vector goes too, via\n * `pruneStaleVector` once the write has committed.\n * `stale` is set only when a reference was actually inherited, which keeps the\n * common decline (a record that was never embedded) free of an index round\n * trip.\n *\n * Pure and static: the decision needs nothing from the instance, and deferring\n * the index call to the caller is what lets it run on the far side of\n * `_persist`.\n */\nfunction declineEmbedding(\n built: IMemoryRecord<string>,\n index: IVectorIndex,\n target: IEdgeTarget,\n embed: MemoryEmbedOutcome\n): IEmbedOnWriteOutcome {\n if (built.envelope.embeddingRef === undefined) {\n return { record: built, embed };\n }\n // Rest-spread rather than `embeddingRef: undefined`: the envelope is YAML-\n // serialized, and an explicitly-undefined key is a serializer-dependent way\n // to say \"absent\" where dropping the key is not.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { embeddingRef, ...envelope } = built.envelope;\n return { record: { envelope, body: built.body }, stale: { index, target }, embed };\n}\n\n/**\n * What {@link VectorMaintenance} needs from the store it serves. Every field is\n * the store's own, passed in rather than reached for, so this collaborator holds\n * no reference back to the store and cannot quietly grow one.\n * @internal\n */\nexport interface IVectorMaintenanceParams {\n readonly vectorIndex?: IVectorIndex;\n readonly embed?: MemoryEmbedder;\n readonly fragmentIndex?: IFragmentVectorIndex;\n readonly fragmentEmbedder?: FragmentEmbedder;\n /** The store's swallowed-failure logger; every vector fault is best-effort. */\n readonly warn: (message: string) => void;\n /**\n * The store's per-kind record-index participation predicate\n * (`IMemoryStore.embedsKind`). Passed in rather than re-derived so the store's\n * declaration and the gate that enforces it cannot disagree.\n */\n readonly embedsKind: (kind: Kind) => boolean;\n}\n\n/**\n * The store's record- and fragment-vector maintenance, extracted from\n * {@link FileTreeMemoryStore} as a collaborator.\n *\n * @remarks\n * Everything here is **best-effort by contract**: the durable record store is\n * authoritative and both indexes are derived, rebuildable views, so a failed\n * embed, add or remove is logged and the write still succeeds. Nothing in this\n * class can turn a committed write into a `Failure` — which is exactly why it\n * separates cleanly from the store's write path, where every step is fallible\n * and fatal.\n *\n * The split is behavior-preserving: these are the same methods the store used to\n * carry as privates, with the same call order and the same logging. What moved is\n * where they live, not what they do.\n * @internal\n */\nexport class VectorMaintenance {\n private readonly _vectorIndex: IVectorIndex | undefined;\n private readonly _embed: MemoryEmbedder | undefined;\n private readonly _fragmentIndex: IFragmentVectorIndex | undefined;\n private readonly _fragmentEmbedder: FragmentEmbedder | undefined;\n private readonly _warn: (message: string) => void;\n private readonly _embedsKind: (kind: Kind) => boolean;\n\n public constructor(params: IVectorMaintenanceParams) {\n this._vectorIndex = params.vectorIndex;\n this._embed = params.embed;\n this._fragmentIndex = params.fragmentIndex;\n this._fragmentEmbedder = params.fragmentEmbedder;\n this._warn = params.warn;\n this._embedsKind = params.embedsKind;\n }\n\n /**\n * Best-effort removal of everything the two indexes hold for one record. Used\n * by the delete path and by cull-oldest eviction, both of which have already\n * committed by the time they call it.\n */\n public async removeAll(target: IEdgeTarget): Promise<void> {\n await this._removeVectorBestEffort(target);\n await this._removeFragmentsBestEffort(target);\n }\n\n /**\n * Best-effort embed-on-write. When a vector index AND an embedder are wired,\n * embeds the built record, `add`s the vector (replace semantics handle a same-id\n * re-embed — no explicit remove), and stamps the returned `embeddingRef`. A\n * failure (returned `fail` OR a thrown/rejected hook) is logged and the\n * unembedded record is returned unchanged — the put still persists, and the\n * derived index is reconciled by a later `rebuild`. A pass-through no-op when\n * unwired (byte-identical record).\n *\n * A **decline** is not a failure and is handled differently: see\n * `declineEmbedding`.\n *\n * Always succeeds (`Result` is the chain's shape, never a vector-induced\n * failure).\n */\n public async embedOnWrite(\n built: IMemoryRecord<string>,\n scope: MemoryScopeKey\n ): Promise<Result<IEmbedOnWriteOutcome>> {\n if (this._vectorIndex === undefined || this._embed === undefined) {\n // No outcome: with nothing wired there is no index for the record to be\n // absent from, so reporting one would invent a coverage question the\n // deployment has not asked.\n return succeed({ record: built });\n }\n const vectorIndex: IVectorIndex = this._vectorIndex;\n const embed: MemoryEmbedder = this._embed;\n const target: IEdgeTarget = { scope, id: built.envelope.id };\n // Gate BEFORE the embedder call, which is the whole point: a `MemoryEmbedder`\n // decline still pays the round trip, and on a locally-hosted model that round\n // trip IS the cost. A kind excluded here is never handed to the embedder.\n //\n // An exclusion reaches the same conclusion as a decline — this record is\n // intentionally not embedded — so it takes the same path: an inherited\n // `embeddingRef` is dropped and the vector it named is pruned after the\n // commit. Otherwise narrowing `embedKinds` on an existing vault would leave\n // every previously-embedded record of the excluded kind claiming an\n // embedding the store no longer maintains.\n if (!this._embedsKind(built.envelope.kind)) {\n return succeed(declineEmbedding(built, vectorIndex, target, 'excluded'));\n }\n const embedded: Result<Float32Array | undefined> = await this._tryVectorOp(\n () => embed(built),\n `embedding '${built.envelope.id}'`\n );\n if (embedded.isFailure()) {\n return succeed({ record: built, embed: 'failed' });\n }\n // A deliberate decline (`undefined`) stores the record with no embedding\n // reference. Deliberately NOT logged, unlike the failure path above: a warning\n // per write would make routine policy look like a recurring fault, which is\n // the confusion this return value exists to end. It is still *reported* — as\n // `embed: 'declined'` on the write observation — because saying nothing at all\n // is what left `embeddingRef` absence three-ways ambiguous.\n if (embedded.value === undefined) {\n return succeed(declineEmbedding(built, vectorIndex, target, 'declined'));\n }\n // Hoisted: the `undefined` check above does not narrow across the callback\n // boundary below, and a local keeps the non-null assertion out of the code.\n const vector: Float32Array = embedded.value;\n const added: Result<string> = await this._tryVectorOp(\n () => vectorIndex.add(target, vector),\n `vector add for '${built.envelope.id}'`\n );\n if (added.isFailure()) {\n return succeed({ record: built, embed: 'failed' });\n }\n return succeed({\n record: { envelope: { ...built.envelope, embeddingRef: added.value }, body: built.body },\n embed: 'embedded'\n });\n }\n\n /**\n * Prune the vector a decline superseded. Best-effort like the rest of the\n * vector path: a failed `remove` is logged and the (already-persisted) record\n * still carries no `embeddingRef`, because the record's own claim about itself\n * should be true even when the derived index is momentarily stale — that is\n * exactly what a later `rebuild` reconciles.\n *\n * The index travels with the target rather than being re-read from the instance\n * so the prune lands on the same index the decline was made against, and so\n * there is no second \"is a vector index wired?\" check whose false branch cannot\n * be reached.\n */\n public async pruneStaleVector(stale: IEmbedOnWriteOutcome['stale']): Promise<void> {\n if (stale === undefined) {\n return;\n }\n await this._tryVectorOp(\n () => stale.index.remove(stale.target),\n `vector remove for declined '${stale.target.id}'`\n );\n }\n\n /**\n * Best-effort fragment-embed-on-write. When a fragment index AND a fragment\n * embedder are wired, chunks + embeds the built record and replaces its\n * fragments in the index (`addFragments` is whole-record-replace, so a re-authored\n * document never leaves stale fragments behind — no explicit remove needed). A\n * failure (returned `fail` OR a thrown/rejected hook) is logged and the record is\n * returned unchanged — the put still persists, and the fragment index is a derived\n * view a later `rebuild` reconciles. Unlike `embedOnWrite`\n * it stamps nothing on the record (fragments have no per-record `embeddingRef`\n * analog). A pass-through no-op when unwired (byte-identical record).\n */\n public async embedFragmentsOnWrite(\n built: IMemoryRecord<string>,\n scope: MemoryScopeKey\n ): Promise<Result<IMemoryRecord<string>>> {\n if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {\n return succeed(built);\n }\n const fragmentIndex: IFragmentVectorIndex = this._fragmentIndex;\n const fragmentEmbedder: FragmentEmbedder = this._fragmentEmbedder;\n const target: IEdgeTarget = { scope, id: built.envelope.id };\n const embedded: Result<ReadonlyArray<IEmbeddedFragment>> = await this._tryVectorOp(\n () => fragmentEmbedder(built),\n `fragment embedding '${built.envelope.id}'`\n );\n if (embedded.isFailure()) {\n return succeed(built);\n }\n await this._tryVectorOp(\n () => fragmentIndex.addFragments(target, embedded.value),\n `fragment add for '${built.envelope.id}'`\n );\n return succeed(built);\n }\n\n /**\n * Best-effort fragment removal. A no-op unless the full fragment lifecycle is\n * wired (both an index AND an embedder), so an unwired store does no fragment\n * work and behaves byte-identically. Failures are logged, never surfaced — a\n * committed delete/eviction must not fail because a derived fragment index could\n * not be pruned.\n */\n private async _removeFragmentsBestEffort(target: IEdgeTarget): Promise<void> {\n if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {\n return;\n }\n const fragmentIndex: IFragmentVectorIndex = this._fragmentIndex;\n await this._tryVectorOp(() => fragmentIndex.remove(target), `fragment removal for '${target.id}'`);\n }\n\n /**\n * Best-effort vector removal for each evicted record (never fails the put).\n * Every evicted record is in the same `scope` as the incoming write (the\n * cull-oldest cohort is the incoming record's `(scope, kind)` cohort), so that\n * scope qualifies each removal target.\n */\n public async removeEvictedVectors(evicted: ReadonlyArray<MemoryId>, scope: MemoryScopeKey): Promise<void> {\n for (const id of evicted) {\n await this.removeAll({ scope, id });\n }\n }\n\n /**\n * Run a consumer-supplied vector hook, normalizing a thrown/rejected hook into a\n * `Failure` and logging any failure at `warn`. Best-effort: the caller proceeds\n * regardless, since the index is rebuildable.\n */\n private async _tryVectorOp<T>(op: () => Promise<Result<T>>, label: string): Promise<Result<T>> {\n let result: Result<T>;\n try {\n result = await op();\n } catch (err) {\n result = fail(`${label} threw: ${String(err)}`);\n }\n if (result.isFailure()) {\n this._warn(`memory: ${label} failed (best-effort; derived index left for rebuild): ${result.message}`);\n }\n return result;\n }\n\n /**\n * Best-effort vector removal. A no-op unless the full vector lifecycle is wired\n * (both an index AND an embedder), so an unwired store does no vector work and\n * behaves byte-identically. Failures are logged, never surfaced — a committed\n * delete/eviction must not fail because a derived index could not be pruned.\n */\n private async _removeVectorBestEffort(target: IEdgeTarget): Promise<void> {\n if (this._vectorIndex === undefined || this._embed === undefined) {\n return;\n }\n const vectorIndex: IVectorIndex = this._vectorIndex;\n await this._tryVectorOp(() => vectorIndex.remove(target), `vector removal for '${target.id}'`);\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { Result } from '@fgv/ts-utils';
2
2
  import { IEdgeTarget } from '../types';
3
- import { IMemoryRecordSource, IVectorIndex, IVectorQueryHit, MemoryEmbedder } from './vectorIndex';
3
+ import { IMemoryRecordSource, IVectorIndex, IVectorQueryHit, IVectorRebuildOptions, IVectorRebuildReport, MemoryEmbedder } from './vectorIndex';
4
4
  /**
5
5
  * The brute-force, in-memory cosine {@link IVectorIndex}. Stores one
6
6
  * `Float32Array` per record and answers a query by computing cosine similarity
@@ -47,17 +47,40 @@ export declare class InMemoryCosineIndex implements IVectorIndex {
47
47
  /**
48
48
  * Re-embed every record from `source` and rebuild the index from scratch.
49
49
  * Clears the current contents (and the established dimension) first, so a
50
- * re-embed with a different model is supported. Returns the number of vectors
51
- * indexed.
50
+ * re-embed with a different model is supported. Returns an
51
+ * {@link IVectorRebuildReport} describing what was indexed, declined and skipped.
52
52
  *
53
- * On any failure (list, embed, or add) the index is rolled back to empty
54
- * rather than left in a partially-rebuilt state a caller that retries a query
55
- * after a failed rebuild sees a clean empty index, never a half-populated one.
53
+ * @remarks
54
+ * **A failure to LIST is always fatal**, under either mode and **leaves the
55
+ * existing index untouched**: an unreadable source says nothing about which
56
+ * records exist, so there is neither an honest partial to report nor any reason
57
+ * to discard what is already held.
58
+ *
59
+ * Per-record embed/add failures are governed by
60
+ * {@link IVectorRebuildOptions.onRecordError}, which defaults to `'fail'` —
61
+ * **the historical all-or-nothing contract, unchanged**: the index is rolled back
62
+ * to empty rather than left partially rebuilt, so a caller that retries a query
63
+ * sees a clean empty index it can reason about.
64
+ *
65
+ * `'skip'` opts into the lenient shape the store's own open already uses: the
66
+ * rebuild continues and every casualty is returned structurally on
67
+ * {@link IVectorRebuildReport.skipped}. **It reports more, it does not report
68
+ * less** — the point is to stop one bad record emptying an entire index, not to
69
+ * make failures quieter. A caller that ignores `skipped` under `'skip'` has
70
+ * chosen to, rather than been given no way to know.
71
+ *
72
+ * A {@link MemoryEmbedder} decline is not a failure under either mode: it is
73
+ * counted on {@link IVectorRebuildReport.declined} and never appears in `skipped`.
74
+ *
75
+ * Both consumer-supplied hooks are capture-wrapped, so a `source` or `embed`
76
+ * that throws or rejects becomes a `Failure` on the path above rather than an
77
+ * exception escaping mid-rebuild — which would bypass the rollback entirely.
56
78
  *
57
79
  * @param source - The scope-qualified record source to re-embed.
58
80
  * @param embed - The embedder applied to each record.
81
+ * @param options - Rebuild options; omit for the historical `'fail'` behavior.
59
82
  */
60
- rebuild(source: IMemoryRecordSource, embed: MemoryEmbedder): Promise<Result<number>>;
83
+ rebuild(source: IMemoryRecordSource, embed: MemoryEmbedder, options?: IVectorRebuildOptions): Promise<Result<IVectorRebuildReport>>;
61
84
  /** Empty the index and forget the established dimension. */
62
85
  private _reset;
63
86
  /** The Euclidean magnitude (L2 norm) of a vector. */
@@ -1 +1 @@
1
- {"version":3,"file":"inMemoryCosineIndex.d.ts","sourceRoot":"","sources":["../../../src/packlets/vector/inMemoryCosineIndex.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAiB,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,WAAW,EAAiB,MAAM,UAAU,CAAC;AACtD,OAAO,EACL,mBAAmB,EAEnB,YAAY,EACZ,eAAe,EACf,cAAc,EACf,MAAM,eAAe,CAAC;AAQvB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IACtD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,+EAA+E;IAC/E,OAAO,CAAC,UAAU,CAAqB;IAEvC,OAAO;IAKP,4CAA4C;IAC5C,IAAW,IAAI,IAAI,MAAM,CAExB;IAED,iCAAiC;WACnB,MAAM,IAAI,MAAM,CAAC,mBAAmB,CAAC;IAInD,qCAAqC;IAC9B,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAsB9E,wCAAwC;IACjC,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAKhE,uCAAuC;IAChC,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC;IAyBjG;;;;;;;;;;;;OAYG;IACU,OAAO,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IA0BjG,4DAA4D;IAC5D,OAAO,CAAC,MAAM;IAKd,qDAAqD;IACrD,OAAO,CAAC,MAAM,CAAC,UAAU;IAQzB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,OAAO;CAWvB"}
1
+ {"version":3,"file":"inMemoryCosineIndex.d.ts","sourceRoot":"","sources":["../../../src/packlets/vector/inMemoryCosineIndex.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAqC,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAiB,MAAM,UAAU,CAAC;AACtD,OAAO,EACL,mBAAmB,EAGnB,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACf,MAAM,eAAe,CAAC;AAkBvB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IACtD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,+EAA+E;IAC/E,OAAO,CAAC,UAAU,CAAqB;IAEvC,OAAO;IAKP,4CAA4C;IAC5C,IAAW,IAAI,IAAI,MAAM,CAExB;IAED,iCAAiC;WACnB,MAAM,IAAI,MAAM,CAAC,mBAAmB,CAAC;IAInD,qCAAqC;IAC9B,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAsB9E,wCAAwC;IACjC,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAKhE,uCAAuC;IAChC,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC;IAyBjG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACU,OAAO,CAClB,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,cAAc,EACrB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAwDxC,4DAA4D;IAC5D,OAAO,CAAC,MAAM;IAKd,qDAAqD;IACrD,OAAO,CAAC,MAAM,CAAC,UAAU;IAQzB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,OAAO;CAWvB"}
@@ -7,6 +7,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.InMemoryCosineIndex = void 0;
8
8
  const ts_utils_1 = require("@fgv/ts-utils");
9
9
  const types_1 = require("../types");
10
+ /**
11
+ * Invoke a consumer-supplied hook that already returns a `Result`, converting a
12
+ * synchronous throw or a promise rejection into a `Failure` rather than letting
13
+ * it escape. `captureAsyncResult` wraps the hook's own `Result`, so the outcome
14
+ * is flattened back to one level.
15
+ */
16
+ async function invokeHook(hook) {
17
+ return (await (0, ts_utils_1.captureAsyncResult)(hook)).onSuccess((inner) => inner);
18
+ }
10
19
  /**
11
20
  * The brute-force, in-memory cosine {@link IVectorIndex}. Stores one
12
21
  * `Float32Array` per record and answers a query by computing cosine similarity
@@ -91,38 +100,92 @@ class InMemoryCosineIndex {
91
100
  /**
92
101
  * Re-embed every record from `source` and rebuild the index from scratch.
93
102
  * Clears the current contents (and the established dimension) first, so a
94
- * re-embed with a different model is supported. Returns the number of vectors
95
- * indexed.
103
+ * re-embed with a different model is supported. Returns an
104
+ * {@link IVectorRebuildReport} describing what was indexed, declined and skipped.
105
+ *
106
+ * @remarks
107
+ * **A failure to LIST is always fatal**, under either mode — and **leaves the
108
+ * existing index untouched**: an unreadable source says nothing about which
109
+ * records exist, so there is neither an honest partial to report nor any reason
110
+ * to discard what is already held.
111
+ *
112
+ * Per-record embed/add failures are governed by
113
+ * {@link IVectorRebuildOptions.onRecordError}, which defaults to `'fail'` —
114
+ * **the historical all-or-nothing contract, unchanged**: the index is rolled back
115
+ * to empty rather than left partially rebuilt, so a caller that retries a query
116
+ * sees a clean empty index it can reason about.
96
117
  *
97
- * On any failure (list, embed, or add) the index is rolled back to empty
98
- * rather than left in a partially-rebuilt state a caller that retries a query
99
- * after a failed rebuild sees a clean empty index, never a half-populated one.
118
+ * `'skip'` opts into the lenient shape the store's own open already uses: the
119
+ * rebuild continues and every casualty is returned structurally on
120
+ * {@link IVectorRebuildReport.skipped}. **It reports more, it does not report
121
+ * less** — the point is to stop one bad record emptying an entire index, not to
122
+ * make failures quieter. A caller that ignores `skipped` under `'skip'` has
123
+ * chosen to, rather than been given no way to know.
124
+ *
125
+ * A {@link MemoryEmbedder} decline is not a failure under either mode: it is
126
+ * counted on {@link IVectorRebuildReport.declined} and never appears in `skipped`.
127
+ *
128
+ * Both consumer-supplied hooks are capture-wrapped, so a `source` or `embed`
129
+ * that throws or rejects becomes a `Failure` on the path above rather than an
130
+ * exception escaping mid-rebuild — which would bypass the rollback entirely.
100
131
  *
101
132
  * @param source - The scope-qualified record source to re-embed.
102
133
  * @param embed - The embedder applied to each record.
134
+ * @param options - Rebuild options; omit for the historical `'fail'` behavior.
103
135
  */
104
- async rebuild(source, embed) {
105
- // Reset up front so the "any failure leaves the index empty" contract holds
106
- // even when the listing itself fails (no stale vectors survive a failed
107
- // rebuild).
108
- this._reset();
109
- const listed = await source.list();
136
+ async rebuild(source, embed, options) {
137
+ var _a;
138
+ const lenient = ((_a = options === null || options === void 0 ? void 0 : options.onRecordError) !== null && _a !== void 0 ? _a : 'fail') === 'skip';
139
+ const listed = await invokeHook(() => source.list());
110
140
  if (listed.isFailure()) {
141
+ // Deliberately BEFORE the reset. This used to reset first, on the reasoning
142
+ // that no stale vectors should survive a failed rebuild — but a failed list
143
+ // is no evidence about the vectors already held, and nothing has been
144
+ // re-embedded yet, so there is no half-rebuilt state to guard against.
145
+ // Leaving the prior contents intact is the more correct answer, and on the
146
+ // durable sibling (`SqliteVecVectorIndex`) resetting here was data loss.
111
147
  return (0, ts_utils_1.fail)(`vector index rebuild: failed to list records: ${listed.message}`);
112
148
  }
149
+ // From here a rebuild is genuinely starting, so clear. A mid-loop failure
150
+ // under `'fail'` still resets, which is what keeps that contract honest.
151
+ this._reset();
152
+ let declined = 0;
153
+ const skipped = [];
113
154
  for (const scoped of listed.value) {
114
- const embedded = await embed(scoped.record);
155
+ // Both hooks are consumer-supplied, so a throw or rejection is captured
156
+ // into a `Failure` rather than escaping as an exception — otherwise a
157
+ // badly-behaved embedder would reject out of `rebuild` mid-loop and leave
158
+ // the index half-populated, which is precisely what the rollback below
159
+ // exists to prevent.
160
+ const embedded = await invokeHook(() => embed(scoped.record));
115
161
  if (embedded.isFailure()) {
116
- this._reset();
117
- return (0, ts_utils_1.fail)(`vector index rebuild: embedding '${(0, types_1.edgeTargetKey)(scoped.target)}' failed: ${embedded.message}`);
162
+ const error = `vector index rebuild: embedding '${(0, types_1.edgeTargetKey)(scoped.target)}' failed: ${embedded.message}`;
163
+ // `'fail'` keeps the historical all-or-nothing contract EXACTLY: reset and
164
+ // abort, so a caller that retries a query sees a clean empty index rather
165
+ // than a partially-rebuilt one it cannot reason about.
166
+ if (!lenient) {
167
+ this._reset();
168
+ return (0, ts_utils_1.fail)(error);
169
+ }
170
+ skipped.push({ target: scoped.target, error });
171
+ continue;
172
+ }
173
+ // A decline is not an error under either mode — it is counted, never skipped.
174
+ if (embedded.value === undefined) {
175
+ declined++;
176
+ continue;
118
177
  }
119
178
  const added = await this.add(scoped.target, embedded.value);
120
179
  if (added.isFailure()) {
121
- this._reset();
122
- return (0, ts_utils_1.fail)(`vector index rebuild: ${added.message}`);
180
+ const error = `vector index rebuild: ${added.message}`;
181
+ if (!lenient) {
182
+ this._reset();
183
+ return (0, ts_utils_1.fail)(error);
184
+ }
185
+ skipped.push({ target: scoped.target, error });
123
186
  }
124
187
  }
125
- return (0, ts_utils_1.succeed)(this._vectors.size);
188
+ return (0, ts_utils_1.succeed)({ indexed: this._vectors.size, declined, skipped });
126
189
  }
127
190
  /** Empty the index and forget the established dimension. */
128
191
  _reset() {