@remnic/core 9.69.87 → 9.69.89

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 (416) hide show
  1. package/dist/access-admin-ops-surface.js +56 -49
  2. package/dist/access-authorization-probe.js +57 -50
  3. package/dist/access-boundary.js +56 -49
  4. package/dist/access-citation-namespace.js +2 -2
  5. package/dist/access-cli.js +129 -122
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-extraction-force-flush.js +56 -49
  8. package/dist/access-http-briefing.js +57 -50
  9. package/dist/access-http-offline-stream.d.ts.map +1 -1
  10. package/dist/access-http-offline-stream.js +1 -1
  11. package/dist/access-http-query.js +58 -51
  12. package/dist/access-http.js +78 -71
  13. package/dist/access-identity-continuity-surface.js +56 -49
  14. package/dist/access-lcm-surface.js +56 -49
  15. package/dist/access-mcp-output-schemas.d.ts.map +1 -1
  16. package/dist/access-mcp-output-schemas.js +1 -1
  17. package/dist/access-mcp.js +69 -62
  18. package/dist/access-namespace-preflight.js +56 -49
  19. package/dist/access-observe-write-category.js +20 -13
  20. package/dist/access-observe-write-surface.js +56 -49
  21. package/dist/access-offline-manifest.js +16 -10
  22. package/dist/access-operations-batch.js +60 -53
  23. package/dist/access-operations.js +67 -60
  24. package/dist/access-recall-concurrency.js +56 -49
  25. package/dist/access-recall-response.js +56 -49
  26. package/dist/access-recall-surface.js +56 -49
  27. package/dist/access-schema.js +16 -7
  28. package/dist/access-service.d.ts.map +1 -1
  29. package/dist/access-service.js +56 -49
  30. package/dist/access-surface-catalog.js +58 -51
  31. package/dist/access-surface-catalog.js.map +1 -1
  32. package/dist/active-recall.js +24 -18
  33. package/dist/active-recall.js.map +1 -1
  34. package/dist/{auto-sync-6IPX2BAC.js → auto-sync-U5N3CB5Y.js} +7 -7
  35. package/dist/briefing.js +11 -11
  36. package/dist/buffer.js +7 -7
  37. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-GWVG7LGC.js} +2 -2
  38. package/dist/{capsule-merge-3MNURAQ3.js → capsule-merge-ZCXUPLNJ.js} +2 -2
  39. package/dist/causal-behavior.js +2 -2
  40. package/dist/causal-chain.js +2 -2
  41. package/dist/causal-consolidation.js +14 -14
  42. package/dist/causal-retrieval.js +2 -2
  43. package/dist/causal-trajectory-graph.js +2 -2
  44. package/dist/causal-trajectory.js +1 -1
  45. package/dist/{chunk-JE334SIA.js → chunk-22DYJLFN.js} +2 -2
  46. package/dist/{chunk-YTQGGO3K.js → chunk-22LMCQ5B.js} +85 -82
  47. package/dist/{chunk-YTQGGO3K.js.map → chunk-22LMCQ5B.js.map} +1 -1
  48. package/dist/{chunk-JGSQ6P4D.js → chunk-2FX374UI.js} +2 -2
  49. package/dist/{chunk-SY5KDWGU.js → chunk-2WLMWP2T.js} +2 -2
  50. package/dist/{chunk-6TT6HRE4.js → chunk-3AGHSQPM.js} +2 -2
  51. package/dist/{chunk-P6WFK4ZJ.js → chunk-3O5BB45T.js} +4 -4
  52. package/dist/{chunk-3C3J243X.js → chunk-3QKO4QN7.js} +3 -3
  53. package/dist/{chunk-KFCMMDHH.js → chunk-3YD44CH2.js} +2 -2
  54. package/dist/{chunk-FRCESVV6.js → chunk-4HUCP4R4.js} +18 -18
  55. package/dist/chunk-4HUCP4R4.js.map +1 -0
  56. package/dist/chunk-4NSLQ7EU.js +137 -0
  57. package/dist/chunk-4NSLQ7EU.js.map +1 -0
  58. package/dist/{chunk-7DE2NOON.js → chunk-4O4XCBOS.js} +3 -3
  59. package/dist/{chunk-GDYU6VF4.js → chunk-5AYUHSAU.js} +2 -2
  60. package/dist/{chunk-ICZQG4XE.js → chunk-5FAGCOZD.js} +4 -4
  61. package/dist/{chunk-VKQI47DJ.js → chunk-5FNZ3G6X.js} +4 -4
  62. package/dist/{chunk-CK5KOVHW.js → chunk-5R627MYY.js} +3 -2
  63. package/dist/{chunk-CK5KOVHW.js.map → chunk-5R627MYY.js.map} +1 -1
  64. package/dist/{chunk-VQNONC4E.js → chunk-5STPRPE7.js} +2 -2
  65. package/dist/{chunk-52II7PB6.js → chunk-645WOU5I.js} +4 -4
  66. package/dist/{chunk-SRZW7FNU.js → chunk-64ZUALXK.js} +2 -2
  67. package/dist/{chunk-4BZP2B4U.js → chunk-6MNKDTDI.js} +2 -2
  68. package/dist/{chunk-DUJYRVUD.js → chunk-6O2VUWV6.js} +3 -3
  69. package/dist/chunk-75PT4N7Q.js +75 -0
  70. package/dist/chunk-75PT4N7Q.js.map +1 -0
  71. package/dist/{chunk-HESDNYRB.js → chunk-76M5OK7V.js} +2 -2
  72. package/dist/{chunk-FAN73COB.js → chunk-7D7OUO5W.js} +2 -2
  73. package/dist/{chunk-DZ525SNQ.js → chunk-7QE46QXI.js} +1 -1
  74. package/dist/{chunk-VX6GCKBS.js → chunk-7WBSMXGC.js} +4 -4
  75. package/dist/{chunk-VVDWZRZG.js → chunk-A6RVIHHK.js} +3 -3
  76. package/dist/{chunk-M536WNTQ.js → chunk-AEJ3TE5H.js} +2 -2
  77. package/dist/{chunk-72JNNJJJ.js → chunk-AXC4WSF3.js} +3 -3
  78. package/dist/chunk-AXGJMDBS.js +684 -0
  79. package/dist/chunk-AXGJMDBS.js.map +1 -0
  80. package/dist/{chunk-UA5DX5C3.js → chunk-AZIOEOK4.js} +2 -2
  81. package/dist/{chunk-N4S4NUX5.js → chunk-B3O3IRZN.js} +1 -1
  82. package/dist/{chunk-XTYX6V52.js → chunk-BJVO4H5C.js} +2 -2
  83. package/dist/{chunk-7LCGFUMB.js → chunk-BUKK5SWA.js} +4 -4
  84. package/dist/{chunk-TM3RXL4G.js → chunk-BUQKSQXL.js} +2 -2
  85. package/dist/chunk-C6HPNPNK.js +64 -0
  86. package/dist/chunk-C6HPNPNK.js.map +1 -0
  87. package/dist/{chunk-RB776UH3.js → chunk-C7V3KPKJ.js} +235 -93
  88. package/dist/chunk-C7V3KPKJ.js.map +1 -0
  89. package/dist/{chunk-FOH7AMMI.js → chunk-CALBQ2NG.js} +71 -72
  90. package/dist/chunk-CALBQ2NG.js.map +1 -0
  91. package/dist/{chunk-37S3IMYB.js → chunk-DGS34QSY.js} +2 -2
  92. package/dist/{chunk-4IEOGIPB.js → chunk-DO4C4AXD.js} +2 -2
  93. package/dist/{chunk-SPSRGEDQ.js → chunk-EAQQ2FD3.js} +9 -9
  94. package/dist/{chunk-3BTFBQLO.js → chunk-EFHNFTWB.js} +49 -3
  95. package/dist/chunk-EFHNFTWB.js.map +1 -0
  96. package/dist/{chunk-C3QXITIP.js → chunk-EQUD7FP4.js} +2 -2
  97. package/dist/chunk-ETUZFFPU.js +218 -0
  98. package/dist/chunk-ETUZFFPU.js.map +1 -0
  99. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  100. package/dist/{chunk-YITN5WXI.js → chunk-FC52YWAO.js} +2 -2
  101. package/dist/{chunk-ZI7QA4MK.js → chunk-FEIXG5FA.js} +2 -2
  102. package/dist/{chunk-CLXTDOKV.js → chunk-FUNBJQYR.js} +2 -2
  103. package/dist/{chunk-CLSYE5OZ.js → chunk-G2STTRYB.js} +2 -2
  104. package/dist/{chunk-TDYAIXHK.js → chunk-H36X4NRH.js} +5 -5
  105. package/dist/{chunk-3GQC3NMB.js → chunk-H56VY4BY.js} +5 -5
  106. package/dist/{chunk-PUKTBULQ.js → chunk-HWLIMQE2.js} +2 -2
  107. package/dist/{chunk-V2ES4WCZ.js → chunk-IIARQTZ2.js} +2 -2
  108. package/dist/{chunk-QEV26L2F.js → chunk-IKDRGVVJ.js} +2 -2
  109. package/dist/{chunk-WQZZDN5M.js → chunk-IVEHY2VT.js} +254 -377
  110. package/dist/chunk-IVEHY2VT.js.map +1 -0
  111. package/dist/{chunk-LS3XH3UE.js → chunk-IYIBGSHJ.js} +2 -2
  112. package/dist/{chunk-YSJTCOHR.js → chunk-J27JML5O.js} +2 -2
  113. package/dist/{chunk-AQK54Q7O.js → chunk-JB56XYHP.js} +2 -2
  114. package/dist/{chunk-NE6PHPLG.js → chunk-JBH26P62.js} +10 -10
  115. package/dist/{chunk-CWHV574S.js → chunk-JUT3OR2L.js} +6 -6
  116. package/dist/{chunk-6ET2VII7.js → chunk-JYG2334B.js} +2 -2
  117. package/dist/{chunk-LF5IGOOD.js → chunk-JYM4Y6SO.js} +16 -16
  118. package/dist/{chunk-FB64XE3R.js → chunk-JZ4TJHR2.js} +2 -2
  119. package/dist/{chunk-EHS5MJEF.js → chunk-KTKHO3UI.js} +3 -3
  120. package/dist/{chunk-WIKO4SCR.js → chunk-LHRQBMJN.js} +2 -2
  121. package/dist/{chunk-K4J4E7WV.js → chunk-LO4UKPKX.js} +2 -2
  122. package/dist/{chunk-JN72EPOE.js → chunk-MAAL6OZV.js} +7 -7
  123. package/dist/chunk-MAAL6OZV.js.map +1 -0
  124. package/dist/{chunk-MY2PLXSM.js → chunk-MCTM4YPL.js} +2 -1
  125. package/dist/{chunk-MY2PLXSM.js.map → chunk-MCTM4YPL.js.map} +1 -1
  126. package/dist/{chunk-6NQ4LSMS.js → chunk-MGZMC6WN.js} +47 -47
  127. package/dist/chunk-MSAI3Q7D.js +372 -0
  128. package/dist/chunk-MSAI3Q7D.js.map +1 -0
  129. package/dist/{chunk-BMYZTZ5D.js → chunk-MY7B3HYC.js} +2 -2
  130. package/dist/{chunk-NB3LRY4T.js → chunk-N4PYB235.js} +2 -2
  131. package/dist/{chunk-S3YM4SZQ.js → chunk-N7QV3CMX.js} +2 -2
  132. package/dist/{chunk-L6UZEAID.js → chunk-NJVR724O.js} +4 -4
  133. package/dist/{chunk-VLLD4TOB.js → chunk-NMZVDRVP.js} +2 -2
  134. package/dist/{chunk-P2WZHLUF.js → chunk-NOL7SEWN.js} +2 -2
  135. package/dist/{chunk-2NLLXCJG.js → chunk-OWHERGF2.js} +2 -2
  136. package/dist/{chunk-6L3PBSAR.js → chunk-PMNLL2TL.js} +2 -2
  137. package/dist/{chunk-VYCOJKSX.js → chunk-PO7NXNGC.js} +2 -2
  138. package/dist/{chunk-4IIMMZZK.js → chunk-PULDSMCJ.js} +10 -10
  139. package/dist/{chunk-G6SVZWPB.js → chunk-PZX3AQLF.js} +44 -1
  140. package/dist/chunk-PZX3AQLF.js.map +1 -0
  141. package/dist/{chunk-HBBHL4YF.js → chunk-QOXNTKXB.js} +2 -2
  142. package/dist/{chunk-CUCWX3MO.js → chunk-QZLCVO2V.js} +2 -2
  143. package/dist/{chunk-NE7I2RCT.js → chunk-R3X2T2LP.js} +2 -2
  144. package/dist/{chunk-S7GCT7NU.js → chunk-RBITPCMK.js} +8 -8
  145. package/dist/{chunk-EVBI7S4J.js → chunk-RMDGAAZF.js} +4 -4
  146. package/dist/{chunk-BJRAELDH.js → chunk-SCXURCVT.js} +3 -3
  147. package/dist/{chunk-EQ6YOIKO.js → chunk-ST2RQUXC.js} +3 -3
  148. package/dist/{chunk-CS52MFDV.js → chunk-SZG4WFAG.js} +2 -2
  149. package/dist/{chunk-BFO3IR56.js → chunk-TPEZ2SHV.js} +2 -2
  150. package/dist/chunk-TYT4TEQV.js +45 -0
  151. package/dist/chunk-TYT4TEQV.js.map +1 -0
  152. package/dist/{chunk-YRLHEEQT.js → chunk-U6VC6RP3.js} +2 -2
  153. package/dist/chunk-UCPGOUDZ.js +726 -0
  154. package/dist/chunk-UCPGOUDZ.js.map +1 -0
  155. package/dist/{chunk-MOQPEFLN.js → chunk-UP75KL3L.js} +2 -2
  156. package/dist/{chunk-W4AOR52H.js → chunk-UQDC3W77.js} +2 -2
  157. package/dist/{chunk-X7Y7WX73.js → chunk-UVYI6VIX.js} +1 -1
  158. package/dist/{chunk-D5F7TKMH.js → chunk-V4JZCTMK.js} +2 -2
  159. package/dist/{chunk-MQJL6CDZ.js → chunk-VA4O3U7U.js} +4 -4
  160. package/dist/{chunk-4CDYONWO.js → chunk-VDO32NLK.js} +3 -3
  161. package/dist/{chunk-SOZUJCME.js → chunk-VHDKPGMG.js} +6 -6
  162. package/dist/{chunk-QWW2JDDX.js → chunk-WNRTXEWW.js} +6 -6
  163. package/dist/{chunk-NR2ASHBL.js → chunk-XOV7NRY5.js} +2 -2
  164. package/dist/{chunk-KP7SANCF.js → chunk-YPI4RQWV.js} +2 -2
  165. package/dist/{chunk-JKMHZF3Q.js → chunk-YSWJXOOL.js} +2 -2
  166. package/dist/{chunk-LQG5O5EP.js → chunk-Z3O53WZR.js} +2 -2
  167. package/dist/{chunk-QVS22WTO.js → chunk-ZG5DWWKK.js} +2 -2
  168. package/dist/cli.js +99 -92
  169. package/dist/coding/export-okf-codegraph.js +12 -12
  170. package/dist/coding/pre-action-gate.js +2 -2
  171. package/dist/compounding/engine.js +12 -12
  172. package/dist/config.js +24 -18
  173. package/dist/connectors/codex-materialize-runner.js +11 -11
  174. package/dist/connectors/index.js +11 -11
  175. package/dist/console/state.d.ts +30 -0
  176. package/dist/console/state.d.ts.map +1 -1
  177. package/dist/contradiction/index.js +5 -5
  178. package/dist/convergence-refresh.js +13 -13
  179. package/dist/corpus-watermark.js +13 -13
  180. package/dist/dashboard-runtime.js +2 -2
  181. package/dist/deep-recall-http-glue.js +16 -8
  182. package/dist/embedding-fallback.d.ts +41 -31
  183. package/dist/embedding-fallback.d.ts.map +1 -1
  184. package/dist/embedding-fallback.js +9 -1
  185. package/dist/embedding-generation-lock.d.ts +49 -0
  186. package/dist/embedding-generation-lock.d.ts.map +1 -0
  187. package/dist/embedding-generation-lock.js +19 -0
  188. package/dist/embedding-index-storage.d.ts +276 -0
  189. package/dist/embedding-index-storage.d.ts.map +1 -0
  190. package/dist/embedding-index-storage.js +28 -0
  191. package/dist/entity-retrieval.js +12 -12
  192. package/dist/event-order-recall.js +3 -3
  193. package/dist/explicit-capture.js +6 -6
  194. package/dist/external-wiki-access.js +57 -50
  195. package/dist/extraction.js +17 -17
  196. package/dist/{first-start-migration-7OATHJKB.js → first-start-migration-GAT7G733.js} +4 -4
  197. package/dist/{graph-edge-decay-KQGS3L3B.js → graph-edge-decay-MKIRZ762.js} +5 -5
  198. package/dist/graph-jsonl.js +3 -3
  199. package/dist/graph-snapshot.js +3 -3
  200. package/dist/graph.js +2 -2
  201. package/dist/harmonic-retrieval.js +1 -1
  202. package/dist/index.d.ts +2 -0
  203. package/dist/index.d.ts.map +1 -1
  204. package/dist/index.js +195 -164
  205. package/dist/lcm/engine.js +2 -2
  206. package/dist/lcm/index.js +5 -5
  207. package/dist/local-llm.js +2 -2
  208. package/dist/location/index.js +14 -14
  209. package/dist/maintenance/memory-governance.js +11 -11
  210. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +11 -11
  211. package/dist/maintenance/rebuild-memory-projection.js +12 -12
  212. package/dist/memory-browse-operations.js +57 -50
  213. package/dist/memory-subject.js +3 -3
  214. package/dist/namespaces/migrate.js +12 -12
  215. package/dist/namespaces/storage.js +11 -11
  216. package/dist/offline-sync-changeset-generations.d.ts +82 -0
  217. package/dist/offline-sync-changeset-generations.d.ts.map +1 -0
  218. package/dist/offline-sync-changeset-generations.js +21 -0
  219. package/dist/offline-sync-embedding-generation.d.ts +257 -0
  220. package/dist/offline-sync-embedding-generation.d.ts.map +1 -0
  221. package/dist/offline-sync-embedding-generation.js +44 -0
  222. package/dist/offline-sync-embedding-generation.js.map +1 -0
  223. package/dist/offline-sync-exclude-globs.d.ts +15 -0
  224. package/dist/offline-sync-exclude-globs.d.ts.map +1 -1
  225. package/dist/offline-sync-exclude-globs.js +11 -3
  226. package/dist/offline-sync-file-io.d.ts +7 -0
  227. package/dist/offline-sync-file-io.d.ts.map +1 -1
  228. package/dist/offline-sync-file-io.js +14 -2
  229. package/dist/offline-sync-impression-drain.js +3 -3
  230. package/dist/offline-sync-runtime-state.d.ts +5 -0
  231. package/dist/offline-sync-runtime-state.d.ts.map +1 -0
  232. package/dist/offline-sync-runtime-state.js +14 -0
  233. package/dist/offline-sync-runtime-state.js.map +1 -0
  234. package/dist/offline-sync-snapshot-stream.d.ts +26 -0
  235. package/dist/offline-sync-snapshot-stream.d.ts.map +1 -0
  236. package/dist/offline-sync-snapshot-stream.js +29 -0
  237. package/dist/offline-sync-snapshot-stream.js.map +1 -0
  238. package/dist/offline-sync-upload-staging.d.ts +34 -0
  239. package/dist/offline-sync-upload-staging.d.ts.map +1 -0
  240. package/dist/offline-sync-upload-staging.js +26 -0
  241. package/dist/offline-sync-upload-staging.js.map +1 -0
  242. package/dist/offline-sync.d.ts +58 -28
  243. package/dist/offline-sync.d.ts.map +1 -1
  244. package/dist/offline-sync.js +22 -8
  245. package/dist/operator-doctor-corpus.js +14 -14
  246. package/dist/operator-toolkit.js +35 -29
  247. package/dist/orchestration/maintenance.js +22 -22
  248. package/dist/orchestrator.d.ts.map +1 -1
  249. package/dist/orchestrator.js +129 -122
  250. package/dist/recall-navigation-operations.js +57 -50
  251. package/dist/recall-navigation.js +56 -49
  252. package/dist/reconcile/manifest.js +4 -4
  253. package/dist/{resolution-DNX7FO7A.js → resolution-K4A3VPKZ.js} +6 -6
  254. package/dist/resolution-K4A3VPKZ.js.map +1 -0
  255. package/dist/resume-bundles.js +25 -19
  256. package/dist/salvage-envelope.js +5 -5
  257. package/dist/secure-store/index.js +2 -2
  258. package/dist/secure-store/secure-fs.d.ts +14 -1
  259. package/dist/secure-store/secure-fs.d.ts.map +1 -1
  260. package/dist/semantic-consolidation.js +12 -12
  261. package/dist/semantic-rule-promotion.js +11 -11
  262. package/dist/semantic-rule-verifier.js +11 -11
  263. package/dist/source-agent-qualifier.js +14 -14
  264. package/dist/standup/build.js +11 -11
  265. package/dist/{state-ITXJKXRE.js → state-JB47KHYV.js} +2 -2
  266. package/dist/state-JB47KHYV.js.map +1 -0
  267. package/dist/storage.d.ts +9 -3
  268. package/dist/storage.d.ts.map +1 -1
  269. package/dist/storage.js +10 -10
  270. package/dist/subject-config.js +4 -4
  271. package/dist/summarizer.js +3 -3
  272. package/dist/support-passport/index.js +59 -52
  273. package/dist/support-passport/offline-sync-guard.d.ts +6 -2
  274. package/dist/support-passport/offline-sync-guard.d.ts.map +1 -1
  275. package/dist/{trace-ZFVRH4GO.js → trace-LXXCMZJA.js} +3 -3
  276. package/dist/transfer/backup.js +2 -2
  277. package/dist/transfer/capsule-export.js +2 -2
  278. package/dist/transfer/capsule-import.js +3 -3
  279. package/dist/transfer/export-okf.js +12 -12
  280. package/dist/{tui-55UJMJX7.js → tui-2Z5WOV7L.js} +3 -3
  281. package/dist/tui-2Z5WOV7L.js.map +1 -0
  282. package/dist/verified-recall.js +11 -11
  283. package/dist/who-knows-cli.js +12 -12
  284. package/dist/who-knows.js +11 -11
  285. package/dist/write-envelope.js +4 -4
  286. package/package.json +2 -2
  287. package/src/access-http-offline-stream.ts +3 -0
  288. package/src/access-mcp-output-schemas.ts +1 -0
  289. package/src/access-service.ts +7 -18
  290. package/src/console/state.test.ts +56 -0
  291. package/src/console/state.ts +83 -0
  292. package/src/embedding-fallback.ts +325 -141
  293. package/src/embedding-generation-lock.ts +141 -0
  294. package/src/embedding-index-storage.test.ts +245 -0
  295. package/src/embedding-index-storage.ts +1068 -0
  296. package/src/index.ts +3 -3
  297. package/src/offline-sync-changeset-generations.test.ts +962 -0
  298. package/src/offline-sync-changeset-generations.ts +549 -0
  299. package/src/offline-sync-embedding-generation.test.ts +2173 -0
  300. package/src/offline-sync-embedding-generation.ts +1153 -0
  301. package/src/offline-sync-exclude-globs.ts +112 -0
  302. package/src/offline-sync-file-io.ts +48 -0
  303. package/src/offline-sync-runtime-state.ts +48 -0
  304. package/src/offline-sync-snapshot-stream.ts +99 -0
  305. package/src/offline-sync-upload-staging.ts +291 -0
  306. package/src/offline-sync.test.ts +164 -1
  307. package/src/offline-sync.ts +372 -454
  308. package/src/orchestrator.ts +2 -1
  309. package/src/secure-store/secure-fs.ts +26 -6
  310. package/src/storage.ts +11 -6
  311. package/src/support-passport/offline-sync-guard.ts +16 -2
  312. package/dist/chunk-3BTFBQLO.js.map +0 -1
  313. package/dist/chunk-AREGMX2J.js +0 -50
  314. package/dist/chunk-AREGMX2J.js.map +0 -1
  315. package/dist/chunk-FOH7AMMI.js.map +0 -1
  316. package/dist/chunk-FRCESVV6.js.map +0 -1
  317. package/dist/chunk-G6SVZWPB.js.map +0 -1
  318. package/dist/chunk-JN72EPOE.js.map +0 -1
  319. package/dist/chunk-RB776UH3.js.map +0 -1
  320. package/dist/chunk-WQZZDN5M.js.map +0 -1
  321. /package/dist/{auto-sync-6IPX2BAC.js.map → auto-sync-U5N3CB5Y.js.map} +0 -0
  322. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
  323. /package/dist/{capsule-merge-3MNURAQ3.js.map → capsule-merge-ZCXUPLNJ.js.map} +0 -0
  324. /package/dist/{chunk-JE334SIA.js.map → chunk-22DYJLFN.js.map} +0 -0
  325. /package/dist/{chunk-JGSQ6P4D.js.map → chunk-2FX374UI.js.map} +0 -0
  326. /package/dist/{chunk-SY5KDWGU.js.map → chunk-2WLMWP2T.js.map} +0 -0
  327. /package/dist/{chunk-6TT6HRE4.js.map → chunk-3AGHSQPM.js.map} +0 -0
  328. /package/dist/{chunk-P6WFK4ZJ.js.map → chunk-3O5BB45T.js.map} +0 -0
  329. /package/dist/{chunk-3C3J243X.js.map → chunk-3QKO4QN7.js.map} +0 -0
  330. /package/dist/{chunk-KFCMMDHH.js.map → chunk-3YD44CH2.js.map} +0 -0
  331. /package/dist/{chunk-7DE2NOON.js.map → chunk-4O4XCBOS.js.map} +0 -0
  332. /package/dist/{chunk-GDYU6VF4.js.map → chunk-5AYUHSAU.js.map} +0 -0
  333. /package/dist/{chunk-ICZQG4XE.js.map → chunk-5FAGCOZD.js.map} +0 -0
  334. /package/dist/{chunk-VKQI47DJ.js.map → chunk-5FNZ3G6X.js.map} +0 -0
  335. /package/dist/{chunk-VQNONC4E.js.map → chunk-5STPRPE7.js.map} +0 -0
  336. /package/dist/{chunk-52II7PB6.js.map → chunk-645WOU5I.js.map} +0 -0
  337. /package/dist/{chunk-SRZW7FNU.js.map → chunk-64ZUALXK.js.map} +0 -0
  338. /package/dist/{chunk-4BZP2B4U.js.map → chunk-6MNKDTDI.js.map} +0 -0
  339. /package/dist/{chunk-DUJYRVUD.js.map → chunk-6O2VUWV6.js.map} +0 -0
  340. /package/dist/{chunk-HESDNYRB.js.map → chunk-76M5OK7V.js.map} +0 -0
  341. /package/dist/{chunk-FAN73COB.js.map → chunk-7D7OUO5W.js.map} +0 -0
  342. /package/dist/{chunk-DZ525SNQ.js.map → chunk-7QE46QXI.js.map} +0 -0
  343. /package/dist/{chunk-VX6GCKBS.js.map → chunk-7WBSMXGC.js.map} +0 -0
  344. /package/dist/{chunk-VVDWZRZG.js.map → chunk-A6RVIHHK.js.map} +0 -0
  345. /package/dist/{chunk-M536WNTQ.js.map → chunk-AEJ3TE5H.js.map} +0 -0
  346. /package/dist/{chunk-72JNNJJJ.js.map → chunk-AXC4WSF3.js.map} +0 -0
  347. /package/dist/{chunk-UA5DX5C3.js.map → chunk-AZIOEOK4.js.map} +0 -0
  348. /package/dist/{chunk-N4S4NUX5.js.map → chunk-B3O3IRZN.js.map} +0 -0
  349. /package/dist/{chunk-XTYX6V52.js.map → chunk-BJVO4H5C.js.map} +0 -0
  350. /package/dist/{chunk-7LCGFUMB.js.map → chunk-BUKK5SWA.js.map} +0 -0
  351. /package/dist/{chunk-TM3RXL4G.js.map → chunk-BUQKSQXL.js.map} +0 -0
  352. /package/dist/{chunk-37S3IMYB.js.map → chunk-DGS34QSY.js.map} +0 -0
  353. /package/dist/{chunk-4IEOGIPB.js.map → chunk-DO4C4AXD.js.map} +0 -0
  354. /package/dist/{chunk-SPSRGEDQ.js.map → chunk-EAQQ2FD3.js.map} +0 -0
  355. /package/dist/{chunk-C3QXITIP.js.map → chunk-EQUD7FP4.js.map} +0 -0
  356. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  357. /package/dist/{chunk-YITN5WXI.js.map → chunk-FC52YWAO.js.map} +0 -0
  358. /package/dist/{chunk-ZI7QA4MK.js.map → chunk-FEIXG5FA.js.map} +0 -0
  359. /package/dist/{chunk-CLXTDOKV.js.map → chunk-FUNBJQYR.js.map} +0 -0
  360. /package/dist/{chunk-CLSYE5OZ.js.map → chunk-G2STTRYB.js.map} +0 -0
  361. /package/dist/{chunk-TDYAIXHK.js.map → chunk-H36X4NRH.js.map} +0 -0
  362. /package/dist/{chunk-3GQC3NMB.js.map → chunk-H56VY4BY.js.map} +0 -0
  363. /package/dist/{chunk-PUKTBULQ.js.map → chunk-HWLIMQE2.js.map} +0 -0
  364. /package/dist/{chunk-V2ES4WCZ.js.map → chunk-IIARQTZ2.js.map} +0 -0
  365. /package/dist/{chunk-QEV26L2F.js.map → chunk-IKDRGVVJ.js.map} +0 -0
  366. /package/dist/{chunk-LS3XH3UE.js.map → chunk-IYIBGSHJ.js.map} +0 -0
  367. /package/dist/{chunk-YSJTCOHR.js.map → chunk-J27JML5O.js.map} +0 -0
  368. /package/dist/{chunk-AQK54Q7O.js.map → chunk-JB56XYHP.js.map} +0 -0
  369. /package/dist/{chunk-NE6PHPLG.js.map → chunk-JBH26P62.js.map} +0 -0
  370. /package/dist/{chunk-CWHV574S.js.map → chunk-JUT3OR2L.js.map} +0 -0
  371. /package/dist/{chunk-6ET2VII7.js.map → chunk-JYG2334B.js.map} +0 -0
  372. /package/dist/{chunk-LF5IGOOD.js.map → chunk-JYM4Y6SO.js.map} +0 -0
  373. /package/dist/{chunk-FB64XE3R.js.map → chunk-JZ4TJHR2.js.map} +0 -0
  374. /package/dist/{chunk-EHS5MJEF.js.map → chunk-KTKHO3UI.js.map} +0 -0
  375. /package/dist/{chunk-WIKO4SCR.js.map → chunk-LHRQBMJN.js.map} +0 -0
  376. /package/dist/{chunk-K4J4E7WV.js.map → chunk-LO4UKPKX.js.map} +0 -0
  377. /package/dist/{chunk-6NQ4LSMS.js.map → chunk-MGZMC6WN.js.map} +0 -0
  378. /package/dist/{chunk-BMYZTZ5D.js.map → chunk-MY7B3HYC.js.map} +0 -0
  379. /package/dist/{chunk-NB3LRY4T.js.map → chunk-N4PYB235.js.map} +0 -0
  380. /package/dist/{chunk-S3YM4SZQ.js.map → chunk-N7QV3CMX.js.map} +0 -0
  381. /package/dist/{chunk-L6UZEAID.js.map → chunk-NJVR724O.js.map} +0 -0
  382. /package/dist/{chunk-VLLD4TOB.js.map → chunk-NMZVDRVP.js.map} +0 -0
  383. /package/dist/{chunk-P2WZHLUF.js.map → chunk-NOL7SEWN.js.map} +0 -0
  384. /package/dist/{chunk-2NLLXCJG.js.map → chunk-OWHERGF2.js.map} +0 -0
  385. /package/dist/{chunk-6L3PBSAR.js.map → chunk-PMNLL2TL.js.map} +0 -0
  386. /package/dist/{chunk-VYCOJKSX.js.map → chunk-PO7NXNGC.js.map} +0 -0
  387. /package/dist/{chunk-4IIMMZZK.js.map → chunk-PULDSMCJ.js.map} +0 -0
  388. /package/dist/{chunk-HBBHL4YF.js.map → chunk-QOXNTKXB.js.map} +0 -0
  389. /package/dist/{chunk-CUCWX3MO.js.map → chunk-QZLCVO2V.js.map} +0 -0
  390. /package/dist/{chunk-NE7I2RCT.js.map → chunk-R3X2T2LP.js.map} +0 -0
  391. /package/dist/{chunk-S7GCT7NU.js.map → chunk-RBITPCMK.js.map} +0 -0
  392. /package/dist/{chunk-EVBI7S4J.js.map → chunk-RMDGAAZF.js.map} +0 -0
  393. /package/dist/{chunk-BJRAELDH.js.map → chunk-SCXURCVT.js.map} +0 -0
  394. /package/dist/{chunk-EQ6YOIKO.js.map → chunk-ST2RQUXC.js.map} +0 -0
  395. /package/dist/{chunk-CS52MFDV.js.map → chunk-SZG4WFAG.js.map} +0 -0
  396. /package/dist/{chunk-BFO3IR56.js.map → chunk-TPEZ2SHV.js.map} +0 -0
  397. /package/dist/{chunk-YRLHEEQT.js.map → chunk-U6VC6RP3.js.map} +0 -0
  398. /package/dist/{chunk-MOQPEFLN.js.map → chunk-UP75KL3L.js.map} +0 -0
  399. /package/dist/{chunk-W4AOR52H.js.map → chunk-UQDC3W77.js.map} +0 -0
  400. /package/dist/{chunk-X7Y7WX73.js.map → chunk-UVYI6VIX.js.map} +0 -0
  401. /package/dist/{chunk-D5F7TKMH.js.map → chunk-V4JZCTMK.js.map} +0 -0
  402. /package/dist/{chunk-MQJL6CDZ.js.map → chunk-VA4O3U7U.js.map} +0 -0
  403. /package/dist/{chunk-4CDYONWO.js.map → chunk-VDO32NLK.js.map} +0 -0
  404. /package/dist/{chunk-SOZUJCME.js.map → chunk-VHDKPGMG.js.map} +0 -0
  405. /package/dist/{chunk-QWW2JDDX.js.map → chunk-WNRTXEWW.js.map} +0 -0
  406. /package/dist/{chunk-NR2ASHBL.js.map → chunk-XOV7NRY5.js.map} +0 -0
  407. /package/dist/{chunk-KP7SANCF.js.map → chunk-YPI4RQWV.js.map} +0 -0
  408. /package/dist/{chunk-JKMHZF3Q.js.map → chunk-YSWJXOOL.js.map} +0 -0
  409. /package/dist/{chunk-LQG5O5EP.js.map → chunk-Z3O53WZR.js.map} +0 -0
  410. /package/dist/{chunk-QVS22WTO.js.map → chunk-ZG5DWWKK.js.map} +0 -0
  411. /package/dist/{graph-edge-decay-KQGS3L3B.js.map → embedding-generation-lock.js.map} +0 -0
  412. /package/dist/{resolution-DNX7FO7A.js.map → embedding-index-storage.js.map} +0 -0
  413. /package/dist/{first-start-migration-7OATHJKB.js.map → first-start-migration-GAT7G733.js.map} +0 -0
  414. /package/dist/{state-ITXJKXRE.js.map → graph-edge-decay-MKIRZ762.js.map} +0 -0
  415. /package/dist/{tui-55UJMJX7.js.map → offline-sync-changeset-generations.js.map} +0 -0
  416. /package/dist/{trace-ZFVRH4GO.js.map → trace-LXXCMZJA.js.map} +0 -0
