@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
@@ -24,7 +24,36 @@ import {
24
24
  type SafeArchiveRoot,
25
25
  } from "./transfer/fs-utils.js";
26
26
  import { parseFlexibleIsoTimestamp } from "./utils/iso-timestamp.js";
27
- import { DEFAULT_OFFLINE_SYNC_EXCLUDE_GLOBS } from "./offline-sync-exclude-globs.js";
27
+ import { EmbeddingIndexStorageError } from "./embedding-index-storage.js";
28
+ import { matchesOfflineSyncDefaultExclude } from "./offline-sync-exclude-globs.js";
29
+ import {
30
+ applyIncomingEmbeddingGenerations,
31
+ assertEmbeddingGenerationStillIncluded,
32
+ computeOmittedEmbeddingGenerationPaths,
33
+ divergedEmbeddingGenerationDeferrals,
34
+ embeddingGenerationMembership,
35
+ isEmbeddingGenerationDirPath,
36
+ isPathInOmittedEmbeddingGeneration,
37
+ } from "./offline-sync-embedding-generation.js";
38
+ import {
39
+ applyChangesetEmbeddingGenerations,
40
+ normalizeChangesetGenerations,
41
+ type OfflineSyncChangesetGeneration,
42
+ } from "./offline-sync-changeset-generations.js";
43
+ import { withEmbeddingGenerationLock, withEmbeddingGenerationLockIter } from "./embedding-generation-lock.js";
44
+ import {
45
+ isCanonicalRuntimeStatePath,
46
+ shouldDeleteAbsentIncomingOfflineRuntimeFile,
47
+ shouldPreferIncomingOfflineRuntimeFile,
48
+ } from "./offline-sync-runtime-state.js";
49
+ export { shouldPreferIncomingOfflineRuntimeFile } from "./offline-sync-runtime-state.js";
50
+ export { computeOmittedEmbeddingGenerationPaths } from "./offline-sync-embedding-generation.js";
51
+ export type { OfflineSyncChangesetGeneration } from "./offline-sync-changeset-generations.js";
52
+ export {
53
+ compileOfflineSyncExcludeGlobs,
54
+ globToRegExp,
55
+ parseOfflineSyncExcludes,
56
+ } from "./offline-sync-exclude-globs.js";
28
57
  import {
29
58
  isEncryptedOfflineSyncFile,
30
59
  readPlainOfflineSyncFileChunk,
@@ -33,8 +62,21 @@ import {
33
62
  type OfflineSyncExcludeFile,
34
63
  type OfflineSyncFileTarget,
35
64
  plainFileDigest,
65
+ SYNC_INTERNAL_DIR,
66
+ setSafeFileMtime,
67
+ assertNonNegativeFinite,
68
+ assertOfflineSyncMtimeMs,
69
+ OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS,
36
70
  } from "./offline-sync-file-io.js";
37
- import { CENSUS_MAX_MTIME_MS, isCensusMtimeMs, isSha256Hex } from "./census-validation.js";
71
+ import { CENSUS_MAX_MTIME_MS, isSha256Hex } from "./census-validation.js";
72
+ import {
73
+ cleanupOfflineUpload,
74
+ digestOfflineUploadStagingContent,
75
+ pruneOfflineUploadStaging,
76
+ writeOfflineUploadChunk,
77
+ writeSafeFileFromUpload,
78
+ hashText,
79
+ } from "./offline-sync-upload-staging.js";
38
80
  export type { OfflineSyncExcludeFile, OfflineSyncFileTarget } from "./offline-sync-file-io.js";
39
81
 
40
82
  export const OFFLINE_SYNC_SNAPSHOT_FORMAT = "remnic.offline-sync.snapshot.v1";
@@ -76,6 +118,14 @@ export interface OfflineSyncSnapshot {
76
118
  includeTranscripts: boolean;
77
119
  files: OfflineSyncFileRecord[];
78
120
  deletions?: OfflineSyncDeletionRevision[];
121
+ /**
122
+ * Embedding generation dirs whose on-disk members were ALL omitted from
123
+ * `files` because a push filter excluded at least one member (PR #3148
124
+ * round 4). Their absence here is NOT a delete instruction: a receiver
125
+ * keeps its local copy and its base entries until the generation is
126
+ * pushed unfiltered or genuinely deleted with deletion metadata.
127
+ */
128
+ omittedEmbeddingGenerationDirs?: string[];
79
129
  }
80
130
 
81
131
  export type OfflineSyncChange =
@@ -99,6 +149,13 @@ export interface OfflineSyncChangeset {
99
149
  sourceId: string;
100
150
  includeTranscripts: boolean;
101
151
  changes: OfflineSyncChange[];
152
+ /**
153
+ * Complete post-change membership of every embedding generation the
154
+ * changeset touches (issue #3150). Absent on legacy senders: receivers
155
+ * defer manifest-less shard upserts as conflicts instead of publishing a
156
+ * mixed generation.
157
+ */
158
+ embeddingGenerations?: OfflineSyncChangesetGeneration[];
102
159
  }
103
160
 
104
161
  export interface OfflineSyncState {
@@ -119,7 +176,12 @@ export interface OfflineSyncConflict {
119
176
  | "remote_exists_for_local_create"
120
177
  | "remote_changed_for_local_update"
121
178
  | "remote_deleted_for_local_update"
122
- | "remote_changed_for_local_delete";
179
+ | "remote_changed_for_local_delete"
180
+ // Changeset generation deferrals (#3150): the receiver refused a whole
181
+ // embedding generation; surfaced as conflicts so the push side never
182
+ // checkpoints it as applied.
183
+ | "embedding_generation_diverged"
184
+ | "embedding_generation_manifest_required";
123
185
  baseSha256?: string;
124
186
  localSha256?: string;
125
187
  incomingSha256?: string;
@@ -166,7 +228,21 @@ export interface OfflineSyncFileWriteChunksTarget extends OfflineSyncFileTarget
166
228
  chunks: AsyncIterable<Buffer>;
167
229
  }
168
230
 
169
- export interface OfflineSyncFileStagingWriteTarget extends OfflineSyncFileWriteTarget {}
231
+ export interface OfflineSyncFileStagingWriteTarget extends OfflineSyncFileWriteTarget {
232
+ /**
233
+ * ABSOLUTE final path whose canonical AAD the staged ciphertext binds
234
+ * (secure-store deployments): the offline-sync embedding generation
235
+ * transaction stages under a staging dir and publishes by rename, so the
236
+ * ciphertext must decrypt at the FINAL path. The AAD derives through the
237
+ * same filePathAad logic as ordinary reads (platform-consistent).
238
+ */
239
+ finalAadFilePath?: string;
240
+ }
241
+
242
+ export interface OfflineSyncFileStagingReadTarget extends OfflineSyncFileTarget {
243
+ /** See {@link OfflineSyncFileStagingWriteTarget.finalAadFilePath}. */
244
+ finalAadFilePath?: string;
245
+ }
170
246
 
171
247
  export interface OfflineSyncFileContentChunk extends Omit<OfflineSyncFileState, "sha256"> {
172
248
  sha256?: string;
@@ -205,9 +281,6 @@ interface OfflineSyncFileRecordOptions {
205
281
  signal?: AbortSignal;
206
282
  }
207
283
 
208
- const SYNC_INTERNAL_DIR = ".offline-sync";
209
- const OFFLINE_SYNC_UPLOAD_STAGING_MAX_AGE_MS = 24 * 60 * 60 * 1000;
210
- const OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS = 1_000;
211
284
  const OFFLINE_SYNC_FAST_BASE_CTIME_TOLERANCE_MS = 1;
212
285
  const EXCLUDED_FILE_NAMES = new Set([
213
286
  ".sync-state.json",
@@ -228,52 +301,7 @@ const EXCLUDED_FILE_PREFIXES = [
228
301
  * matches zero or more whole segments, and a trailing `dir/star-star`
229
302
  * matches everything under `dir/` at any depth.
230
303
  */
231
- export function globToRegExp(glob: string): RegExp {
232
- if (typeof glob !== "string" || glob.length === 0) {
233
- throw new Error("offlineSyncExcludes entry must be a non-empty string");
234
- }
235
- if (glob.includes("\0")) {
236
- throw new Error("offlineSyncExcludes entry must not contain NUL bytes");
237
- }
238
- let source = "";
239
- for (let i = 0; i < glob.length; i += 1) {
240
- const ch = glob[i];
241
- if (ch === "*") {
242
- if (glob[i + 1] === "*") {
243
- // `**` is cross-segment wherever it appears:
244
- // leading `**/` -> zero or more whole segments
245
- // `/**` at end -> everything under the directory
246
- // `a/**/b` -> any depth between segments
247
- // (Cursor review on PR #1793: trailing `scratch/**` must match
248
- // nested `scratch/a/b.md`, matching the offline-mode guide.)
249
- if (glob[i + 2] === "/") {
250
- source += "(?:.*/)?";
251
- i += 2;
252
- continue;
253
- }
254
- source += ".*";
255
- i += 1;
256
- continue;
257
- }
258
- source += "[^/]*";
259
- continue;
260
- }
261
- if (ch === "?") {
262
- source += "[^/]";
263
- continue;
264
- }
265
- if (ch === "/") {
266
- source += "/";
267
- continue;
268
- }
269
- source += ch.replace(/[\\^$.+()|{}\[\]]/g, "\\$&");
270
- }
271
- return new RegExp(`^${source}$`);
272
- }
273
304
 
274
- function hashText(value: string): string {
275
- return createHash("sha256").update(value).digest("hex");
276
- }
277
305
 
278
306
  function sha256Buffer(buffer: Buffer): { sha256: string; bytes: number } {
279
307
  return sha256Bytes(buffer);
@@ -307,20 +335,7 @@ function assertNonNegativeInteger(value: unknown, field: string): number {
307
335
  return value;
308
336
  }
309
337
 
310
- function assertNonNegativeFinite(value: unknown, field: string): number {
311
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
312
- throw new Error(`${field} must be a non-negative finite number`);
313
- }
314
- return value;
315
- }
316
338
 
317
- function assertOfflineSyncMtimeMs(value: unknown, field: string): number {
318
- const mtimeMs = assertNonNegativeFinite(value, field);
319
- if (!isCensusMtimeMs(mtimeMs)) {
320
- throw new Error(`${field} must be within JavaScript Date range`);
321
- }
322
- return mtimeMs;
323
- }
324
339
 
325
340
  function assertBoolean(value: unknown, field: string): boolean {
326
341
  if (typeof value !== "boolean") {
@@ -407,6 +422,10 @@ function snapshotBuilderDeletions(options: {
407
422
  includeTranscripts: boolean;
408
423
  excludeNodeLocalState?: boolean;
409
424
  userExcludeRegexps?: readonly RegExp[];
425
+ /** Generation dirs omitted by the push enumeration (PR #3148): revisions
426
+ * for ANY member — including already-absent ones — are suppressed, so a
427
+ * filtered generation can never be announced as deleted. */
428
+ omittedGenerationDirs?: ReadonlySet<string>;
410
429
  }): OfflineSyncDeletionRevision[] | undefined {
411
430
  const deletions = normalizeDeletionRevisions(options.deletions, "deletions");
412
431
  if (deletions === undefined) return undefined;
@@ -418,6 +437,7 @@ function snapshotBuilderDeletions(options: {
418
437
  return deletions.filter((deletion) =>
419
438
  (scopedPaths === undefined || scopedPaths.has(deletion.path.toLowerCase())) &&
420
439
  !presentPaths.has(deletion.path.toLowerCase()) &&
440
+ !isDeletionInOmittedGeneration(deletion.path, options.omittedGenerationDirs) &&
421
441
  !(options.excludeNodeLocalState === false
422
442
  ? shouldExcludeRelPath(deletion.path, options.includeTranscripts)
423
443
  : shouldExcludePushRelPath(
@@ -427,11 +447,21 @@ function snapshotBuilderDeletions(options: {
427
447
  )));
428
448
  }
429
449
 
450
+ function isDeletionInOmittedGeneration(
451
+ relPath: string,
452
+ omittedGenerationDirs: ReadonlySet<string> | undefined,
453
+ ): boolean {
454
+ if (!omittedGenerationDirs || omittedGenerationDirs.size === 0) return false;
455
+ const membership = embeddingGenerationMembership(relPath);
456
+ return membership !== null && omittedGenerationDirs.has(membership.shardDir);
457
+ }
458
+
430
459
  export async function filterOfflineSyncDeletionRevisions(options: {
431
460
  root: string;
432
461
  deletions: readonly OfflineSyncDeletionRevision[];
433
462
  includeTranscripts?: boolean;
434
463
  userExcludeRegexps?: readonly RegExp[];
464
+ excludeFile?: OfflineSyncExcludeFile;
435
465
  }): Promise<OfflineSyncDeletionRevision[]> {
436
466
  const deletions = normalizeDeletionRevisions(options.deletions, "deletions");
437
467
  if (deletions === undefined) throw new Error("deletions must be an array");
@@ -442,9 +472,20 @@ export async function filterOfflineSyncDeletionRevisions(options: {
442
472
  "root",
443
473
  );
444
474
  const includeTranscripts = options.includeTranscripts !== false;
475
+ const omission = await computeOmittedEmbeddingGenerationPaths({
476
+ rootAbs: root.abs,
477
+ userExcludeRegexps: options.userExcludeRegexps,
478
+ excludeFile: options.excludeFile,
479
+ isExcludedRelPath: (relPath) =>
480
+ shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps),
481
+ });
482
+ const omittedDirs = new Set(omission.omittedDirs);
445
483
  const filtered: OfflineSyncDeletionRevision[] = [];
446
484
  for (const deletion of deletions) {
447
- if (shouldExcludePushRelPath(deletion.path, includeTranscripts, options.userExcludeRegexps)) {
485
+ if (
486
+ shouldExcludePushRelPath(deletion.path, includeTranscripts, options.userExcludeRegexps) ||
487
+ isDeletionInOmittedGeneration(deletion.path, omittedDirs)
488
+ ) {
448
489
  continue;
449
490
  }
450
491
  const filePath = path.join(root.abs, ...deletion.path.split("/"));
@@ -503,6 +544,26 @@ export function normalizeOfflineSyncSnapshot(
503
544
  if (excludedPath) {
504
545
  throw new Error(`offline sync snapshot contains excluded path: ${excludedPath}`);
505
546
  }
547
+ let omittedEmbeddingGenerationDirs: string[] | undefined;
548
+ if (obj.omittedEmbeddingGenerationDirs !== undefined) {
549
+ if (!Array.isArray(obj.omittedEmbeddingGenerationDirs)) {
550
+ throw new Error("offline sync snapshot omittedEmbeddingGenerationDirs must be an array");
551
+ }
552
+ const seenDirs = new Set<string>();
553
+ for (const [index, entry] of obj.omittedEmbeddingGenerationDirs.entries()) {
554
+ if (
555
+ typeof entry !== "string" ||
556
+ !isEmbeddingGenerationDirPath(entry) ||
557
+ seenDirs.has(entry)
558
+ ) {
559
+ throw new Error(
560
+ `omittedEmbeddingGenerationDirs[${index}] must be a unique canonical embeddings generation dir`,
561
+ );
562
+ }
563
+ seenDirs.add(entry);
564
+ }
565
+ omittedEmbeddingGenerationDirs = [...seenDirs].sort((left, right) => left.localeCompare(right));
566
+ }
506
567
  return {
507
568
  format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
508
569
  schemaVersion: 1,
@@ -511,6 +572,7 @@ export function normalizeOfflineSyncSnapshot(
511
572
  includeTranscripts,
512
573
  files,
513
574
  ...(deletions === undefined ? {} : { deletions }),
575
+ ...(omittedEmbeddingGenerationDirs === undefined ? {} : { omittedEmbeddingGenerationDirs }),
514
576
  };
515
577
  }
516
578
 
@@ -586,6 +648,7 @@ export function normalizeOfflineSyncChangeset(
586
648
  if (excludedPath) {
587
649
  throw new Error(`offline sync changeset contains excluded path: ${excludedPath}`);
588
650
  }
651
+ const embeddingGenerations = normalizeChangesetGenerations(obj.embeddingGenerations, changes);
589
652
  return {
590
653
  format: OFFLINE_SYNC_CHANGESET_FORMAT,
591
654
  schemaVersion: 1,
@@ -593,6 +656,7 @@ export function normalizeOfflineSyncChangeset(
593
656
  sourceId,
594
657
  includeTranscripts,
595
658
  changes: changes.sort(compareByPath),
659
+ ...(embeddingGenerations === undefined ? {} : { embeddingGenerations }),
596
660
  };
597
661
  }
598
662
 
@@ -675,15 +739,7 @@ function shouldExcludePushRelPath(
675
739
 
676
740
  // Precompiled once at module load — this check sits on the hot
677
741
  // enumeration path for every walked file (Kilo review, PR #1793).
678
- const DEFAULT_OFFLINE_SYNC_EXCLUDE_REGEXPS: readonly RegExp[] =
679
- DEFAULT_OFFLINE_SYNC_EXCLUDE_GLOBS.map((glob) => globToRegExp(glob));
680
742
 
681
- function matchesOfflineSyncDefaultExclude(relPosix: string): boolean {
682
- for (const regexp of DEFAULT_OFFLINE_SYNC_EXCLUDE_REGEXPS) {
683
- if (regexp.test(relPosix)) return true;
684
- }
685
- return false;
686
- }
687
743
 
688
744
  /**
689
745
  * Compile operator-supplied `offlineSyncExcludes` glob strings into a
@@ -692,46 +748,6 @@ function matchesOfflineSyncDefaultExclude(relPosix: string): boolean {
692
748
  * a thrown error as a fatal configuration mistake and refuse to start the
693
749
  * sync run rather than silently dropping the bad entry.
694
750
  */
695
- export function compileOfflineSyncExcludeGlobs(
696
- globs: readonly unknown[],
697
- ): RegExp[] {
698
- const out: RegExp[] = [];
699
- for (const entry of globs) {
700
- if (typeof entry !== "string" || entry.length === 0) {
701
- throw new Error("offlineSyncExcludes must contain only non-empty strings");
702
- }
703
- out.push(globToRegExp(entry));
704
- }
705
- return out;
706
- }
707
-
708
- /**
709
- * Validate the operator-supplied offline-sync exclude list (#1786).
710
- * Rejects loudly instead of silently defaulting (CLAUDE.md rule 39):
711
- * a misspelled key value must fail config parse, not be ignored.
712
- * Lives next to the glob compiler so config.ts only carries the call.
713
- */
714
- export function parseOfflineSyncExcludes(raw: unknown): string[] {
715
- if (raw === undefined || raw === null) return [];
716
- if (!Array.isArray(raw)) {
717
- throw new Error(
718
- `offlineSyncExcludes must be an array of non-empty glob strings; got ${typeof raw}`,
719
- );
720
- }
721
- for (const entry of raw) {
722
- if (typeof entry !== "string" || entry.trim().length === 0) {
723
- throw new Error(
724
- "offlineSyncExcludes must contain only non-empty glob strings",
725
- );
726
- }
727
- }
728
- const globs = raw.map((entry) => (entry as string).trim());
729
- // Compile-check every glob now so a bad pattern fails at parse time
730
- // rather than mid-sync. compileOfflineSyncExcludeGlobs throws with a
731
- // per-entry message.
732
- compileOfflineSyncExcludeGlobs(globs);
733
- return globs;
734
- }
735
751
 
736
752
  function shouldIgnoreIncomingRuntimePath(relPosix: string): boolean {
737
753
  if (isInternalRemnicStatePath(relPosix)) return true;
@@ -740,43 +756,6 @@ function shouldIgnoreIncomingRuntimePath(relPosix: string): boolean {
740
756
  return isCanonicalRuntimeStatePath(parts) && basename.includes(".tmp-");
741
757
  }
742
758
 
743
- function isCanonicalRuntimeStatePath(parts: string[]): boolean {
744
- if (parts[0] === "state") return true;
745
- return parts[0] === "namespaces" && parts.length >= 4 && parts[2] === "state";
746
- }
747
-
748
- const REMOTE_AUTHORITATIVE_RUNTIME_STATE_FILES = new Set([
749
- ".artifact-write-version.log",
750
- ".memory-status-version.log",
751
- "buffer.json",
752
- "embeddings.json",
753
- "index_time.json",
754
- "last_intent.json",
755
- "last_qmd_recall.json",
756
- "last_recall.json",
757
- "lcm.sqlite-shm",
758
- "lcm.sqlite-wal",
759
- "memory-lifecycle-ledger.jsonl",
760
- "recall_impressions.jsonl",
761
- ]);
762
-
763
- const ABSENT_INCOMING_RUNTIME_DELETE_FILES = new Set([
764
- "lcm.sqlite-shm",
765
- "lcm.sqlite-wal",
766
- ]);
767
-
768
- export function shouldPreferIncomingOfflineRuntimeFile(relPosix: string): boolean {
769
- const parts = relPosix.split("/");
770
- const basename = parts[parts.length - 1] ?? "";
771
- return isCanonicalRuntimeStatePath(parts) && REMOTE_AUTHORITATIVE_RUNTIME_STATE_FILES.has(basename);
772
- }
773
-
774
- function shouldDeleteAbsentIncomingOfflineRuntimeFile(relPosix: string): boolean {
775
- const parts = relPosix.split("/");
776
- const basename = parts[parts.length - 1] ?? "";
777
- return isCanonicalRuntimeStatePath(parts) && ABSENT_INCOMING_RUNTIME_DELETE_FILES.has(basename);
778
- }
779
-
780
759
  function filterBaseFilesForMode(
781
760
  files: readonly OfflineSyncFileState[],
782
761
  includeTranscripts: boolean,
@@ -850,6 +829,13 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
850
829
  readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
851
830
  excludeFile?: OfflineSyncExcludeFile;
852
831
  userExcludeRegexps?: readonly RegExp[];
832
+ /**
833
+ * Precomputed whole-generation omission set (PR #3148 round 4): paths the
834
+ * walk must skip because their embedding generation lost ANY member to a
835
+ * push filter. When omitted and push excludes are active, the set is
836
+ * computed here so streaming census consumers get the same semantics.
837
+ */
838
+ skipEmbeddingGenerationPaths?: ReadonlySet<string>;
853
839
  /**
854
840
  * When false, enumeration uses only the legacy structural excludes —
855
841
  * the apply/pull-side view of local files. Push-side callers keep the
@@ -864,8 +850,19 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
864
850
  const rootAbs = path.resolve(options.root);
865
851
  const root = await prepareSafeArchiveRoot(rootAbs, "iterateOfflineSyncSnapshotFileRecords", "root");
866
852
  const includeTranscripts = options.includeTranscripts !== false;
867
-
868
- async function* walk(dirAbs: string): AsyncIterable<OfflineSyncFileRecord> {
853
+ const pushExcludes = options.excludeNodeLocalState !== false;
854
+ const skipGenerationPaths = options.skipEmbeddingGenerationPaths ??
855
+ (pushExcludes
856
+ ? (await computeOmittedEmbeddingGenerationPaths({
857
+ rootAbs: root.abs,
858
+ userExcludeRegexps: options.userExcludeRegexps,
859
+ excludeFile: options.excludeFile,
860
+ isExcludedRelPath: (relPath) =>
861
+ shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps),
862
+ })).omittedPaths
863
+ : undefined);
864
+
865
+ async function* walk(dirAbs: string, includeContent = options.includeContent === true): AsyncIterable<OfflineSyncFileRecord> {
869
866
  throwIfOfflineSyncAborted(options.signal);
870
867
  let entries = await readdir(dirAbs, { withFileTypes: true });
871
868
  entries = entries.sort((left, right) => left.name.localeCompare(right.name));
@@ -877,9 +874,17 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
877
874
  ? shouldExcludeRelPath(relPosix, includeTranscripts)
878
875
  : shouldExcludePushRelPath(relPosix, includeTranscripts, options.userExcludeRegexps)
879
876
  ) continue;
877
+ if (skipGenerationPaths?.has(relPosix)) continue;
880
878
  if (entry.isSymbolicLink()) continue;
881
879
  if (entry.isDirectory()) {
882
- yield* walk(abs);
880
+ if (isEmbeddingGenerationDirPath(relPosix)) {
881
+ yield* withEmbeddingGenerationLockIter(path.dirname(abs), (content) => walk(abs, content), includeContent,
882
+ () => assertEmbeddingGenerationStillIncluded(root.abs, relPosix, options.excludeFile,
883
+ (rel) => shouldExcludePushRelPath(rel, includeTranscripts, options.userExcludeRegexps),
884
+ []));
885
+ continue;
886
+ }
887
+ yield* walk(abs, includeContent);
883
888
  continue;
884
889
  }
885
890
  if (!entry.isFile()) continue;
@@ -892,7 +897,7 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
892
897
  root,
893
898
  relPath: relPosix,
894
899
  filePath: abs,
895
- includeContent: options.includeContent === true,
900
+ includeContent,
896
901
  readFile: options.readFile,
897
902
  readFileDigest: options.readFileDigest,
898
903
  signal: options.signal,
@@ -926,8 +931,23 @@ export async function buildOfflineSyncSnapshot(options: {
926
931
  }): Promise<OfflineSyncSnapshot> {
927
932
  throwIfOfflineSyncAborted(options.signal);
928
933
  const includeTranscripts = options.includeTranscripts !== false;
934
+ const pushExcludes = options.excludeNodeLocalState !== false;
935
+ const normalizedDeletions = normalizeDeletionRevisions(options.deletions, "deletions");
936
+ const omission = pushExcludes
937
+ ? await computeOmittedEmbeddingGenerationPaths({
938
+ rootAbs: path.resolve(options.root),
939
+ userExcludeRegexps: options.userExcludeRegexps,
940
+ excludeFile: options.excludeFile,
941
+ tombstonedPaths: (normalizedDeletions ?? []).map((deletion) => deletion.path),
942
+ isExcludedRelPath: (relPath) =>
943
+ shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps),
944
+ })
945
+ : undefined;
929
946
  const files: OfflineSyncFileRecord[] = [];
930
- for await (const file of iterateOfflineSyncSnapshotFileRecords(options)) files.push(file);
947
+ for await (const file of iterateOfflineSyncSnapshotFileRecords({
948
+ ...options,
949
+ skipEmbeddingGenerationPaths: omission?.omittedPaths,
950
+ })) files.push(file);
931
951
  throwIfOfflineSyncAborted(options.signal);
932
952
 
933
953
  const sortedFiles = files.sort(compareByPath);
@@ -937,6 +957,9 @@ export async function buildOfflineSyncSnapshot(options: {
937
957
  includeTranscripts,
938
958
  excludeNodeLocalState: options.excludeNodeLocalState,
939
959
  userExcludeRegexps: options.userExcludeRegexps,
960
+ omittedGenerationDirs: omission && omission.omittedDirs.length > 0
961
+ ? new Set(omission.omittedDirs)
962
+ : undefined,
940
963
  });
941
964
  return {
942
965
  format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
@@ -946,6 +969,9 @@ export async function buildOfflineSyncSnapshot(options: {
946
969
  includeTranscripts,
947
970
  files: sortedFiles,
948
971
  ...(deletions === undefined ? {} : { deletions }),
972
+ ...(omission && omission.omittedDirs.length > 0
973
+ ? { omittedEmbeddingGenerationDirs: omission.omittedDirs }
974
+ : {}),
949
975
  };
950
976
  }
951
977
 
@@ -984,6 +1010,18 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
984
1010
  const baseCapturedAtMs = rawBaseCapturedAtMs !== undefined && Number.isFinite(rawBaseCapturedAtMs)
985
1011
  ? rawBaseCapturedAtMs
986
1012
  : null;
1013
+ const pushExcludes = options.excludeNodeLocalState !== false;
1014
+ const normalizedDeletions = normalizeDeletionRevisions(options.deletions, "deletions");
1015
+ const omission = pushExcludes
1016
+ ? await computeOmittedEmbeddingGenerationPaths({
1017
+ rootAbs: root.abs,
1018
+ userExcludeRegexps: options.userExcludeRegexps,
1019
+ excludeFile: options.excludeFile,
1020
+ tombstonedPaths: (normalizedDeletions ?? []).map((deletion) => deletion.path),
1021
+ isExcludedRelPath: (relPath) =>
1022
+ shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps),
1023
+ })
1024
+ : undefined;
987
1025
  const files: OfflineSyncFileRecord[] = [];
988
1026
 
989
1027
  async function walk(dirAbs: string): Promise<void> {
@@ -999,8 +1037,18 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
999
1037
  ? shouldExcludeRelPath(relPosix, includeTranscripts)
1000
1038
  : shouldExcludePushRelPath(relPosix, includeTranscripts, options.userExcludeRegexps)
1001
1039
  ) continue;
1040
+ if (omission?.omittedPaths.has(relPosix)) continue;
1002
1041
  if (entry.isSymbolicLink()) continue;
1003
1042
  if (entry.isDirectory()) {
1043
+ if (isEmbeddingGenerationDirPath(relPosix)) {
1044
+ await withEmbeddingGenerationLock(path.dirname(abs), async () => {
1045
+ await assertEmbeddingGenerationStillIncluded(root.abs, relPosix, options.excludeFile,
1046
+ (rel) => shouldExcludePushRelPath(rel, includeTranscripts, options.userExcludeRegexps),
1047
+ (normalizedDeletions ?? []).map((deletion) => deletion.path));
1048
+ await walk(abs);
1049
+ });
1050
+ continue;
1051
+ }
1004
1052
  await walk(abs);
1005
1053
  continue;
1006
1054
  }
@@ -1043,6 +1091,9 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
1043
1091
  includeTranscripts,
1044
1092
  excludeNodeLocalState: options.excludeNodeLocalState,
1045
1093
  userExcludeRegexps: options.userExcludeRegexps,
1094
+ omittedGenerationDirs: omission && omission.omittedDirs.length > 0
1095
+ ? new Set(omission.omittedDirs)
1096
+ : undefined,
1046
1097
  });
1047
1098
  return {
1048
1099
  format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
@@ -1052,6 +1103,9 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
1052
1103
  includeTranscripts,
1053
1104
  files: sortedFiles,
1054
1105
  ...(deletions === undefined ? {} : { deletions }),
1106
+ ...(omission && omission.omittedDirs.length > 0
1107
+ ? { omittedEmbeddingGenerationDirs: omission.omittedDirs }
1108
+ : {}),
1055
1109
  };
1056
1110
  }
1057
1111
 
@@ -1491,10 +1545,21 @@ export async function applyOfflineSyncSnapshot(options: {
1491
1545
  currentFiles?: readonly OfflineSyncFileState[];
1492
1546
  deferredPaths?: readonly string[];
1493
1547
  allowMissingConflictContent?: boolean;
1548
+ /**
1549
+ * Explicit INCOMING content source for snapshot records without inline
1550
+ * `contentBase64` (oversized generation members the client fetched
1551
+ * chunk-wise into a private staging root). Consulted only for such
1552
+ * records; return null to keep the metadata-only behavior (the local
1553
+ * file must hash-match the record). Returned buffers are digest-verified
1554
+ * against the incoming record before anything is published.
1555
+ */
1556
+ readIncomingFile?: (target: { path: string; sha256: string; bytes: number }) => Promise<Buffer | null>;
1494
1557
  writeConflictCopies?: boolean;
1495
1558
  readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
1496
1559
  readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
1497
1560
  writeFile?: (target: OfflineSyncFileWriteTarget) => Promise<void>;
1561
+ writeStagingFile?: (target: OfflineSyncFileStagingWriteTarget) => Promise<void>;
1562
+ readStagingFile?: (target: OfflineSyncFileStagingReadTarget) => Promise<Buffer>;
1498
1563
  deleteFile?: (target: OfflineSyncFileDeleteTarget) => Promise<void>;
1499
1564
  recordDeletionRevision?: OfflineSyncRecordDeletionRevision;
1500
1565
  }): Promise<OfflineSyncApplySnapshotResult> {
@@ -1507,8 +1572,9 @@ export async function applyOfflineSyncSnapshot(options: {
1507
1572
  const deletionMtimeByPath = snapshot.deletions === undefined
1508
1573
  ? undefined
1509
1574
  : new Map(snapshot.deletions.map((deletion) => [deletion.path, deletion.mtimeMs] as const));
1510
- const incomingBuffers = verifyRecordContents(snapshot.files, "offline sync snapshot", {
1575
+ const incomingBuffers = await verifyRecordContents(snapshot.files, "offline sync snapshot", {
1511
1576
  requireContent: false,
1577
+ readIncomingFile: options.readIncomingFile,
1512
1578
  });
1513
1579
  const root = await ensureSyncRoot(options.root, "applyOfflineSyncSnapshot");
1514
1580
  const currentFiles = options.currentFiles
@@ -1523,12 +1589,19 @@ export async function applyOfflineSyncSnapshot(options: {
1523
1589
  excludeNodeLocalState: false,
1524
1590
  })).files;
1525
1591
  const currentMap = byPath(currentFiles);
1526
- const deferredPaths = new Set(options.deferredPaths ?? []);
1592
+ const deferredPaths = new Set([...(options.deferredPaths ?? []),
1593
+ ...divergedEmbeddingGenerationDeferrals({
1594
+ incomingFiles: snapshot.files, baseFiles: [...baseMap.values()], currentFiles,
1595
+ }),
1596
+ ]);
1597
+ const omittedGenerationDirs = new Set(snapshot.omittedEmbeddingGenerationDirs ?? []);
1527
1598
  if (deletionMtimeByPath && options.recordDeletionRevision) {
1528
1599
  for (const [relPath, mtimeMs] of deletionMtimeByPath) {
1529
1600
  if (
1530
1601
  currentMap.has(relPath) ||
1531
1602
  deferredPaths.has(relPath) ||
1603
+ (omittedGenerationDirs.size > 0 &&
1604
+ isPathInOmittedEmbeddingGeneration(relPath, omittedGenerationDirs)) ||
1532
1605
  matchesOfflineSyncDefaultExclude(relPath)
1533
1606
  ) {
1534
1607
  continue;
@@ -1554,6 +1627,52 @@ export async function applyOfflineSyncSnapshot(options: {
1554
1627
  return requiredBuffer(incomingBuffers, relPath);
1555
1628
  };
1556
1629
 
1630
+ // Embedding generations travel and replace as ONE unit (issue #3148):
1631
+ // atomic replacement, cross-layout marker conversion, whole-generation
1632
+ // removal on tombstones — all extracted to the generation module (issue
1633
+ // #3150) so the changeset path can share it without growing this file.
1634
+ const { transactionResults, deferredGenerationDirs } = await applyIncomingEmbeddingGenerations({
1635
+ root,
1636
+ incomingMap,
1637
+ incomingBuffers,
1638
+ baseMap,
1639
+ currentMap,
1640
+ deferredPaths,
1641
+ omittedGenerationDirs,
1642
+ deletionMtimeByPath,
1643
+ io: {
1644
+ readFile: options.readFile,
1645
+ writeStagingFile: options.writeStagingFile,
1646
+ readStagingFile: options.readStagingFile,
1647
+ deleteFile: options.deleteFile,
1648
+ },
1649
+ customIoPresent: Boolean(options.writeFile || options.readFile || options.readFileDigest || options.deleteFile),
1650
+ now: Date.now(),
1651
+ });
1652
+
1653
+ const transactionHandled = new Set<string>();
1654
+ let generationUpserted = 0;
1655
+ let generationDeleted = 0;
1656
+ for (const result of transactionResults) {
1657
+ if (result.deferredLocalDivergence) {
1658
+ for (const relPath of result.handledPaths) deferredPaths.add(relPath);
1659
+ deferredGenerationDirs.add(result.shardDirRel);
1660
+ continue;
1661
+ }
1662
+ generationUpserted += result.upserted;
1663
+ generationDeleted += result.deleted;
1664
+ for (const relPath of result.handledPaths) transactionHandled.add(relPath);
1665
+ // Record converted shard paths directly; they may not be in any input map.
1666
+ for (const [relPath, state] of result.writtenStates) {
1667
+ nextBase.set(relPath, state);
1668
+ transactionHandled.add(relPath);
1669
+ }
1670
+ for (const relPath of result.removedPaths) {
1671
+ nextBase.delete(relPath);
1672
+ transactionHandled.add(relPath);
1673
+ }
1674
+ }
1675
+
1557
1676
  for (const relPath of unionPaths(baseMap, incomingMap, currentMap)) {
1558
1677
  const base = baseMap.get(relPath);
1559
1678
  const incoming = incomingMap.get(relPath);
@@ -1566,6 +1685,21 @@ export async function applyOfflineSyncSnapshot(options: {
1566
1685
  continue;
1567
1686
  }
1568
1687
 
1688
+ // The generation transaction already published or removed this path and
1689
+ // applied its nextBase bookkeeping above; nothing left to do.
1690
+ if (transactionHandled.has(relPath)) continue;
1691
+
1692
+ // A deferral anywhere in a generation defers the WHOLE generation: no
1693
+ // partial replacement in either direction, local files and base entries
1694
+ // stay exactly as they are.
1695
+ const membership = embeddingGenerationMembership(relPath);
1696
+ if (membership && deferredGenerationDirs.has(membership.shardDir)) {
1697
+ if (base) nextBase.set(relPath, base);
1698
+ else nextBase.delete(relPath);
1699
+ skipped += 1;
1700
+ continue;
1701
+ }
1702
+
1569
1703
  if (incoming) {
1570
1704
  if (currentEntry?.sha256 === incoming.sha256) {
1571
1705
  if (await setSafeFileMtime(root, relPath, incoming.mtimeMs)) {
@@ -1578,7 +1712,14 @@ export async function applyOfflineSyncSnapshot(options: {
1578
1712
  skipped += 1;
1579
1713
  continue;
1580
1714
  }
1581
- if (shouldPreferIncomingOfflineRuntimeFile(relPath) && currentEntry && base && incoming.sha256 === base.sha256) {
1715
+ // Remote unchanged from the shared base while the local file drifted:
1716
+ // keep the local copy (no content is needed to make that decision).
1717
+ // Members of a replaced generation never reach this branch — the
1718
+ // generation transaction or the deferral guard handled them above.
1719
+ if (
1720
+ shouldPreferIncomingOfflineRuntimeFile(relPath) &&
1721
+ currentEntry && base && incoming.sha256 === base.sha256
1722
+ ) {
1582
1723
  nextBase.set(relPath, base);
1583
1724
  skipped += 1;
1584
1725
  continue;
@@ -1659,6 +1800,18 @@ export async function applyOfflineSyncSnapshot(options: {
1659
1800
  skipped += 1;
1660
1801
  continue;
1661
1802
  }
1803
+ if (
1804
+ shouldPreferIncomingOfflineRuntimeFile(relPath) &&
1805
+ membership !== null && omittedGenerationDirs.has(membership.shardDir)
1806
+ ) {
1807
+ // The push omitted this whole generation because a filter excluded at
1808
+ // least one member (snapshot.omittedEmbeddingGenerationDirs): absence
1809
+ // is NOT a deletion — keep the local generation and its base entries
1810
+ // until it is pushed unfiltered or deleted with deletion metadata.
1811
+ pendingLocal += 1;
1812
+ skipped += 1;
1813
+ continue;
1814
+ }
1662
1815
  if (
1663
1816
  shouldPreferIncomingOfflineRuntimeFile(relPath) &&
1664
1817
  (base || shouldDeleteAbsentIncomingOfflineRuntimeFile(relPath))
@@ -1704,8 +1857,8 @@ export async function applyOfflineSyncSnapshot(options: {
1704
1857
  }
1705
1858
 
1706
1859
  return {
1707
- upserted,
1708
- deleted,
1860
+ upserted: upserted + generationUpserted,
1861
+ deleted: deleted + generationDeleted,
1709
1862
  skipped,
1710
1863
  pendingLocal,
1711
1864
  conflicts,
@@ -1722,6 +1875,8 @@ export async function applyOfflineSyncChangeset(options: {
1722
1875
  readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
1723
1876
  readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
1724
1877
  writeFile?: (target: OfflineSyncFileWriteTarget) => Promise<void>;
1878
+ writeStagingFile?: (target: OfflineSyncFileStagingWriteTarget) => Promise<void>;
1879
+ readStagingFile?: (target: OfflineSyncFileStagingReadTarget) => Promise<Buffer>;
1725
1880
  deleteFile?: (target: OfflineSyncFileDeleteTarget) => Promise<void>;
1726
1881
  recordDeletionRevision?: OfflineSyncRecordDeletionRevision;
1727
1882
  }): Promise<OfflineSyncApplyChangesetResult> {
@@ -1740,7 +1895,7 @@ export async function applyOfflineSyncChangeset(options: {
1740
1895
  const records = changeset.changes
1741
1896
  .filter((change): change is Extract<OfflineSyncChange, { type: "upsert" }> => change.type === "upsert")
1742
1897
  .map((change) => change.file);
1743
- const incomingBuffers = verifyRecordContents(records, "offline sync changeset");
1898
+ const incomingBuffers = await verifyRecordContents(records, "offline sync changeset");
1744
1899
  const currentFiles = options.currentFiles
1745
1900
  ? filterBaseFilesForMode(normalizeFileStates(options.currentFiles), changeset.includeTranscripts).sort(compareByPath)
1746
1901
  : (await buildOfflineSyncSnapshotForPaths({
@@ -1759,7 +1914,30 @@ export async function applyOfflineSyncChangeset(options: {
1759
1914
  let appliedDeletes = 0;
1760
1915
  let skipped = 0;
1761
1916
 
1917
+ // Embedding generations apply as ONE unit through the shared locked
1918
+ // transaction (issue #3150). Deferred generations surface as conflicts so
1919
+ // the push side never checkpoints them as applied.
1920
+ const generationRouting = await applyChangesetEmbeddingGenerations({
1921
+ root,
1922
+ changeset,
1923
+ incomingBuffers,
1924
+ currentMap,
1925
+ io: {
1926
+ readFile: options.readFile,
1927
+ writeStagingFile: options.writeStagingFile,
1928
+ readStagingFile: options.readStagingFile,
1929
+ deleteFile: options.deleteFile,
1930
+ },
1931
+ customIoPresent: Boolean(options.writeFile || options.readFile || options.readFileDigest || options.deleteFile),
1932
+ now: Date.now(),
1933
+ });
1934
+ appliedUpserts += generationRouting.appliedUpserts;
1935
+ appliedDeletes += generationRouting.appliedDeletes;
1936
+ conflicts.push(...generationRouting.conflicts);
1937
+
1762
1938
  for (const change of changeset.changes) {
1939
+ if (generationRouting.conflictedPaths.has(change.path)) continue;
1940
+ if (generationRouting.transactionHandled.has(change.path)) continue;
1763
1941
  const currentEntry = currentMap.get(change.path);
1764
1942
  if (change.type === "upsert") {
1765
1943
  if (currentEntry?.sha256 === change.file.sha256) {
@@ -1839,6 +2017,12 @@ export async function applyOfflineSyncChangeset(options: {
1839
2017
  });
1840
2018
  }
1841
2019
 
2020
+ // Generation transactions bypassed the per-file loop; fold their published
2021
+ // and removed members into the partial result map (returnCurrentFiles ===
2022
+ // false consumers) so it reflects the post-apply generation exactly.
2023
+ for (const [relPath, state] of generationRouting.writtenStates) currentMap.set(relPath, state);
2024
+ for (const relPath of generationRouting.removedPaths) currentMap.delete(relPath);
2025
+
1842
2026
  return {
1843
2027
  appliedUpserts,
1844
2028
  appliedDeletes,
@@ -1859,14 +2043,37 @@ export async function applyOfflineSyncChangeset(options: {
1859
2043
  };
1860
2044
  }
1861
2045
 
1862
- function verifyRecordContents(
2046
+ async function verifyRecordContents(
1863
2047
  records: readonly OfflineSyncFileRecord[],
1864
2048
  context: string,
1865
- options: { requireContent?: boolean } = {},
1866
- ): Map<string, Buffer> {
2049
+ options: {
2050
+ requireContent?: boolean;
2051
+ readIncomingFile?: (target: { path: string; sha256: string; bytes: number }) => Promise<Buffer | null>;
2052
+ } = {},
2053
+ ): Promise<Map<string, Buffer>> {
1867
2054
  const buffers = new Map<string, Buffer>();
1868
2055
  for (const record of records) {
1869
2056
  if (typeof record.contentBase64 !== "string") {
2057
+ if (options.readIncomingFile) {
2058
+ // Explicit INCOMING content source for records whose bytes travel
2059
+ // out-of-band (chunked fetch into the client's private staging
2060
+ // root). Never the local current-state file: the buffer is
2061
+ // digest-verified against the incoming record before anything is
2062
+ // published. null keeps the metadata-only behavior below.
2063
+ const incoming = await options.readIncomingFile({
2064
+ path: record.path,
2065
+ sha256: record.sha256,
2066
+ bytes: record.bytes,
2067
+ });
2068
+ if (incoming) {
2069
+ const digest = sha256Buffer(incoming);
2070
+ if (digest.sha256 !== record.sha256 || digest.bytes !== record.bytes) {
2071
+ throw new Error(`${context}: incoming content checksum mismatch for ${record.path}`);
2072
+ }
2073
+ buffers.set(record.path, incoming);
2074
+ continue;
2075
+ }
2076
+ }
1870
2077
  if (options.requireContent === false) continue;
1871
2078
  throw new Error(`${context}: contentBase64 is required for ${record.path}`);
1872
2079
  }
@@ -1956,29 +2163,6 @@ async function writeSafeFile(
1956
2163
  }
1957
2164
  }
1958
2165
 
1959
- async function setSafeFileMtime(
1960
- root: SafeArchiveRoot,
1961
- relPath: string,
1962
- mtimeMs: number | undefined,
1963
- ): Promise<boolean> {
1964
- if (mtimeMs === undefined) return true;
1965
- const target = await resolveSafeArchiveTarget(root, relPath);
1966
- const targetStat = await lstat(target).catch((error: unknown) => {
1967
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
1968
- throw error;
1969
- });
1970
- if (!targetStat) return false;
1971
- if (targetStat.isSymbolicLink()) {
1972
- throw new Error(`offline sync target is a symlink: ${relPath}`);
1973
- }
1974
- const targetMtimeMs = assertOfflineSyncMtimeMs(mtimeMs, "mtimeMs");
1975
- if (Math.abs(targetStat.mtimeMs - targetMtimeMs) <= OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS) {
1976
- return true;
1977
- }
1978
- const mtime = new Date(targetMtimeMs);
1979
- await utimes(target, mtime, mtime);
1980
- return true;
1981
- }
1982
2166
 
1983
2167
  /**
1984
2168
  * Read the local file digest for a single relative path without applying
@@ -2232,272 +2416,6 @@ export async function applyOfflineSyncFileContentChunk(options: {
2232
2416
  }
2233
2417
  }
2234
2418
 
2235
- function offlineUploadRelPath(options: {
2236
- sourceId: string;
2237
- relPath: string;
2238
- sha256: string;
2239
- bytes: number;
2240
- }): string {
2241
- const key = hashText([
2242
- options.sourceId,
2243
- options.relPath,
2244
- options.sha256,
2245
- String(options.bytes),
2246
- ].join("\0"));
2247
- return `${SYNC_INTERNAL_DIR}/uploads/${key}.part`;
2248
- }
2249
-
2250
- async function offlineUploadPath(root: SafeArchiveRoot, options: {
2251
- sourceId: string;
2252
- relPath: string;
2253
- sha256: string;
2254
- bytes: number;
2255
- }): Promise<OfflineUploadStaging> {
2256
- const relPath = offlineUploadRelPath(options);
2257
- return {
2258
- kind: "single",
2259
- relPath,
2260
- filePath: await resolveSafeArchiveTarget(root, relPath),
2261
- };
2262
- }
2263
-
2264
- async function offlineUploadChunkPath(root: SafeArchiveRoot, options: {
2265
- sourceId: string;
2266
- relPath: string;
2267
- sha256: string;
2268
- bytes: number;
2269
- offset: number;
2270
- }): Promise<OfflineUploadStaging> {
2271
- const uploadRelPath = offlineUploadRelPath(options);
2272
- const relPath = `${uploadRelPath}/${String(options.offset).padStart(20, "0")}.part`;
2273
- return {
2274
- kind: "chunks",
2275
- relPath,
2276
- filePath: await resolveSafeArchiveTarget(root, relPath),
2277
- };
2278
- }
2279
-
2280
- async function writeOfflineUploadChunk(options: {
2281
- root: SafeArchiveRoot;
2282
- sourceId: string;
2283
- relPath: string;
2284
- sha256: string;
2285
- bytes: number;
2286
- offset: number;
2287
- content: Buffer;
2288
- readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
2289
- writeFile?: (target: OfflineSyncFileWriteTarget) => Promise<void>;
2290
- writeStagingFile?: (target: OfflineSyncFileStagingWriteTarget) => Promise<void>;
2291
- }): Promise<OfflineUploadStaging> {
2292
- if ((options.writeFile || options.writeStagingFile) && !options.readFile) {
2293
- throw new Error("offline sync upload chunk storage hooks require readFile");
2294
- }
2295
- const uploadRoot = {
2296
- ...(await offlineUploadPath(options.root, options)),
2297
- kind: "chunks" as const,
2298
- };
2299
- if (options.offset === 0) {
2300
- await rm(uploadRoot.filePath, { recursive: true, force: true }).catch(() => {});
2301
- } else {
2302
- const existing = await stat(uploadRoot.filePath).catch((error: unknown) => {
2303
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
2304
- throw error;
2305
- });
2306
- if (!existing || !existing.isDirectory()) {
2307
- throw new Error(`offline sync upload is missing initial chunk for ${options.relPath}`);
2308
- }
2309
- }
2310
- const chunk = await offlineUploadChunkPath(options.root, { ...options, offset: options.offset });
2311
-
2312
- const writeStagingFile = options.writeStagingFile ?? options.writeFile;
2313
- if (writeStagingFile) {
2314
- // Storage-backed services provide these hooks so secure-store deployments
2315
- // keep staged partial uploads encrypted at rest without mutating indexes.
2316
- await writeOfflineUploadContent({
2317
- root: options.root,
2318
- relPath: chunk.relPath,
2319
- filePath: chunk.filePath,
2320
- content: options.content,
2321
- writeFile: writeStagingFile,
2322
- });
2323
- return uploadRoot;
2324
- }
2325
-
2326
- await mkdir(path.dirname(chunk.filePath), { recursive: true });
2327
- const existingChunk = await lstat(chunk.filePath).catch((error: unknown) => {
2328
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
2329
- throw error;
2330
- });
2331
- if (existingChunk?.isSymbolicLink()) {
2332
- throw new Error(`offline sync upload chunk is a symlink: ${chunk.relPath}`);
2333
- }
2334
- await writeFile(chunk.filePath, options.content, { mode: 0o600 });
2335
- return uploadRoot;
2336
- }
2337
-
2338
- async function pruneOfflineUploadStaging(root: SafeArchiveRoot): Promise<void> {
2339
- const uploadsRelPath = `${SYNC_INTERNAL_DIR}/uploads`;
2340
- const uploadsPath = await resolveSafeArchiveTarget(root, uploadsRelPath);
2341
- const entries = await readdir(uploadsPath, { withFileTypes: true }).catch((error: unknown) => {
2342
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
2343
- throw error;
2344
- });
2345
- const now = Date.now();
2346
- await Promise.all(entries.map(async (entry) => {
2347
- if (!/^[a-f0-9]{64}\.part$/i.test(entry.name)) return;
2348
- const relPath = `${uploadsRelPath}/${entry.name}`;
2349
- const filePath = await resolveSafeArchiveTarget(root, relPath);
2350
- const info = await lstat(filePath).catch((error: unknown) => {
2351
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
2352
- throw error;
2353
- });
2354
- if (!info) return;
2355
- if (now - info.mtimeMs <= OFFLINE_SYNC_UPLOAD_STAGING_MAX_AGE_MS) return;
2356
- await rm(filePath, { recursive: true, force: true });
2357
- }));
2358
- }
2359
-
2360
- async function* readOfflineUploadStagingChunks(options: {
2361
- root: SafeArchiveRoot;
2362
- upload: OfflineUploadStaging;
2363
- readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
2364
- }): AsyncGenerator<Buffer> {
2365
- if (options.upload.kind === "single") {
2366
- yield await readOfflineUploadContent({
2367
- root: options.root,
2368
- relPath: options.upload.relPath,
2369
- filePath: options.upload.filePath,
2370
- readFile: options.readFile,
2371
- });
2372
- return;
2373
- }
2374
-
2375
- const entries = await readdir(options.upload.filePath);
2376
- const chunkNames = entries
2377
- .filter((entry) => /^\d{20}\.part$/.test(entry))
2378
- .sort();
2379
- if (chunkNames.length === 0) {
2380
- throw new Error(`offline sync upload is missing chunks for ${options.upload.relPath}`);
2381
- }
2382
- let expectedOffset = 0;
2383
- for (const chunkName of chunkNames) {
2384
- const offset = Number(chunkName.slice(0, 20));
2385
- if (!Number.isSafeInteger(offset) || offset !== expectedOffset) {
2386
- throw new Error(
2387
- `offline sync upload offset mismatch for ${options.upload.relPath}: expected ${expectedOffset}, got ${offset}`,
2388
- );
2389
- }
2390
- const relPath = `${options.upload.relPath}/${chunkName}`;
2391
- const filePath = await resolveSafeArchiveTarget(options.root, relPath);
2392
- const content = await readOfflineUploadContent({
2393
- root: options.root,
2394
- relPath,
2395
- filePath,
2396
- readFile: options.readFile,
2397
- });
2398
- expectedOffset += content.length;
2399
- yield content;
2400
- }
2401
- }
2402
-
2403
- async function digestOfflineUploadStagingContent(options: {
2404
- root: SafeArchiveRoot;
2405
- upload: OfflineUploadStaging;
2406
- readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
2407
- }): Promise<{ sha256: string; bytes: number }> {
2408
- const hash = createHash("sha256");
2409
- let bytes = 0;
2410
- for await (const chunk of readOfflineUploadStagingChunks(options)) {
2411
- hash.update(chunk);
2412
- bytes += chunk.length;
2413
- }
2414
- return { sha256: hash.digest("hex"), bytes };
2415
- }
2416
-
2417
- async function writeSafeFileFromUpload(
2418
- root: SafeArchiveRoot,
2419
- relPath: string,
2420
- upload: OfflineUploadStaging,
2421
- readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>,
2422
- writeFileChunks?: (target: OfflineSyncFileWriteChunksTarget) => Promise<void>,
2423
- mtimeMs?: number,
2424
- ): Promise<void> {
2425
- const target = await resolveSafeArchiveTarget(root, relPath);
2426
- const chunks = readOfflineUploadStagingChunks({ root, upload, readFile });
2427
- if (writeFileChunks) {
2428
- await writeFileChunks({ root: root.abs, path: relPath, filePath: target, chunks });
2429
- await setSafeFileMtime(root, relPath, mtimeMs);
2430
- return;
2431
- }
2432
-
2433
- await mkdir(path.dirname(target), { recursive: true });
2434
- const tmp = path.join(
2435
- path.dirname(target),
2436
- `.remnic-sync.${process.pid}.${randomUUID()}.tmp`,
2437
- );
2438
- const handle = await open(tmp, "w", 0o600);
2439
- try {
2440
- for await (const chunk of chunks) {
2441
- if (chunk.length > 0) await handle.write(chunk);
2442
- }
2443
- await handle.close();
2444
- const targetStat = await lstat(target).catch((error: unknown) => {
2445
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
2446
- throw error;
2447
- });
2448
- if (targetStat?.isSymbolicLink()) {
2449
- throw new Error(`offline sync target is a symlink: ${relPath}`);
2450
- }
2451
- await rename(tmp, target);
2452
- await setSafeFileMtime(root, relPath, mtimeMs);
2453
- } catch (error) {
2454
- await handle.close().catch(() => {});
2455
- await unlink(tmp).catch(() => {});
2456
- throw error;
2457
- }
2458
- }
2459
-
2460
- async function cleanupOfflineUpload(upload: OfflineUploadStaging): Promise<void> {
2461
- if (upload.kind === "chunks") {
2462
- await rm(upload.filePath, { recursive: true, force: true });
2463
- return;
2464
- }
2465
- await unlink(upload.filePath).catch((error: unknown) => {
2466
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
2467
- throw error;
2468
- });
2469
- }
2470
-
2471
- async function readOfflineUploadContent(options: {
2472
- root: SafeArchiveRoot;
2473
- relPath: string;
2474
- filePath: string;
2475
- readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
2476
- }): Promise<Buffer> {
2477
- if (options.readFile) {
2478
- return options.readFile({
2479
- root: options.root.abs,
2480
- path: options.relPath,
2481
- filePath: options.filePath,
2482
- });
2483
- }
2484
- return readFile(options.filePath);
2485
- }
2486
-
2487
- async function writeOfflineUploadContent(options: {
2488
- root: SafeArchiveRoot;
2489
- relPath: string;
2490
- filePath: string;
2491
- content: Buffer;
2492
- writeFile: (target: OfflineSyncFileWriteTarget) => Promise<void>;
2493
- }): Promise<void> {
2494
- await options.writeFile({
2495
- root: options.root.abs,
2496
- path: options.relPath,
2497
- filePath: options.filePath,
2498
- content: options.content,
2499
- });
2500
- }
2501
2419
 
2502
2420
  async function deleteSafeFile(
2503
2421
  root: SafeArchiveRoot,