@fgv/ts-agent-memory 5.1.0-49 → 5.1.0-51

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 (182) hide show
  1. package/dist/packlets/index/memoryIndex.js +23 -16
  2. package/dist/packlets/index/memoryIndex.js.map +1 -1
  3. package/dist/packlets/ingest/orchestrator.js +13 -1
  4. package/dist/packlets/ingest/orchestrator.js.map +1 -1
  5. package/dist/packlets/retrieve/fragmentSemanticRetriever.js +94 -5
  6. package/dist/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -1
  7. package/dist/packlets/retrieve/linkTraversalRetriever.js +12 -26
  8. package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
  9. package/dist/packlets/retrieve/recencyRetriever.js +7 -7
  10. package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
  11. package/dist/packlets/retrieve/retriever.js +91 -10
  12. package/dist/packlets/retrieve/retriever.js.map +1 -1
  13. package/dist/packlets/retrieve/semanticRetriever.js +16 -16
  14. package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
  15. package/dist/packlets/retrieve/structuredFilterRetriever.js +7 -7
  16. package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
  17. package/dist/packlets/retrieve/tagRetriever.js +7 -7
  18. package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
  19. package/dist/packlets/retrieve/temporalRetrievers.js +23 -20
  20. package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
  21. package/dist/packlets/store/coverage.js +6 -0
  22. package/dist/packlets/store/coverage.js.map +1 -0
  23. package/dist/packlets/store/fileTreeMemoryStore.js +234 -103
  24. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  25. package/dist/packlets/store/index.js +4 -0
  26. package/dist/packlets/store/index.js.map +1 -1
  27. package/dist/packlets/store/listSelection.js +36 -0
  28. package/dist/packlets/store/listSelection.js.map +1 -0
  29. package/dist/packlets/store/memoryStore.js +6 -0
  30. package/dist/packlets/store/memoryStore.js.map +1 -0
  31. package/dist/packlets/store/reconcile.js +6 -0
  32. package/dist/packlets/store/reconcile.js.map +1 -0
  33. package/dist/packlets/store/storeCoverage.js +102 -0
  34. package/dist/packlets/store/storeCoverage.js.map +1 -0
  35. package/dist/packlets/store/storeIdentity.js +62 -0
  36. package/dist/packlets/store/storeIdentity.js.map +1 -0
  37. package/dist/packlets/store/storeReconcile.js +122 -0
  38. package/dist/packlets/store/storeReconcile.js.map +1 -0
  39. package/dist/packlets/store/vectorMaintenance.js +116 -8
  40. package/dist/packlets/store/vectorMaintenance.js.map +1 -1
  41. package/dist/packlets/store/vectorRecordSource.js +44 -0
  42. package/dist/packlets/store/vectorRecordSource.js.map +1 -0
  43. package/dist/packlets/tools/memoryTools.js +25 -2
  44. package/dist/packlets/tools/memoryTools.js.map +1 -1
  45. package/dist/packlets/types/envelope.js +25 -0
  46. package/dist/packlets/types/envelope.js.map +1 -1
  47. package/dist/packlets/types/identityResolver.js +6 -0
  48. package/dist/packlets/types/identityResolver.js.map +1 -0
  49. package/dist/packlets/types/index.js +2 -0
  50. package/dist/packlets/types/index.js.map +1 -1
  51. package/dist/packlets/types/recordResolver.js +6 -0
  52. package/dist/packlets/types/recordResolver.js.map +1 -0
  53. package/dist/packlets/types/temporal.js.map +1 -1
  54. package/dist/packlets/vector/inMemoryCosineIndex.js +39 -18
  55. package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  56. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +109 -14
  57. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  58. package/dist/packlets/vector/rebuildHelpers.js +38 -0
  59. package/dist/packlets/vector/rebuildHelpers.js.map +1 -0
  60. package/dist/packlets/vector/vectorIndex.js.map +1 -1
  61. package/dist/ts-agent-memory.d.ts +1226 -114
  62. package/lib/packlets/index/memoryIndex.d.ts +118 -27
  63. package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
  64. package/lib/packlets/index/memoryIndex.js +23 -16
  65. package/lib/packlets/index/memoryIndex.js.map +1 -1
  66. package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
  67. package/lib/packlets/ingest/orchestrator.js +13 -1
  68. package/lib/packlets/ingest/orchestrator.js.map +1 -1
  69. package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts +79 -1
  70. package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts.map +1 -1
  71. package/lib/packlets/retrieve/fragmentSemanticRetriever.js +94 -5
  72. package/lib/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -1
  73. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +3 -10
  74. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
  75. package/lib/packlets/retrieve/linkTraversalRetriever.js +11 -25
  76. package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
  77. package/lib/packlets/retrieve/recencyRetriever.d.ts +3 -3
  78. package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -1
  79. package/lib/packlets/retrieve/recencyRetriever.js +6 -6
  80. package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
  81. package/lib/packlets/retrieve/retriever.d.ts +88 -7
  82. package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
  83. package/lib/packlets/retrieve/retriever.js +94 -9
  84. package/lib/packlets/retrieve/retriever.js.map +1 -1
  85. package/lib/packlets/retrieve/semanticRetriever.d.ts +3 -5
  86. package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
  87. package/lib/packlets/retrieve/semanticRetriever.js +15 -15
  88. package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
  89. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +3 -3
  90. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -1
  91. package/lib/packlets/retrieve/structuredFilterRetriever.js +6 -6
  92. package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
  93. package/lib/packlets/retrieve/tagRetriever.d.ts +3 -3
  94. package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -1
  95. package/lib/packlets/retrieve/tagRetriever.js +6 -6
  96. package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
  97. package/lib/packlets/retrieve/temporalRetrievers.d.ts +7 -5
  98. package/lib/packlets/retrieve/temporalRetrievers.d.ts.map +1 -1
  99. package/lib/packlets/retrieve/temporalRetrievers.js +22 -19
  100. package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
  101. package/lib/packlets/store/coverage.d.ts +102 -0
  102. package/lib/packlets/store/coverage.d.ts.map +1 -0
  103. package/lib/packlets/store/coverage.js +7 -0
  104. package/lib/packlets/store/coverage.js.map +1 -0
  105. package/lib/packlets/store/fileTreeMemoryStore.d.ts +56 -167
  106. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  107. package/lib/packlets/store/fileTreeMemoryStore.js +234 -103
  108. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  109. package/lib/packlets/store/index.d.ts +4 -0
  110. package/lib/packlets/store/index.d.ts.map +1 -1
  111. package/lib/packlets/store/index.js +4 -0
  112. package/lib/packlets/store/index.js.map +1 -1
  113. package/lib/packlets/store/listSelection.d.ts +101 -0
  114. package/lib/packlets/store/listSelection.d.ts.map +1 -0
  115. package/lib/packlets/store/listSelection.js +40 -0
  116. package/lib/packlets/store/listSelection.js.map +1 -0
  117. package/lib/packlets/store/memoryStore.d.ts +237 -0
  118. package/lib/packlets/store/memoryStore.d.ts.map +1 -0
  119. package/lib/packlets/store/memoryStore.js +7 -0
  120. package/lib/packlets/store/memoryStore.js.map +1 -0
  121. package/lib/packlets/store/reconcile.d.ts +82 -0
  122. package/lib/packlets/store/reconcile.d.ts.map +1 -0
  123. package/lib/packlets/store/reconcile.js +7 -0
  124. package/lib/packlets/store/reconcile.js.map +1 -0
  125. package/lib/packlets/store/storeCoverage.d.ts +45 -0
  126. package/lib/packlets/store/storeCoverage.d.ts.map +1 -0
  127. package/lib/packlets/store/storeCoverage.js +105 -0
  128. package/lib/packlets/store/storeCoverage.js.map +1 -0
  129. package/lib/packlets/store/storeIdentity.d.ts +38 -0
  130. package/lib/packlets/store/storeIdentity.d.ts.map +1 -0
  131. package/lib/packlets/store/storeIdentity.js +67 -0
  132. package/lib/packlets/store/storeIdentity.js.map +1 -0
  133. package/lib/packlets/store/storeReconcile.d.ts +41 -0
  134. package/lib/packlets/store/storeReconcile.d.ts.map +1 -0
  135. package/lib/packlets/store/storeReconcile.js +125 -0
  136. package/lib/packlets/store/storeReconcile.js.map +1 -0
  137. package/lib/packlets/store/vectorMaintenance.d.ts +74 -0
  138. package/lib/packlets/store/vectorMaintenance.d.ts.map +1 -1
  139. package/lib/packlets/store/vectorMaintenance.js +117 -8
  140. package/lib/packlets/store/vectorMaintenance.js.map +1 -1
  141. package/lib/packlets/store/vectorRecordSource.d.ts +36 -0
  142. package/lib/packlets/store/vectorRecordSource.d.ts.map +1 -0
  143. package/lib/packlets/store/vectorRecordSource.js +47 -0
  144. package/lib/packlets/store/vectorRecordSource.js.map +1 -0
  145. package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
  146. package/lib/packlets/tools/memoryTools.js +25 -2
  147. package/lib/packlets/tools/memoryTools.js.map +1 -1
  148. package/lib/packlets/types/envelope.d.ts +24 -2
  149. package/lib/packlets/types/envelope.d.ts.map +1 -1
  150. package/lib/packlets/types/envelope.js +26 -0
  151. package/lib/packlets/types/envelope.js.map +1 -1
  152. package/lib/packlets/types/identityResolver.d.ts +42 -0
  153. package/lib/packlets/types/identityResolver.d.ts.map +1 -0
  154. package/lib/packlets/types/identityResolver.js +7 -0
  155. package/lib/packlets/types/identityResolver.js.map +1 -0
  156. package/lib/packlets/types/index.d.ts +2 -0
  157. package/lib/packlets/types/index.d.ts.map +1 -1
  158. package/lib/packlets/types/index.js +2 -0
  159. package/lib/packlets/types/index.js.map +1 -1
  160. package/lib/packlets/types/recordResolver.d.ts +39 -0
  161. package/lib/packlets/types/recordResolver.d.ts.map +1 -0
  162. package/lib/packlets/types/recordResolver.js +7 -0
  163. package/lib/packlets/types/recordResolver.js.map +1 -0
  164. package/lib/packlets/types/temporal.d.ts +26 -6
  165. package/lib/packlets/types/temporal.d.ts.map +1 -1
  166. package/lib/packlets/types/temporal.js.map +1 -1
  167. package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -2
  168. package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
  169. package/lib/packlets/vector/inMemoryCosineIndex.js +40 -19
  170. package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  171. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +18 -4
  172. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
  173. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +108 -13
  174. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  175. package/lib/packlets/vector/rebuildHelpers.d.ts +30 -0
  176. package/lib/packlets/vector/rebuildHelpers.d.ts.map +1 -0
  177. package/lib/packlets/vector/rebuildHelpers.js +42 -0
  178. package/lib/packlets/vector/rebuildHelpers.js.map +1 -0
  179. package/lib/packlets/vector/vectorIndex.d.ts +326 -20
  180. package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
  181. package/lib/packlets/vector/vectorIndex.js.map +1 -1
  182. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"inMemoryFragmentCosineIndex.js","sourceRoot":"","sources":["../../../src/packlets/vector/inMemoryFragmentCosineIndex.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,4CAAsD;AACtD,oCAAsD;AAkBtD,0FAA0F;AAC1F,SAAS,gBAAgB,CAAC,QAA2B;IACnD,uCACK,CAAC,QAAQ,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GACrE,CAAC,QAAQ,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EACjF;AACJ,CAAC;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAa,2BAA2B;IAUtC;QACE,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,EAAkC,CAAC;QAC1D,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;IAC9B,CAAC;IAED,8EAA8E;IAC9E,IAAW,WAAW;QACpB,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,uEAAuE;IACvE,IAAW,aAAa;QACtB,IAAI,KAAK,GAAW,CAAC,CAAC;QACtB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5C,KAAK,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;QACnC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,iCAAiC;IAC1B,MAAM,CAAC,MAAM;QAClB,OAAO,IAAA,kBAAO,EAAC,IAAI,2BAA2B,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,sDAAsD;IAC/C,YAAY,CACjB,MAAmB,EACnB,SAA2C;QAE3C,MAAM,GAAG,GAAW,IAAA,qBAAa,EAAC,MAAM,CAAC,CAAC;QAC1C,6EAA6E;QAC7E,0EAA0E;QAC1E,gFAAgF;QAChF,8EAA8E;QAC9E,iFAAiF;QACjF,MAAM,MAAM,GAAsB,EAAE,CAAC;QACrC,IAAI,SAAS,GAAuB,IAAI,CAAC,UAAU,CAAC;QACpD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACjC,OAAO,OAAO,CAAC,OAAO,CAAC,IAAA,eAAI,EAAC,+BAA+B,GAAG,0BAA0B,CAAC,CAAC,CAAC;YAC7F,CAAC;YACD,gFAAgF;YAChF,4EAA4E;YAC5E,uEAAuE;YACvE,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBACxE,OAAO,OAAO,CAAC,OAAO,CACpB,IAAA,eAAI,EACF,+BAA+B,GAAG,gEAAgE,CACnG,CACF,CAAC;YACJ,CAAC;YACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC5B,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;YACrC,CAAC;iBAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAChD,OAAO,OAAO,CAAC,OAAO,CACpB,IAAA,eAAI,EACF,+BAA+B,GAAG,yBAAyB,QAAQ,CAAC,MAAM,CAAC,MAAM,mCAAmC,SAAS,EAAE,CAChI,CACF,CAAC;YACJ,CAAC;YACD,kFAAkF;YAClF,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACpG,CAAC;QACD,6EAA6E;QAC7E,iFAAiF;QACjF,0DAA0D;QAC1D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;YAC5B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAA,kBAAO,EAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,gDAAgD;IACzC,MAAM,CAAC,MAAmB;QAC/B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAA,qBAAa,EAAC,MAAM,CAAC,CAAC,CAAC;QAC5C,OAAO,OAAO,CAAC,OAAO,CAAC,IAAA,kBAAO,EAAC,MAAM,CAAC,CAAC,CAAC;IAC1C,CAAC;IAED,+CAA+C;IACxC,KAAK,CACV,MAAoB,EACpB,IAAY,EACZ,YAAqB;;QAErB,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC1C,OAAO,OAAO,CAAC,OAAO,CAAC,IAAA,kBAAO,EAAC,EAAE,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YACtC,OAAO,OAAO,CAAC,OAAO,CACpB,IAAA,eAAI,EACF,mCAAmC,MAAM,CAAC,MAAM,mCAAmC,IAAI,CAAC,UAAU,EAAE,CACrG,CACF,CAAC;QACJ,CAAC;QACD,MAAM,cAAc,GAAW,2BAA2B,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC9E,MAAM,MAAM,GAAsB,EAAE,CAAC;QACrC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5C,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBACxC,MAAM,CAAC,IAAI,CAAC;oBACV,GAAG,EAAE,IAAA,qBAAa,EAAC,MAAM,CAAC,MAAM,CAAC;oBACjC,GAAG,kBACD,MAAM,EAAE,MAAM,CAAC,MAAM,EACrB,KAAK,EAAE,2BAA2B,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,IAChF,QAAQ,CAAC,QAAQ,CACrB;iBACF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,4EAA4E;QAC5E,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEjD,MAAM,IAAI,GAAsB,EAAE,CAAC;QACnC,8EAA8E;QAC9E,6EAA6E;QAC7E,4EAA4E;QAC5E,wDAAwD;QACxD,MAAM,SAAS,GAAwB,IAAI,GAAG,EAAkB,CAAC;QACjE,KAAK,MAAM,SAAS,IAAI,MAAM,EAAE,CAAC;YAC/B,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;gBACxB,MAAM;YACR,CAAC;YACD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,IAAI,GAAW,MAAA,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,mCAAI,CAAC,CAAC;gBACvD,IAAI,IAAI,IAAI,YAAY,EAAE,CAAC;oBACzB,SAAS;gBACX,CAAC;gBACD,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;YACzC,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAA,kBAAO,EAAC,IAAI,CAAC,CAAC,CAAC;IACxC,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACI,KAAK,CAAC,OAAO,CAAC,MAA2B,EAAE,KAAuB;QACvE,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,MAAM,MAAM,GAA+C,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAC/E,IAAI,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;YACvB,OAAO,IAAA,eAAI,EAAC,mDAAmD,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QACnF,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAClC,MAAM,QAAQ,GAA6C,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtF,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;gBACzB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,OAAO,IAAA,eAAI,EACT,sCAAsC,IAAA,qBAAa,EAAC,MAAM,CAAC,MAAM,CAAC,aAAa,QAAQ,CAAC,OAAO,EAAE,CAClG,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,GAAmB,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;YACrF,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;gBACtB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,OAAO,IAAA,eAAI,EAAC,2BAA2B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;QACD,OAAO,IAAA,kBAAO,EAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACrC,CAAC;IAED,4DAA4D;IACpD,MAAM;QACZ,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;IAC9B,CAAC;IAED,qDAAqD;IAC7C,MAAM,CAAC,UAAU,CAAC,MAAoB;QAC5C,IAAI,GAAG,GAAW,CAAC,CAAC;QACpB,KAAK,IAAI,CAAC,GAAW,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/C,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,OAAO,CAAC,KAAmB,EAAE,cAAsB,EAAE,MAAoB;QACtF,MAAM,eAAe,GAAW,2BAA2B,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC/E,IAAI,cAAc,KAAK,CAAC,IAAI,eAAe,KAAK,CAAC,EAAE,CAAC;YAClD,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,GAAG,GAAW,CAAC,CAAC;QACpB,KAAK,IAAI,CAAC,GAAW,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,GAAG,GAAG,CAAC,cAAc,GAAG,eAAe,CAAC,CAAC;IAClD,CAAC;CACF;AA3ND,kEA2NC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { IEdgeTarget, edgeTargetKey } from '../types';\nimport {\n FragmentEmbedder,\n IEmbeddedFragment,\n IFragmentVectorIndex,\n IMemoryRecordSource,\n IScopedMemoryRecord,\n IVectorQueryHit\n} from './vectorIndex';\n\n/**\n * The identity fields a fragment was added with, already in query-hit shape: a field\n * the fragment did not carry is *absent*, never present-but-`undefined`, so a hit\n * for a fragment added without a `fragmentId` is structurally identical to one\n * produced before `fragmentId` existed.\n */\ntype FragmentIdentity = Pick<IVectorQueryHit, 'locator' | 'fragmentId'>;\n\n/** Project an incoming fragment's identity fields, dropping the ones it did not carry. */\nfunction fragmentIdentity(fragment: IEmbeddedFragment): FragmentIdentity {\n return {\n ...(fragment.locator !== undefined ? { locator: fragment.locator } : {}),\n ...(fragment.fragmentId !== undefined ? { fragmentId: fragment.fragmentId } : {})\n };\n}\n\n/** One stored fragment: the identity it was added with plus its vector. */\ninterface IStoredFragment {\n readonly identity: FragmentIdentity;\n readonly vector: Float32Array;\n}\n\n/** Every stored fragment for one record, tagged with the record's scoped address. */\ninterface IStoredRecordFragments {\n readonly target: IEdgeTarget;\n readonly fragments: IStoredFragment[];\n}\n\n/** A candidate hit carried through selection: the fragment's key, hit, and score. */\ninterface IScoredFragment {\n readonly key: string;\n readonly hit: IVectorQueryHit;\n}\n\n/**\n * The brute-force, in-memory cosine {@link IFragmentVectorIndex} — the\n * fragment-granular sibling of {@link InMemoryCosineIndex}. Stores many\n * `Float32Array`s per record (one per {@link IEmbeddedFragment | fragment}) and\n * answers a query by computing cosine similarity against every stored fragment,\n * returning the top-k fragment hits by descending score. Each hit carries back\n * whichever of {@link IFragmentLocator | locator} / `fragmentId` its fragment was\n * added with; a fragment must carry at least one of the two.\n *\n * @remarks\n * Same regime and same non-goals as {@link InMemoryCosineIndex}: no external\n * dependency, no ANN structure, a linear scan over the stored fragments — the seam\n * ({@link IFragmentVectorIndex}) stays open for a consumer to swap a persistent /\n * ANN backend once N grows. `addFragments` is whole-record-replace, so re-authoring\n * a document never leaves stale fragments behind. The index has a single dimension\n * established by the first fragment added; every subsequent fragment and every\n * `query` vector must match it or fail loudly — a mismatched dimension is an\n * embedder-wiring bug, never a silent zero-similarity result.\n * {@link InMemoryFragmentCosineIndex.rebuild | rebuild} clears the index (and the\n * established dimension), so a re-embed with a different model is supported.\n *\n * The optional `maxPerRecord` cap on `query` is applied **during selection**, before\n * the `topK` cut, so one long document with many strong fragments cannot crowd every\n * other record out of the result.\n * @public\n */\nexport class InMemoryFragmentCosineIndex implements IFragmentVectorIndex {\n /**\n * Stored fragments keyed by the canonical {@link edgeTargetKey} of the record's\n * scope-qualified address, so two records that share a filename stem across\n * scopes occupy distinct entries and never overwrite each other's fragments.\n */\n private readonly _records: Map<string, IStoredRecordFragments>;\n /** The dimension of every stored fragment vector; `undefined` until the first `add`. */\n private _dimension: number | undefined;\n\n private constructor() {\n this._records = new Map<string, IStoredRecordFragments>();\n this._dimension = undefined;\n }\n\n /** The number of records that currently have at least one stored fragment. */\n public get recordCount(): number {\n return this._records.size;\n }\n\n /** The total number of fragments currently held across all records. */\n public get fragmentCount(): number {\n let total: number = 0;\n for (const record of this._records.values()) {\n total += record.fragments.length;\n }\n return total;\n }\n\n /** Family-convention factory. */\n public static create(): Result<InMemoryFragmentCosineIndex> {\n return succeed(new InMemoryFragmentCosineIndex());\n }\n\n /** {@inheritDoc IFragmentVectorIndex.addFragments} */\n public addFragments(\n target: IEdgeTarget,\n fragments: ReadonlyArray<IEmbeddedFragment>\n ): Promise<Result<number>> {\n const key: string = edgeTargetKey(target);\n // Validate every fragment before mutating any state, so a bad fragment never\n // leaves the record half-replaced OR the index dimension half-established\n // (whole-record-replace must be all-or-nothing). The effective dimension is the\n // established one, or — on a still-dimensionless index — the first fragment's\n // length; it is only committed to `this._dimension` once the whole batch passes.\n const stored: IStoredFragment[] = [];\n let dimension: number | undefined = this._dimension;\n for (const fragment of fragments) {\n if (fragment.vector.length === 0) {\n return Promise.resolve(fail(`fragment index: cannot add '${key}': empty fragment vector`));\n }\n // A fragment carrying neither identity cannot be resolved back to anything by a\n // consumer holding the hit — the same invariant `embeddedFragmentConverter`\n // enforces at the untyped boundary, re-checked here at the index seam.\n if (fragment.locator === undefined && fragment.fragmentId === undefined) {\n return Promise.resolve(\n fail(\n `fragment index: cannot add '${key}': fragment requires at least one of 'locator' or 'fragmentId'`\n )\n );\n }\n if (dimension === undefined) {\n dimension = fragment.vector.length;\n } else if (fragment.vector.length !== dimension) {\n return Promise.resolve(\n fail(\n `fragment index: cannot add '${key}': fragment dimension ${fragment.vector.length} does not match index dimension ${dimension}`\n )\n );\n }\n // Defensive copy: the caller may reuse or mutate the buffer after `addFragments`.\n stored.push({ identity: fragmentIdentity(fragment), vector: Float32Array.from(fragment.vector) });\n }\n // Whole-record replace: an empty `fragments` array drops the record entirely\n // rather than leaving an empty shell behind. Commit the (possibly newly-derived)\n // dimension only alongside a successful, non-empty store.\n if (stored.length === 0) {\n this._records.delete(key);\n } else {\n this._dimension = dimension;\n this._records.set(key, { target, fragments: stored });\n }\n return Promise.resolve(succeed(stored.length));\n }\n\n /** {@inheritDoc IFragmentVectorIndex.remove} */\n public remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>> {\n this._records.delete(edgeTargetKey(target));\n return Promise.resolve(succeed(target));\n }\n\n /** {@inheritDoc IFragmentVectorIndex.query} */\n public query(\n vector: Float32Array,\n topK: number,\n maxPerRecord?: number\n ): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (topK <= 0 || this._records.size === 0) {\n return Promise.resolve(succeed([]));\n }\n if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `fragment index: query dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n const queryMagnitude: number = InMemoryFragmentCosineIndex._magnitude(vector);\n const scored: IScoredFragment[] = [];\n for (const record of this._records.values()) {\n for (const fragment of record.fragments) {\n scored.push({\n key: edgeTargetKey(record.target),\n hit: {\n target: record.target,\n score: InMemoryFragmentCosineIndex._cosine(vector, queryMagnitude, fragment.vector),\n ...fragment.identity\n }\n });\n }\n }\n // Descending by score; the caller re-resolves each `(target, locator)` hit.\n scored.sort((a, b) => b.hit.score - a.hit.score);\n\n const hits: IVectorQueryHit[] = [];\n // Apply the per-record cap during selection (before the topK cut) so a single\n // long document cannot monopolize the result. `undefined` maxPerRecord means\n // uncapped; the counter map is always allocated (tiny) so the guard narrows\n // `maxPerRecord` directly without a non-null assertion.\n const perRecord: Map<string, number> = new Map<string, number>();\n for (const candidate of scored) {\n if (hits.length >= topK) {\n break;\n }\n if (maxPerRecord !== undefined) {\n const used: number = perRecord.get(candidate.key) ?? 0;\n if (used >= maxPerRecord) {\n continue;\n }\n perRecord.set(candidate.key, used + 1);\n }\n hits.push(candidate.hit);\n }\n return Promise.resolve(succeed(hits));\n }\n\n /**\n * Re-embed every record from `source` and rebuild the fragment index from\n * scratch. Clears the current contents (and the established dimension) first, so\n * a re-embed with a different model is supported. Returns the total number of\n * fragments indexed.\n *\n * On any failure (list, embed, or add) the index is rolled back to empty rather\n * than left in a partially-rebuilt state.\n *\n * @remarks\n * **Deliberately still returns a bare count**, unlike the record-granular\n * {@link InMemoryCosineIndex.rebuild}, which reports an\n * {@link IVectorRebuildReport}. The asymmetry is scope, not oversight: the\n * fragment path is tracked separately and gains the same treatment when the\n * `IVectorIndex`/`IFragmentVectorIndex` contracts are revisited together.\n *\n * @param source - The scope-qualified record source to re-embed.\n * @param embed - The fragment embedder applied to each record.\n */\n public async rebuild(source: IMemoryRecordSource, embed: FragmentEmbedder): Promise<Result<number>> {\n this._reset();\n const listed: Result<ReadonlyArray<IScopedMemoryRecord>> = await source.list();\n if (listed.isFailure()) {\n return fail(`fragment index rebuild: failed to list records: ${listed.message}`);\n }\n for (const scoped of listed.value) {\n const embedded: Result<ReadonlyArray<IEmbeddedFragment>> = await embed(scoped.record);\n if (embedded.isFailure()) {\n this._reset();\n return fail(\n `fragment index rebuild: embedding '${edgeTargetKey(scoped.target)}' failed: ${embedded.message}`\n );\n }\n const added: Result<number> = await this.addFragments(scoped.target, embedded.value);\n if (added.isFailure()) {\n this._reset();\n return fail(`fragment index rebuild: ${added.message}`);\n }\n }\n return succeed(this.fragmentCount);\n }\n\n /** Empty the index and forget the established dimension. */\n private _reset(): void {\n this._records.clear();\n this._dimension = undefined;\n }\n\n /** The Euclidean magnitude (L2 norm) of a vector. */\n private static _magnitude(vector: Float32Array): number {\n let sum: number = 0;\n for (let i: number = 0; i < vector.length; i++) {\n sum += vector[i] * vector[i];\n }\n return Math.sqrt(sum);\n }\n\n /**\n * Cosine similarity between the query (whose magnitude is precomputed once and\n * reused across the scan) and a stored fragment vector. A zero-magnitude vector\n * on either side yields `0` rather than `NaN`.\n */\n private static _cosine(query: Float32Array, queryMagnitude: number, stored: Float32Array): number {\n const storedMagnitude: number = InMemoryFragmentCosineIndex._magnitude(stored);\n if (queryMagnitude === 0 || storedMagnitude === 0) {\n return 0;\n }\n let dot: number = 0;\n for (let i: number = 0; i < query.length; i++) {\n dot += query[i] * stored[i];\n }\n return dot / (queryMagnitude * storedMagnitude);\n }\n}\n"]}