@@ -0,0 +1,141 @@
1
+ // Cross-process embedding-generation mutation lock (issue #3148, Codex P1).
2
+ //
3
+ // Two processes legitimately mutate the SAME embedding generation: the
4
+ // daemon's EmbeddingFallback index mutations (load → modify → persist) and
5
+ // the offline-sync apply transaction (census → stage → swap) — plus the
6
+ // snapshot walkers that must capture one COMPLETE generation. All of them
7
+ // enumerate or replace `state/embeddings/` (or the legacy marker), so an
8
+ // unsynchronized writer's bytes are silently discarded by the other side's
9
+ // wholesale directory swap.
10
+ //
11
+ // Built directly on the existing utilities: `serializeMutations` (in-process
12
+ // keyed chain, rejection-recovering — gotcha #40) around `withHeldFileLock`
13
+ // (cross-process held advisory lock, replacement-safe stale breaking,
14
+ // ownership-checked release). A lock that cannot be acquired within the
15
+ // bounded wait FAILS CLOSED — proceeding unsynchronized would reintroduce
16
+ // the lost-write race this lock exists to prevent.
17
+ //
18
+ // The lock is keyed by the PHYSICAL state directory (dirname of the
19
+ // generation's shard dir / of the legacy marker), so every caller
20
+ // convention — memory-root-relative (`namespaces/<ns>/state`) or
21
+ // namespace-scoped roots (`<ns>/state`) — contends on the SAME lock file
22
+ // for the same generation. The lock file lives under that state dir's
23
+ // `.offline-sync/` (sync-internal, excluded from every offline-sync
24
+ // snapshot by the default excludes).
25
+ import path from "node:path";
26
+ import { type HeldFileLockController, serializeMutations, withHeldFileLock } from "./utils/serialize-mutations.js";
27
+
28
+ /** Stale-lock window for a crashed holder (mirrors the graph lock). */
29
+ const LOCK_STALE_MS = 30_000;
30
+ /** Bounded acquisition wait; a busy lock FAILS the operation (strict). */
31
+ const LOCK_MAX_WAIT_MS = 10_000;
32
+
33
+ /** Control surface handed to a held embedding-generation lock section. */
34
+ export type EmbeddingGenerationLockSection = HeldFileLockController;
35
+
36
+ /**
37
+ * A fence the store calls immediately before a destructive write: reasserts
38
+ * lock ownership so a peer that stale-broke and replaced the lock aborts
39
+ * this write instead of clobbering the peer's.
40
+ */
41
+ export type EmbeddingGenerationFence = () => Promise<void>;
42
+
43
+ /**
44
+ * A held embedding-generation lock section was lost mid-critical-section —
45
+ * a peer stale-broke and replaced it. Abort; never publish.
46
+ */
47
+ export class EmbeddingGenerationLockLostError extends Error {
48
+ constructor(lockPath: string) {
49
+ super(
50
+ `embedding generation: the mutation lock for ${lockPath} was lost mid-section — a peer stale-broke and replaced it; aborting instead of clobbering the peer's write`
51
+ );
52
+ this.name = "EmbeddingGenerationLockLostError";
53
+ }
54
+ }
55
+
56
+ /** The generation lock could not be acquired; callers fail closed and report it. */
57
+ export class EmbeddingGenerationLockUnavailableError extends Error {
58
+ constructor(lockPath: string, reason: "timeout" | "error") {
59
+ super(`embedding generation: could not acquire the mutation lock for ${lockPath} (${reason})`);
60
+ this.name = "EmbeddingGenerationLockUnavailableError";
61
+ }
62
+ }
63
+
64
+ /**
65
+ * The lock file path for the generation whose canonical state dir is
66
+ * `absoluteStateDir` (physical path; e.g. `<memoryRoot>/state` or
67
+ * `<memoryRoot>/namespaces/<ns>/state`). Keyed by the physical directory so
68
+ * every root convention contends on one lock file per generation.
69
+ */
70
+ export function embeddingGenerationLockPath(absoluteStateDir: string): string {
71
+ const stateDir = path.resolve(absoluteStateDir);
72
+ return path.join(stateDir, ".offline-sync", "locks", "embedding-generation.lock");
73
+ }
74
+
75
+ /**
76
+ * Revalidate lock ownership immediately before a destructive publish.
77
+ * Throws {@link EmbeddingGenerationLockLostError} when the lock was
78
+ * broken/replaced, so the caller aborts and leaves the peer's write intact.
79
+ */
80
+ export async function assertEmbeddingGenerationLockHeld(
81
+ lockPath: string,
82
+ section: EmbeddingGenerationLockSection
83
+ ): Promise<void> {
84
+ if (!(await section.refresh())) throw new EmbeddingGenerationLockLostError(lockPath);
85
+ }
86
+
87
+ /**
88
+ * Run `fn` while holding the embedding-generation mutation lock for the
89
+ * generation whose state dir is `absoluteStateDir`, handing it the section
90
+ * controller. `serializeMutations` provides the in-process keyed chain
91
+ * INSIDE the cross-process advisory lock, so peers serialize against every
92
+ * section this process runs, one at a time. Times out fail-closed.
93
+ */
94
+ export function withEmbeddingGenerationLock<T>(
95
+ absoluteStateDir: string,
96
+ fn: (lock: EmbeddingGenerationLockSection) => Promise<T>
97
+ ): Promise<T> {
98
+ const lockPath = embeddingGenerationLockPath(absoluteStateDir);
99
+ return serializeMutations(lockPath, () =>
100
+ withHeldFileLock(lockPath, { staleMs: LOCK_STALE_MS, maxWaitMs: LOCK_MAX_WAIT_MS }, (acquired, lock) => {
101
+ if (!acquired) {
102
+ throw new EmbeddingGenerationLockUnavailableError(lockPath, lock.failure ?? "error");
103
+ }
104
+ return fn(lock);
105
+ })
106
+ );
107
+ }
108
+
109
+ /** Capture a complete bounded digest census under lock, then stream outside
110
+ * the lock. If any member changes, throw rather than emit a complete subset. */
111
+ export async function* withEmbeddingGenerationLockIter<T extends { path: string; sha256: string; bytes: number }>(
112
+ absoluteStateDir: string,
113
+ make: (includeContent: boolean) => AsyncIterable<T>,
114
+ includeContent: boolean,
115
+ verify?: () => Promise<void>
116
+ ): AsyncIterable<T> {
117
+ const lockPath = embeddingGenerationLockPath(absoluteStateDir);
118
+ // Capture only bounded metadata under the lock, never large content or
119
+ // network backpressure. Every emitted record must match this complete
120
+ // generation census; a concurrent swap/dirty write aborts the stream.
121
+ const baseline = await withEmbeddingGenerationLock(absoluteStateDir, async (section) => {
122
+ await verify?.();
123
+ const records: Array<{ path: string; sha256: string; bytes: number }> = [];
124
+ for await (const record of make(false)) {
125
+ await assertEmbeddingGenerationLockHeld(lockPath, section);
126
+ records.push({ path: record.path, sha256: record.sha256, bytes: record.bytes });
127
+ }
128
+ return records;
129
+ });
130
+ let count = 0;
131
+ for await (const record of make(includeContent)) {
132
+ const expected = baseline[count++];
133
+ if (!expected || expected.path !== record.path || expected.sha256 !== record.sha256 || expected.bytes !== record.bytes) {
134
+ throw new Error(`embedding generation changed while streaming ${absoluteStateDir}; retry the snapshot`);
135
+ }
136
+ yield record;
137
+ }
138
+ if (count !== baseline.length) {
139
+ throw new Error(`embedding generation changed while streaming ${absoluteStateDir}; retry the snapshot`);
140
+ }
141
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Issue #3148 review round 1: detectLayout() must decide the layout on the
3
+ * FIRST call after a restart inside the replacement rename gap — rolling the
4
+ * transaction backup back into place and returning "sharded" immediately, so
5
+ * a stray legacy embeddings.json never wins. Higher-level search tests can
6
+ * hide this because a second detectLayout() call inside the same operation
7
+ * sees the already-rolled-back directory.
8
+ */
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ import { lstat, mkdtemp, mkdir, rm, stat, symlink, writeFile } from "node:fs/promises";
14
+ import { EmbeddingIndexFileStore, EmbeddingIndexStorageError } from "./embedding-index-storage.js";
15
+
16
+ const SHARD_FILE = JSON.stringify({
17
+ version: 1,
18
+ provider: "openai",
19
+ model: "text-embedding-3-small",
20
+ entries: { "mem-old": { vector: [1, 0], path: "facts/old.md" } },
21
+ });
22
+
23
+ function newStore(memoryDir: string): EmbeddingIndexFileStore {
24
+ const stateDir = path.join(memoryDir, "state");
25
+ return new EmbeddingIndexFileStore(
26
+ path.join(stateDir, "embeddings.json"),
27
+ path.join(stateDir, "embeddings"),
28
+ path.join(stateDir, "embedding-fallback-status.json"),
29
+ );
30
+ }
31
+
32
+ test("detectLayout returns sharded on the first post-gap call and ignores a stray legacy file", async () => {
33
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-emb3146-gapfirst-"));
34
+ const store = newStore(memoryDir);
35
+ const stateDir = path.join(memoryDir, "state");
36
+ try {
37
+ const backupDir = path.join(stateDir, "embeddings.pre-replace.tmp");
38
+ await mkdir(backupDir, { recursive: true });
39
+ await writeFile(path.join(backupDir, "shard-0058.json"), SHARD_FILE, "utf-8");
40
+ await writeFile(
41
+ path.join(stateDir, "embeddings.json"),
42
+ JSON.stringify({
43
+ version: 1,
44
+ provider: "openai",
45
+ model: "text-embedding-3-small",
46
+ entries: { "mem-stale": { vector: [0, 1], path: "facts/stale.md" } },
47
+ }),
48
+ "utf-8",
49
+ );
50
+
51
+ // First post-gap call reports SHARDED without writing: reads fail open
52
+ // to an empty view (the gap generation cannot be enumerated) and the
53
+ // stray legacy file never wins.
54
+ assert.equal(await store.detectLayout(), "sharded");
55
+ let probe: Record<string, { vector: number[]; path: string }> = {};
56
+ const identity = await store.readShardGenerationInto(probe);
57
+ assert.equal(identity, null);
58
+ assert.deepEqual(probe, {});
59
+ try {
60
+ await stat(path.join(stateDir, "embeddings"));
61
+ assert.fail("detectLayout must not write on a read path");
62
+ } catch (err) {
63
+ assert.equal((err as NodeJS.ErrnoException).code, "ENOENT");
64
+ }
65
+
66
+ // The mutation wrapper performs the recovery (restored generation
67
+ // becomes the loaded view) and lands the write on it: old + new.
68
+ assert.equal(await store.recoverIfInterrupted(), true);
69
+ const loaded = await (async () => {
70
+ const merged: Record<string, { vector: number[]; path: string }> = {};
71
+ await store.readShardGenerationInto(merged);
72
+ return merged;
73
+ })();
74
+ loaded["mem-new"] = { vector: [1, 1], path: "facts/new.md" };
75
+ await store.persist(
76
+ {
77
+ version: 1,
78
+ provider: "openai",
79
+ model: "text-embedding-3-small",
80
+ entries: loaded,
81
+ },
82
+ { touchedIds: ["mem-new"], memoryId: "mem-new" },
83
+ );
84
+ assert.equal(await store.detectLayout(), "sharded");
85
+ probe = {};
86
+ const identityAfter = await store.readShardGenerationInto(probe);
87
+ assert.deepEqual(identityAfter, { provider: "openai", model: "text-embedding-3-small" });
88
+ assert.deepEqual(Object.keys(probe).sort(), ["mem-new", "mem-old"]);
89
+ assert.equal(probe["mem-old"].path, "facts/old.md");
90
+ } finally {
91
+ await rm(memoryDir, { recursive: true, force: true }).catch(() => undefined);
92
+ }
93
+ });
94
+
95
+ test("a corrupt replacement backup shape surfaces as a tagged storage error, never the legacy fallback", async () => {
96
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-emb3146-gapbackup-"));
97
+ const store = newStore(memoryDir);
98
+ try {
99
+ const stateDir = path.join(memoryDir, "state");
100
+ await mkdir(stateDir, { recursive: true });
101
+ // `embeddings.pre-replace.tmp` exists but is NOT a directory: the
102
+ // rollback moves that junk file into the published position, where the
103
+ // shard enumeration must fail loudly (tagged) instead of degrading to
104
+ // the legacy fallback.
105
+ await writeFile(path.join(stateDir, "embeddings.pre-replace.tmp"), "junk", "utf-8");
106
+ // The mutation wrapper's recovery moves the junk file into the published
107
+ // position; the subsequent persist must then fail loudly (tagged) when
108
+ // shard enumeration hits it, instead of degrading to the legacy
109
+ // fallback.
110
+ assert.equal(await store.recoverIfInterrupted(), true);
111
+ await assert.rejects(
112
+ store.persist({
113
+ version: 1,
114
+ provider: "openai",
115
+ model: "text-embedding-3-small",
116
+ entries: {},
117
+ }),
118
+ (err: NodeJS.ErrnoException) => err.name === "EmbeddingIndexStorageError",
119
+ );
120
+ } finally {
121
+ await rm(memoryDir, { recursive: true, force: true }).catch(() => undefined);
122
+ }
123
+ });
124
+
125
+ test("removing an index on an empty store publishes an empty layout marker", async () => {
126
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-remove-empty-"));
127
+ try {
128
+ const store = newStore(root);
129
+ await store.removePublishedGeneration();
130
+ assert.equal(await newStore(root).detectLayout(), "sharded");
131
+ } finally {
132
+ await rm(root, { recursive: true, force: true });
133
+ }
134
+ });
135
+
136
+ test("misplaced shard entries record read diagnostics before failing closed", async () => {
137
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-misplaced-status-"));
138
+ try {
139
+ await mkdir(path.join(root, "state/embeddings"), { recursive: true });
140
+ // mem-old hashes to shard-0058, not 0000.
141
+ await writeFile(path.join(root, "state/embeddings/shard-0000.json"), SHARD_FILE);
142
+ const store = newStore(root);
143
+ await assert.rejects(() => store.readShardGenerationInto({}), /shard/);
144
+ const { readFile } = await import("node:fs/promises");
145
+ const status = JSON.parse(await readFile(path.join(root, "state/embedding-fallback-status.json"), "utf-8"));
146
+ assert.match(status.lastReadRecovery.message, /shard-0000/);
147
+ assert.equal(await readFile(path.join(root, "state/embeddings/shard-0000.json"), "utf-8"), SHARD_FILE);
148
+ } finally {
149
+ await rm(root, { recursive: true, force: true });
150
+ }
151
+ });
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Codex PRRT_kwDORJXyws6mZ72r — symlinked generation dir / members are
155
+ // rejected at read and mutation boundaries; planted links must never serve
156
+ // or write through to paths outside the state directory.
157
+ // ---------------------------------------------------------------------------
158
+
159
+ test("a symlinked embedding shard directory is rejected at the layout and read boundaries", async () => {
160
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-emb-symlink-dir-"));
161
+ const outside = await mkdtemp(path.join(os.tmpdir(), "remnic-emb-symlink-outside-"));
162
+ try {
163
+ // Use a shard whose entry id hashes to the same shard name so a
164
+ // successful parse would otherwise validate; the symlink boundary must
165
+ // throw BEFORE any shard membership check.
166
+ const shardNameForId = (id: string): string => {
167
+ let h = 0x811c9dc5;
168
+ for (let i = 0; i < id.length; i++) {
169
+ h ^= id.charCodeAt(i);
170
+ h = Math.imul(h, 0x01000193) >>> 0;
171
+ }
172
+ return `shard-${String(h % 64).padStart(4, "0")}.json`;
173
+ };
174
+ const fixtureId = "mem-old";
175
+ const fixtureShard = shardNameForId(fixtureId);
176
+ await mkdir(path.join(outside, "embeddings"), { recursive: true });
177
+ await writeFile(
178
+ path.join(outside, "embeddings", fixtureShard),
179
+ JSON.stringify({
180
+ version: 1,
181
+ provider: "openai",
182
+ model: "text-embedding-3-small",
183
+ entries: { [fixtureId]: { vector: [1, 0], path: "facts/old.md" } },
184
+ }),
185
+ "utf-8",
186
+ );
187
+ const stateDir = path.join(memoryDir, "state");
188
+ await mkdir(stateDir, { recursive: true });
189
+ await symlink(path.join(outside, "embeddings"), path.join(stateDir, "embeddings"), "dir");
190
+ assert.equal((await (await import("node:fs/promises")).lstat(path.join(stateDir, "embeddings"))).isSymbolicLink(), true, "fixture assumption: state/embeddings must be a symlink");
191
+ const store = newStore(memoryDir);
192
+
193
+ await assert.rejects(
194
+ () => store.detectLayout(),
195
+ (err: unknown) => err instanceof EmbeddingIndexStorageError && /symlink/.test(err.message),
196
+ );
197
+ await assert.rejects(
198
+ () => store.readShardGenerationInto({}),
199
+ (err: unknown) => err instanceof EmbeddingIndexStorageError && /symlink/.test(err.message),
200
+ );
201
+ } finally {
202
+ await rm(memoryDir, { recursive: true, force: true });
203
+ await rm(outside, { recursive: true, force: true });
204
+ }
205
+ });
206
+
207
+ test("a symlinked shard member pointing outside the state dir is rejected on read", async () => {
208
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-emb-symlink-mem-"));
209
+ const outside = await mkdtemp(path.join(os.tmpdir(), "remnic-emb-symlink-mem-out-"));
210
+ try {
211
+ const shardDir = path.join(memoryDir, "state", "embeddings");
212
+ await mkdir(shardDir, { recursive: true });
213
+ const fixtureId = "mem-old";
214
+ let h = 0x811c9dc5;
215
+ for (let i = 0; i < fixtureId.length; i++) {
216
+ h ^= fixtureId.charCodeAt(i);
217
+ h = Math.imul(h, 0x01000193) >>> 0;
218
+ }
219
+ const fixtureShard = `shard-${String(h % 64).padStart(4, "0")}.json`;
220
+ await writeFile(
221
+ path.join(outside, "planted.json"),
222
+ JSON.stringify({
223
+ version: 1,
224
+ provider: "openai",
225
+ model: "text-embedding-3-small",
226
+ entries: { [fixtureId]: { vector: [1, 0], path: "facts/old.md" } },
227
+ }),
228
+ "utf-8",
229
+ );
230
+ await symlink(
231
+ path.join(outside, "planted.json"),
232
+ path.join(shardDir, fixtureShard),
233
+ );
234
+ const store = newStore(memoryDir);
235
+ await assert.rejects(
236
+ () => store.readShardGenerationInto({}),
237
+ (err: unknown) =>
238
+ err instanceof EmbeddingIndexStorageError &&
239
+ (/symlink/.test(err.message) || /escapes the state directory/.test(err.message)),
240
+ );
241
+ } finally {
242
+ await rm(memoryDir, { recursive: true, force: true });
243
+ await rm(outside, { recursive: true, force: true });
244
+ }
245
+ });