@neuralis/brain-core 0.1.0 → 0.2.0

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 (383) hide show
  1. package/README.md +26 -35
  2. package/app/cards/DiffView.tsx +9 -7
  3. package/app/cards/README.md +1 -1
  4. package/app/cards/diffLine.tsx +2 -4
  5. package/app/filesystem/README.md +1 -1
  6. package/app/filesystem/api/index.ts +14 -5
  7. package/app/filesystem/domain/types.ts +24 -5
  8. package/app/filesystem/store/filesystemStore.ts +20 -1
  9. package/app/filesystem/store/listSlice.ts +2 -0
  10. package/app/filesystem/store/searchSlice.ts +24 -1
  11. package/app/filesystem/ui/FileTreeSearch.tsx +29 -2
  12. package/app/filesystem/ui/PendingChangesPanel.tsx +74 -17
  13. package/app/filesystem/ui/PendingReviewDialog.tsx +45 -20
  14. package/app/filesystem/ui/README.md +8 -7
  15. package/app/filesystem/ui/ReembedDialog.tsx +20 -9
  16. package/app/filesystem/ui/SourceDeleteConfirm.tsx +1 -1
  17. package/app/filesystem/ui/SourcesPanel.tsx +110 -17
  18. package/app/filesystem/ui/VectorHealthPanel.tsx +14 -0
  19. package/app/filesystem/ui/pendingChangesModel.ts +9 -5
  20. package/dist/app/chunks/{astro-LRAFGXNX.js → astro-B2J4AS2D.js} +1 -1
  21. package/dist/app/chunks/bash-KY6SAQTH.js +1 -0
  22. package/dist/app/chunks/c-OBOPYVZ7.js +1 -0
  23. package/dist/app/chunks/{chunk-5H7ZCOD3.js → chunk-2X3GNKJ3.js} +1 -1
  24. package/dist/app/chunks/{chunk-XIFGTNRM.js → chunk-JPJIVRFI.js} +1 -1
  25. package/dist/app/chunks/{chunk-RH3QY5LF.js → chunk-LEFIFWND.js} +1 -1
  26. package/dist/app/chunks/{chunk-G4RZ3UNX.js → chunk-N6RXX3PH.js} +1 -1
  27. package/dist/app/chunks/{chunk-QXXJ73P5.js → chunk-PZAW4HPB.js} +1 -1
  28. package/dist/app/chunks/{chunk-3QLE7Y4L.js → chunk-ZBYWWTO6.js} +1 -1
  29. package/dist/app/chunks/cpp-TVWVOTEQ.js +1 -0
  30. package/dist/app/chunks/css-MANZBYA4.js +1 -0
  31. package/dist/app/chunks/{elixir-M36XROA6.js → elixir-S725Z7EH.js} +1 -1
  32. package/dist/app/chunks/{elm-7BG6QAIK.js → elm-5WUUSTNB.js} +1 -1
  33. package/dist/app/chunks/graphql-UGOPR3WU.js +1 -0
  34. package/dist/app/chunks/html-F6FWTBSG.js +1 -0
  35. package/dist/app/chunks/java-ANIVV5H5.js +1 -0
  36. package/dist/app/chunks/javascript-IH374YWG.js +1 -0
  37. package/dist/app/chunks/json-WGGVVXSB.js +1 -0
  38. package/dist/app/chunks/jsx-OTXJQD32.js +1 -0
  39. package/dist/app/chunks/{latex-ZWK7SXB6.js → latex-QCZNITGN.js} +1 -1
  40. package/dist/app/chunks/lua-4CRZ64PU.js +1 -0
  41. package/dist/app/chunks/{nginx-2L6VRV3A.js → nginx-V4CYS7WR.js} +1 -1
  42. package/dist/app/chunks/{perl-PBUAXXK6.js → perl-BHTX7F5B.js} +1 -1
  43. package/dist/app/chunks/{php-QJX57PA6.js → php-AYGJ5WYZ.js} +1 -1
  44. package/dist/app/chunks/r-4YFCFA4B.js +1 -0
  45. package/dist/app/chunks/{ruby-RLOWMVPO.js → ruby-42NMJWAL.js} +1 -1
  46. package/dist/app/chunks/{scss-B5CAI3HR.js → scss-XD7VU2CU.js} +1 -1
  47. package/dist/app/chunks/sql-B55MH6JJ.js +1 -0
  48. package/dist/app/chunks/{svelte-GT3D4C44.js → svelte-WR3QPIJU.js} +1 -1
  49. package/dist/app/chunks/tsx-S73GAE2L.js +1 -0
  50. package/dist/app/chunks/typescript-NEWOAB5Z.js +1 -0
  51. package/dist/app/chunks/{vue-JOW62MHD.js → vue-CCDS4VZK.js} +1 -1
  52. package/dist/app/chunks/xml-UXKAGLDR.js +1 -0
  53. package/dist/app/chunks/yaml-UMWKDTK7.js +1 -0
  54. package/dist/app/host.js +40 -40
  55. package/dist/scripts/embeddingLockV3.d.ts +30 -0
  56. package/dist/scripts/embeddingLockV3.d.ts.map +1 -0
  57. package/dist/scripts/embeddingLockV3.js +100 -0
  58. package/dist/scripts/embeddingLockV3.js.map +1 -0
  59. package/dist/scripts/vectorProofV1.d.ts +90 -0
  60. package/dist/scripts/vectorProofV1.d.ts.map +1 -0
  61. package/dist/scripts/vectorProofV1.js +220 -0
  62. package/dist/scripts/vectorProofV1.js.map +1 -0
  63. package/dist/src/config/env.d.ts +21 -0
  64. package/dist/src/config/env.d.ts.map +1 -1
  65. package/dist/src/config/env.js +55 -0
  66. package/dist/src/config/env.js.map +1 -1
  67. package/dist/src/connectors/AppSourceConfigStore.d.ts +12 -2
  68. package/dist/src/connectors/AppSourceConfigStore.d.ts.map +1 -1
  69. package/dist/src/connectors/AppSourceConfigStore.js +48 -40
  70. package/dist/src/connectors/AppSourceConfigStore.js.map +1 -1
  71. package/dist/src/connectors/LocalDiskConnector.d.ts +11 -14
  72. package/dist/src/connectors/LocalDiskConnector.d.ts.map +1 -1
  73. package/dist/src/connectors/LocalDiskConnector.js +87 -107
  74. package/dist/src/connectors/LocalDiskConnector.js.map +1 -1
  75. package/dist/src/connectors/sourceConfigMutation.d.ts +11 -1
  76. package/dist/src/connectors/sourceConfigMutation.d.ts.map +1 -1
  77. package/dist/src/connectors/sourceConfigMutation.js +36 -3
  78. package/dist/src/connectors/sourceConfigMutation.js.map +1 -1
  79. package/dist/src/domain/chunker.js +8 -3
  80. package/dist/src/domain/chunker.js.map +1 -1
  81. package/dist/src/domain/healthTypes.d.ts +11 -0
  82. package/dist/src/domain/healthTypes.d.ts.map +1 -1
  83. package/dist/src/domain/mime.d.ts +1 -0
  84. package/dist/src/domain/mime.d.ts.map +1 -1
  85. package/dist/src/domain/mime.js +48 -0
  86. package/dist/src/domain/mime.js.map +1 -1
  87. package/dist/src/domain/pendingChange.d.ts +7 -0
  88. package/dist/src/domain/pendingChange.d.ts.map +1 -1
  89. package/dist/src/domain/pendingChange.js +11 -0
  90. package/dist/src/domain/pendingChange.js.map +1 -1
  91. package/dist/src/domain/sourceConfig.d.ts +7 -14
  92. package/dist/src/domain/sourceConfig.d.ts.map +1 -1
  93. package/dist/src/domain/sourceConfig.js +1 -13
  94. package/dist/src/domain/sourceConfig.js.map +1 -1
  95. package/dist/src/domain/sourcePackages.d.ts +11 -2
  96. package/dist/src/domain/sourcePackages.d.ts.map +1 -1
  97. package/dist/src/domain/syncTrigger.d.ts +2 -3
  98. package/dist/src/domain/syncTrigger.d.ts.map +1 -1
  99. package/dist/src/domain/syncTrigger.js +2 -3
  100. package/dist/src/domain/syncTrigger.js.map +1 -1
  101. package/dist/src/domain/syncTypes.d.ts +21 -4
  102. package/dist/src/domain/syncTypes.d.ts.map +1 -1
  103. package/dist/src/domain/syncTypes.js.map +1 -1
  104. package/dist/src/domain/text.d.ts +3 -0
  105. package/dist/src/domain/text.d.ts.map +1 -1
  106. package/dist/src/domain/text.js +5 -0
  107. package/dist/src/domain/text.js.map +1 -1
  108. package/dist/src/domain/types.d.ts +9 -0
  109. package/dist/src/domain/types.d.ts.map +1 -1
  110. package/dist/src/embeddings/clients/GeminiEmbedder.d.ts.map +1 -1
  111. package/dist/src/embeddings/clients/GeminiEmbedder.js +11 -5
  112. package/dist/src/embeddings/clients/GeminiEmbedder.js.map +1 -1
  113. package/dist/src/embeddings/clients/OllamaEmbedder.d.ts.map +1 -1
  114. package/dist/src/embeddings/clients/OllamaEmbedder.js +13 -6
  115. package/dist/src/embeddings/clients/OllamaEmbedder.js.map +1 -1
  116. package/dist/src/embeddings/clients/OpenAIEmbedder.d.ts.map +1 -1
  117. package/dist/src/embeddings/clients/OpenAIEmbedder.js +12 -9
  118. package/dist/src/embeddings/clients/OpenAIEmbedder.js.map +1 -1
  119. package/dist/src/embeddings/clients/QwenEmbedder.d.ts.map +1 -1
  120. package/dist/src/embeddings/clients/QwenEmbedder.js +12 -5
  121. package/dist/src/embeddings/clients/QwenEmbedder.js.map +1 -1
  122. package/dist/src/embeddings/clients/VoyageEmbedder.d.ts.map +1 -1
  123. package/dist/src/embeddings/clients/VoyageEmbedder.js +12 -5
  124. package/dist/src/embeddings/clients/VoyageEmbedder.js.map +1 -1
  125. package/dist/src/embeddings/factory.d.ts +2 -0
  126. package/dist/src/embeddings/factory.d.ts.map +1 -1
  127. package/dist/src/embeddings/factory.js +23 -3
  128. package/dist/src/embeddings/factory.js.map +1 -1
  129. package/dist/src/embeddings/lock.d.ts +22 -11
  130. package/dist/src/embeddings/lock.d.ts.map +1 -1
  131. package/dist/src/embeddings/lock.js +47 -29
  132. package/dist/src/embeddings/lock.js.map +1 -1
  133. package/dist/src/embeddings/retry.d.ts +5 -0
  134. package/dist/src/embeddings/retry.d.ts.map +1 -1
  135. package/dist/src/embeddings/retry.js +35 -0
  136. package/dist/src/embeddings/retry.js.map +1 -1
  137. package/dist/src/events/pending.d.ts +56 -0
  138. package/dist/src/events/pending.d.ts.map +1 -0
  139. package/dist/src/events/pending.js +108 -0
  140. package/dist/src/events/pending.js.map +1 -0
  141. package/dist/src/events/source.d.ts +60 -14
  142. package/dist/src/events/source.d.ts.map +1 -1
  143. package/dist/src/events/source.js +149 -31
  144. package/dist/src/events/source.js.map +1 -1
  145. package/dist/src/events/vector.d.ts +33 -0
  146. package/dist/src/events/vector.d.ts.map +1 -0
  147. package/dist/src/events/vector.js +57 -0
  148. package/dist/src/events/vector.js.map +1 -0
  149. package/dist/src/git/GitRunner.d.ts.map +1 -1
  150. package/dist/src/git/GitRunner.js +11 -8
  151. package/dist/src/git/GitRunner.js.map +1 -1
  152. package/dist/src/git/sourceGitControl.d.ts.map +1 -1
  153. package/dist/src/git/sourceGitControl.js +2 -1
  154. package/dist/src/git/sourceGitControl.js.map +1 -1
  155. package/dist/src/index.d.ts +5 -4
  156. package/dist/src/index.d.ts.map +1 -1
  157. package/dist/src/index.js +4 -3
  158. package/dist/src/index.js.map +1 -1
  159. package/dist/src/lifecycle.d.ts +20 -4
  160. package/dist/src/lifecycle.d.ts.map +1 -1
  161. package/dist/src/lifecycle.js +5 -1
  162. package/dist/src/lifecycle.js.map +1 -1
  163. package/dist/src/routes/health.js +6 -1
  164. package/dist/src/routes/health.js.map +1 -1
  165. package/dist/src/routes/pending.d.ts.map +1 -1
  166. package/dist/src/routes/pending.js +2 -1
  167. package/dist/src/routes/pending.js.map +1 -1
  168. package/dist/src/routes/sources.d.ts.map +1 -1
  169. package/dist/src/routes/sources.js +60 -11
  170. package/dist/src/routes/sources.js.map +1 -1
  171. package/dist/src/services/BridgeService.d.ts +2 -2
  172. package/dist/src/services/BridgeService.d.ts.map +1 -1
  173. package/dist/src/services/BridgeService.js +11 -2
  174. package/dist/src/services/BridgeService.js.map +1 -1
  175. package/dist/src/services/FilesystemService.d.ts +3 -1
  176. package/dist/src/services/FilesystemService.d.ts.map +1 -1
  177. package/dist/src/services/FilesystemService.js +3 -1
  178. package/dist/src/services/FilesystemService.js.map +1 -1
  179. package/dist/src/services/FilesystemUiService.js +1 -1
  180. package/dist/src/services/FilesystemUiService.js.map +1 -1
  181. package/dist/src/services/SyncScheduler.d.ts.map +1 -1
  182. package/dist/src/services/SyncScheduler.js +0 -14
  183. package/dist/src/services/SyncScheduler.js.map +1 -1
  184. package/dist/src/services/VectorHealthService.d.ts.map +1 -1
  185. package/dist/src/services/VectorHealthService.js +2 -46
  186. package/dist/src/services/VectorHealthService.js.map +1 -1
  187. package/dist/src/services/baselineRepair.d.ts +2 -2
  188. package/dist/src/services/baselineRepair.js +2 -2
  189. package/dist/src/services/declaredSources.d.ts +8 -1
  190. package/dist/src/services/declaredSources.d.ts.map +1 -1
  191. package/dist/src/services/declaredSources.js +19 -19
  192. package/dist/src/services/declaredSources.js.map +1 -1
  193. package/dist/src/store/dataFormats.d.ts +14 -3
  194. package/dist/src/store/dataFormats.d.ts.map +1 -1
  195. package/dist/src/store/dataFormats.js +15 -5
  196. package/dist/src/store/dataFormats.js.map +1 -1
  197. package/dist/src/sync/DeltaSyncRunner.d.ts.map +1 -1
  198. package/dist/src/sync/DeltaSyncRunner.js +78 -243
  199. package/dist/src/sync/DeltaSyncRunner.js.map +1 -1
  200. package/dist/src/sync/SyncJobQueue.d.ts +33 -4
  201. package/dist/src/sync/SyncJobQueue.d.ts.map +1 -1
  202. package/dist/src/sync/SyncJobQueue.js +179 -19
  203. package/dist/src/sync/SyncJobQueue.js.map +1 -1
  204. package/dist/src/sync/moveDetector.d.ts +2 -1
  205. package/dist/src/sync/moveDetector.d.ts.map +1 -1
  206. package/dist/src/sync/moveDetector.js +2 -1
  207. package/dist/src/sync/moveDetector.js.map +1 -1
  208. package/dist/src/tools/fs_search.d.ts.map +1 -1
  209. package/dist/src/tools/fs_search.js +8 -0
  210. package/dist/src/tools/fs_search.js.map +1 -1
  211. package/dist/src/tools/fs_write.js +3 -1
  212. package/dist/src/tools/fs_write.js.map +1 -1
  213. package/dist/src/usecases/chunkUpsert.d.ts +4 -0
  214. package/dist/src/usecases/chunkUpsert.d.ts.map +1 -1
  215. package/dist/src/usecases/chunkUpsert.js +103 -60
  216. package/dist/src/usecases/chunkUpsert.js.map +1 -1
  217. package/dist/src/usecases/embeddingBacklog.d.ts +31 -0
  218. package/dist/src/usecases/embeddingBacklog.d.ts.map +1 -0
  219. package/dist/src/usecases/embeddingBacklog.js +244 -0
  220. package/dist/src/usecases/embeddingBacklog.js.map +1 -0
  221. package/dist/src/usecases/listSourcePackages.js +17 -1
  222. package/dist/src/usecases/listSourcePackages.js.map +1 -1
  223. package/dist/src/usecases/mediaLane.d.ts +11 -4
  224. package/dist/src/usecases/mediaLane.d.ts.map +1 -1
  225. package/dist/src/usecases/mediaLane.js +67 -30
  226. package/dist/src/usecases/mediaLane.js.map +1 -1
  227. package/dist/src/usecases/pendingReview.d.ts.map +1 -1
  228. package/dist/src/usecases/pendingReview.js +24 -7
  229. package/dist/src/usecases/pendingReview.js.map +1 -1
  230. package/dist/src/usecases/purgeExcludedPoints.d.ts +2 -0
  231. package/dist/src/usecases/purgeExcludedPoints.d.ts.map +1 -1
  232. package/dist/src/usecases/purgeExcludedPoints.js +3 -2
  233. package/dist/src/usecases/purgeExcludedPoints.js.map +1 -1
  234. package/dist/src/usecases/readFiles.d.ts.map +1 -1
  235. package/dist/src/usecases/readFiles.js +33 -6
  236. package/dist/src/usecases/readFiles.js.map +1 -1
  237. package/dist/src/usecases/reconcileSourcePackageRoots.d.ts +31 -0
  238. package/dist/src/usecases/reconcileSourcePackageRoots.d.ts.map +1 -0
  239. package/dist/src/usecases/reconcileSourcePackageRoots.js +114 -0
  240. package/dist/src/usecases/reconcileSourcePackageRoots.js.map +1 -0
  241. package/dist/src/usecases/searchFiles.d.ts.map +1 -1
  242. package/dist/src/usecases/searchFiles.js +62 -64
  243. package/dist/src/usecases/searchFiles.js.map +1 -1
  244. package/dist/src/usecases/syncIndexedFile.d.ts +42 -0
  245. package/dist/src/usecases/syncIndexedFile.d.ts.map +1 -0
  246. package/dist/src/usecases/syncIndexedFile.js +242 -0
  247. package/dist/src/usecases/syncIndexedFile.js.map +1 -0
  248. package/dist/src/usecases/syncPointWrites.d.ts +5 -0
  249. package/dist/src/usecases/syncPointWrites.d.ts.map +1 -1
  250. package/dist/src/usecases/syncPointWrites.js +32 -5
  251. package/dist/src/usecases/syncPointWrites.js.map +1 -1
  252. package/dist/src/usecases/uploadBinary.d.ts.map +1 -1
  253. package/dist/src/usecases/uploadBinary.js +36 -49
  254. package/dist/src/usecases/uploadBinary.js.map +1 -1
  255. package/dist/src/usecases/vector/endpointGuard.js +2 -1
  256. package/dist/src/usecases/vector/endpointGuard.js.map +1 -1
  257. package/dist/src/usecases/vector/estimate.d.ts +7 -4
  258. package/dist/src/usecases/vector/estimate.d.ts.map +1 -1
  259. package/dist/src/usecases/vector/estimate.js +8 -5
  260. package/dist/src/usecases/vector/estimate.js.map +1 -1
  261. package/dist/src/usecases/vector/rebuildGeneration.d.ts +29 -5
  262. package/dist/src/usecases/vector/rebuildGeneration.d.ts.map +1 -1
  263. package/dist/src/usecases/vector/rebuildGeneration.js +224 -82
  264. package/dist/src/usecases/vector/rebuildGeneration.js.map +1 -1
  265. package/dist/src/usecases/vectorMaintenance.d.ts +46 -4
  266. package/dist/src/usecases/vectorMaintenance.d.ts.map +1 -1
  267. package/dist/src/usecases/vectorMaintenance.js +190 -11
  268. package/dist/src/usecases/vectorMaintenance.js.map +1 -1
  269. package/dist/src/usecases/write/commit.d.ts +6 -8
  270. package/dist/src/usecases/write/commit.d.ts.map +1 -1
  271. package/dist/src/usecases/write/commit.js +96 -112
  272. package/dist/src/usecases/write/commit.js.map +1 -1
  273. package/dist/src/usecases/write/content.js +1 -2
  274. package/dist/src/usecases/write/content.js.map +1 -1
  275. package/dist/src/usecases/write/ports.d.ts +1 -0
  276. package/dist/src/usecases/write/ports.d.ts.map +1 -1
  277. package/dist/src/vector/FilesystemVectorIndex.d.ts +34 -3
  278. package/dist/src/vector/FilesystemVectorIndex.d.ts.map +1 -1
  279. package/dist/src/vector/FilesystemVectorIndex.js +254 -20
  280. package/dist/src/vector/FilesystemVectorIndex.js.map +1 -1
  281. package/dist/src/vector/InMemoryVectorIndex.d.ts +9 -2
  282. package/dist/src/vector/InMemoryVectorIndex.d.ts.map +1 -1
  283. package/dist/src/vector/InMemoryVectorIndex.js +25 -6
  284. package/dist/src/vector/InMemoryVectorIndex.js.map +1 -1
  285. package/dist/src/vector/QdrantVectorIndex.d.ts +35 -9
  286. package/dist/src/vector/QdrantVectorIndex.d.ts.map +1 -1
  287. package/dist/src/vector/QdrantVectorIndex.js +209 -90
  288. package/dist/src/vector/QdrantVectorIndex.js.map +1 -1
  289. package/dist/src/vector/qdrantFilter.d.ts +3 -1
  290. package/dist/src/vector/qdrantFilter.d.ts.map +1 -1
  291. package/dist/src/vector/qdrantFilter.js +2 -0
  292. package/dist/src/vector/qdrantFilter.js.map +1 -1
  293. package/dist/src/vector/types.d.ts +81 -0
  294. package/dist/src/vector/types.d.ts.map +1 -1
  295. package/dist/src/vector/types.js +2 -0
  296. package/dist/src/vector/types.js.map +1 -1
  297. package/dist/src/wiring.d.ts +6 -0
  298. package/dist/src/wiring.d.ts.map +1 -1
  299. package/dist/src/wiring.js +62 -3
  300. package/dist/src/wiring.js.map +1 -1
  301. package/dist/tools/fs_search.json +2 -2
  302. package/docs/drive-overview.md +2 -2
  303. package/instructions/brain-model.md +1 -1
  304. package/package.json +999 -10
  305. package/rules/brain-rules.md +1 -1
  306. package/skills/connect-business-data/SKILL.md +6 -4
  307. package/skills/create-package/SKILL.md +14 -12
  308. package/skills/create-package/references/config-and-credentials.md +3 -3
  309. package/skills/create-package/references/connectors-and-uri-policies.md +5 -3
  310. package/skills/create-package/references/manifest-and-discovery.md +4 -3
  311. package/skills/create-package/references/runtime-and-build.md +9 -9
  312. package/skills/create-package/references/skills-and-context.md +3 -3
  313. package/skills/create-package/references/surfaces-and-design.md +2 -1
  314. package/skills/create-package/references/team-and-workflows.md +2 -2
  315. package/skills/create-package/references/test-and-ship.md +15 -15
  316. package/skills/create-package/references/tools-and-schemas.md +1 -1
  317. package/skills/create-package/scripts/check-package.sh +3 -3
  318. package/skills/create-package/scripts/verify-loaded.sh +16 -16
  319. package/skills/manage-files/SKILL.md +2 -2
  320. package/skills/manage-sources/SKILL.md +16 -15
  321. package/skills/manage-sources/scripts/reembed.sh +9 -3
  322. package/skills/sync/SKILL.md +4 -4
  323. package/tools/fs_search.json +2 -2
  324. package/dist/app/chunks/bash-M45GHPBQ.js +0 -1
  325. package/dist/app/chunks/c-X3REOAWD.js +0 -1
  326. package/dist/app/chunks/cpp-VOWI743W.js +0 -1
  327. package/dist/app/chunks/css-2ZVPHE25.js +0 -1
  328. package/dist/app/chunks/graphql-7V2NP54C.js +0 -1
  329. package/dist/app/chunks/html-UF3LFLNA.js +0 -1
  330. package/dist/app/chunks/java-PNTAU4L3.js +0 -1
  331. package/dist/app/chunks/javascript-4JJTQGEL.js +0 -1
  332. package/dist/app/chunks/json-VLQ6D3JH.js +0 -1
  333. package/dist/app/chunks/jsx-KQKSLAAH.js +0 -1
  334. package/dist/app/chunks/lua-UFPYUJ2O.js +0 -1
  335. package/dist/app/chunks/r-QEZB45SR.js +0 -1
  336. package/dist/app/chunks/sql-DTZHNUZP.js +0 -1
  337. package/dist/app/chunks/tsx-WPR2WRZD.js +0 -1
  338. package/dist/app/chunks/typescript-LWO362OA.js +0 -1
  339. package/dist/app/chunks/xml-A5H72RTS.js +0 -1
  340. package/dist/app/chunks/yaml-MXHSIJCI.js +0 -1
  341. package/dist/src/services/purgePendingNodes.d.ts +0 -32
  342. package/dist/src/services/purgePendingNodes.d.ts.map +0 -1
  343. package/dist/src/services/purgePendingNodes.js +0 -61
  344. package/dist/src/services/purgePendingNodes.js.map +0 -1
  345. /package/dist/app/chunks/{chunk-737Y24HS.js → chunk-3JASFUPF.js} +0 -0
  346. /package/dist/app/chunks/{chunk-DKD2L57N.js → chunk-6EYLOQ4F.js} +0 -0
  347. /package/dist/app/chunks/{chunk-KYH5EHHY.js → chunk-7YRZ5LOQ.js} +0 -0
  348. /package/dist/app/chunks/{chunk-6TC3DVWV.js → chunk-C7LU4RID.js} +0 -0
  349. /package/dist/app/chunks/{chunk-TY33BMKR.js → chunk-CD52YDZP.js} +0 -0
  350. /package/dist/app/chunks/{chunk-OLEIKJ3X.js → chunk-DCJKTCJ5.js} +0 -0
  351. /package/dist/app/chunks/{chunk-QLBZ4JR2.js → chunk-EXIU2KBV.js} +0 -0
  352. /package/dist/app/chunks/{chunk-SFUNBBEA.js → chunk-FDP4LO4P.js} +0 -0
  353. /package/dist/app/chunks/{chunk-BGRVFATT.js → chunk-HNETUS7U.js} +0 -0
  354. /package/dist/app/chunks/{chunk-4QNMHAIR.js → chunk-LBFZJEDR.js} +0 -0
  355. /package/dist/app/chunks/{chunk-TRRUHDPN.js → chunk-ULRXL5VM.js} +0 -0
  356. /package/dist/app/chunks/{chunk-S6B4WWIF.js → chunk-XJK47SMJ.js} +0 -0
  357. /package/dist/app/chunks/{chunk-DZ5C5FKI.js → chunk-YYAAIAU2.js} +0 -0
  358. /package/dist/app/chunks/{clojure-OC6KPSJO.js → clojure-4QCN4GHG.js} +0 -0
  359. /package/dist/app/chunks/{core-KGE7TQJ3.js → core-ZHTX3P2Q.js} +0 -0
  360. /package/dist/app/chunks/{csharp-E7VIFITL.js → csharp-HQWR4N52.js} +0 -0
  361. /package/dist/app/chunks/{dark-plus-BOSCZXE4.js → dark-plus-GYPKKZES.js} +0 -0
  362. /package/dist/app/chunks/{dart-6Y3X27SZ.js → dart-FO4TTVO2.js} +0 -0
  363. /package/dist/app/chunks/{diff-TR2NPA64.js → diff-SHANV4YC.js} +0 -0
  364. /package/dist/app/chunks/{dist-T32U2VVR.js → dist-WSDF34DM.js} +0 -0
  365. /package/dist/app/chunks/{docker-N33HOW54.js → docker-VX55HTDS.js} +0 -0
  366. /package/dist/app/chunks/{go-6DGG5UTP.js → go-5UCPEC6S.js} +0 -0
  367. /package/dist/app/chunks/{haskell-KK36PMMM.js → haskell-25PJ3OSG.js} +0 -0
  368. /package/dist/app/chunks/{ini-VHVQ5TPB.js → ini-BJIVD5MK.js} +0 -0
  369. /package/dist/app/chunks/{kotlin-44WS3JHE.js → kotlin-MNR6XOEQ.js} +0 -0
  370. /package/dist/app/chunks/{less-5WZ3KLAK.js → less-KA5JURX6.js} +0 -0
  371. /package/dist/app/chunks/{light-plus-HGGBC43G.js → light-plus-QXH2UUWZ.js} +0 -0
  372. /package/dist/app/chunks/{make-X7RXMVW7.js → make-LPD5KOCO.js} +0 -0
  373. /package/dist/app/chunks/{markdown-46IKUAIB.js → markdown-LMWF6MQR.js} +0 -0
  374. /package/dist/app/chunks/{mdx-DGLHDAO7.js → mdx-WPMNN5BS.js} +0 -0
  375. /package/dist/app/chunks/{nix-Q6FYQZLX.js → nix-QGWLUUMZ.js} +0 -0
  376. /package/dist/app/chunks/{powershell-L4KJJ6DK.js → powershell-7GSBBEFF.js} +0 -0
  377. /package/dist/app/chunks/{prisma-IKS4LVH2.js → prisma-FPMPQISO.js} +0 -0
  378. /package/dist/app/chunks/{proto-AYCDOTJO.js → proto-7KDX5Z2U.js} +0 -0
  379. /package/dist/app/chunks/{python-KXMCSAFQ.js → python-CFG4FTQW.js} +0 -0
  380. /package/dist/app/chunks/{rust-J3BC3P6V.js → rust-X7A37WU3.js} +0 -0
  381. /package/dist/app/chunks/{scala-AYIIHBJK.js → scala-OYHJSUG2.js} +0 -0
  382. /package/dist/app/chunks/{swift-QPHHCQD3.js → swift-PE7VNYW5.js} +0 -0
  383. /package/dist/app/chunks/{toml-UV7JK7K7.js → toml-ETQRDILP.js} +0 -0