1
+ {"version":3,"file":"inMemoryFragmentCosineIndex.js","sourceRoot":"","sources":["../../../src/packlets/vector/inMemoryFragmentCosineIndex.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { DetailedResult, Result, fail, failWithDetail, succeed, succeedWithDetail } from '@fgv/ts-utils';\nimport { IEdgeTarget, Kind, MemoryId, MemoryScopeKey, edgeTargetKey } from '../types';\nimport {\n FragmentEmbedder,\n IEmbeddedFragment,\n IFragmentQueryOptions,\n IFragmentVectorIndex,\n IFragmentVectorRebuildReport,\n IMemoryRecordListing,\n IMemoryRecordSource,\n ISkippedVectorRecord,\n IVectorQueryHit,\n IVectorRebuildOptions\n} from './vectorIndex';\nimport { invokeHook, tally } from './rebuildHelpers';\n\n/**\n * The identity fields a fragment was added with, already in query-hit shape: a field\n * the fragment did not carry is *absent*, never present-but-`undefined`, so a hit\n * for a fragment added without a `fragmentId` is structurally identical to one\n * produced before `fragmentId` existed.\n */\ntype FragmentIdentity = Pick<IVectorQueryHit, 'locator' | 'fragmentId'>;\n\n/** Project an incoming fragment's identity fields, dropping the ones it did not carry. */\nfunction fragmentIdentity(fragment: IEmbeddedFragment): FragmentIdentity {\n return {\n ...(fragment.locator !== undefined ? { locator: fragment.locator } : {}),\n ...(fragment.fragmentId !== undefined ? { fragmentId: fragment.fragmentId } : {})\n };\n}\n\n/** One stored fragment: the identity it was added with plus its vector. */\ninterface IStoredFragment {\n readonly identity: FragmentIdentity;\n readonly vector: Float32Array;\n}\n\n/** Every stored fragment for one record, tagged with the record's scoped address. */\ninterface IStoredRecordFragments {\n readonly target: IEdgeTarget;\n readonly fragments: IStoredFragment[];\n}\n\n/** A candidate hit carried through selection: the fragment's key, hit, and score. */\ninterface IScoredFragment {\n readonly key: string;\n readonly hit: IVectorQueryHit;\n}\n\n/**\n * The brute-force, in-memory cosine {@link IFragmentVectorIndex} — the\n * fragment-granular sibling of {@link InMemoryCosineIndex}. Stores many\n * `Float32Array`s per record (one per {@link IEmbeddedFragment | fragment}) and\n * answers a query by computing cosine similarity against every stored fragment,\n * returning the top-k fragment hits by descending score. Each hit carries back\n * whichever of {@link IFragmentLocator | locator} / `fragmentId` its fragment was\n * added with; a fragment must carry at least one of the two.\n *\n * @remarks\n * Same regime and same non-goals as {@link InMemoryCosineIndex}: no external\n * dependency, no ANN structure, a linear scan over the stored fragments — the seam\n * ({@link IFragmentVectorIndex}) stays open for a consumer to swap a persistent /\n * ANN backend once N grows. `addFragments` is whole-record-replace, so re-authoring\n * a document never leaves stale fragments behind. The index has a single dimension\n * established by the first fragment added; every subsequent fragment and every\n * `query` vector must match it or fail loudly — a mismatched dimension is an\n * embedder-wiring bug, never a silent zero-similarity result.\n * {@link InMemoryFragmentCosineIndex.rebuild | rebuild} clears the index (and the\n * established dimension), so a re-embed with a different model is supported.\n *\n * The optional `maxPerRecord` cap on `query` is applied **during selection**, before\n * the `topK` cut, so one long document with many strong fragments cannot crowd every\n * other record out of the result.\n * @public\n */\nexport class InMemoryFragmentCosineIndex implements IFragmentVectorIndex {\n /**\n * Stored fragments keyed by the canonical {@link edgeTargetKey} of the record's\n * scope-qualified address, so two records that share a filename stem across\n * scopes occupy distinct entries and never overwrite each other's fragments.\n */\n private readonly _records: Map<string, IStoredRecordFragments>;\n /** The dimension of every stored fragment vector; `undefined` until the first `add`. */\n private _dimension: number | undefined;\n\n private constructor() {\n this._records = new Map<string, IStoredRecordFragments>();\n this._dimension = undefined;\n }\n\n /** The number of records that currently have at least one stored fragment. */\n public get recordCount(): number {\n return this._records.size;\n }\n\n /** The total number of fragments currently held across all records. */\n public get fragmentCount(): number {\n let total: number = 0;\n for (const record of this._records.values()) {\n total += record.fragments.length;\n }\n return total;\n }\n\n /** Family-convention factory. */\n public static create(): Result<InMemoryFragmentCosineIndex> {\n return succeed(new InMemoryFragmentCosineIndex());\n }\n\n /** {@inheritDoc IFragmentVectorIndex.addFragments} */\n public addFragments(\n target: IEdgeTarget,\n fragments: ReadonlyArray<IEmbeddedFragment>\n ): Promise<Result<number>> {\n const key: string = edgeTargetKey(target);\n // Validate every fragment before mutating any state, so a bad fragment never\n // leaves the record half-replaced OR the index dimension half-established\n // (whole-record-replace must be all-or-nothing). The effective dimension is the\n // established one, or — on a still-dimensionless index — the first fragment's\n // length; it is only committed to `this._dimension` once the whole batch passes.\n const stored: IStoredFragment[] = [];\n let dimension: number | undefined = this._dimension;\n for (const fragment of fragments) {\n if (fragment.vector.length === 0) {\n return Promise.resolve(fail(`fragment index: cannot add '${key}': empty fragment vector`));\n }\n // A fragment carrying neither identity cannot be resolved back to anything by a\n // consumer holding the hit — the same invariant `embeddedFragmentConverter`\n // enforces at the untyped boundary, re-checked here at the index seam.\n if (fragment.locator === undefined && fragment.fragmentId === undefined) {\n return Promise.resolve(\n fail(\n `fragment index: cannot add '${key}': fragment requires at least one of 'locator' or 'fragmentId'`\n )\n );\n }\n if (dimension === undefined) {\n dimension = fragment.vector.length;\n } else if (fragment.vector.length !== dimension) {\n return Promise.resolve(\n fail(\n `fragment index: cannot add '${key}': fragment dimension ${fragment.vector.length} does not match index dimension ${dimension}`\n )\n );\n }\n // Defensive copy: the caller may reuse or mutate the buffer after `addFragments`.\n stored.push({ identity: fragmentIdentity(fragment), vector: Float32Array.from(fragment.vector) });\n }\n // Whole-record replace: an empty `fragments` array drops the record entirely\n // rather than leaving an empty shell behind. Commit the (possibly newly-derived)\n // dimension only alongside a successful, non-empty store.\n if (stored.length === 0) {\n this._records.delete(key);\n } else {\n this._dimension = dimension;\n this._records.set(key, { target, fragments: stored });\n }\n return Promise.resolve(succeed(stored.length));\n }\n\n /** {@inheritDoc IFragmentVectorIndex.has} */\n public has(target: IEdgeTarget): Promise<Result<boolean>> {\n return Promise.resolve(succeed(this._records.has(edgeTargetKey(target))));\n }\n\n /** {@inheritDoc IFragmentVectorIndex.remove} */\n public remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>> {\n this._records.delete(edgeTargetKey(target));\n return Promise.resolve(succeed(target));\n }\n\n /** {@inheritDoc IFragmentVectorIndex.query} */\n public query(\n vector: Float32Array,\n topK: number,\n options?: IFragmentQueryOptions\n ): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n const maxPerRecord: number | undefined = options?.maxPerRecord;\n if (topK <= 0 || this._records.size === 0) {\n return Promise.resolve(succeed([]));\n }\n if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `fragment index: query dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n const queryMagnitude: number = InMemoryFragmentCosineIndex._magnitude(vector);\n const scored: IScoredFragment[] = [];\n // The narrowing is applied HERE — choosing which records are scored at all —\n // rather than by filtering hits afterwards. That is the whole point: a\n // post-filter would leave `topK` applied to the global set, so a scoped search\n // would silently return fewer than `topK` whenever other records outscored the\n // target's fragments.\n for (const record of this._selectRecords(options)) {\n for (const fragment of record.fragments) {\n scored.push({\n key: edgeTargetKey(record.target),\n hit: {\n target: record.target,\n score: InMemoryFragmentCosineIndex._cosine(vector, queryMagnitude, fragment.vector),\n ...fragment.identity\n }\n });\n }\n }\n // Descending by score; the caller re-resolves each `(target, locator)` hit.\n scored.sort((a, b) => b.hit.score - a.hit.score);\n\n const hits: IVectorQueryHit[] = [];\n // Apply the per-record cap during selection (before the topK cut) so a single\n // long document cannot monopolize the result. `undefined` maxPerRecord means\n // uncapped; the counter map is always allocated (tiny) so the guard narrows\n // `maxPerRecord` directly without a non-null assertion.\n const perRecord: Map<string, number> = new Map<string, number>();\n for (const candidate of scored) {\n if (hits.length >= topK) {\n break;\n }\n if (maxPerRecord !== undefined) {\n const used: number = perRecord.get(candidate.key) ?? 0;\n if (used >= maxPerRecord) {\n continue;\n }\n perRecord.set(candidate.key, used + 1);\n }\n hits.push(candidate.hit);\n }\n return Promise.resolve(succeed(hits));\n }\n\n /**\n * The records a query is allowed to score, honoring the `scope` / `id` narrowing.\n *\n * @remarks\n * The single-record case is an O(1) map lookup rather than a scan, because the\n * record map is keyed by `edgeTargetKey`. The scope-only case (a versioned kind's\n * per-entity subtree) is a filtered walk — still bounded by the vault, but it\n * scores only the entity's own fragments, which is what makes the caller's `topK`\n * meaningful.\n */\n private *_selectRecords(options: IFragmentQueryOptions | undefined): Generator<IStoredRecordFragments> {\n // Narrowed once, deliberately: re-deriving through `options?.` a second time\n // after an early return that already implies `options !== undefined` creates an\n // optional-chain arm that cannot fire, which is a dead branch rather than an\n // untested one.\n if (options === undefined || options.scope === undefined) {\n yield* this._records.values();\n return;\n }\n const scope: MemoryScopeKey = options.scope;\n const id: MemoryId | undefined = options.id;\n if (id !== undefined) {\n const record: IStoredRecordFragments | undefined = this._records.get(edgeTargetKey({ scope, id }));\n if (record !== undefined) {\n yield record;\n }\n return;\n }\n for (const record of this._records.values()) {\n if (record.target.scope === scope) {\n yield record;\n }\n }\n }\n\n /**\n * Re-embed every record from `source` and rebuild the fragment index from\n * scratch. Clears the current contents (and the established dimension) first, so\n * a re-embed with a different model is supported. Returns the total number of\n * fragments indexed.\n *\n * On any failure (list, embed, or add) the index is rolled back to empty rather\n * than left in a partially-rebuilt state.\n *\n * @remarks\n * **Deliberately still returns a bare count**, unlike the record-granular\n * {@link InMemoryCosineIndex.rebuild}, which reports an\n * {@link IVectorRebuildReport}. The asymmetry is scope, not oversight: the\n * fragment path is tracked separately and gains the same treatment when the\n * `IVectorIndex`/`IFragmentVectorIndex` contracts are revisited together.\n *\n * @param source - The scope-qualified record source to re-embed.\n * @param embed - The fragment embedder applied to each record.\n */\n /** {@inheritDoc IFragmentVectorIndex.rebuild} */\n public async rebuild(\n source: IMemoryRecordSource,\n embed: FragmentEmbedder,\n options?: IVectorRebuildOptions\n ): Promise<DetailedResult<IFragmentVectorRebuildReport, IFragmentVectorRebuildReport>> {\n const lenient: boolean = (options?.onRecordError ?? 'fail') === 'skip';\n const listed: Result<IMemoryRecordListing> = await invokeHook(() => source.list());\n if (listed.isFailure()) {\n // Deliberately BEFORE the reset, matching the record-granular sibling: a\n // failed list is no evidence about the fragments already held, and nothing\n // has been re-embedded yet, so there is no half-rebuilt state to guard\n // against. Discarding a healthy index over a transient read error is data\n // loss, not caution. No detail either — an all-zero report would describe\n // an index this call never touched.\n return failWithDetail(`fragment index rebuild: failed to list records: ${listed.message}`);\n }\n // From here a rebuild is genuinely starting, so clear. A mid-loop failure\n // under `'fail'` still resets, which is what keeps that contract honest.\n this._reset();\n const indexed: Map<Kind, number> = new Map<Kind, number>();\n const fragments: Map<Kind, number> = new Map<Kind, number>();\n const declined: Map<Kind, number> = new Map<Kind, number>();\n const skipped: ISkippedVectorRecord[] = [];\n // Only the source knows what it filtered, so an absent `excluded` propagates\n // as absent rather than becoming an empty map. Before this contract existed\n // the fragment path dropped this tally on the floor, having nowhere honest to\n // put it.\n const report = (): IFragmentVectorRebuildReport => ({\n indexed,\n fragments,\n declined,\n excluded: listed.value.excluded,\n skipped\n });\n for (const scoped of listed.value.records) {\n const kind: Kind = scoped.record.envelope.kind;\n // Consumer-supplied, so a throw or rejection is captured rather than\n // escaping mid-loop and leaving the index half-populated.\n const embedded: Result<ReadonlyArray<IEmbeddedFragment>> = await invokeHook(() => embed(scoped.record));\n if (embedded.isFailure()) {\n const error: string = `fragment index rebuild: embedding '${edgeTargetKey(scoped.target)}' failed: ${\n embedded.message\n }`;\n if (!lenient) {\n this._reset();\n return failWithDetail(error, report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n // An empty array is this lane's decline. It still performs a real\n // whole-record-replace — which is what clears any stale fragments — so it\n // is written, then counted as declined rather than indexed.\n const added: Result<number> = await this.addFragments(scoped.target, embedded.value);\n if (added.isFailure()) {\n const error: string = `fragment index rebuild: ${added.message}`;\n if (!lenient) {\n this._reset();\n return failWithDetail(error, report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n if (added.value === 0) {\n tally(declined, kind);\n continue;\n }\n // Tallied per successful add rather than read back off the counts at the\n // end, so the per-kind buckets line up with their siblings.\n tally(indexed, kind);\n tally(fragments, kind, added.value);\n }\n return succeedWithDetail(report());\n }\n\n /** Empty the index and forget the established dimension. */\n private _reset(): void {\n this._records.clear();\n this._dimension = undefined;\n }\n\n /** The Euclidean magnitude (L2 norm) of a vector. */\n private static _magnitude(vector: Float32Array): number {\n let sum: number = 0;\n for (let i: number = 0; i < vector.length; i++) {\n sum += vector[i] * vector[i];\n }\n return Math.sqrt(sum);\n }\n\n /**\n * Cosine similarity between the query (whose magnitude is precomputed once and\n * reused across the scan) and a stored fragment vector. A zero-magnitude vector\n * on either side yields `0` rather than `NaN`.\n */\n private static _cosine(query: Float32Array, queryMagnitude: number, stored: Float32Array): number {\n const storedMagnitude: number = InMemoryFragmentCosineIndex._magnitude(stored);\n if (queryMagnitude === 0 || storedMagnitude === 0) {\n return 0;\n }\n let dot: number = 0;\n for (let i: number = 0; i < query.length; i++) {\n dot += query[i] * stored[i];\n }\n return dot / (queryMagnitude * storedMagnitude);\n }\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import { Result } from '@fgv/ts-utils';
2
+ import { Kind } from '../types';
3
+ /**
4
+ * Invoke a consumer-supplied hook that already returns a `Result`, converting a
5
+ * synchronous throw or a promise rejection into a `Failure` rather than letting
6
+ * it escape. `captureAsyncResult` wraps the hook's own `Result`, so the outcome
7
+ * is flattened back to one level.
8
+ *
9
+ * @remarks
10
+ * Package-internal, and deliberately not exported from the package surface: both
11
+ * shipped index implementations need it, which is what moved it out of
12
+ * `inMemoryCosineIndex.ts`, but publishing it would invite treating the
13
+ * invocation shape as part of the `IVectorIndex` contract when only the report
14
+ * is.
15
+ */
16
+ export declare function invokeHook<T>(hook: () => Promise<Result<T>>): Promise<Result<T>>;
17
+ /**
18
+ * Increment `kind`'s tally by `by` (default one).
19
+ *
20
+ * @remarks
21
+ * Package-internal for the same reason as {@link invokeHook}: publishing a
22
+ * mutation primitive would buy nothing a caller could not write, and would invite
23
+ * treating the accumulation shape as contractual.
24
+ *
25
+ * The `by` parameter exists for the fragment lane, whose `fragments` count
26
+ * accumulates a fan-out rather than a record count — the one place a rebuild adds
27
+ * more than one per record.
28
+ */
29
+ export declare function tally(counts: Map<Kind, number>, kind: Kind, by?: number): void;
30
+ //# sourceMappingURL=rebuildHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebuildHelpers.d.ts","sourceRoot":"","sources":["../../../src/packlets/vector/rebuildHelpers.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAsB,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAEtF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,GAAE,MAAU,GAAG,IAAI,CAEjF"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.invokeHook = invokeHook;
8
+ exports.tally = tally;
9
+ const ts_utils_1 = require("@fgv/ts-utils");
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
+ * @remarks
17
+ * Package-internal, and deliberately not exported from the package surface: both
18
+ * shipped index implementations need it, which is what moved it out of
19
+ * `inMemoryCosineIndex.ts`, but publishing it would invite treating the
20
+ * invocation shape as part of the `IVectorIndex` contract when only the report
21
+ * is.
22
+ */
23
+ async function invokeHook(hook) {
24
+ return (await (0, ts_utils_1.captureAsyncResult)(hook)).onSuccess((inner) => inner);
25
+ }
26
+ /**
27
+ * Increment `kind`'s tally by `by` (default one).
28
+ *
29
+ * @remarks
30
+ * Package-internal for the same reason as {@link invokeHook}: publishing a
31
+ * mutation primitive would buy nothing a caller could not write, and would invite
32
+ * treating the accumulation shape as contractual.
33
+ *
34
+ * The `by` parameter exists for the fragment lane, whose `fragments` count
35
+ * accumulates a fan-out rather than a record count — the one place a rebuild adds
36
+ * more than one per record.
37
+ */
38
+ function tally(counts, kind, by = 1) {
39
+ var _a;
40
+ counts.set(kind, ((_a = counts.get(kind)) !== null && _a !== void 0 ? _a : 0) + by);
41
+ }
42
+ //# sourceMappingURL=rebuildHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebuildHelpers.js","sourceRoot":"","sources":["../../../src/packlets/vector/rebuildHelpers.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAkBH,gCAEC;AAcD,sBAEC;AAlCD,4CAA2D;AAG3D;;;;;;;;;;;;GAYG;AACI,KAAK,UAAU,UAAU,CAAI,IAA8B;IAChE,OAAO,CAAC,MAAM,IAAA,6BAAkB,EAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,KAAK,CAAC,MAAyB,EAAE,IAAU,EAAE,KAAa,CAAC;;IACzE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAA,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,mCAAI,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AACjD,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, captureAsyncResult } from '@fgv/ts-utils';\nimport { Kind } from '../types';\n\n/**\n * Invoke a consumer-supplied hook that already returns a `Result`, converting a\n * synchronous throw or a promise rejection into a `Failure` rather than letting\n * it escape. `captureAsyncResult` wraps the hook's own `Result`, so the outcome\n * is flattened back to one level.\n *\n * @remarks\n * Package-internal, and deliberately not exported from the package surface: both\n * shipped index implementations need it, which is what moved it out of\n * `inMemoryCosineIndex.ts`, but publishing it would invite treating the\n * invocation shape as part of the `IVectorIndex` contract when only the report\n * is.\n */\nexport async function invokeHook<T>(hook: () => Promise<Result<T>>): Promise<Result<T>> {\n return (await captureAsyncResult(hook)).onSuccess((inner) => inner);\n}\n\n/**\n * Increment `kind`'s tally by `by` (default one).\n *\n * @remarks\n * Package-internal for the same reason as {@link invokeHook}: publishing a\n * mutation primitive would buy nothing a caller could not write, and would invite\n * treating the accumulation shape as contractual.\n *\n * The `by` parameter exists for the fragment lane, whose `fragments` count\n * accumulates a fan-out rather than a record count — the one place a rebuild adds\n * more than one per record.\n */\nexport function tally(counts: Map<Kind, number>, kind: Kind, by: number = 1): void {\n counts.set(kind, (counts.get(kind) ?? 0) + by);\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { Result } from '@fgv/ts-utils';
2
- import { IEdgeTarget, IMemoryRecord } from '../types';
1
+ import { DetailedResult, Result } from '@fgv/ts-utils';
2
+ import { IEdgeTarget, IMemoryRecord, Kind, MemoryId, MemoryScopeKey } from '../types';
3
3
  /**
4
4
  * A half-open `[start, end)` span into a record's body — the in-record locator a
5
5
  * {@link IFragmentVectorIndex} may carry on a fragment. `start` is inclusive,
@@ -117,6 +117,32 @@ export interface IVectorIndex {
117
117
  * Return the `topK` nearest records to `vector`, in descending score order.
118
118
  */
119
119
  query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;
120
+ /**
121
+ * Whether this index holds a vector for the scope-qualified `target`.
122
+ *
123
+ * @remarks
124
+ * On the contract because it is what makes a **targeted** repair possible, and
125
+ * the reason is sharper than convenience: the only other way to ask *"is this
126
+ * record indexed?"* is {@link IMemoryEnvelope.embeddingRef} — and that field is
127
+ * the store's **belief**, which is wrong in precisely the situation a repair
128
+ * runs in. A reopened vault backed by a fresh in-memory index carries an
129
+ * `embeddingRef` on every record while holding no vectors at all. **A repair
130
+ * that trusts the field it is repairing is not a repair.**
131
+ *
132
+ * It also makes a case *detectable* that an `embeddingRef`-only check cannot
133
+ * see at all: the index holds the vector but the envelope lost its reference
134
+ * (a failure swallowed after the vector was committed). That record needs its
135
+ * reference restamped and **no embedder call**, which is only knowable by
136
+ * asking the index.
137
+ *
138
+ * `Promise<Result<boolean>>`, unlike the synchronous {@link IVectorIndex.size},
139
+ * because the two are not the same kind of accessor and should not be made to
140
+ * look alike: `size` is a count both shipped implementations hold or can read
141
+ * without a failure mode, while `has` on a durable backend is a keyed query
142
+ * that can fail. Idempotent and side-effect-free; an absent target is
143
+ * `succeed(false)`, never a failure.
144
+ */
145
+ has(target: IEdgeTarget): Promise<Result<boolean>>;
120
146
  /**
121
147
  * The number of vectors currently held.
122
148
  *
@@ -154,8 +180,36 @@ export interface IVectorIndex {
154
180
  *
155
181
  * See {@link IVectorRebuildReport} for what it reports and
156
182
  * {@link IVectorRebuildOptions} for the failure mode.
183
+ *
184
+ * **A failure carries the report too, on the `detail`** — coverage is most
185
+ * wanted exactly when a rebuild did not complete, and withholding it there made
186
+ * the answer depend on the error-handling mode rather than on the question. The
187
+ * `'fail'` contract itself is unchanged: it still resets, still aborts, still
188
+ * returns a failure. It simply also says what it had established before it
189
+ * stopped. See {@link IVectorRebuildReport} for how to read a report that
190
+ * arrived on a failure — it describes the attempt, not the surviving index.
191
+ *
192
+ * On success the report is the **value** — that is where it belongs, and the
193
+ * `detail` is not also populated. A failure carries no report when nothing was
194
+ * attempted: always a `source.list()` failure, and additionally whatever
195
+ * pre-loop step an implementation needs before it can start (the durable
196
+ * `SqliteVecVectorIndex` must clear its table, and a failure to do so is such a
197
+ * case). Those leave the existing index untouched, so an all-zero report would
198
+ * describe an index the call never disturbed.
199
+ *
200
+ * **A rebuild does NOT re-establish the vector dimension on a persistent
201
+ * index, and this is the one place the two shipped implementations genuinely
202
+ * differ.** The in-memory indexes forget their dimension when they reset, so a
203
+ * rebuild with a different-dimension embedder simply re-establishes it. A
204
+ * `vec0`-backed index cannot: its reset is a `DELETE FROM`, the table's
205
+ * declared dimension is part of its schema, and `vec0` has no `ALTER TABLE`.
206
+ * So a rebuild that changes dimension **succeeds in memory and fails on
207
+ * SQLite**, where it needs the same drop-and-re-index the package README
208
+ * prescribes for any schema change (drop the table, or point the index at a
209
+ * fresh `tableName`). Only embedding time is at risk — vectors are derived and
210
+ * the vault records remain authoritative.
157
211
  */
158
- rebuild(source: IMemoryRecordSource, embed: MemoryEmbedder, options?: IVectorRebuildOptions): Promise<Result<IVectorRebuildReport>>;
212
+ rebuild(source: IMemoryRecordSource, embed: MemoryEmbedder, options?: IVectorRebuildOptions): Promise<DetailedResult<IVectorRebuildReport, IVectorRebuildReport>>;
159
213
  }
160
214
  /**
161
215
  * One embedded fragment of a record: the fragment's vector, plus at least one of the
@@ -198,6 +252,53 @@ export interface IEmbeddedFragment {
198
252
  /** The embedding vector for this fragment. */
199
253
  readonly vector: Float32Array;
200
254
  }
