@fgv/ts-agent-memory 5.1.0-36

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 (331) hide show
  1. package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +9 -0
  2. package/.rush/temp/f6a88bfdd66517ccb98c2c7ae1be6e6fe9e15d38.tar.log +282 -0
  3. package/.rush/temp/operation/build/all.log +9 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +688 -0
  7. package/LICENSE +21 -0
  8. package/README.md +45 -0
  9. package/config/api-extractor.json +343 -0
  10. package/config/jest.config.json +14 -0
  11. package/config/rig.json +4 -0
  12. package/dist/index.js +12 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/packlets/converters/bodyConverterRegistry.js +51 -0
  15. package/dist/packlets/converters/bodyConverterRegistry.js.map +1 -0
  16. package/dist/packlets/converters/envelopeConverter.js +159 -0
  17. package/dist/packlets/converters/envelopeConverter.js.map +1 -0
  18. package/dist/packlets/converters/index.js +7 -0
  19. package/dist/packlets/converters/index.js.map +1 -0
  20. package/dist/packlets/index/index.js +6 -0
  21. package/dist/packlets/index/index.js.map +1 -0
  22. package/dist/packlets/index/memoryIndex.js +170 -0
  23. package/dist/packlets/index/memoryIndex.js.map +1 -0
  24. package/dist/packlets/observe/index.js +7 -0
  25. package/dist/packlets/observe/index.js.map +1 -0
  26. package/dist/packlets/observe/memoryObservationStore.js +119 -0
  27. package/dist/packlets/observe/memoryObservationStore.js.map +1 -0
  28. package/dist/packlets/observe/observer.js +6 -0
  29. package/dist/packlets/observe/observer.js.map +1 -0
  30. package/dist/packlets/retrieve/hybridRetriever.js +135 -0
  31. package/dist/packlets/retrieve/hybridRetriever.js.map +1 -0
  32. package/dist/packlets/retrieve/index.js +12 -0
  33. package/dist/packlets/retrieve/index.js.map +1 -0
  34. package/dist/packlets/retrieve/linkTraversalRetriever.js +143 -0
  35. package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -0
  36. package/dist/packlets/retrieve/recencyRetriever.js +33 -0
  37. package/dist/packlets/retrieve/recencyRetriever.js.map +1 -0
  38. package/dist/packlets/retrieve/retriever.js +110 -0
  39. package/dist/packlets/retrieve/retriever.js.map +1 -0
  40. package/dist/packlets/retrieve/semanticRetriever.js +86 -0
  41. package/dist/packlets/retrieve/semanticRetriever.js.map +1 -0
  42. package/dist/packlets/retrieve/structuredFilterRetriever.js +37 -0
  43. package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -0
  44. package/dist/packlets/retrieve/tagRetriever.js +37 -0
  45. package/dist/packlets/retrieve/tagRetriever.js.map +1 -0
  46. package/dist/packlets/store/fileTreeMemoryStore.js +698 -0
  47. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -0
  48. package/dist/packlets/store/index.js +7 -0
  49. package/dist/packlets/store/index.js.map +1 -0
  50. package/dist/packlets/store/scopeEncoding.js +31 -0
  51. package/dist/packlets/store/scopeEncoding.js.map +1 -0
  52. package/dist/packlets/types/envelope.js +6 -0
  53. package/dist/packlets/types/envelope.js.map +1 -0
  54. package/dist/packlets/types/filenameSafety.js +52 -0
  55. package/dist/packlets/types/filenameSafety.js.map +1 -0
  56. package/dist/packlets/types/identityCodec.js +184 -0
  57. package/dist/packlets/types/identityCodec.js.map +1 -0
  58. package/dist/packlets/types/ids.js +67 -0
  59. package/dist/packlets/types/ids.js.map +1 -0
  60. package/dist/packlets/types/index.js +10 -0
  61. package/dist/packlets/types/index.js.map +1 -0
  62. package/dist/packlets/types/writePolicy.js +263 -0
  63. package/dist/packlets/types/writePolicy.js.map +1 -0
  64. package/dist/packlets/vector/inMemoryCosineIndex.js +150 -0
  65. package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -0
  66. package/dist/packlets/vector/index.js +7 -0
  67. package/dist/packlets/vector/index.js.map +1 -0
  68. package/dist/packlets/vector/vectorIndex.js +6 -0
  69. package/dist/packlets/vector/vectorIndex.js.map +1 -0
  70. package/dist/test/unit/converters/bodyConverterRegistry.test.js +72 -0
  71. package/dist/test/unit/converters/bodyConverterRegistry.test.js.map +1 -0
  72. package/dist/test/unit/converters/envelopeConverter.test.js +196 -0
  73. package/dist/test/unit/converters/envelopeConverter.test.js.map +1 -0
  74. package/dist/test/unit/index/memoryIndex.test.js +152 -0
  75. package/dist/test/unit/index/memoryIndex.test.js.map +1 -0
  76. package/dist/test/unit/observe/memoryObservationStore.test.js +118 -0
  77. package/dist/test/unit/observe/memoryObservationStore.test.js.map +1 -0
  78. package/dist/test/unit/retrieve/linkTraversalRetriever.test.js +182 -0
  79. package/dist/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -0
  80. package/dist/test/unit/retrieve/retrievers.test.js +506 -0
  81. package/dist/test/unit/retrieve/retrievers.test.js.map +1 -0
  82. package/dist/test/unit/store/embedOnWrite.test.js +260 -0
  83. package/dist/test/unit/store/embedOnWrite.test.js.map +1 -0
  84. package/dist/test/unit/store/fileTreeMemoryStore.test.js +647 -0
  85. package/dist/test/unit/store/fileTreeMemoryStore.test.js.map +1 -0
  86. package/dist/test/unit/store/observations.test.js +239 -0
  87. package/dist/test/unit/store/observations.test.js.map +1 -0
  88. package/dist/test/unit/store/scopeEncoding.test.js +24 -0
  89. package/dist/test/unit/store/scopeEncoding.test.js.map +1 -0
  90. package/dist/test/unit/types/identityCodec.test.js +187 -0
  91. package/dist/test/unit/types/identityCodec.test.js.map +1 -0
  92. package/dist/test/unit/types/ids.test.js +84 -0
  93. package/dist/test/unit/types/ids.test.js.map +1 -0
  94. package/dist/test/unit/types/writePolicy.test.js +241 -0
  95. package/dist/test/unit/types/writePolicy.test.js.map +1 -0
  96. package/dist/test/unit/vector/inMemoryCosineIndex.test.js +192 -0
  97. package/dist/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -0
  98. package/dist/test/unit/vector/vectorIndex.test.js +42 -0
  99. package/dist/test/unit/vector/vectorIndex.test.js.map +1 -0
  100. package/dist/ts-agent-memory.d.ts +1901 -0
  101. package/dist/tsdoc-metadata.json +11 -0
  102. package/eslint.config.js +15 -0
  103. package/etc/ts-agent-memory.api.md +525 -0
  104. package/lib/index.d.ts +8 -0
  105. package/lib/index.d.ts.map +1 -0
  106. package/lib/index.js +28 -0
  107. package/lib/index.js.map +1 -0
  108. package/lib/packlets/converters/bodyConverterRegistry.d.ts +64 -0
  109. package/lib/packlets/converters/bodyConverterRegistry.d.ts.map +1 -0
  110. package/lib/packlets/converters/bodyConverterRegistry.js +55 -0
  111. package/lib/packlets/converters/bodyConverterRegistry.js.map +1 -0
  112. package/lib/packlets/converters/envelopeConverter.d.ts +70 -0
  113. package/lib/packlets/converters/envelopeConverter.d.ts.map +1 -0
  114. package/lib/packlets/converters/envelopeConverter.js +166 -0
  115. package/lib/packlets/converters/envelopeConverter.js.map +1 -0
  116. package/lib/packlets/converters/index.d.ts +3 -0
  117. package/lib/packlets/converters/index.d.ts.map +1 -0
  118. package/lib/packlets/converters/index.js +23 -0
  119. package/lib/packlets/converters/index.js.map +1 -0
  120. package/lib/packlets/index/index.d.ts +2 -0
  121. package/lib/packlets/index/index.d.ts.map +1 -0
  122. package/lib/packlets/index/index.js +22 -0
  123. package/lib/packlets/index/index.js.map +1 -0
  124. package/lib/packlets/index/memoryIndex.d.ts +127 -0
  125. package/lib/packlets/index/memoryIndex.d.ts.map +1 -0
  126. package/lib/packlets/index/memoryIndex.js +174 -0
  127. package/lib/packlets/index/memoryIndex.js.map +1 -0
  128. package/lib/packlets/observe/index.d.ts +3 -0
  129. package/lib/packlets/observe/index.d.ts.map +1 -0
  130. package/lib/packlets/observe/index.js +23 -0
  131. package/lib/packlets/observe/index.js.map +1 -0
  132. package/lib/packlets/observe/memoryObservationStore.d.ts +91 -0
  133. package/lib/packlets/observe/memoryObservationStore.d.ts.map +1 -0
  134. package/lib/packlets/observe/memoryObservationStore.js +123 -0
  135. package/lib/packlets/observe/memoryObservationStore.js.map +1 -0
  136. package/lib/packlets/observe/observer.d.ts +110 -0
  137. package/lib/packlets/observe/observer.d.ts.map +1 -0
  138. package/lib/packlets/observe/observer.js +7 -0
  139. package/lib/packlets/observe/observer.js.map +1 -0
  140. package/lib/packlets/retrieve/hybridRetriever.d.ts +79 -0
  141. package/lib/packlets/retrieve/hybridRetriever.d.ts.map +1 -0
  142. package/lib/packlets/retrieve/hybridRetriever.js +140 -0
  143. package/lib/packlets/retrieve/hybridRetriever.js.map +1 -0
  144. package/lib/packlets/retrieve/index.d.ts +8 -0
  145. package/lib/packlets/retrieve/index.d.ts.map +1 -0
  146. package/lib/packlets/retrieve/index.js +28 -0
  147. package/lib/packlets/retrieve/index.js.map +1 -0
  148. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +61 -0
  149. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -0
  150. package/lib/packlets/retrieve/linkTraversalRetriever.js +147 -0
  151. package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -0
  152. package/lib/packlets/retrieve/recencyRetriever.d.ts +21 -0
  153. package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -0
  154. package/lib/packlets/retrieve/recencyRetriever.js +37 -0
  155. package/lib/packlets/retrieve/recencyRetriever.js.map +1 -0
  156. package/lib/packlets/retrieve/retriever.d.ts +133 -0
  157. package/lib/packlets/retrieve/retriever.d.ts.map +1 -0
  158. package/lib/packlets/retrieve/retriever.js +119 -0
  159. package/lib/packlets/retrieve/retriever.js.map +1 -0
  160. package/lib/packlets/retrieve/semanticRetriever.d.ts +69 -0
  161. package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -0
  162. package/lib/packlets/retrieve/semanticRetriever.js +90 -0
  163. package/lib/packlets/retrieve/semanticRetriever.js.map +1 -0
  164. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +22 -0
  165. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -0
  166. package/lib/packlets/retrieve/structuredFilterRetriever.js +41 -0
  167. package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -0
  168. package/lib/packlets/retrieve/tagRetriever.d.ts +22 -0
  169. package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -0
  170. package/lib/packlets/retrieve/tagRetriever.js +41 -0
  171. package/lib/packlets/retrieve/tagRetriever.js.map +1 -0
  172. package/lib/packlets/store/fileTreeMemoryStore.d.ts +327 -0
  173. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -0
  174. package/lib/packlets/store/fileTreeMemoryStore.js +702 -0
  175. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -0
  176. package/lib/packlets/store/index.d.ts +3 -0
  177. package/lib/packlets/store/index.d.ts.map +1 -0
  178. package/lib/packlets/store/index.js +23 -0
  179. package/lib/packlets/store/index.js.map +1 -0
  180. package/lib/packlets/store/scopeEncoding.d.ts +19 -0
  181. package/lib/packlets/store/scopeEncoding.d.ts.map +1 -0
  182. package/lib/packlets/store/scopeEncoding.js +34 -0
  183. package/lib/packlets/store/scopeEncoding.js.map +1 -0
  184. package/lib/packlets/types/envelope.d.ts +119 -0
  185. package/lib/packlets/types/envelope.d.ts.map +1 -0
  186. package/lib/packlets/types/envelope.js +7 -0
  187. package/lib/packlets/types/envelope.js.map +1 -0
  188. package/lib/packlets/types/filenameSafety.d.ts +16 -0
  189. package/lib/packlets/types/filenameSafety.d.ts.map +1 -0
  190. package/lib/packlets/types/filenameSafety.js +55 -0
  191. package/lib/packlets/types/filenameSafety.js.map +1 -0
  192. package/lib/packlets/types/identityCodec.d.ts +136 -0
  193. package/lib/packlets/types/identityCodec.d.ts.map +1 -0
  194. package/lib/packlets/types/identityCodec.js +190 -0
  195. package/lib/packlets/types/identityCodec.js.map +1 -0
  196. package/lib/packlets/types/ids.d.ts +55 -0
  197. package/lib/packlets/types/ids.d.ts.map +1 -0
  198. package/lib/packlets/types/ids.js +70 -0
  199. package/lib/packlets/types/ids.js.map +1 -0
  200. package/lib/packlets/types/index.d.ts +6 -0
  201. package/lib/packlets/types/index.d.ts.map +1 -0
  202. package/lib/packlets/types/index.js +26 -0
  203. package/lib/packlets/types/index.js.map +1 -0
  204. package/lib/packlets/types/writePolicy.d.ts +213 -0
  205. package/lib/packlets/types/writePolicy.d.ts.map +1 -0
  206. package/lib/packlets/types/writePolicy.js +268 -0
  207. package/lib/packlets/types/writePolicy.js.map +1 -0
  208. package/lib/packlets/vector/inMemoryCosineIndex.d.ts +69 -0
  209. package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -0
  210. package/lib/packlets/vector/inMemoryCosineIndex.js +154 -0
  211. package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -0
  212. package/lib/packlets/vector/index.d.ts +3 -0
  213. package/lib/packlets/vector/index.d.ts.map +1 -0
  214. package/lib/packlets/vector/index.js +23 -0
  215. package/lib/packlets/vector/index.js.map +1 -0
  216. package/lib/packlets/vector/vectorIndex.d.ts +68 -0
  217. package/lib/packlets/vector/vectorIndex.d.ts.map +1 -0
  218. package/lib/packlets/vector/vectorIndex.js +7 -0
  219. package/lib/packlets/vector/vectorIndex.js.map +1 -0
  220. package/lib/test/unit/converters/bodyConverterRegistry.test.d.ts +2 -0
  221. package/lib/test/unit/converters/bodyConverterRegistry.test.d.ts.map +1 -0
  222. package/lib/test/unit/converters/bodyConverterRegistry.test.js +74 -0
  223. package/lib/test/unit/converters/bodyConverterRegistry.test.js.map +1 -0
  224. package/lib/test/unit/converters/envelopeConverter.test.d.ts +2 -0
  225. package/lib/test/unit/converters/envelopeConverter.test.d.ts.map +1 -0
  226. package/lib/test/unit/converters/envelopeConverter.test.js +198 -0
  227. package/lib/test/unit/converters/envelopeConverter.test.js.map +1 -0
  228. package/lib/test/unit/index/memoryIndex.test.d.ts +2 -0
  229. package/lib/test/unit/index/memoryIndex.test.d.ts.map +1 -0
  230. package/lib/test/unit/index/memoryIndex.test.js +154 -0
  231. package/lib/test/unit/index/memoryIndex.test.js.map +1 -0
  232. package/lib/test/unit/observe/memoryObservationStore.test.d.ts +2 -0
  233. package/lib/test/unit/observe/memoryObservationStore.test.d.ts.map +1 -0
  234. package/lib/test/unit/observe/memoryObservationStore.test.js +120 -0
  235. package/lib/test/unit/observe/memoryObservationStore.test.js.map +1 -0
  236. package/lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts +2 -0
  237. package/lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts.map +1 -0
  238. package/lib/test/unit/retrieve/linkTraversalRetriever.test.js +184 -0
  239. package/lib/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -0
  240. package/lib/test/unit/retrieve/retrievers.test.d.ts +2 -0
  241. package/lib/test/unit/retrieve/retrievers.test.d.ts.map +1 -0
  242. package/lib/test/unit/retrieve/retrievers.test.js +508 -0
  243. package/lib/test/unit/retrieve/retrievers.test.js.map +1 -0
  244. package/lib/test/unit/store/embedOnWrite.test.d.ts +2 -0
  245. package/lib/test/unit/store/embedOnWrite.test.d.ts.map +1 -0
  246. package/lib/test/unit/store/embedOnWrite.test.js +262 -0
  247. package/lib/test/unit/store/embedOnWrite.test.js.map +1 -0
  248. package/lib/test/unit/store/fileTreeMemoryStore.test.d.ts +2 -0
  249. package/lib/test/unit/store/fileTreeMemoryStore.test.d.ts.map +1 -0
  250. package/lib/test/unit/store/fileTreeMemoryStore.test.js +649 -0
  251. package/lib/test/unit/store/fileTreeMemoryStore.test.js.map +1 -0
  252. package/lib/test/unit/store/observations.test.d.ts +2 -0
  253. package/lib/test/unit/store/observations.test.d.ts.map +1 -0
  254. package/lib/test/unit/store/observations.test.js +241 -0
  255. package/lib/test/unit/store/observations.test.js.map +1 -0
  256. package/lib/test/unit/store/scopeEncoding.test.d.ts +2 -0
  257. package/lib/test/unit/store/scopeEncoding.test.d.ts.map +1 -0
  258. package/lib/test/unit/store/scopeEncoding.test.js +26 -0
  259. package/lib/test/unit/store/scopeEncoding.test.js.map +1 -0
  260. package/lib/test/unit/types/identityCodec.test.d.ts +2 -0
  261. package/lib/test/unit/types/identityCodec.test.d.ts.map +1 -0
  262. package/lib/test/unit/types/identityCodec.test.js +189 -0
  263. package/lib/test/unit/types/identityCodec.test.js.map +1 -0
  264. package/lib/test/unit/types/ids.test.d.ts +2 -0
  265. package/lib/test/unit/types/ids.test.d.ts.map +1 -0
  266. package/lib/test/unit/types/ids.test.js +86 -0
  267. package/lib/test/unit/types/ids.test.js.map +1 -0
  268. package/lib/test/unit/types/writePolicy.test.d.ts +2 -0
  269. package/lib/test/unit/types/writePolicy.test.d.ts.map +1 -0
  270. package/lib/test/unit/types/writePolicy.test.js +243 -0
  271. package/lib/test/unit/types/writePolicy.test.js.map +1 -0
  272. package/lib/test/unit/vector/inMemoryCosineIndex.test.d.ts +2 -0
  273. package/lib/test/unit/vector/inMemoryCosineIndex.test.d.ts.map +1 -0
  274. package/lib/test/unit/vector/inMemoryCosineIndex.test.js +194 -0
  275. package/lib/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -0
  276. package/lib/test/unit/vector/vectorIndex.test.d.ts +2 -0
  277. package/lib/test/unit/vector/vectorIndex.test.d.ts.map +1 -0
  278. package/lib/test/unit/vector/vectorIndex.test.js +44 -0
  279. package/lib/test/unit/vector/vectorIndex.test.js.map +1 -0
  280. package/package.json +81 -0
  281. package/rush-logs/ts-agent-memory.build.cache.log +3 -0
  282. package/rush-logs/ts-agent-memory.build.log +9 -0
  283. package/src/index.ts +12 -0
  284. package/src/packlets/converters/bodyConverterRegistry.ts +105 -0
  285. package/src/packlets/converters/envelopeConverter.ts +210 -0
  286. package/src/packlets/converters/index.ts +7 -0
  287. package/src/packlets/index/index.ts +6 -0
  288. package/src/packlets/index/memoryIndex.ts +268 -0
  289. package/src/packlets/observe/index.ts +7 -0
  290. package/src/packlets/observe/memoryObservationStore.ts +153 -0
  291. package/src/packlets/observe/observer.ts +119 -0
  292. package/src/packlets/retrieve/hybridRetriever.ts +181 -0
  293. package/src/packlets/retrieve/index.ts +12 -0
  294. package/src/packlets/retrieve/linkTraversalRetriever.ts +169 -0
  295. package/src/packlets/retrieve/recencyRetriever.ts +54 -0
  296. package/src/packlets/retrieve/retriever.ts +207 -0
  297. package/src/packlets/retrieve/semanticRetriever.ts +147 -0
  298. package/src/packlets/retrieve/structuredFilterRetriever.ts +58 -0
  299. package/src/packlets/retrieve/tagRetriever.ts +58 -0
  300. package/src/packlets/store/fileTreeMemoryStore.ts +1073 -0
  301. package/src/packlets/store/index.ts +7 -0
  302. package/src/packlets/store/scopeEncoding.ts +36 -0
  303. package/src/packlets/types/envelope.ts +138 -0
  304. package/src/packlets/types/filenameSafety.ts +57 -0
  305. package/src/packlets/types/identityCodec.ts +263 -0
  306. package/src/packlets/types/ids.ts +124 -0
  307. package/src/packlets/types/index.ts +10 -0
  308. package/src/packlets/types/writePolicy.ts +447 -0
  309. package/src/packlets/vector/inMemoryCosineIndex.ts +173 -0
  310. package/src/packlets/vector/index.ts +7 -0
  311. package/src/packlets/vector/vectorIndex.ts +78 -0
  312. package/src/test/unit/converters/bodyConverterRegistry.test.ts +89 -0
  313. package/src/test/unit/converters/envelopeConverter.test.ts +261 -0
  314. package/src/test/unit/index/memoryIndex.test.ts +187 -0
  315. package/src/test/unit/observe/memoryObservationStore.test.ts +158 -0
  316. package/src/test/unit/retrieve/linkTraversalRetriever.test.ts +230 -0
  317. package/src/test/unit/retrieve/retrievers.test.ts +662 -0
  318. package/src/test/unit/store/embedOnWrite.test.ts +346 -0
  319. package/src/test/unit/store/fileTreeMemoryStore.test.ts +875 -0
  320. package/src/test/unit/store/observations.test.ts +290 -0
  321. package/src/test/unit/store/scopeEncoding.test.ts +37 -0
  322. package/src/test/unit/types/identityCodec.test.ts +266 -0
  323. package/src/test/unit/types/ids.test.ts +94 -0
  324. package/src/test/unit/types/writePolicy.test.ts +325 -0
  325. package/src/test/unit/vector/inMemoryCosineIndex.test.ts +242 -0
  326. package/src/test/unit/vector/vectorIndex.test.ts +48 -0
  327. package/temp/build/lint/_eslint-5eVG3S6w.json +182 -0
  328. package/temp/build/typescript/ts_8nwakTlr.json +1 -0
  329. package/temp/ts-agent-memory.api.json +12438 -0
  330. package/temp/ts-agent-memory.api.md +525 -0
  331. package/tsconfig.json +8 -0