@@ -1,8 +1,12 @@
1
1
  import { indexEligibilityReader, recoveryPayload } from './indexEligibility.js';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { computeContentHash } from '../../domain/contentHash.js';
4
+ import { mediaEmbeddingInput } from '../syncPointWrites.js';
5
+ import { VECTOR_WRITE_REQUEST_BYTES } from '../../vector/types.js';
2
6
  import { getActiveLock, mutateLock, sameVectorSpace, generationOf, } from '../../embeddings/lock.js';
3
7
  import { embedModelMarker, supportsEmbedInput } from '../../embeddings/models.js';
4
- import { invalidateEmbedders } from '../../embeddings/factory.js';
5
- import { generationFor, getBrainConfigBoolean, getBrainConfigNumber, resolveEmbeddingModel, resolveTargetEmbeddingShape, } from '../../config/env.js';
8
+ import { embeddingUnavailableReason, invalidateEmbedders } from '../../embeddings/factory.js';
9
+ import { generationFor, getBrainConfigBoolean, getBrainConfigNumber, resolveEmbeddingModel, resolveTargetEmbeddingShape, embeddingEnabled, } from '../../config/env.js';
6
10
  import { buildEmbedderFor } from '../../embeddings/factory.js';
7
11
  import { readHealContent } from '../syncPointWrites.js';