255
+ /**
256
+ * Selection-time narrowing for {@link IFragmentVectorIndex.query}. Every member is
257
+ * applied **before** the `topK` cut.
258
+ *
259
+ * @remarks
260
+ * `scope` (optionally with `id`) is the record narrowing. It is expressed as a
261
+ * storage address rather than as a consumer-facing `(kind, entityId)` because that
262
+ * is what the index is keyed by; resolving one to the other is
263
+ * `IIdentityResolver.resolveIdentity`'s job, and doing it above the index keeps the
264
+ * index dealing only in the keys it actually holds.
265
+ *
266
+ * **`scope` alone is not a coarser accident — it is what a versioned kind needs.**
267
+ * `TemporalIdentityCodec` files every version of an entity in its own per-entity
268
+ * subtree, so for a versioned kind the entity's subtree *is* the narrowing and
269
+ * `{ scope }` means "every version of this entity". A non-versioned kind resolves to
270
+ * a single record and supplies `{ scope, id }`.
271
+ *
272
+ * **"Every version" is literal, and includes superseded ones.** Invalidation stamps
273
+ * `invalid_at` on an envelope; it does not prune that version's fragments from the
274
+ * index. So a scope-narrowed query returns current and historical fragments alike,
275
+ * and a hit carries nothing that distinguishes them — `IVectorQueryHit` has no
276
+ * temporal fields. This is the record-granular vector lane's existing behaviour
277
+ * rather than something this narrowing introduces, but do not read the subtree
278
+ * narrowing as currency filtering: it is not, and there is no `asOf` axis here to
279
+ * make it so.
280
+ * @public
281
+ */
282
+ export interface IFragmentQueryOptions {
283
+ /**
284
+ * Maximum number of fragments any single record may contribute. Omit for
285
+ * uncapped. With a single-record narrowing (`scope` + `id`) this caps the one
286
+ * record's contribution, which is a second cap on the same axis rather than a
287
+ * cross-record fairness knob — usually you want one or the other, not both.
288
+ */
289
+ readonly maxPerRecord?: number;
290
+ /**
291
+ * Restrict the search to fragments of records in this scope. Omit to search every
292
+ * record.
293
+ */
294
+ readonly scope?: MemoryScopeKey;
295
+ /**
296
+ * With {@link IFragmentQueryOptions.scope}, restrict further to the single record
297
+ * at `(scope, id)`. Ignored — and meaningless — without `scope`, since a bare `id`
298
+ * does not address a record.
299
+ */
300
+ readonly id?: MemoryId;
301
+ }
201
302
  /**
202
303
  * The fragment-granular sibling of {@link IVectorIndex}: instead of one vector per
203
304
  * record it holds many vectors per record, each tagged with the identity its
@@ -234,12 +335,123 @@ export interface IFragmentVectorIndex {
234
335
  /**
235
336
  * Return the `topK` nearest fragments to `vector`, in descending score order,
236
337
  * each hit carrying its record `target` plus whichever of `locator` /
237
- * `fragmentId` the stored fragment was added with. When
238
- * `maxPerRecord` is supplied, no more than that many fragments of any single
239
- * record appear in the result — the cap is applied during selection (before the
240
- * `topK` cut) so one long document cannot crowd out others.
338
+ * `fragmentId` the stored fragment was added with.
339
+ *
340
+ * @remarks
341
+ * **Every member of `options` is applied during selection, before the `topK`
342
+ * cut.** That ordering is the contract, not an implementation detail: a narrowing
343
+ * applied afterwards would mean the caller's `topK` is not the `topK` that reached
344
+ * the index, so a scoped search would be exact only when a global over-fetch
345
+ * happened to be generous enough. See {@link IFragmentQueryOptions}.
346
+ */
347
+ query(vector: Float32Array, topK: number, options?: IFragmentQueryOptions): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;
348
+ /**
349
+ * Whether this index holds **any** fragment for the scope-qualified `target`.
350
+ *
351
+ * @remarks
352
+ * The record-granular rationale on {@link IVectorIndex.has} applies verbatim —
353
+ * a repair that trusts `embeddingRef` is not a repair. Note the granularity
354
+ * this deliberately does **not** offer: it answers *"is this record
355
+ * represented?"*, not *"is this particular fragment present?"*. Fragment
356
+ * writes are whole-record-replace, so a record is either represented by the
357
+ * current fragment set or not represented at all; a per-fragment membership
358
+ * check would imply an incremental write path that does not exist.
359
+ */
360
+ has(target: IEdgeTarget): Promise<Result<boolean>>;
361
+ /**
362
+ * The number of **records** with at least one fragment held.
363
+ *
364
+ * @remarks
365
+ * Deliberately **not** named `size`, unlike {@link IVectorIndex.size}. This
366
+ * index is one-to-many, so `size` has two defensible readings and a reader
367
+ * arriving from the record-granular sibling — where `size` counts vectors —
368
+ * would take the wrong one silently. Two explicitly-named counts cost one extra
369
+ * member and cannot be misread.
370
+ */
371
+ readonly recordCount: number;
372
+ /**
373
+ * The total number of **fragments** held across all records.
374
+ *
375
+ * @remarks
376
+ * The fan-out, and the number a caller actually watches: fragments-per-record
377
+ * is what makes a fragment reconcile expensive, and neither `recordCount` nor a
378
+ * record-granular count answers it.
379
+ */
380
+ readonly fragmentCount: number;
381
+ /**
382
+ * Re-embed every record from `source` and rebuild the fragment index from
383
+ * scratch — the **backfill / reconcile** operation, sibling to
384
+ * {@link IVectorIndex.rebuild}.
385
+ *
386
+ * @remarks
387
+ * On the contract for exactly the reasons its record-granular sibling is, and
388
+ * the fragment lane was worse off: `rebuild` existed only on the bundled
389
+ * in-memory class, and the durable `SqliteVecFragmentIndex` had **no backfill
390
+ * at all**, so a persistent fragment index could not be reconciled by any
391
+ * route — contractual or concrete. Records written while it was unwired, a
392
+ * re-embed after a segmenter change, and reconciliation after a swallowed
393
+ * fragment-embed failure were all unreachable.
394
+ *
395
+ * Semantics are kept observably identical to the record-granular sibling so a
396
+ * caller who has learned one has learned both: a `source.list()` failure is
397
+ * fatal and carries no detail (nothing was attempted, and the existing index is
398
+ * untouched); a genuine rebuild then resets first; `onRecordError` defaults to
399
+ * `'fail'`; and a failure carries whatever the attempt had established on the
400
+ * `detail`.
401
+ *
402
+ * **A rebuild does NOT re-establish the vector dimension on a persistent
403
+ * index, and this is the one place the two shipped implementations genuinely
404
+ * differ.** The in-memory indexes forget their dimension when they reset, so a
405
+ * rebuild with a different-dimension embedder simply re-establishes it. A
406
+ * `vec0`-backed index cannot: its reset is a `DELETE FROM`, the table's
407
+ * declared dimension is part of its schema, and `vec0` has no `ALTER TABLE`.
408
+ * So a rebuild that changes dimension **succeeds in memory and fails on
409
+ * SQLite**, where it needs the same drop-and-re-index the package README
410
+ * prescribes for any schema change (drop the table, or point the index at a
411
+ * fresh `tableName`). Only embedding time is at risk — vectors are derived and
412
+ * the vault records remain authoritative.
413
+ */
414
+ rebuild(source: IMemoryRecordSource, embed: FragmentEmbedder, options?: IVectorRebuildOptions): Promise<DetailedResult<IFragmentVectorRebuildReport, IFragmentVectorRebuildReport>>;
415
+ }
416
+ /**
417
+ * What an {@link IFragmentVectorIndex.rebuild} established, resolved by
418
+ * {@link Kind} — the fragment-granular sibling of {@link IVectorRebuildReport}.
419
+ *
420
+ * @remarks
421
+ * The rule stated on {@link IVectorRebuildReport} applies here verbatim and is
422
+ * not re-opened: **every count is resolved by kind unless there is a stated
423
+ * reason it cannot be**, `excluded` is optional because only the source can know
424
+ * it, and the report is carried on a failure as well as a success.
425
+ *
426
+ * The one member with no record-granular analogue is
427
+ * {@link IFragmentVectorRebuildReport.fragments | fragments} — the fan-out. It is
428
+ * the number that distinguishes this lane: `indexed: 40` says forty records are
429
+ * represented and says nothing about whether that cost forty embedding round
430
+ * trips or four thousand, which is the difference between a reconcile that
431
+ * finishes in a second and one that blocks a request past thirty.
432
+ *
433
+ * A **declined** record is one whose {@link FragmentEmbedder} returned an empty
434
+ * array — the fragment lane's way of saying *intentionally not embedded*. Note
435
+ * this is a different mechanism from a {@link MemoryEmbedder} decline: an empty
436
+ * array still performs a real whole-record-replace (which is what clears any
437
+ * stale fragments), where a record-granular decline skips the index entirely.
438
+ * @public
439
+ */
440
+ export interface IFragmentVectorRebuildReport {
441
+ /** Records that ended with at least one fragment held, per kind. */
442
+ readonly indexed: ReadonlyMap<Kind, number>;
443
+ /** Fragments held, per kind — the fan-out `indexed` cannot express. */
444
+ readonly fragments: ReadonlyMap<Kind, number>;
445
+ /** Records whose embedder intentionally produced no fragments, per kind. */
446
+ readonly declined: ReadonlyMap<Kind, number>;
447
+ /**
448
+ * Records the source filtered out before the rebuild saw them, per kind.
449
+ * `undefined` means *this source does not report exclusions*; an empty map
450
+ * means *it does, and excluded nothing*.
241
451
  */
242
- query(vector: Float32Array, topK: number, maxPerRecord?: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;
452
+ readonly excluded?: ReadonlyMap<Kind, number>;
453
+ /** Records that failed, per record, with the error — a fault, never a decline. */
454
+ readonly skipped: ReadonlyArray<ISkippedVectorRecord>;
243
455
  }
244
456
  /**
245
457
  * How a vector-index rebuild treats a record it cannot index — whether the
@@ -277,22 +489,83 @@ export interface ISkippedVectorRecord {
277
489
  * What a rebuild actually did — the structural answer to "is this index complete?".
278
490
  *
279
491
  * @remarks
280
- * A bare count cannot distinguish the three ways a record can be absent from the
281
- * index, and that distinction is the entire point: **`declined` was intentional,
282
- * `skipped` was a fault, and neither is the same as "never attempted"**. A caller
283
- * deriving coverage from a count alone cannot tell an embedder outage from a
284
- * deliberate policy, which is precisely the confusion this type exists to end.
492
+ * A bare count cannot distinguish the ways a record can be absent from the index,
493
+ * and that distinction is the entire point: **`declined` was intentional,
494
+ * `excluded` was never offered, `skipped` was a fault, and none of them is the
495
+ * same as "never attempted"**. A caller deriving coverage from a count alone
496
+ * cannot tell an embedder outage from a deliberate policy, which is precisely the
497
+ * confusion this type exists to end.
498
+ *
499
+ * **Every count in this report is resolved by kind.** A coverage report exists to
500
+ * answer *"is my coverage what I intended?"*, and a bare total cannot:
501
+ * `indexed: 500` reads identically whether the right kinds were indexed or a
502
+ * policy drift silently redirected coverage, and the same is true of every other
503
+ * count here. Totals are derivable by summing; the per-kind breakdown is not
504
+ * derivable from anything else — {@link IVectorQueryHit} carries no `kind`,
505
+ * {@link IVectorIndex.query} answers "what is near this" rather than "what is in
506
+ * here", and {@link IVectorIndex.size} is a scalar, so the index cannot be
507
+ * interrogated after the fact for any of them. **A new count added to this report
508
+ * is resolved by kind unless there is a stated reason it cannot be.**
509
+ *
510
+ * `indexed` is the count most tempting to leave bare and the most dangerous to,
511
+ * because it is the number a coverage surface actually renders: 500 bookkeeping
512
+ * rows and zero knowledge rows is a healthy-looking number for a catastrophically
513
+ * broken index.
514
+ *
515
+ * **Reading a report that arrived on a failure.** Under
516
+ * {@link VectorRebuildErrorMode | `onRecordError: 'fail'`} the report is handed
517
+ * back on the failure's `detail` — *after* the rollback has already run. It
518
+ * describes the attempt, not the surviving index: `indexed` names what had been
519
+ * established when the rebuild stopped, and the index itself now holds nothing. It
520
+ * is a diagnostic ("we were 340 knowledge rows in when the embedder died"), not a
521
+ * coverage statement. Only a report from a **successful** rebuild describes what
522
+ * the index holds.
285
523
  * @public
286
524
  */
287
525
  export interface IVectorRebuildReport {
288
- /** Records embedded and added to the index. */
289
- readonly indexed: number;
290
- /** Records the embedder deliberately declined (resolved `undefined`). */
291
- readonly declined: number;
526
+ /**
527
+ * Records embedded and added to the index, counted by {@link Kind}.
528
+ *
529
+ * @remarks
530
+ * A count of successful `add` calls, so it lines up with its per-record
531
+ * siblings and the buckets sum back to the listing. It is deliberately **not**
532
+ * read back off {@link IVectorIndex.size} at the end, which no implementation
533
+ * could resolve by kind anyway. The trade that makes: a `source` that lists the
534
+ * same `(scope, id)` twice contributes twice here while the index holds one
535
+ * vector, where a size read would have self-corrected. A source that does that
536
+ * is malformed, and a total that silently disagreed with the per-kind
537
+ * breakdown would be the worse failure.
538
+ */
539
+ readonly indexed: ReadonlyMap<Kind, number>;
540
+ /**
541
+ * Records the embedder deliberately declined (resolved `undefined`), counted by
542
+ * {@link Kind}. The embedder was called and answered — contrast `excluded`,
543
+ * where it never was.
544
+ */
545
+ readonly declined: ReadonlyMap<Kind, number>;
546
+ /**
547
+ * Records the `source` filtered out before the rebuild ever saw them, counted by
548
+ * {@link Kind} — for a store-backed source, the kinds outside
549
+ * {@link IMemoryStore.embedsKind | embedsKind}.
550
+ *
551
+ * **Optional, and the optionality is semantic rather than cosmetic**: it is the
552
+ * one count a rebuild genuinely cannot know for itself, because the decision is
553
+ * made upstream in the source. `undefined` means *this source does not report
554
+ * exclusions* — distinct from an empty map, which means *this source reports
555
+ * them and excluded nothing*. `indexed` and `declined` are knowable by
556
+ * construction (the rebuild either added the vector or the embedder answered)
557
+ * and so are never optional.
558
+ */
559
+ readonly excluded?: ReadonlyMap<Kind, number>;
292
560
  /**
293
561
  * Records whose embedding or add FAILED and were skipped. Non-empty only under
294
562
  * {@link VectorRebuildErrorMode | `onRecordError: 'skip'`} — under `'fail'` the
295
- * first failure aborts the rebuild and no report is returned at all.
563
+ * first failure aborts the rebuild, so a `'fail'` report names the casualty in
564
+ * its failure message rather than here.
565
+ *
566
+ * Per-record and carrying the error, so it already implies the per-kind
567
+ * breakdown the counts above spell out; that is the stated reason this one field
568
+ * is not a `ReadonlyMap<Kind, number>`.
296
569
  */
297
570
  readonly skipped: ReadonlyArray<ISkippedVectorRecord>;
298
571
  }
@@ -361,6 +634,35 @@ export interface IScopedMemoryRecord {
361
634
  /** The record itself, passed to the embedder. */
362
635
  readonly record: IMemoryRecord<unknown>;
363
636
  }
637
+ /**
638
+ * What a {@link IMemoryRecordSource.list} call yields: the records the rebuild
639
+ * should embed, plus — when the source can say — what it filtered out on the way.
640
+ *
641
+ * @remarks
642
+ * The exclusion count originates here because **this is the layer where the
643
+ * decision is made**. A rebuild never sees an excluded record, so it cannot count
644
+ * one; a report assembled without this would silently undercount coverage, and
645
+ * undercount in the direction of looking healthier.
646
+ *
647
+ * A store accessor answering "how many are excluded right now" was considered and
648
+ * declined: it answers a *different question* than the report does — "excluded
649
+ * right now" versus "excluded in this reconcile" — and the two legitimately differ
650
+ * whenever records are written between reconciles. Two correct-and-unequal numbers
651
+ * are worse than one absent number: they invite treating a real difference as a
652
+ * bug, or picking whichever supports the conclusion already held.
653
+ * @public
654
+ */
655
+ export interface IMemoryRecordListing {
656
+ /** Every record the rebuild should embed, each paired with its scoped address. */
657
+ readonly records: ReadonlyArray<IScopedMemoryRecord>;
658
+ /**
659
+ * Records this source filtered out, counted by {@link Kind}. Omit it entirely if
660
+ * the source does not track exclusions — that reads as *"cannot say"* on
661
+ * {@link IVectorRebuildReport.excluded}, which is distinct from an empty map
662
+ * (*"nothing was excluded"*).
663
+ */
664
+ readonly excluded?: ReadonlyMap<Kind, number>;
665
+ }
364
666
  /**
365
667
  * The minimal record-source surface {@link InMemoryCosineIndex.rebuild} reads to
366
668
  * re-embed an entire vault. Each entry carries the record's scope-qualified
@@ -372,7 +674,11 @@ export interface IScopedMemoryRecord {
372
674
  * @public
373
675
  */
374
676
  export interface IMemoryRecordSource {
375
- /** List every record in the vault, each paired with its scoped address. */
376
- list(): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>>;
677
+ /**
678
+ * List every record the rebuild should embed, each paired with its scoped
679
+ * address, plus the exclusions this source applied if it tracks them. See
680
+ * {@link IMemoryRecordListing}.
681
+ */
682
+ list(): Promise<Result<IMemoryRecordListing>>;
377
683
  }
378
684
  //# sourceMappingURL=vectorIndex.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vectorIndex.d.ts","sourceRoot":"","sources":["../../../src/packlets/vector/vectorIndex.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vectorIndex.d.ts","sourceRoot":"","sources":["../../../src/packlets/vector/vectorIndex.ts"],"names":[],"mappings":"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"}