@@ -0,0 +1,213 @@
1
+ import { Result } from '@fgv/ts-utils';
2
+ import { IMemoryRecord } from './envelope';
3
+ import { MemoryId } from './ids';
4
+ /**
5
+ * The granularity at which the store deduplicates an incoming write against the
6
+ * existing vault, declared per kind via {@link IWritePolicy.dedupScope}.
7
+ *
8
+ * - `'content'`: scope-wide cross-id content dedup. An identical
9
+ * `{ kind, body, links }` triple ANYWHERE in the scope — even under a
10
+ * different id — is a no-op that returns the existing record. The knowledge
11
+ * kind family uses this.
12
+ * - `'entity'`: same-id content dedup only. An identical re-put of the SAME
13
+ * entity is a no-op, but two DISTINCT entities with identical content never
14
+ * collapse. The experience (memory) kind families use this so that, e.g.,
15
+ * two turns whose summaries happen to be byte-identical both persist.
16
+ * @public
17
+ */
18
+ export type DedupScope = 'content' | 'entity';
19
+ /**
20
+ * The default {@link DedupScope} applied when a policy does not declare one.
21
+ * Entity-scoped dedup is the safe default — it never silently collapses two
22
+ * distinct entities with coincidentally-identical content.
23
+ * @public
24
+ */
25
+ export declare const DEFAULT_DEDUP_SCOPE: DedupScope;
26
+ /**
27
+ * The admission decision returned by {@link IWritePolicy.admit}.
28
+ * @public
29
+ */
30
+ export type AdmissionDecision = {
31
+ readonly decision: 'accept';
32
+ } | {
33
+ readonly decision: 'reject';
34
+ readonly reason: string;
35
+ } | {
36
+ readonly decision: 'cull-oldest';
37
+ /** {@link MemoryId}s to evict before writing the incoming record. */
38
+ readonly evict: ReadonlyArray<MemoryId>;
39
+ };
40
+ /**
41
+ * A per-kind write policy, injected at store construction. Invoked AFTER
42
+ * content-hash dedup (dedup is always pre-policy). The policy decides
43
+ * admission and, on update, applies a JSON Merge Patch (RFC-7386) restricted
44
+ * to the declared mutable fields.
45
+ * @public
46
+ */
47
+ export interface IWritePolicy {
48
+ /**
49
+ * Names of the fields a merge-patch update may touch. Fields outside this
50
+ * list are immutable; a change to one constitutes a new entity (its
51
+ * `contentHash` differs). Used by {@link IWritePolicy.applyUpdate} to
52
+ * restrict which fields the patch applies.
53
+ */
54
+ readonly mutableFields: ReadonlyArray<string>;
55
+ /**
56
+ * The granularity at which the store deduplicates an incoming write for this
57
+ * kind. Optional; when absent the store applies {@link DEFAULT_DEDUP_SCOPE}
58
+ * (`'entity'`). See {@link DedupScope}.
59
+ */
60
+ readonly dedupScope?: DedupScope;
61
+ /**
62
+ * Determine whether the incoming record is admitted.
63
+ * @param incoming - The record about to be written.
64
+ * @param existing - The admission cohort the kind's cap applies to: the
65
+ * records in the same scope of the same kind, EXCLUDING the record at
66
+ * `incoming`'s target id. Empty on a first write into an empty cohort.
67
+ * Excluding the target id makes the post-write count uniform
68
+ * (`existing.length + 1`) across first-writes and same-id updates. A
69
+ * last-write-wins policy that has no cap ignores this argument.
70
+ * @returns A {@link AdmissionDecision}.
71
+ */
72
+ admit(incoming: IMemoryRecord<unknown>, existing: ReadonlyArray<IMemoryRecord<unknown>>): Result<AdmissionDecision>;
73
+ /**
74
+ * Apply a JSON Merge Patch (RFC-7386) to the mutable fields of an existing
75
+ * record. Called when admission is `accept` AND a record with the same
76
+ * `entityId` already exists (an update, not a first write).
77
+ *
78
+ * @param existing - The current persisted record.
79
+ * @param patch - A partial JSON object in Merge Patch format. `null`
80
+ * deletes the corresponding key; arrays replace wholesale; nested objects
81
+ * deep-merge. Only keys in {@link IWritePolicy.mutableFields} are applied.
82
+ * @returns The updated record (envelope + body).
83
+ */
84
+ applyUpdate(existing: IMemoryRecord<unknown>, patch: Record<string, unknown>): Result<IMemoryRecord<unknown>>;
85
+ }
86
+ /**
87
+ * Last-write-wins write policy for the knowledge kind family. Admission
88
+ * always accepts (no cap, no cull); updates apply an RFC-7386 merge patch
89
+ * restricted to the knowledge mutable surface.
90
+ *
91
+ * @remarks
92
+ * **Merge-surface pin (resolves design-lock §5.3's body-vs-envelope muddle).**
93
+ * The declared `mutableFields` span both axes of a record: `body` is the
94
+ * per-kind body, while `tags` / `links` / `provenance` / `embeddingRef` live
95
+ * on the {@link IMemoryEnvelope}. `applyUpdate` projects exactly those fields
96
+ * — each read from its canonical location — into a single record-level JSON
97
+ * view, runs the merge over that view, then rebuilds a coherent record. The
98
+ * identity and transaction-time envelope fields (`id`, `entityId`, `kind`,
99
+ * `created`, `updated`, `seq`, `contentHash`) are NOT mutable and are
100
+ * preserved verbatim; the store stamps `updated` / `seq` on write.
101
+ * @public
102
+ */
103
+ export declare class KnowledgeLwwPolicy implements IWritePolicy {
104
+ /**
105
+ * The knowledge mutable surface: the body plus the envelope metadata a
106
+ * consumer may revise without minting a new entity.
107
+ */
108
+ readonly mutableFields: ReadonlyArray<string>;
109
+ /**
110
+ * Knowledge dedups scope-wide: an identical `{ kind, body, links }` triple
111
+ * anywhere in the `knowledge` scope — even under a different `docId` — is a
112
+ * no-op. Declared explicitly so the B1 content-dedup behavior (and its tests)
113
+ * are unchanged by the {@link DedupScope} amendment.
114
+ */
115
+ readonly dedupScope: DedupScope;
116
+ /** Deep-clones the mutable view without RFC-7386 null-deletion semantics. */
117
+ private readonly _cloneEditor;
118
+ /** Applies the RFC-7386 merge patch. */
119
+ private readonly _mergeEditor;
120
+ private constructor();
121
+ /**
122
+ * Family-convention factory. Constructs the shared `JsonEditor` instances
123
+ * (one for cloning, one for the RFC-7386 merge), both with the template /
124
+ * conditional / multivalue / reference rules disabled (empty rules array).
125
+ */
126
+ static create(): Result<KnowledgeLwwPolicy>;
127
+ /** {@inheritDoc IWritePolicy.admit} */
128
+ admit(__incoming: IMemoryRecord<unknown>, __existing: ReadonlyArray<IMemoryRecord<unknown>>): Result<AdmissionDecision>;
129
+ /** {@inheritDoc IWritePolicy.applyUpdate} */
130
+ applyUpdate(existing: IMemoryRecord<unknown>, patch: Record<string, unknown>): Result<IMemoryRecord<unknown>>;
131
+ /**
132
+ * Reassemble a record from the merged mutable view. `body` / `tags` /
133
+ * `links` / `provenance` are required and may not be deleted by a patch.
134
+ * `embeddingRef` is optional: when it is absent from the merged view —
135
+ * because the existing record never carried it OR a `null` patch deleted it
136
+ * (RFC-7386) — it is restored as `undefined` (absent), NOT `null`. This keeps
137
+ * an originally-absent `embeddingRef` from silently flipping to `null` on an
138
+ * unrelated update, so the field round-trips hash-stably through the store's
139
+ * content-hash recomputation.
140
+ */
141
+ private _rebuild;
142
+ }
143
+ /**
144
+ * Parameters for {@link MemoryCapCullPolicy.create}.
145
+ * @public
146
+ */
147
+ export interface IMemoryCapCullPolicyParams {
148
+ /**
149
+ * Maximum number of records the policy admits before culling the oldest.
150
+ * Counted over the `existing` cohort passed to {@link IWritePolicy.admit}.
151
+ * Absent = no cap (admission always accepts).
152
+ */
153
+ readonly maxRecords?: number;
154
+ /**
155
+ * The fields a merge-patch update may touch (drawn from the record-level
156
+ * mutable vocabulary: `body` / `tags` / `links` / `provenance` /
157
+ * `embeddingRef`). Fields outside this list are immutable.
158
+ */
159
+ readonly mutableFields: ReadonlyArray<string>;
160
+ }
161
+ /**
162
+ * Bounded-ring write policy for the experience (memory) kind families.
163
+ * Admission accepts until `maxRecords` is reached, then evicts the oldest
164
+ * record(s) by `created` ascending (design-lock §5.3); updates apply the same
165
+ * RFC-7386 merge patch as {@link KnowledgeLwwPolicy}, restricted to the declared
166
+ * {@link IMemoryCapCullPolicyParams.mutableFields | mutableFields}.
167
+ *
168
+ * @remarks
169
+ * - **Dedup scope.** Declares `dedupScope: 'entity'` — two distinct memory
170
+ * entities (e.g. `turn-5` / `turn-9`) with identical `{ kind, body, links }`
171
+ * never collapse; only an identical re-put of the SAME entity is a no-op.
172
+ * - **Eviction boundary.** `admit` only DECIDES (returns the `MemoryId`s to
173
+ * evict); the store executes the file deletions and index patches. The
174
+ * `existing` cohort the cap counts against is whatever the store supplies to
175
+ * `admit`.
176
+ * @public
177
+ */
178
+ export declare class MemoryCapCullPolicy implements IWritePolicy {
179
+ /** {@inheritDoc IWritePolicy.mutableFields} */
180
+ readonly mutableFields: ReadonlyArray<string>;
181
+ /** Experience kinds dedup per-entity (see the class remarks). */
182
+ readonly dedupScope: DedupScope;
183
+ /** The admission cap; `undefined` = no cap. */
184
+ private readonly _maxRecords;
185
+ /** Deep-clones the mutable view without RFC-7386 null-deletion semantics. */
186
+ private readonly _cloneEditor;
187
+ /** Applies the RFC-7386 merge patch. */
188
+ private readonly _mergeEditor;
189
+ private constructor();
190
+ /**
191
+ * Family-convention factory. Constructs the shared `JsonEditor` instances
192
+ * (one for cloning, one for the RFC-7386 merge) with the same merge config as
193
+ * {@link KnowledgeLwwPolicy} (`nullAsDelete` true, `arrayMergeBehavior`
194
+ * `'replace'`, rules disabled).
195
+ */
196
+ static create(params: IMemoryCapCullPolicyParams): Result<MemoryCapCullPolicy>;
197
+ /** {@inheritDoc IWritePolicy.admit} */
198
+ admit(__incoming: IMemoryRecord<unknown>, existing: ReadonlyArray<IMemoryRecord<unknown>>): Result<AdmissionDecision>;
199
+ /** {@inheritDoc IWritePolicy.applyUpdate} */
200
+ applyUpdate(existing: IMemoryRecord<unknown>, patch: Record<string, unknown>): Result<IMemoryRecord<unknown>>;
201
+ /**
202
+ * Reassemble a record from the merged mutable view. Only the declared mutable
203
+ * fields are taken from the merge; undeclared fields are preserved verbatim
204
+ * from `existing`. A `null` patch that deletes a *declared mutable* required
205
+ * field (`body` / `tags` / `links` / `provenance`) is an error — a required
206
+ * field that is NOT declared mutable simply falls through to its `existing.*`
207
+ * value and is never at risk. `embeddingRef`, when mutable, is restored as
208
+ * `undefined` (absent) if the merge dropped it — same hash-stable semantics as
209
+ * {@link KnowledgeLwwPolicy}.
210
+ */
211
+ private _rebuild;
212
+ }
213
+ //# sourceMappingURL=writePolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writePolicy.d.ts","sourceRoot":"","sources":["../../../src/packlets/types/writePolicy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,268 @@
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.MemoryCapCullPolicy = exports.KnowledgeLwwPolicy = exports.DEFAULT_DEDUP_SCOPE = void 0;
8
+ const ts_utils_1 = require("@fgv/ts-utils");
9
+ const ts_json_1 = require("@fgv/ts-json");
10
+ /**
11
+ * The default {@link DedupScope} applied when a policy does not declare one.
12
+ * Entity-scoped dedup is the safe default — it never silently collapses two
13
+ * distinct entities with coincidentally-identical content.
14
+ * @public
15
+ */
16
+ exports.DEFAULT_DEDUP_SCOPE = 'entity';
17
+ /**
18
+ * RFC-7386-compliant merge options for {@link IWritePolicy.applyUpdate}.
19
+ *
20
+ * @remarks
21
+ * Phase-A verification (design-lock §5.1) confirmed `@fgv/ts-json`'s
22
+ * `JsonEditor` diverges from RFC-7386 under its defaults on two axes —
23
+ * `nullAsDelete` defaults `false` and `arrayMergeBehavior` defaults
24
+ * `'append'`. Both are corrected here via the existing option surface, so the
25
+ * primitive is composed (not extended, not hand-rolled).
26
+ */
27
+ const MERGE_PATCH_OPTIONS = {
28
+ merge: {
29
+ nullAsDelete: true,
30
+ arrayMergeBehavior: 'replace'
31
+ }
32
+ };
33
+ /**
34
+ * Last-write-wins write policy for the knowledge kind family. Admission
35
+ * always accepts (no cap, no cull); updates apply an RFC-7386 merge patch
36
+ * restricted to the knowledge mutable surface.
37
+ *
38
+ * @remarks
39
+ * **Merge-surface pin (resolves design-lock §5.3's body-vs-envelope muddle).**
40
+ * The declared `mutableFields` span both axes of a record: `body` is the
41
+ * per-kind body, while `tags` / `links` / `provenance` / `embeddingRef` live
42
+ * on the {@link IMemoryEnvelope}. `applyUpdate` projects exactly those fields
43
+ * — each read from its canonical location — into a single record-level JSON
44
+ * view, runs the merge over that view, then rebuilds a coherent record. The
45
+ * identity and transaction-time envelope fields (`id`, `entityId`, `kind`,
46
+ * `created`, `updated`, `seq`, `contentHash`) are NOT mutable and are
47
+ * preserved verbatim; the store stamps `updated` / `seq` on write.
48
+ * @public
49
+ */
50
+ class KnowledgeLwwPolicy {
51
+ constructor(cloneEditor, mergeEditor) {
52
+ /**
53
+ * The knowledge mutable surface: the body plus the envelope metadata a
54
+ * consumer may revise without minting a new entity.
55
+ */
56
+ this.mutableFields = [
57
+ 'body',
58
+ 'tags',
59
+ 'links',
60
+ 'provenance',
61
+ 'embeddingRef'
62
+ ];
63
+ /**
64
+ * Knowledge dedups scope-wide: an identical `{ kind, body, links }` triple
65
+ * anywhere in the `knowledge` scope — even under a different `docId` — is a
66
+ * no-op. Declared explicitly so the B1 content-dedup behavior (and its tests)
67
+ * are unchanged by the {@link DedupScope} amendment.
68
+ */
69
+ this.dedupScope = 'content';
70
+ this._cloneEditor = cloneEditor;
71
+ this._mergeEditor = mergeEditor;
72
+ }
73
+ /**
74
+ * Family-convention factory. Constructs the shared `JsonEditor` instances
75
+ * (one for cloning, one for the RFC-7386 merge), both with the template /
76
+ * conditional / multivalue / reference rules disabled (empty rules array).
77
+ */
78
+ static create() {
79
+ return ts_json_1.JsonEditor.create({}, []).onSuccess((cloneEditor) => ts_json_1.JsonEditor.create(MERGE_PATCH_OPTIONS, []).onSuccess((mergeEditor) => (0, ts_utils_1.succeed)(new KnowledgeLwwPolicy(cloneEditor, mergeEditor))));
80
+ }
81
+ /** {@inheritDoc IWritePolicy.admit} */
82
+ admit(__incoming, __existing) {
83
+ // Last-write-wins: always accept. No cap, no cull.
84
+ return (0, ts_utils_1.succeed)({ decision: 'accept' });
85
+ }
86
+ /** {@inheritDoc IWritePolicy.applyUpdate} */
87
+ applyUpdate(existing, patch) {
88
+ // Project the mutable fields into a single record-level view, each sourced
89
+ // from its canonical location. `embeddingRef` is omitted when `undefined`
90
+ // (the editor rejects undefined property values).
91
+ const view = {
92
+ body: existing.body,
93
+ tags: existing.envelope.tags,
94
+ links: existing.envelope.links,
95
+ provenance: existing.envelope.provenance
96
+ };
97
+ if (existing.envelope.embeddingRef !== undefined) {
98
+ view.embeddingRef = existing.envelope.embeddingRef;
99
+ }
100
+ // Restrict the incoming patch to the declared mutable fields; out-of-scope
101
+ // keys are never applied (the store enforces the same constraint).
102
+ const scopedPatch = {};
103
+ for (const field of this.mutableFields) {
104
+ if (field in patch) {
105
+ scopedPatch[field] = patch[field];
106
+ }
107
+ }
108
+ // Clone the view (no null-deletion), then apply the RFC-7386 merge patch
109
+ // onto the clone so the persisted record is never mutated in place.
110
+ return this._cloneEditor
111
+ .mergeObjectInPlace({}, view)
112
+ .onSuccess((clone) => this._mergeEditor.mergeObjectInPlace(clone, scopedPatch))
113
+ .onSuccess((merged) => this._rebuild(existing, merged));
114
+ }
115
+ /**
116
+ * Reassemble a record from the merged mutable view. `body` / `tags` /
117
+ * `links` / `provenance` are required and may not be deleted by a patch.
118
+ * `embeddingRef` is optional: when it is absent from the merged view —
119
+ * because the existing record never carried it OR a `null` patch deleted it
120
+ * (RFC-7386) — it is restored as `undefined` (absent), NOT `null`. This keeps
121
+ * an originally-absent `embeddingRef` from silently flipping to `null` on an
122
+ * unrelated update, so the field round-trips hash-stably through the store's
123
+ * content-hash recomputation.
124
+ */
125
+ _rebuild(existing, merged) {
126
+ const required = ['body', 'tags', 'links', 'provenance'];
127
+ const missing = required.filter((field) => !(field in merged));
128
+ if (missing.length > 0) {
129
+ return (0, ts_utils_1.fail)(`knowledge LWW: merge patch may not delete required field(s): ${missing.join(', ')}`);
130
+ }
131
+ // The merged values are JSON projections of the already-validated typed
132
+ // record; restore the domain types. (The types packlet cannot import the
133
+ // converters packlet without a cycle, so these are structural restorations
134
+ // of fields the merge preserved, not fresh untrusted input.)
135
+ const envelope = Object.assign(Object.assign({}, existing.envelope), { tags: merged.tags, links: merged.links, provenance: merged.provenance, embeddingRef: 'embeddingRef' in merged ? merged.embeddingRef : undefined });
136
+ return (0, ts_utils_1.succeed)({ envelope, body: merged.body });
137
+ }
138
+ }
139
+ exports.KnowledgeLwwPolicy = KnowledgeLwwPolicy;
140
+ /**
141
+ * The record-level fields a {@link MemoryCapCullPolicy} merge-patch may project,
142
+ * mapped to their canonical location on a record. Mirrors the store's
143
+ * mutable-field accessor vocabulary; a declared mutable field outside this set
144
+ * is inert (the store cannot project it either).
145
+ */
146
+ const CAP_CULL_FIELD_READERS = new Map([
147
+ ['body', (r) => r.body],
148
+ ['tags', (r) => r.envelope.tags],
149
+ ['links', (r) => r.envelope.links],
150
+ ['provenance', (r) => r.envelope.provenance],
151
+ ['embeddingRef', (r) => r.envelope.embeddingRef]
152
+ ]);
153
+ /** The record-level mutable fields that may never be deleted by a merge patch. */
154
+ const CAP_CULL_REQUIRED_FIELDS = new Set([
155
+ 'body',
156
+ 'tags',
157
+ 'links',
158
+ 'provenance'
159
+ ]);
160
+ /**
161
+ * Bounded-ring write policy for the experience (memory) kind families.
162
+ * Admission accepts until `maxRecords` is reached, then evicts the oldest
163
+ * record(s) by `created` ascending (design-lock §5.3); updates apply the same
164
+ * RFC-7386 merge patch as {@link KnowledgeLwwPolicy}, restricted to the declared
165
+ * {@link IMemoryCapCullPolicyParams.mutableFields | mutableFields}.
166
+ *
167
+ * @remarks
168
+ * - **Dedup scope.** Declares `dedupScope: 'entity'` — two distinct memory
169
+ * entities (e.g. `turn-5` / `turn-9`) with identical `{ kind, body, links }`
170
+ * never collapse; only an identical re-put of the SAME entity is a no-op.
171
+ * - **Eviction boundary.** `admit` only DECIDES (returns the `MemoryId`s to
172
+ * evict); the store executes the file deletions and index patches. The
173
+ * `existing` cohort the cap counts against is whatever the store supplies to
174
+ * `admit`.
175
+ * @public
176
+ */
177
+ class MemoryCapCullPolicy {
178
+ constructor(params, cloneEditor, mergeEditor) {
179
+ /** Experience kinds dedup per-entity (see the class remarks). */
180
+ this.dedupScope = 'entity';
181
+ this.mutableFields = params.mutableFields;
182
+ this._maxRecords = params.maxRecords;
183
+ this._cloneEditor = cloneEditor;
184
+ this._mergeEditor = mergeEditor;
185
+ }
186
+ /**
187
+ * Family-convention factory. Constructs the shared `JsonEditor` instances
188
+ * (one for cloning, one for the RFC-7386 merge) with the same merge config as
189
+ * {@link KnowledgeLwwPolicy} (`nullAsDelete` true, `arrayMergeBehavior`
190
+ * `'replace'`, rules disabled).
191
+ */
192
+ static create(params) {
193
+ return ts_json_1.JsonEditor.create({}, []).onSuccess((cloneEditor) => ts_json_1.JsonEditor.create(MERGE_PATCH_OPTIONS, []).onSuccess((mergeEditor) => (0, ts_utils_1.succeed)(new MemoryCapCullPolicy(params, cloneEditor, mergeEditor))));
194
+ }
195
+ /** {@inheritDoc IWritePolicy.admit} */
196
+ admit(__incoming, existing) {
197
+ if (this._maxRecords === undefined || existing.length < this._maxRecords) {
198
+ return (0, ts_utils_1.succeed)({ decision: 'accept' });
199
+ }
200
+ // Cap reached: evict the oldest by `created` ascending so the post-write
201
+ // count is exactly `maxRecords` (existing.length - maxRecords + 1 victims).
202
+ const evict = [...existing]
203
+ .sort((a, b) => a.envelope.created - b.envelope.created)
204
+ .slice(0, existing.length - this._maxRecords + 1)
205
+ .map((record) => record.envelope.id);
206
+ return (0, ts_utils_1.succeed)({ decision: 'cull-oldest', evict });
207
+ }
208
+ /** {@inheritDoc IWritePolicy.applyUpdate} */
209
+ applyUpdate(existing, patch) {
210
+ // Project the declared mutable fields (restricted to the known record-level
211
+ // vocabulary) into a single record-level view from their canonical
212
+ // locations. An `undefined` value is omitted (the editor rejects undefined
213
+ // property values).
214
+ const view = {};
215
+ for (const field of this.mutableFields) {
216
+ const reader = CAP_CULL_FIELD_READERS.get(field);
217
+ if (reader !== undefined) {
218
+ const value = reader(existing);
219
+ if (value !== undefined) {
220
+ view[field] = value;
221
+ }
222
+ }
223
+ }
224
+ // Restrict the incoming patch to the declared mutable fields.
225
+ const scopedPatch = {};
226
+ for (const field of this.mutableFields) {
227
+ if (field in patch) {
228
+ scopedPatch[field] = patch[field];
229
+ }
230
+ }
231
+ // Clone the view (no null-deletion), then apply the RFC-7386 merge patch
232
+ // onto the clone so the persisted record is never mutated in place.
233
+ return this._cloneEditor
234
+ .mergeObjectInPlace({}, view)
235
+ .onSuccess((clone) => this._mergeEditor.mergeObjectInPlace(clone, scopedPatch))
236
+ .onSuccess((merged) => this._rebuild(existing, merged));
237
+ }
238
+ /**
239
+ * Reassemble a record from the merged mutable view. Only the declared mutable
240
+ * fields are taken from the merge; undeclared fields are preserved verbatim
241
+ * from `existing`. A `null` patch that deletes a *declared mutable* required
242
+ * field (`body` / `tags` / `links` / `provenance`) is an error — a required
243
+ * field that is NOT declared mutable simply falls through to its `existing.*`
244
+ * value and is never at risk. `embeddingRef`, when mutable, is restored as
245
+ * `undefined` (absent) if the merge dropped it — same hash-stable semantics as
246
+ * {@link KnowledgeLwwPolicy}.
247
+ */
248
+ _rebuild(existing, merged) {
249
+ const deleted = this.mutableFields.filter((field) => CAP_CULL_REQUIRED_FIELDS.has(field) && !(field in merged));
250
+ if (deleted.length > 0) {
251
+ return (0, ts_utils_1.fail)(`memory cap-cull: merge patch may not delete required field(s): ${deleted.join(', ')}`);
252
+ }
253
+ // The merged values are JSON projections of the already-validated typed
254
+ // record; restore the domain types. (The types packlet cannot import the
255
+ // converters packlet without a cycle, so these are structural restorations
256
+ // of fields the merge preserved, not fresh untrusted input — mirrors
257
+ // KnowledgeLwwPolicy._rebuild.)
258
+ const embeddingRefMutable = this.mutableFields.includes('embeddingRef');
259
+ const envelope = Object.assign(Object.assign({}, existing.envelope), { tags: 'tags' in merged ? merged.tags : existing.envelope.tags, links: 'links' in merged ? merged.links : existing.envelope.links, provenance: 'provenance' in merged ? merged.provenance : existing.envelope.provenance, embeddingRef: embeddingRefMutable
260
+ ? 'embeddingRef' in merged
261
+ ? merged.embeddingRef
262
+ : undefined
263
+ : existing.envelope.embeddingRef });
264
+ return (0, ts_utils_1.succeed)({ envelope, body: 'body' in merged ? merged.body : existing.body });
265
+ }
266
+ }
267
+ exports.MemoryCapCullPolicy = MemoryCapCullPolicy;
268
+ //# sourceMappingURL=writePolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writePolicy.js","sourceRoot":"","sources":["../../../src/packlets/types/writePolicy.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 { JsonObject } from '@fgv/ts-json-base';\nimport { IJsonEditorOptions, JsonEditor } from '@fgv/ts-json';\nimport { IEdge, IMemoryEnvelope, IMemoryRecord, IProvenance } from './envelope';\nimport { MemoryId, Tag } from './ids';\n\n/**\n * The granularity at which the store deduplicates an incoming write against the\n * existing vault, declared per kind via {@link IWritePolicy.dedupScope}.\n *\n * - `'content'`: scope-wide cross-id content dedup. An identical\n * `{ kind, body, links }` triple ANYWHERE in the scope — even under a\n * different id — is a no-op that returns the existing record. The knowledge\n * kind family uses this.\n * - `'entity'`: same-id content dedup only. An identical re-put of the SAME\n * entity is a no-op, but two DISTINCT entities with identical content never\n * collapse. The experience (memory) kind families use this so that, e.g.,\n * two turns whose summaries happen to be byte-identical both persist.\n * @public\n */\nexport type DedupScope = 'content' | 'entity';\n\n/**\n * The default {@link DedupScope} applied when a policy does not declare one.\n * Entity-scoped dedup is the safe default — it never silently collapses two\n * distinct entities with coincidentally-identical content.\n * @public\n */\nexport const DEFAULT_DEDUP_SCOPE: DedupScope = 'entity';\n\n/**\n * The admission decision returned by {@link IWritePolicy.admit}.\n * @public\n */\nexport type AdmissionDecision =\n | { readonly decision: 'accept' }\n | { readonly decision: 'reject'; readonly reason: string }\n | {\n readonly decision: 'cull-oldest';\n /** {@link MemoryId}s to evict before writing the incoming record. */\n readonly evict: ReadonlyArray<MemoryId>;\n };\n\n/**\n * A per-kind write policy, injected at store construction. Invoked AFTER\n * content-hash dedup (dedup is always pre-policy). The policy decides\n * admission and, on update, applies a JSON Merge Patch (RFC-7386) restricted\n * to the declared mutable fields.\n * @public\n */\nexport interface IWritePolicy {\n /**\n * Names of the fields a merge-patch update may touch. Fields outside this\n * list are immutable; a change to one constitutes a new entity (its\n * `contentHash` differs). Used by {@link IWritePolicy.applyUpdate} to\n * restrict which fields the patch applies.\n */\n readonly mutableFields: ReadonlyArray<string>;\n\n /**\n * The granularity at which the store deduplicates an incoming write for this\n * kind. Optional; when absent the store applies {@link DEFAULT_DEDUP_SCOPE}\n * (`'entity'`). See {@link DedupScope}.\n */\n readonly dedupScope?: DedupScope;\n\n /**\n * Determine whether the incoming record is admitted.\n * @param incoming - The record about to be written.\n * @param existing - The admission cohort the kind's cap applies to: the\n * records in the same scope of the same kind, EXCLUDING the record at\n * `incoming`'s target id. Empty on a first write into an empty cohort.\n * Excluding the target id makes the post-write count uniform\n * (`existing.length + 1`) across first-writes and same-id updates. A\n * last-write-wins policy that has no cap ignores this argument.\n * @returns A {@link AdmissionDecision}.\n */\n admit(\n incoming: IMemoryRecord<unknown>,\n existing: ReadonlyArray<IMemoryRecord<unknown>>\n ): Result<AdmissionDecision>;\n\n /**\n * Apply a JSON Merge Patch (RFC-7386) to the mutable fields of an existing\n * record. Called when admission is `accept` AND a record with the same\n * `entityId` already exists (an update, not a first write).\n *\n * @param existing - The current persisted record.\n * @param patch - A partial JSON object in Merge Patch format. `null`\n * deletes the corresponding key; arrays replace wholesale; nested objects\n * deep-merge. Only keys in {@link IWritePolicy.mutableFields} are applied.\n * @returns The updated record (envelope + body).\n */\n applyUpdate(\n existing: IMemoryRecord<unknown>,\n patch: Record<string, unknown>\n ): Result<IMemoryRecord<unknown>>;\n}\n\n/**\n * RFC-7386-compliant merge options for {@link IWritePolicy.applyUpdate}.\n *\n * @remarks\n * Phase-A verification (design-lock §5.1) confirmed `@fgv/ts-json`'s\n * `JsonEditor` diverges from RFC-7386 under its defaults on two axes —\n * `nullAsDelete` defaults `false` and `arrayMergeBehavior` defaults\n * `'append'`. Both are corrected here via the existing option surface, so the\n * primitive is composed (not extended, not hand-rolled).\n */\nconst MERGE_PATCH_OPTIONS: Partial<IJsonEditorOptions> = {\n merge: {\n nullAsDelete: true,\n arrayMergeBehavior: 'replace'\n }\n};\n\n/**\n * Last-write-wins write policy for the knowledge kind family. Admission\n * always accepts (no cap, no cull); updates apply an RFC-7386 merge patch\n * restricted to the knowledge mutable surface.\n *\n * @remarks\n * **Merge-surface pin (resolves design-lock §5.3's body-vs-envelope muddle).**\n * The declared `mutableFields` span both axes of a record: `body` is the\n * per-kind body, while `tags` / `links` / `provenance` / `embeddingRef` live\n * on the {@link IMemoryEnvelope}. `applyUpdate` projects exactly those fields\n * — each read from its canonical location — into a single record-level JSON\n * view, runs the merge over that view, then rebuilds a coherent record. The\n * identity and transaction-time envelope fields (`id`, `entityId`, `kind`,\n * `created`, `updated`, `seq`, `contentHash`) are NOT mutable and are\n * preserved verbatim; the store stamps `updated` / `seq` on write.\n * @public\n */\nexport class KnowledgeLwwPolicy implements IWritePolicy {\n /**\n * The knowledge mutable surface: the body plus the envelope metadata a\n * consumer may revise without minting a new entity.\n */\n public readonly mutableFields: ReadonlyArray<string> = [\n 'body',\n 'tags',\n 'links',\n 'provenance',\n 'embeddingRef'\n ];\n\n /**\n * Knowledge dedups scope-wide: an identical `{ kind, body, links }` triple\n * anywhere in the `knowledge` scope — even under a different `docId` — is a\n * no-op. Declared explicitly so the B1 content-dedup behavior (and its tests)\n * are unchanged by the {@link DedupScope} amendment.\n */\n public readonly dedupScope: DedupScope = 'content';\n\n /** Deep-clones the mutable view without RFC-7386 null-deletion semantics. */\n private readonly _cloneEditor: JsonEditor;\n /** Applies the RFC-7386 merge patch. */\n private readonly _mergeEditor: JsonEditor;\n\n private constructor(cloneEditor: JsonEditor, mergeEditor: JsonEditor) {\n this._cloneEditor = cloneEditor;\n this._mergeEditor = mergeEditor;\n }\n\n /**\n * Family-convention factory. Constructs the shared `JsonEditor` instances\n * (one for cloning, one for the RFC-7386 merge), both with the template /\n * conditional / multivalue / reference rules disabled (empty rules array).\n */\n public static create(): Result<KnowledgeLwwPolicy> {\n return JsonEditor.create({}, []).onSuccess((cloneEditor) =>\n JsonEditor.create(MERGE_PATCH_OPTIONS, []).onSuccess((mergeEditor) =>\n succeed(new KnowledgeLwwPolicy(cloneEditor, mergeEditor))\n )\n );\n }\n\n /** {@inheritDoc IWritePolicy.admit} */\n public admit(\n __incoming: IMemoryRecord<unknown>,\n __existing: ReadonlyArray<IMemoryRecord<unknown>>\n ): Result<AdmissionDecision> {\n // Last-write-wins: always accept. No cap, no cull.\n return succeed({ decision: 'accept' });\n }\n\n /** {@inheritDoc IWritePolicy.applyUpdate} */\n public applyUpdate(\n existing: IMemoryRecord<unknown>,\n patch: Record<string, unknown>\n ): Result<IMemoryRecord<unknown>> {\n // Project the mutable fields into a single record-level view, each sourced\n // from its canonical location. `embeddingRef` is omitted when `undefined`\n // (the editor rejects undefined property values).\n const view: Record<string, unknown> = {\n body: existing.body,\n tags: existing.envelope.tags,\n links: existing.envelope.links,\n provenance: existing.envelope.provenance\n };\n if (existing.envelope.embeddingRef !== undefined) {\n view.embeddingRef = existing.envelope.embeddingRef;\n }\n\n // Restrict the incoming patch to the declared mutable fields; out-of-scope\n // keys are never applied (the store enforces the same constraint).\n const scopedPatch: Record<string, unknown> = {};\n for (const field of this.mutableFields) {\n if (field in patch) {\n scopedPatch[field] = patch[field];\n }\n }\n\n // Clone the view (no null-deletion), then apply the RFC-7386 merge patch\n // onto the clone so the persisted record is never mutated in place.\n return this._cloneEditor\n .mergeObjectInPlace({}, view as JsonObject)\n .onSuccess((clone) => this._mergeEditor.mergeObjectInPlace(clone, scopedPatch as JsonObject))\n .onSuccess((merged) => this._rebuild(existing, merged));\n }\n\n /**\n * Reassemble a record from the merged mutable view. `body` / `tags` /\n * `links` / `provenance` are required and may not be deleted by a patch.\n * `embeddingRef` is optional: when it is absent from the merged view —\n * because the existing record never carried it OR a `null` patch deleted it\n * (RFC-7386) — it is restored as `undefined` (absent), NOT `null`. This keeps\n * an originally-absent `embeddingRef` from silently flipping to `null` on an\n * unrelated update, so the field round-trips hash-stably through the store's\n * content-hash recomputation.\n */\n private _rebuild(existing: IMemoryRecord<unknown>, merged: JsonObject): Result<IMemoryRecord<unknown>> {\n const required: ReadonlyArray<string> = ['body', 'tags', 'links', 'provenance'];\n const missing: ReadonlyArray<string> = required.filter((field) => !(field in merged));\n if (missing.length > 0) {\n return fail(`knowledge LWW: merge patch may not delete required field(s): ${missing.join(', ')}`);\n }\n\n // The merged values are JSON projections of the already-validated typed\n // record; restore the domain types. (The types packlet cannot import the\n // converters packlet without a cycle, so these are structural restorations\n // of fields the merge preserved, not fresh untrusted input.)\n const envelope: IMemoryEnvelope = {\n ...existing.envelope,\n tags: merged.tags as unknown as ReadonlyArray<Tag>,\n links: merged.links as unknown as ReadonlyArray<IEdge>,\n provenance: merged.provenance as unknown as IProvenance,\n embeddingRef: 'embeddingRef' in merged ? (merged.embeddingRef as string | null) : undefined\n };\n return succeed({ envelope, body: merged.body });\n }\n}\n\n/**\n * Parameters for {@link MemoryCapCullPolicy.create}.\n * @public\n */\nexport interface IMemoryCapCullPolicyParams {\n /**\n * Maximum number of records the policy admits before culling the oldest.\n * Counted over the `existing` cohort passed to {@link IWritePolicy.admit}.\n * Absent = no cap (admission always accepts).\n */\n readonly maxRecords?: number;\n /**\n * The fields a merge-patch update may touch (drawn from the record-level\n * mutable vocabulary: `body` / `tags` / `links` / `provenance` /\n * `embeddingRef`). Fields outside this list are immutable.\n */\n readonly mutableFields: ReadonlyArray<string>;\n}\n\n/**\n * The record-level fields a {@link MemoryCapCullPolicy} merge-patch may project,\n * mapped to their canonical location on a record. Mirrors the store's\n * mutable-field accessor vocabulary; a declared mutable field outside this set\n * is inert (the store cannot project it either).\n */\nconst CAP_CULL_FIELD_READERS: ReadonlyMap<string, (record: IMemoryRecord<unknown>) => unknown> = new Map<\n string,\n (record: IMemoryRecord<unknown>) => unknown\n>([\n ['body', (r) => r.body],\n ['tags', (r) => r.envelope.tags],\n ['links', (r) => r.envelope.links],\n ['provenance', (r) => r.envelope.provenance],\n ['embeddingRef', (r) => r.envelope.embeddingRef]\n]);\n\n/** The record-level mutable fields that may never be deleted by a merge patch. */\nconst CAP_CULL_REQUIRED_FIELDS: ReadonlySet<string> = new Set<string>([\n 'body',\n 'tags',\n 'links',\n 'provenance'\n]);\n\n/**\n * Bounded-ring write policy for the experience (memory) kind families.\n * Admission accepts until `maxRecords` is reached, then evicts the oldest\n * record(s) by `created` ascending (design-lock §5.3); updates apply the same\n * RFC-7386 merge patch as {@link KnowledgeLwwPolicy}, restricted to the declared\n * {@link IMemoryCapCullPolicyParams.mutableFields | mutableFields}.\n *\n * @remarks\n * - **Dedup scope.** Declares `dedupScope: 'entity'` — two distinct memory\n * entities (e.g. `turn-5` / `turn-9`) with identical `{ kind, body, links }`\n * never collapse; only an identical re-put of the SAME entity is a no-op.\n * - **Eviction boundary.** `admit` only DECIDES (returns the `MemoryId`s to\n * evict); the store executes the file deletions and index patches. The\n * `existing` cohort the cap counts against is whatever the store supplies to\n * `admit`.\n * @public\n */\nexport class MemoryCapCullPolicy implements IWritePolicy {\n /** {@inheritDoc IWritePolicy.mutableFields} */\n public readonly mutableFields: ReadonlyArray<string>;\n\n /** Experience kinds dedup per-entity (see the class remarks). */\n public readonly dedupScope: DedupScope = 'entity';\n\n /** The admission cap; `undefined` = no cap. */\n private readonly _maxRecords: number | undefined;\n /** Deep-clones the mutable view without RFC-7386 null-deletion semantics. */\n private readonly _cloneEditor: JsonEditor;\n /** Applies the RFC-7386 merge patch. */\n private readonly _mergeEditor: JsonEditor;\n\n private constructor(params: IMemoryCapCullPolicyParams, cloneEditor: JsonEditor, mergeEditor: JsonEditor) {\n this.mutableFields = params.mutableFields;\n this._maxRecords = params.maxRecords;\n this._cloneEditor = cloneEditor;\n this._mergeEditor = mergeEditor;\n }\n\n /**\n * Family-convention factory. Constructs the shared `JsonEditor` instances\n * (one for cloning, one for the RFC-7386 merge) with the same merge config as\n * {@link KnowledgeLwwPolicy} (`nullAsDelete` true, `arrayMergeBehavior`\n * `'replace'`, rules disabled).\n */\n public static create(params: IMemoryCapCullPolicyParams): Result<MemoryCapCullPolicy> {\n return JsonEditor.create({}, []).onSuccess((cloneEditor) =>\n JsonEditor.create(MERGE_PATCH_OPTIONS, []).onSuccess((mergeEditor) =>\n succeed(new MemoryCapCullPolicy(params, cloneEditor, mergeEditor))\n )\n );\n }\n\n /** {@inheritDoc IWritePolicy.admit} */\n public admit(\n __incoming: IMemoryRecord<unknown>,\n existing: ReadonlyArray<IMemoryRecord<unknown>>\n ): Result<AdmissionDecision> {\n if (this._maxRecords === undefined || existing.length < this._maxRecords) {\n return succeed({ decision: 'accept' });\n }\n // Cap reached: evict the oldest by `created` ascending so the post-write\n // count is exactly `maxRecords` (existing.length - maxRecords + 1 victims).\n const evict: ReadonlyArray<MemoryId> = [...existing]\n .sort((a, b) => a.envelope.created - b.envelope.created)\n .slice(0, existing.length - this._maxRecords + 1)\n .map((record) => record.envelope.id);\n return succeed({ decision: 'cull-oldest', evict });\n }\n\n /** {@inheritDoc IWritePolicy.applyUpdate} */\n public applyUpdate(\n existing: IMemoryRecord<unknown>,\n patch: Record<string, unknown>\n ): Result<IMemoryRecord<unknown>> {\n // Project the declared mutable fields (restricted to the known record-level\n // vocabulary) into a single record-level view from their canonical\n // locations. An `undefined` value is omitted (the editor rejects undefined\n // property values).\n const view: Record<string, unknown> = {};\n for (const field of this.mutableFields) {\n const reader: ((record: IMemoryRecord<unknown>) => unknown) | undefined =\n CAP_CULL_FIELD_READERS.get(field);\n if (reader !== undefined) {\n const value: unknown = reader(existing);\n if (value !== undefined) {\n view[field] = value;\n }\n }\n }\n\n // Restrict the incoming patch to the declared mutable fields.\n const scopedPatch: Record<string, unknown> = {};\n for (const field of this.mutableFields) {\n if (field in patch) {\n scopedPatch[field] = patch[field];\n }\n }\n\n // Clone the view (no null-deletion), then apply the RFC-7386 merge patch\n // onto the clone so the persisted record is never mutated in place.\n return this._cloneEditor\n .mergeObjectInPlace({}, view as JsonObject)\n .onSuccess((clone) => this._mergeEditor.mergeObjectInPlace(clone, scopedPatch as JsonObject))\n .onSuccess((merged) => this._rebuild(existing, merged));\n }\n\n /**\n * Reassemble a record from the merged mutable view. Only the declared mutable\n * fields are taken from the merge; undeclared fields are preserved verbatim\n * from `existing`. A `null` patch that deletes a *declared mutable* required\n * field (`body` / `tags` / `links` / `provenance`) is an error — a required\n * field that is NOT declared mutable simply falls through to its `existing.*`\n * value and is never at risk. `embeddingRef`, when mutable, is restored as\n * `undefined` (absent) if the merge dropped it — same hash-stable semantics as\n * {@link KnowledgeLwwPolicy}.\n */\n private _rebuild(existing: IMemoryRecord<unknown>, merged: JsonObject): Result<IMemoryRecord<unknown>> {\n const deleted: ReadonlyArray<string> = this.mutableFields.filter(\n (field) => CAP_CULL_REQUIRED_FIELDS.has(field) && !(field in merged)\n );\n if (deleted.length > 0) {\n return fail(`memory cap-cull: merge patch may not delete required field(s): ${deleted.join(', ')}`);\n }\n\n // The merged values are JSON projections of the already-validated typed\n // record; restore the domain types. (The types packlet cannot import the\n // converters packlet without a cycle, so these are structural restorations\n // of fields the merge preserved, not fresh untrusted input — mirrors\n // KnowledgeLwwPolicy._rebuild.)\n const embeddingRefMutable: boolean = this.mutableFields.includes('embeddingRef');\n const envelope: IMemoryEnvelope = {\n ...existing.envelope,\n tags: 'tags' in merged ? (merged.tags as unknown as ReadonlyArray<Tag>) : existing.envelope.tags,\n links: 'links' in merged ? (merged.links as unknown as ReadonlyArray<IEdge>) : existing.envelope.links,\n provenance:\n 'provenance' in merged ? (merged.provenance as unknown as IProvenance) : existing.envelope.provenance,\n embeddingRef: embeddingRefMutable\n ? 'embeddingRef' in merged\n ? (merged.embeddingRef as string | null)\n : undefined\n : existing.envelope.embeddingRef\n };\n return succeed({ envelope, body: 'body' in merged ? merged.body : existing.body });\n }\n}\n"]}
@@ -0,0 +1,69 @@
1
+ import { Result } from '@fgv/ts-utils';
2
+ import { MemoryId } from '../types';
3
+ import { IMemoryRecordSource, IVectorIndex, IVectorQueryHit, MemoryEmbedder } from './vectorIndex';
4
+ /**
5
+ * The brute-force, in-memory cosine {@link IVectorIndex}. Stores one
6
+ * `Float32Array` per record and answers a query by computing cosine similarity
7
+ * against every stored vector, returning the top-k by descending score.
8
+ *
9
+ * @remarks
10
+ * This is the **complete** vector implementation for the fgv regime — large-N is
11
+ * explicitly out of scope (the seam stays open for a consumer to swap an external
12
+ * ANN backend once N grows beyond "thousands of records"). No external dependency
13
+ * and no ANN structure: a linear scan over a few thousand vectors is well within
14
+ * an interactive budget.
15
+ *
16
+ * The index has a single dimension established by the first vector added; every
17
+ * subsequent `add` and every `query` vector must match that dimension or fail
18
+ * loudly — a mismatched dimension is an embedder-wiring bug, never a silent
19
+ * zero-similarity result. {@link InMemoryCosineIndex.rebuild | rebuild} clears
20
+ * the index, so a re-embed with a different model (hence dimension) is supported.
21
+ *
22
+ * Persistence (a JSON sidecar) is deliberately out of scope for this layer — the
23
+ * index is in-memory and rebuilt from the store via `rebuild`; a sidecar is a
24
+ * future nicety.
25
+ * @public
26
+ */
27
+ export declare class InMemoryCosineIndex implements IVectorIndex {
28
+ private readonly _vectors;
29
+ /** The dimension of every stored vector; `undefined` until the first `add`. */
30
+ private _dimension;
31
+ private constructor();
32
+ /** The number of vectors currently held. */
33
+ get size(): number;
34
+ /** Family-convention factory. */
35
+ static create(): Result<InMemoryCosineIndex>;
36
+ /** {@inheritDoc IVectorIndex.add} */
37
+ add(id: MemoryId, vector: Float32Array): Promise<Result<string>>;
38
+ /** {@inheritDoc IVectorIndex.remove} */
39
+ remove(id: MemoryId): Promise<Result<MemoryId>>;
40
+ /** {@inheritDoc IVectorIndex.query} */
41
+ query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;
42
+ /**
43
+ * Re-embed every record from `source` and rebuild the index from scratch.
44
+ * Clears the current contents (and the established dimension) first, so a
45
+ * re-embed with a different model is supported. Returns the number of vectors
46
+ * indexed.
47
+ *
48
+ * On any failure (list, embed, or add) the index is rolled back to empty
49
+ * rather than left in a partially-rebuilt state — a caller that retries a query
50
+ * after a failed rebuild sees a clean empty index, never a half-populated one.
51
+ *
52
+ * @param source - The record source to re-embed (an {@link IMemoryStore}
53
+ * satisfies this structurally).
54
+ * @param embed - The embedder applied to each record.
55
+ */
56
+ rebuild(source: IMemoryRecordSource, embed: MemoryEmbedder): Promise<Result<number>>;
57
+ /** Empty the index and forget the established dimension. */
58
+ private _reset;
59
+ /** The Euclidean magnitude (L2 norm) of a vector. */
60
+ private static _magnitude;
61
+ /**
62
+ * Cosine similarity between the query (whose magnitude is precomputed once and
63
+ * reused across the scan) and a stored vector. A zero-magnitude vector on
64
+ * either side yields `0` rather than `NaN` — a degenerate vector is simply
65
+ * maximally dissimilar, not an error.
66
+ */
67
+ private static _cosine;
68
+ }
69
+ //# sourceMappingURL=inMemoryCosineIndex.d.ts.map
@@ -0,0 +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,EAAiB,QAAQ,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEnG;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IACvD,+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,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAoBvE,wCAAwC;IACjC,MAAM,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAKtD,uCAAuC;IAChC,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC;IAsBjG;;;;;;;;;;;;;OAaG;IACU,OAAO,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAwBjG,4DAA4D;IAC5D,OAAO,CAAC,MAAM;IAKd,qDAAqD;IACrD,OAAO,CAAC,MAAM,CAAC,UAAU;IAQzB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,OAAO;CAWvB"}