8
12
  import { readMediaInputForPoint } from '../mediaLane.js';
@@ -39,6 +43,9 @@ export class RebuildError extends Error {
39
43
  this.name = 'RebuildError';
40
44
  }
41
45
  }
46
+ function hasWriteBoundary(vector) {
47
+ return typeof vector.exclusive === 'function';
48
+ }
42
49
  /**
43
50
  * The text a point's vector is built from — the ONE rule the scan and the
44
51
  * mirror share: a chunk embeds its own `content`; a document embeds its
@@ -62,7 +69,7 @@ export function embedTextForPoint(payload) {
62
69
  * content-less text point's stored text through the same text rule, a media
63
70
  * point's bytes or page. `undefined` = unreadable.
64
71
  */
65
- async function inputFor(deps, payload) {
72
+ export async function inputFor(deps, payload) {
66
73
  const inline = embedTextForPoint(payload);
67
74
  if (inline !== undefined)
68
75
  return { kind: 'text', text: inline };
@@ -77,7 +84,7 @@ async function inputFor(deps, payload) {
77
84
  * one call each. A media input the target cannot take (`supportsEmbedInput`
78
85
  * over its `modalities`) answers `null` — the caller leaves it out.
79
86
  */
80
- async function embedInputs(embedder, inputs, signal) {
87
+ export async function embedInputs(embedder, inputs, signal) {
81
88
  const out = new Array(inputs.length).fill(null);
82
89
  const textAt = [];
83
90
  for (let i = 0; i < inputs.length; i += 1)
@@ -101,10 +108,13 @@ async function embedInputs(embedder, inputs, signal) {
101
108
  }
102
109
  /** Eligibility handles recovery state; compare content/provenance independently of index markers. */
103
110
  function samePayload(a, b) {
111
+ if (a.embedding_status !== 'current' || typeof a.embedding_generation !== 'number'
112
+ || typeof a.embedding_input_hash !== 'string' || typeof a.embed_model !== 'string')
113
+ return false;
104
114
  return stableStringify(withoutMarker(a)) === stableStringify(withoutMarker(b));
105
115
  }
106
116
  function withoutMarker(payload) {
107
- const { embed_model: _marker, recovery_only: _recovery, ...rest } = payload;
117
+ const { embed_model: _marker, recovery_only: _recovery, embedding_status: _status, embedding_generation: _generation, embedding_input_hash: _hash, embedding_updated_at: _updated, ...rest } = payload;
108
118
  return rest;
109
119
  }
110
120
  function stableStringify(value) {
@@ -134,6 +144,7 @@ export class GenerationMirror {
134
144
  #shadow;
135
145
  #embedder;
136
146
  #marker;
147
+ generation;
137
148
  #queue = [];
138
149
  #queueBytes = 0;
139
150
  #dirty = new Set();
@@ -150,6 +161,7 @@ export class GenerationMirror {
150
161
  this.#shadow = shadow;
151
162
  this.#embedder = embedder;
152
163
  this.#marker = embedModelMarker(target.modelId, target.dimension, target.embedRecipe);
164
+ this.generation = seed.generation;
153
165
  for (const id of seed.dirty)
154
166
  this.#dirty.add(id);
155
167
  for (const filter of seed.failedDeletes)
@@ -194,6 +206,17 @@ export class GenerationMirror {
194
206
  afterRetention(point) {
195
207
  this.#enqueue({ kind: 'retention', id: point.id, payload: point.payload });
196
208
  }
209
+ /** A native multi-operation failure may have committed a prefix; re-read before swap. */
210
+ afterUncertainWrite(ids) {
211
+ for (const id of ids)
212
+ this.#dirty.add(id);
213
+ this.#persistDirty();
214
+ }
215
+ /** A failed primary delete is reconciliation intent, never a destructive replay. */
216
+ afterUncertainDelete(filter) {
217
+ this.#failedDeletes.push(filter);
218
+ this.#persistDirty();
219
+ }
197
220
  afterDelete(filter) {
198
221
  this.#enqueue({ kind: 'delete', filter });
199
222
  }
@@ -250,8 +273,18 @@ export class GenerationMirror {
250
273
  window.touched.add(item.id);
251
274
  return;
252
275
  }
253
- const payload = searchablePayload(item.payload, this.#marker);
276
+ const input = await inputFor(this.#deps, item.payload);
277
+ if (!input)
278
+ throw new RebuildError('classification_unresolved', 'source-input-unavailable');
279
+ const payload = searchablePayload(item.payload, this.#marker, this.generation, input);
254
280
  if (item.kind === 'payload' && !item.payload.recovery_only) {
281
+ const previous = (await this.#deps.generations.payloads(this.#shadow, [item.id])).get(item.id);
282
+ if (!previous || previous.embedding_status !== 'current' || previous.embedding_generation !== this.generation
283
+ || previous.embed_model !== this.#marker || previous.embedding_input_hash !== payload.embedding_input_hash) {
284
+ payload.embedding_status = 'stale';
285
+ this.#dirty.add(item.id);
286
+ this.#persistDirty();
287
+ }
255
288
  if (!(await this.#deps.generations.overwritePayload(this.#shadow, item.id, payload))) {
256
289
  // Not in the shadow yet: the scan may already hold an OLDER read of
257
290
  // it, so the point is re-processed from the active one before the swap.
@@ -261,7 +294,6 @@ export class GenerationMirror {
261
294
  }
262
295
  }
263
296
  else {
264
- const input = await inputFor(this.#deps, item.payload);
265
297
  const [embedding] = input ? await embedInputs(this.#embedder, [input]) : [null];
266
298
  if (!embedding) {
267
299
  throw new RebuildError('classification_unresolved', 'source-input-unavailable');
@@ -345,6 +377,8 @@ let runningLoop = null;
345
377
  export function getGenerationMirror() {
346
378
  return installedMirror;
347
379
  }
380
+ /** Cancellation cannot take over maintenance admission while setup is still awaiting creation. */
381
+ export function isRebuildRunning() { return runningLoop !== null; }
348
382
  /** Test seam: drop the process state between cases. */
349
383
  export function resetRebuildStateForTesting() {
350
384
  installedMirror = null;
@@ -373,14 +407,18 @@ export async function pauseInterruptedRebuild(appRoot, logger) {
373
407
  * Start a rebuild toward the configured target, or resume a paused one.
374
408
  * Returns once the run is set up (shadow created, mirror installed); the scan
375
409
  * continues in the background and calls `release` when it ends — the caller's
376
- * latch is held until then.
410
+ * latch is held until then. `onEnd` hears how the background run ended
411
+ * (`completed` = swapped to the new generation, `paused` = dirty or failed); a
412
+ * cancel reports nothing. It lives in this process only (never in the lock).
377
413
  */
378
- export async function startRebuild(deps, release) {
414
+ export async function startRebuild(deps, release, onEnd) {
379
415
  if (runningLoop)
380
416
  throw new RebuildError('rebuild_running', 'A rebuild is already running in this process');
417
+ if (!embeddingEnabled())
418
+ throw new RebuildError('target_unavailable', 'embedding_disabled');
381
419
  const lock = await getActiveLock(deps.appRoot);
382
420
  if (!lock)
383
- throw new RebuildError('no_active_generation', 'There is no active vector index to rebuild yet');
421
+ throw new RebuildError('no_active_generation', 'Initialize the serving data index before starting a rebuild');
384
422
  let rebuild = lock.rebuild;
385
423
  const resumed = Boolean(rebuild);
386
424
  if (!rebuild) {
@@ -388,15 +426,18 @@ export async function startRebuild(deps, release) {
388
426
  if ('error' in resolved)
389
427
  throw new RebuildError('target_unavailable', resolved.error);
390
428
  const target = generationFor(resolved.info, resolved.dimension);
391
- if (sameVectorSpace(generationOf(lock), target)) {
429
+ if (lock.mode === 'dense' && sameVectorSpace(generationOf(lock), target)) {
392
430
  throw new RebuildError('no_switch_pending', 'The configured model already is the active one — nothing to rebuild');
393
431
  }
394
- const generation = lock.generation + 1;
432
+ const generation = lock.mode === 'dense' ? lock.generation + 1 : 1;
395
433
  rebuild = {
396
434
  target,
397
435
  generation,
398
- shadowCollection: deps.generations.collectionName(generation),
436
+ shadowCollection: lock.mode === 'metadata'
437
+ ? `${deps.generations.collectionName(generation)}__shadow_${randomUUID()}`
438
+ : deps.generations.collectionName(generation),
399
439
  state: 'running',
440
+ phase: 'prepared',
400
441
  startedAt: now(deps),
401
442
  processed: 0,
402
443
  dirty: [],
@@ -411,36 +452,68 @@ export async function startRebuild(deps, release) {
411
452
  embedder = await deps.buildTargetEmbedder(rebuild.target);
412
453
  }
413
454
  catch (error) {
414
- throw new RebuildError('target_unavailable', error instanceof Error ? error.message : String(error));
455
+ const reason = embeddingUnavailableReason(error);
456
+ if (!reason)
457
+ throw error;
458
+ throw new RebuildError('target_unavailable', reason);
459
+ }
460
+ if (!resumed && await deps.generations.exists(rebuild.shadowCollection)) {
461
+ throw new RebuildError('target_unavailable', 'unrecorded_shadow_exists');
415
462
  }
416
- if (!(await deps.generations.exists(rebuild.shadowCollection))) {
417
- await deps.generations.create(rebuild.shadowCollection, rebuild.target.dimension);
418
- // A shadow that vanished while paused is rebuilt from nothing.
419
- if (resumed)
420
- rebuild = { ...rebuild, processed: 0, dirty: [], failedDeletes: [] };
463
+ // Record ownership before creating a shadow; restart/cancel never guesses.
464
+ await mutateLock(deps.appRoot, current => current ? { ...current, rebuild } : null);
465
+ try {
466
+ if (!(await deps.generations.exists(rebuild.shadowCollection))) {
467
+ await deps.generations.create(rebuild.shadowCollection, rebuild.target.dimension);
468
+ // A shadow that vanished while paused is rebuilt from nothing.
469
+ if (resumed)
470
+ rebuild = { ...rebuild, processed: 0, dirty: [], failedDeletes: [] };
471
+ }
472
+ await deps.generations.validate(rebuild.shadowCollection, rebuild.target.dimension);
421
473
  }
422
- else if (!resumed) {
423
- // Debris of an earlier run nobody recorded — never build on it.
424
- await deps.generations.drop(rebuild.shadowCollection);
425
- await deps.generations.create(rebuild.shadowCollection, rebuild.target.dimension);
474
+ catch (error) {
475
+ await pause(deps, 'error', error instanceof Error ? error.message : String(error));
476
+ throw error;
426
477
  }
427
478
  // A resume re-verifies from the start (see the module doc).
428
479
  const running = {
429
480
  ...rebuild,
430
481
  state: 'running',
482
+ phase: 'copying',
431
483
  cursor: undefined,
432
484
  scanDone: false,
433
485
  updatedAt: now(deps),
434
486
  };
435
487
  delete running.pausedReason;
436
488
  delete running.lastError;
437
- await mutateLock(deps.appRoot, (current) => current ? { ...current, rebuild: running } : null);
438
489
  const mirror = new GenerationMirror(deps, running.shadowCollection, embedder, running.target, running);
439
- installedMirror = mirror;
490
+ const install = async () => {
491
+ await mutateLock(deps.appRoot, current => current ? { ...current, rebuild: running } : null);
492
+ installedMirror = mirror;
493
+ };
494
+ try {
495
+ await deps.vector.exclusive(install);
496
+ }
497
+ catch (error) {
498
+ await pause(deps, 'error', error instanceof Error ? error.message : String(error));
499
+ throw error;
500
+ }
440
501
  const abort = new AbortController();
441
502
  const done = runLoop(deps, mirror, embedder, abort.signal)
442
503
  .catch(async (error) => {
443
504
  await pause(deps, 'error', error instanceof Error ? error.message : String(error));
505
+ })
506
+ .then(async () => {
507
+ if (!onEnd)
508
+ return;
509
+ const after = await getActiveLock(deps.appRoot).catch(() => null);
510
+ try {
511
+ if (after?.mode === 'dense' && !after.rebuild && after.generation === running.generation)
512
+ onEnd('completed');
513
+ else if (after?.rebuild?.state === 'paused')
514
+ onEnd('paused');
515
+ }
516
+ catch { /* a notification never changes the run */ }
444
517
  })
445
518
  .finally(() => {
446
519
  runningLoop = null;
@@ -478,6 +551,24 @@ async function runLoop(deps, mirror, embedder, signal) {
478
551
  const ids = page.points.map((p) => p.id);
479
552
  const inShadow = await deps.generations.payloads(shadow, ids);
480
553
  const work = [];
554
+ let inputBytes = 0;
555
+ const flush = async () => {
556
+ if (!work.length)
557
+ return;
558
+ const vectors = await embedInputs(embedder, work.map(w => w.input), signal);
559
+ const points = work.flatMap((w, i) => {
560
+ if (!vectors[i])
561
+ throw new RebuildError('classification_unresolved', 'unsupported-input-kind');
562
+ if (window.touched.has(w.id))
563
+ return [];
564
+ return [{ id: w.id, vector: vectors[i], payload: searchablePayload(w.payload, mirror.marker, mirror.generation, w.input) }];
565
+ });
566
+ if (points.length)
567
+ await deps.generations.upsert(shadow, points, { signal });
568
+ processed += points.length;
569
+ work.length = 0;
570
+ inputBytes = 0;
571
+ };
481
572
  const leaveOut = (uri) => {
482
573
  unreadable.count += 1;
483
574
  if (unreadable.sample.length < UNREADABLE_SAMPLE && typeof uri === 'string')
@@ -488,30 +579,25 @@ async function runLoop(deps, mirror, embedder, signal) {
488
579
  const copy = inShadow.get(point.id);
489
580
  if (!(await carryEligibility(deps, shadow, point.id, point.payload)))
490
581
  continue;
491
- if (copy && !copy.recovery_only && samePayload(copy, payload))
492
- continue;
493
582
  const input = await inputFor(deps, point.payload);
494
583
  if (input === undefined) {
495
584
  leaveOut(point.payload.uri);
496
585
  throw new RebuildError('classification_unresolved', 'source-input-unavailable');
497
586
  }
587
+ const proof = searchablePayload(payload, mirror.marker, mirror.generation, input);
588
+ if (copy && !copy.recovery_only && samePayload(copy, payload) && copy.embedding_status === 'current'
589
+ && copy.embedding_generation === mirror.generation && copy.embed_model === mirror.marker
590
+ && copy.embedding_input_hash === proof.embedding_input_hash)
591
+ continue;
592
+ const bytes = input.kind === 'text' ? Buffer.byteLength(input.text, 'utf8') : input.bytes.length;
593
+ if (work.length && inputBytes + bytes > VECTOR_WRITE_REQUEST_BYTES)
594
+ await flush();
498
595
  work.push({ id: point.id, input, payload });
596
+ inputBytes += bytes;
597
+ if (inputBytes >= VECTOR_WRITE_REQUEST_BYTES)
598
+ await flush();
499
599
  }
500
- if (work.length > 0) {
501
- const vectors = await embedInputs(embedder, work.map((w) => w.input), signal);
502
- const points = [];
503
- work.forEach((w, i) => {
504
- // A media point the target model cannot embed is left out and named.
505
- if (!vectors[i])
506
- throw new RebuildError('classification_unresolved', 'unsupported-input-kind');
507
- // A mirror write that landed after this page was read is newer — keep it.
508
- if (window.touched.has(w.id))
509
- return;
510
- points.push({ id: w.id, vector: vectors[i], payload: searchablePayload(w.payload, mirror.marker) });
511
- });
512
- await deps.generations.upsert(shadow, points, { signal });
513
- processed += points.length;
514
- }
600
+ await flush();
515
601
  // Retained payloads can overwrite newer mirror writes too; keep the window
516
602
  // open through every page write and re-process raced IDs from the active store.
517
603
  const touched = mirror.closeWindow(window);
@@ -519,7 +605,7 @@ async function runLoop(deps, mirror, embedder, signal) {
519
605
  if (raced.length > 0)
520
606
  mirror.restoreDirty(raced, []);
521
607
  for (const filter of touched.deletes)
522
- await deps.generations.deleteByFilter(shadow, filter);
608
+ mirror.afterUncertainDelete(filter);
523
609
  cursor = page.nextCursor;
524
610
  await mutateLock(deps.appRoot, (current) => current?.rebuild
525
611
  ? { ...current, rebuild: { ...current.rebuild, cursor, processed, unreadable, updatedAt: now(deps) } }
@@ -594,19 +680,25 @@ async function runLoop(deps, mirror, embedder, signal) {
594
680
  mirror.restoreDirty(dirty, []);
595
681
  return false;
596
682
  }
683
+ await mutateLock(deps.appRoot, current => current?.rebuild
684
+ ? { ...current, rebuild: { ...current.rebuild, phase: 'ready', updatedAt: now(deps) } }
685
+ : null);
597
686
  await swap(deps, shadow);
598
687
  deps.mutations?.clear(changed);
599
688
  return true;
600
689
  };
601
- const swapped = await mirror.trySwap(() => deps.mutations ? deps.mutations.exclusive(finish) : finish());
690
+ const finishSwap = () => mirror.trySwap(() => deps.mutations ? deps.mutations.exclusive(finish) : finish());
691
+ const swapped = await deps.vector.exclusive(finishSwap);
602
692
  if (swapped)
603
693
  return;
604
694
  }
605
695
  await pause(deps, 'dirty', `${mirror.dirtyCount} point(s) still differ after ${MAX_DIRTY_ROUNDS} rounds`);
606
696
  }
607
- function searchablePayload(payload, marker) {
697
+ function searchablePayload(payload, marker, generation, input) {
608
698
  const { recovery_only: ignored, ...rest } = payload;
609
- return { ...rest, embed_model: marker };
699
+ return { ...rest, embed_model: marker, embedding_status: 'current', embedding_generation: generation,
700
+ embedding_input_hash: computeContentHash(input.kind === 'text' ? input.text : mediaEmbeddingInput(payload)),
701
+ embedding_updated_at: new Date().toISOString(), };
610
702
  }
611
703
  async function carryEligibility(deps, shadow, id, payload) {
612
704
  const classification = await deps.classify(payload);
@@ -634,8 +726,28 @@ async function embedAll(embedder, texts, signal) {
634
726
  return out;
635
727
  }
636
728
  async function reprocess(deps, mirror, embedder, shadow, ids, deletes, signal) {
637
- for (const filter of deletes)
638
- await deps.generations.deleteByFilter(shadow, filter);
729
+ // The existing id-only shadow port has no filter projection. On uncertain
730
+ // deletion, reconcile bounded pages against the primary rather than replay
731
+ // a filter that could destroy a surviving or newly written point.
732
+ if (deletes.length) {
733
+ let cursor;
734
+ do {
735
+ signal.throwIfAborted();
736
+ const scanned = await deps.generations.scanIds(shadow, { limit: DIRTY_BATCH, ...(cursor ? { cursor } : {}) });
737
+ if (scanned.ids.length) {
738
+ const primary = await deps.vector.scroll({ filter: { mustMatchAny: [{ field: 'id', values: scanned.ids }] }, limit: scanned.ids.length });
739
+ const alive = new Map(primary.points.map(point => [point.id, point.payload]));
740
+ const copies = await deps.generations.payloads(shadow, scanned.ids);
741
+ const absent = scanned.ids.filter(id => !alive.has(id));
742
+ if (absent.length)
743
+ await deps.generations.deleteByFilter(shadow, { mustMatchAny: [{ field: 'id', values: absent }] });
744
+ const changed = primary.points.filter(point => !samePayload(copies.get(point.id) ?? {}, point.payload)).map(point => point.id);
745
+ if (changed.length)
746
+ await reprocess(deps, mirror, embedder, shadow, changed, [], signal);
747
+ }
748
+ cursor = scanned.nextCursor;
749
+ } while (cursor);
750
+ }
639
751
  for (let i = 0; i < ids.length; i += DIRTY_BATCH) {
640
752
  const batch = ids.slice(i, i + DIRTY_BATCH);
641
753
  const page = await deps.vector.scroll({ filter: { mustMatchAny: [{ field: 'id', values: batch }] }, limit: batch.length });
@@ -644,24 +756,35 @@ async function reprocess(deps, mirror, embedder, shadow, ids, deletes, signal) {
644
756
  if (gone.length > 0)
645
757
  await deps.generations.deleteByFilter(shadow, { mustMatchAny: [{ field: 'id', values: gone }] });
646
758
  const work = [];
759
+ let inputBytes = 0;
760
+ const flush = async () => {
761
+ if (!work.length)
762
+ return;
763
+ const vectors = await embedInputs(embedder, work.map(w => w.input), signal);
764
+ const points = work.map((w, j) => {
765
+ if (!vectors[j])
766
+ throw new RebuildError('classification_unresolved', 'unsupported-input-kind');
767
+ return { id: w.id, vector: vectors[j], payload: searchablePayload(w.payload, mirror.marker, mirror.generation, w.input) };
768
+ });
769
+ await deps.generations.upsert(shadow, points, { signal });
770
+ work.length = 0;
771
+ inputBytes = 0;
772
+ };
647
773
  for (const [id, payload] of alive) {
648
774
  if (!(await carryEligibility(deps, shadow, id, payload)))
649
775
  continue;
650
776
  const input = await inputFor(deps, payload);
651
777
  if (input === undefined)
652
778
  throw new RebuildError('classification_unresolved', 'source-input-unavailable');
779
+ const bytes = input.kind === 'text' ? Buffer.byteLength(input.text, 'utf8') : input.bytes.length;
780
+ if (work.length && inputBytes + bytes > VECTOR_WRITE_REQUEST_BYTES)
781
+ await flush();
653
782
  work.push({ id, input, payload: payload });
783
+ inputBytes += bytes;
784
+ if (inputBytes >= VECTOR_WRITE_REQUEST_BYTES)
785
+ await flush();
654
786
  }
655
- if (work.length === 0)
656
- continue;
657
- const vectors = await embedInputs(embedder, work.map((w) => w.input), signal);
658
- if (vectors.some(vector => !vector))
659
- throw new RebuildError('classification_unresolved', 'unsupported-input-kind');
660
- const points = work.flatMap((w, j) => (vectors[j]
661
- ? [{ id: w.id, vector: vectors[j], payload: searchablePayload(w.payload, mirror.marker) }]
662
- : []));
663
- if (points.length > 0)
664
- await deps.generations.upsert(shadow, points, { signal });
787
+ await flush();
665
788
  }
666
789
  }
667
790
  async function swap(deps, shadow) {
@@ -672,7 +795,8 @@ async function swap(deps, shadow) {
672
795
  previous = lock.collection;
673
796
  const { target, generation } = lock.rebuild;
674
797
  return {
675
- version: 2,
798
+ version: 3,
799
+ mode: 'dense',
676
800
  generation,
677
801
  collection: shadow,
678
802
  ...target,
@@ -709,19 +833,32 @@ export async function cancelRebuild(deps) {
709
833
  await loop.done;
710
834
  }
711
835
  const shadow = lock.rebuild.shadowCollection;
712
- await mutateLock(deps.appRoot, (current) => {
713
- if (!current?.rebuild)
714
- return null;
715
- const { rebuild: _dropped, ...rest } = current;
716
- return rest;
717
- });
718
- let dropped;
719
- if (await deps.generations.exists(shadow)) {
720
- await deps.generations.drop(shadow);
721
- dropped = shadow;
722
- }
723
- deps.logger?.info('vector.rebuild.cancelled', { shadow });
724
- return { cancelled: true, ...(dropped ? { dropped } : {}) };
836
+ const remove = async () => {
837
+ const current = await getActiveLock(deps.appRoot);
838
+ if (current?.collection === shadow || current?.rebuild?.shadowCollection !== shadow) {
839
+ throw new RebuildError('no_rebuild', 'The recorded rebuild changed before cancellation');
840
+ }
841
+ let dropped;
842
+ try {
843
+ if (await deps.generations.exists(shadow)) {
844
+ await deps.generations.drop(shadow);
845
+ dropped = shadow;
846
+ }
847
+ }
848
+ catch (error) {
849
+ await pause(deps, 'error', error instanceof Error ? error.message : String(error));
850
+ throw error;
851
+ }
852
+ await mutateLock(deps.appRoot, current => {
853
+ if (!current?.rebuild || current.rebuild.shadowCollection !== shadow)
854
+ return null;
855
+ const { rebuild: _dropped, ...rest } = current;
856
+ return rest;
857
+ });
858
+ deps.logger?.info('vector.rebuild.cancelled', { shadow });
859
+ return { cancelled: true, ...(dropped ? { dropped } : {}) };
860
+ };
861
+ return deps.vector.exclusive(remove);
725
862
  }
726
863
  /** Wait for a running loop (tests; a swap/cancel is observable through the lock). */
727
864
  export async function awaitRebuildLoop() {
@@ -775,24 +912,26 @@ export async function describeEmbeddingState(deps, pointsTotal = null) {
775
912
  const lock = await getActiveLock(deps.appRoot);
776
913
  const resolved = deps.resolveTarget();
777
914
  const target = 'error' in resolved ? null : generationFor(resolved.info, resolved.dimension);
778
- const active = lock ? generationOf(lock) : null;
779
- const marker = lock ? embedModelMarker(lock.modelId, lock.dimension, lock.embedRecipe) : null;
915
+ const dense = lock?.mode === 'dense' ? lock : null;
916
+ const active = dense ? generationOf(dense) : null;
917
+ const marker = dense ? embedModelMarker(dense.modelId, dense.dimension, dense.embedRecipe) : null;
780
918
  let points = null;
781
- if (lock && deps.vector.countByFilter) {
919
+ if (dense && deps.vector.countByFilter) {
782
920
  const total = pointsTotal ?? await deps.vector.countByFilter({});
783
921
  const stampedActive = await deps.vector.countByFilter({ must: [{ field: 'embed_model', equals: marker }] });
784
922
  points = { total, stampedActive, unstamped: Math.max(0, total - stampedActive) };
785
923
  }
786
924
  const targetInfo = 'error' in resolved ? null : resolved.info;
787
925
  return {
788
- active: lock
926
+ active: dense
789
927
  ? {
790
928
  ...active,
791
- generation: lock.generation,
792
- collection: lock.collection,
793
- createdAt: lock.createdAt,
794
- ...(lock.backfilledAt ? { backfilledAt: lock.backfilledAt } : {}),
795
- resolvable: Boolean(resolveEmbeddingModel(lock.modelId)),
929
+ mode: 'dense',
930
+ generation: dense.generation,
931
+ collection: dense.collection,
932
+ createdAt: dense.createdAt,
933
+ ...(dense.backfilledAt ? { backfilledAt: dense.backfilledAt } : {}),
934
+ resolvable: Boolean(resolveEmbeddingModel(dense.modelId)),
796
935
  }
797
936
  : null,
798
937
  target: target
@@ -800,6 +939,7 @@ export async function describeEmbeddingState(deps, pointsTotal = null) {
800
939
  : { error: resolved.error },
801
940
  switchPending: Boolean(active && target && !sameVectorSpace(active, target)),
802
941
  points,
942
+ serving: lock ? { mode: lock.mode, collection: lock.collection } : null,
803
943
  rebuild: describeRebuild(lock?.rebuild),
804
944
  lastEstimate: currentTargetEstimate(target?.modelId),
805
945
  spend: getEmbeddingUsageMeter()?.snapshot() ?? null,
@@ -847,6 +987,8 @@ export async function primeEmbeddingState(opts) {
847
987
  * through ITS project's connector (only bytes that still hash to the point).
848
988
  */
849
989
  export function buildRebuildDeps(opts) {
990
+ if (!hasWriteBoundary(opts.vector))
991
+ throw new Error('FILESYSTEM_WRITE_BOUNDARY_REQUIRED');
850
992
  return {
851
993
  appRoot: opts.appRoot,
852
994
  classify: indexEligibilityReader(opts.sourceConfigs),
@@ -916,7 +1058,7 @@ export async function bootEmbeddingGeneration(opts) {
916
1058
  }
917
1059
  await opts.vector.ensureAgentCollection('');
918
1060
  const lock = await getActiveLock(opts.appRoot).catch(() => null);
919
- if (lock && !resolveEmbeddingModel(lock.modelId)) {
1061
+ if (lock?.mode === 'dense' && !resolveEmbeddingModel(lock.modelId)) {
920
1062
  opts.logger.warn('vector.active_model_unresolvable', {
921
1063
  modelId: lock.modelId,
922
1064
  impact: 'semantic search falls back to full-text; writes are stored unembedded until the model is defined again or the index is rebuilt',