@remnic/core 9.69.87 → 9.69.88

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 (414) hide show
  1. package/dist/access-admin-ops-surface.js +61 -55
  2. package/dist/access-authorization-probe.js +62 -56
  3. package/dist/access-boundary.js +61 -55
  4. package/dist/access-citation-namespace.js +2 -2
  5. package/dist/access-cli.js +140 -134
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-extraction-force-flush.js +61 -55
  8. package/dist/access-http-briefing.js +62 -56
  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 +63 -57
  12. package/dist/access-http.js +85 -79
  13. package/dist/access-identity-continuity-surface.js +61 -55
  14. package/dist/access-lcm-surface.js +61 -55
  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 +74 -68
  18. package/dist/access-namespace-preflight.js +61 -55
  19. package/dist/access-observe-write-category.js +19 -13
  20. package/dist/access-observe-write-surface.js +61 -55
  21. package/dist/access-offline-manifest.js +15 -10
  22. package/dist/access-operations-batch.js +65 -59
  23. package/dist/access-operations.js +72 -66
  24. package/dist/access-recall-concurrency.js +61 -55
  25. package/dist/access-recall-response.js +61 -55
  26. package/dist/access-recall-surface.js +61 -55
  27. package/dist/access-schema.js +15 -7
  28. package/dist/access-service.d.ts.map +1 -1
  29. package/dist/access-service.js +61 -55
  30. package/dist/access-surface-catalog.js +63 -57
  31. package/dist/access-surface-catalog.js.map +1 -1
  32. package/dist/active-recall.js +27 -22
  33. package/dist/active-recall.js.map +1 -1
  34. package/dist/adapters/index.js +4 -4
  35. package/dist/adapters/registry.js +2 -2
  36. package/dist/{auto-sync-6IPX2BAC.js → auto-sync-U5N3CB5Y.js} +7 -7
  37. package/dist/briefing.js +15 -15
  38. package/dist/buffer.js +7 -7
  39. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
  40. package/dist/{capsule-merge-3MNURAQ3.js → capsule-merge-ZCXUPLNJ.js} +2 -2
  41. package/dist/causal-behavior.js +2 -2
  42. package/dist/causal-chain.js +2 -2
  43. package/dist/causal-consolidation.js +18 -18
  44. package/dist/causal-retrieval.js +2 -2
  45. package/dist/causal-trajectory-graph.js +2 -2
  46. package/dist/causal-trajectory.js +1 -1
  47. package/dist/{chunk-JE334SIA.js → chunk-22DYJLFN.js} +2 -2
  48. package/dist/{chunk-JGSQ6P4D.js → chunk-2DLWLNO4.js} +3 -3
  49. package/dist/{chunk-LS3XH3UE.js → chunk-2NAD4YCI.js} +2 -2
  50. package/dist/{chunk-SY5KDWGU.js → chunk-2WLMWP2T.js} +2 -2
  51. package/dist/{chunk-P6WFK4ZJ.js → chunk-3O5BB45T.js} +4 -4
  52. package/dist/{chunk-SOZUJCME.js → chunk-3SGTV7RD.js} +6 -6
  53. package/dist/{chunk-YITN5WXI.js → chunk-4KN2JLQV.js} +2 -2
  54. package/dist/{chunk-JKMHZF3Q.js → chunk-4MP7XR5J.js} +2 -2
  55. package/dist/chunk-4NSLQ7EU.js +137 -0
  56. package/dist/chunk-4NSLQ7EU.js.map +1 -0
  57. package/dist/{chunk-7DE2NOON.js → chunk-4O4XCBOS.js} +3 -3
  58. package/dist/{chunk-SLEAJ67D.js → chunk-4OQET73E.js} +4 -4
  59. package/dist/{chunk-GDYU6VF4.js → chunk-5AYUHSAU.js} +2 -2
  60. package/dist/{chunk-QWW2JDDX.js → chunk-5E6RYRYW.js} +6 -6
  61. package/dist/{chunk-ICZQG4XE.js → chunk-5FAGCOZD.js} +4 -4
  62. package/dist/{chunk-4IEOGIPB.js → chunk-5MNUBT7T.js} +2 -2
  63. package/dist/{chunk-CK5KOVHW.js → chunk-5R627MYY.js} +3 -2
  64. package/dist/{chunk-CK5KOVHW.js.map → chunk-5R627MYY.js.map} +1 -1
  65. package/dist/chunk-5X4MXNEU.js +64 -0
  66. package/dist/chunk-5X4MXNEU.js.map +1 -0
  67. package/dist/{chunk-52II7PB6.js → chunk-645WOU5I.js} +4 -4
  68. package/dist/{chunk-DUJYRVUD.js → chunk-6O2VUWV6.js} +3 -3
  69. package/dist/{chunk-CS52MFDV.js → chunk-6UQFJULG.js} +2 -2
  70. package/dist/chunk-75PT4N7Q.js +75 -0
  71. package/dist/chunk-75PT4N7Q.js.map +1 -0
  72. package/dist/{chunk-HESDNYRB.js → chunk-76M5OK7V.js} +2 -2
  73. package/dist/{chunk-4CDYONWO.js → chunk-A4VSOUCB.js} +3 -3
  74. package/dist/{chunk-HBBHL4YF.js → chunk-AE5U5NMS.js} +2 -2
  75. package/dist/{chunk-CUCWX3MO.js → chunk-AEHVZY3X.js} +2 -2
  76. package/dist/{chunk-DZ525SNQ.js → chunk-AEU4EQGV.js} +1 -1
  77. package/dist/{chunk-VLLD4TOB.js → chunk-AWG3TIAY.js} +2 -2
  78. package/dist/{chunk-72JNNJJJ.js → chunk-AXC4WSF3.js} +3 -3
  79. package/dist/{chunk-N4S4NUX5.js → chunk-B3O3IRZN.js} +1 -1
  80. package/dist/{chunk-FAN73COB.js → chunk-BGI4NUAO.js} +2 -2
  81. package/dist/{chunk-XTYX6V52.js → chunk-BJVO4H5C.js} +2 -2
  82. package/dist/{chunk-EHS5MJEF.js → chunk-BLVR4WYU.js} +3 -3
  83. package/dist/{chunk-KFCMMDHH.js → chunk-BOZJD5PK.js} +2 -2
  84. package/dist/{chunk-TM3RXL4G.js → chunk-BUQKSQXL.js} +2 -2
  85. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  86. package/dist/{chunk-EVBI7S4J.js → chunk-C6U57KG4.js} +4 -4
  87. package/dist/{chunk-RB776UH3.js → chunk-C7V3KPKJ.js} +235 -93
  88. package/dist/chunk-C7V3KPKJ.js.map +1 -0
  89. package/dist/{chunk-VX6GCKBS.js → chunk-C7ZDKWPS.js} +4 -4
  90. package/dist/{chunk-ZI7QA4MK.js → chunk-CRXRZA3G.js} +2 -2
  91. package/dist/{chunk-VQNONC4E.js → chunk-CZJA2EQE.js} +2 -2
  92. package/dist/{chunk-M536WNTQ.js → chunk-D5BSQR63.js} +2 -2
  93. package/dist/{chunk-37S3IMYB.js → chunk-DGS34QSY.js} +2 -2
  94. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  95. package/dist/{chunk-3BTFBQLO.js → chunk-EFHNFTWB.js} +49 -3
  96. package/dist/chunk-EFHNFTWB.js.map +1 -0
  97. package/dist/{chunk-C3QXITIP.js → chunk-EQUD7FP4.js} +2 -2
  98. package/dist/chunk-ETUZFFPU.js +218 -0
  99. package/dist/chunk-ETUZFFPU.js.map +1 -0
  100. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  101. package/dist/{chunk-EQ6YOIKO.js → chunk-FJLQXMTU.js} +3 -3
  102. package/dist/{chunk-SRZW7FNU.js → chunk-FKD656D2.js} +2 -2
  103. package/dist/{chunk-CLXTDOKV.js → chunk-FUNBJQYR.js} +2 -2
  104. package/dist/{chunk-CLSYE5OZ.js → chunk-G2STTRYB.js} +2 -2
  105. package/dist/{chunk-KP7SANCF.js → chunk-GWM6T5EQ.js} +2 -2
  106. package/dist/{chunk-UA5DX5C3.js → chunk-GZDTVYK2.js} +2 -2
  107. package/dist/{chunk-3GQC3NMB.js → chunk-H56VY4BY.js} +5 -5
  108. package/dist/{chunk-L6UZEAID.js → chunk-H5BJWPUI.js} +4 -4
  109. package/dist/{chunk-YSJTCOHR.js → chunk-HLB7GKGG.js} +2 -2
  110. package/dist/{chunk-PUKTBULQ.js → chunk-HWLIMQE2.js} +2 -2
  111. package/dist/{chunk-CWHV574S.js → chunk-IOSZHHJC.js} +6 -6
  112. package/dist/{chunk-P2WZHLUF.js → chunk-JVPWRNHA.js} +2 -2
  113. package/dist/{chunk-6ET2VII7.js → chunk-JYG2334B.js} +2 -2
  114. package/dist/chunk-K6T33R4Q.js +535 -0
  115. package/dist/chunk-K6T33R4Q.js.map +1 -0
  116. package/dist/{chunk-WIKO4SCR.js → chunk-LHRQBMJN.js} +2 -2
  117. package/dist/{chunk-K4J4E7WV.js → chunk-LO4UKPKX.js} +2 -2
  118. package/dist/{chunk-LF5IGOOD.js → chunk-LU7E2MXE.js} +17 -17
  119. package/dist/{chunk-RJLV4BKR.js → chunk-LV5H6FTN.js} +2 -2
  120. package/dist/{chunk-3C3J243X.js → chunk-LW3HKRL7.js} +3 -3
  121. package/dist/{chunk-4BZP2B4U.js → chunk-M6C7SOR6.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-SPSRGEDQ.js → chunk-MXCAJKMA.js} +9 -9
  127. package/dist/{chunk-BMYZTZ5D.js → chunk-MY7B3HYC.js} +2 -2
  128. package/dist/{chunk-NB3LRY4T.js → chunk-N4PYB235.js} +2 -2
  129. package/dist/{chunk-S3YM4SZQ.js → chunk-N7QV3CMX.js} +2 -2
  130. package/dist/{chunk-AQK54Q7O.js → chunk-NYKIUQL6.js} +2 -2
  131. package/dist/{chunk-WQZZDN5M.js → chunk-OCFXVDBZ.js} +322 -376
  132. package/dist/chunk-OCFXVDBZ.js.map +1 -0
  133. package/dist/{chunk-NE6PHPLG.js → chunk-OK52IY3W.js} +10 -10
  134. package/dist/{chunk-6TT6HRE4.js → chunk-P2ODTYOF.js} +2 -2
  135. package/dist/{chunk-W4AOR52H.js → chunk-PEBHPCX2.js} +2 -2
  136. package/dist/{chunk-VYCOJKSX.js → chunk-PO7NXNGC.js} +2 -2
  137. package/dist/{chunk-G6SVZWPB.js → chunk-PZX3AQLF.js} +44 -1
  138. package/dist/chunk-PZX3AQLF.js.map +1 -0
  139. package/dist/{chunk-XAITGGX5.js → chunk-Q4AUIFUA.js} +4 -4
  140. package/dist/{chunk-MQJL6CDZ.js → chunk-QAW55ZHJ.js} +4 -4
  141. package/dist/{chunk-FOH7AMMI.js → chunk-QB57WIS6.js} +73 -82
  142. package/dist/chunk-QB57WIS6.js.map +1 -0
  143. package/dist/{chunk-QVS22WTO.js → chunk-QI7K7BZ5.js} +2 -2
  144. package/dist/{chunk-NE7I2RCT.js → chunk-R3X2T2LP.js} +2 -2
  145. package/dist/{chunk-S7GCT7NU.js → chunk-RBITPCMK.js} +8 -8
  146. package/dist/{chunk-YTQGGO3K.js → chunk-S2GE2K36.js} +81 -78
  147. package/dist/{chunk-YTQGGO3K.js.map → chunk-S2GE2K36.js.map} +1 -1
  148. package/dist/{chunk-BJRAELDH.js → chunk-SCXURCVT.js} +3 -3
  149. package/dist/{chunk-FRCESVV6.js → chunk-STEOT4DI.js} +19 -19
  150. package/dist/chunk-STEOT4DI.js.map +1 -0
  151. package/dist/{chunk-BFO3IR56.js → chunk-SWBC3UER.js} +2 -2
  152. package/dist/chunk-TYT4TEQV.js +45 -0
  153. package/dist/chunk-TYT4TEQV.js.map +1 -0
  154. package/dist/{chunk-YRLHEEQT.js → chunk-U6VC6RP3.js} +2 -2
  155. package/dist/chunk-UCPGOUDZ.js +726 -0
  156. package/dist/chunk-UCPGOUDZ.js.map +1 -0
  157. package/dist/{chunk-MOQPEFLN.js → chunk-UP75KL3L.js} +2 -2
  158. package/dist/{chunk-D5F7TKMH.js → chunk-V4JZCTMK.js} +2 -2
  159. package/dist/{chunk-QEV26L2F.js → chunk-VALKBYBN.js} +2 -2
  160. package/dist/{chunk-7LCGFUMB.js → chunk-VDX2J7OX.js} +4 -4
  161. package/dist/{chunk-TDYAIXHK.js → chunk-VVX5PZFS.js} +5 -5
  162. package/dist/{chunk-6NQ4LSMS.js → chunk-W3XXFPRI.js} +52 -52
  163. package/dist/{chunk-4IIMMZZK.js → chunk-WKY7N3L7.js} +10 -10
  164. package/dist/{chunk-VVDWZRZG.js → chunk-X5SHE2O3.js} +3 -3
  165. package/dist/{chunk-NR2ASHBL.js → chunk-XOV7NRY5.js} +2 -2
  166. package/dist/{chunk-6L3PBSAR.js → chunk-YJ7MW627.js} +2 -2
  167. package/dist/{chunk-LQG5O5EP.js → chunk-Z3O53WZR.js} +2 -2
  168. package/dist/{chunk-FB64XE3R.js → chunk-Z5LP6ILP.js} +2 -2
  169. package/dist/{chunk-VKQI47DJ.js → chunk-Z6EZCN6Z.js} +8 -8
  170. package/dist/{chunk-V2ES4WCZ.js → chunk-ZZZ4I455.js} +2 -2
  171. package/dist/cli.js +106 -100
  172. package/dist/coding/export-okf-codegraph.js +16 -16
  173. package/dist/coding/pre-action-gate.js +2 -2
  174. package/dist/compounding/engine.js +16 -16
  175. package/dist/config.js +27 -22
  176. package/dist/connectors/codex-materialize-runner.js +15 -15
  177. package/dist/connectors/index.js +15 -15
  178. package/dist/console/state.d.ts +30 -0
  179. package/dist/console/state.d.ts.map +1 -1
  180. package/dist/contradiction/index.js +5 -5
  181. package/dist/convergence-refresh.js +17 -17
  182. package/dist/corpus-watermark.js +17 -17
  183. package/dist/dashboard-runtime.js +2 -2
  184. package/dist/deep-recall-http-glue.js +15 -8
  185. package/dist/embedding-fallback.d.ts +41 -31
  186. package/dist/embedding-fallback.d.ts.map +1 -1
  187. package/dist/embedding-fallback.js +9 -1
  188. package/dist/embedding-generation-lock.d.ts +49 -0
  189. package/dist/embedding-generation-lock.d.ts.map +1 -0
  190. package/dist/embedding-generation-lock.js +19 -0
  191. package/dist/embedding-index-storage.d.ts +276 -0
  192. package/dist/embedding-index-storage.d.ts.map +1 -0
  193. package/dist/embedding-index-storage.js +28 -0
  194. package/dist/entity-retrieval.js +16 -16
  195. package/dist/event-order-recall.js +3 -3
  196. package/dist/explicit-capture.js +6 -6
  197. package/dist/external-wiki-access.js +62 -56
  198. package/dist/extraction.js +21 -21
  199. package/dist/{graph-edge-decay-KQGS3L3B.js → graph-edge-decay-MKIRZ762.js} +5 -5
  200. package/dist/graph-jsonl.js +3 -3
  201. package/dist/graph-snapshot.js +3 -3
  202. package/dist/graph.js +2 -2
  203. package/dist/harmonic-retrieval.js +1 -1
  204. package/dist/index.d.ts +2 -0
  205. package/dist/index.d.ts.map +1 -1
  206. package/dist/index.js +207 -177
  207. package/dist/lcm/engine.js +3 -3
  208. package/dist/lcm/index.js +9 -9
  209. package/dist/local-llm.js +2 -2
  210. package/dist/location/index.js +18 -18
  211. package/dist/maintenance/memory-governance.js +15 -15
  212. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -15
  213. package/dist/maintenance/rebuild-memory-projection.js +16 -16
  214. package/dist/memory-browse-operations.js +62 -56
  215. package/dist/memory-subject.js +3 -3
  216. package/dist/namespaces/migrate.js +16 -16
  217. package/dist/namespaces/search.js +4 -4
  218. package/dist/namespaces/storage.js +15 -15
  219. package/dist/offline-sync-embedding-generation.d.ts +207 -0
  220. package/dist/offline-sync-embedding-generation.d.ts.map +1 -0
  221. package/dist/offline-sync-embedding-generation.js +42 -0
  222. package/dist/offline-sync-exclude-globs.d.ts +15 -0
  223. package/dist/offline-sync-exclude-globs.d.ts.map +1 -1
  224. package/dist/offline-sync-exclude-globs.js +11 -3
  225. package/dist/offline-sync-file-io.d.ts +7 -0
  226. package/dist/offline-sync-file-io.d.ts.map +1 -1
  227. package/dist/offline-sync-file-io.js +14 -2
  228. package/dist/offline-sync-impression-drain.js +3 -3
  229. package/dist/offline-sync-runtime-state.d.ts +5 -0
  230. package/dist/offline-sync-runtime-state.d.ts.map +1 -0
  231. package/dist/offline-sync-runtime-state.js +14 -0
  232. package/dist/offline-sync-runtime-state.js.map +1 -0
  233. package/dist/offline-sync-snapshot-stream.d.ts +26 -0
  234. package/dist/offline-sync-snapshot-stream.d.ts.map +1 -0
  235. package/dist/offline-sync-snapshot-stream.js +28 -0
  236. package/dist/offline-sync-snapshot-stream.js.map +1 -0
  237. package/dist/offline-sync-upload-staging.d.ts +34 -0
  238. package/dist/offline-sync-upload-staging.d.ts.map +1 -0
  239. package/dist/offline-sync-upload-staging.js +26 -0
  240. package/dist/offline-sync-upload-staging.js.map +1 -0
  241. package/dist/offline-sync.d.ts +46 -27
  242. package/dist/offline-sync.d.ts.map +1 -1
  243. package/dist/offline-sync.js +21 -8
  244. package/dist/operator-doctor-corpus.js +18 -18
  245. package/dist/operator-toolkit.js +38 -33
  246. package/dist/orchestration/maintenance.js +26 -26
  247. package/dist/orchestrator.d.ts.map +1 -1
  248. package/dist/orchestrator.js +142 -136
  249. package/dist/recall-navigation-operations.js +62 -56
  250. package/dist/recall-navigation.js +61 -55
  251. package/dist/reconcile/manifest.js +4 -4
  252. package/dist/{resolution-DNX7FO7A.js → resolution-K4A3VPKZ.js} +6 -6
  253. package/dist/resolution-K4A3VPKZ.js.map +1 -0
  254. package/dist/resume-bundles.js +28 -23
  255. package/dist/salvage-envelope.js +5 -5
  256. package/dist/search/factory.js +3 -3
  257. package/dist/search/index.js +7 -7
  258. package/dist/secure-store/index.js +2 -2
  259. package/dist/secure-store/secure-fs.d.ts +14 -1
  260. package/dist/secure-store/secure-fs.d.ts.map +1 -1
  261. package/dist/semantic-consolidation.js +16 -16
  262. package/dist/semantic-rule-promotion.js +15 -15
  263. package/dist/semantic-rule-verifier.js +15 -15
  264. package/dist/source-agent-qualifier.js +18 -18
  265. package/dist/standup/build.js +15 -15
  266. package/dist/{state-ITXJKXRE.js → state-JB47KHYV.js} +2 -2
  267. package/dist/state-JB47KHYV.js.map +1 -0
  268. package/dist/storage.d.ts +9 -3
  269. package/dist/storage.d.ts.map +1 -1
  270. package/dist/storage.js +14 -14
  271. package/dist/subject-config.js +4 -4
  272. package/dist/summarizer.js +3 -3
  273. package/dist/support-passport/index.js +64 -58
  274. package/dist/{trace-ZFVRH4GO.js → trace-LXXCMZJA.js} +3 -3
  275. package/dist/transfer/backup.js +2 -2
  276. package/dist/transfer/capsule-export.js +2 -2
  277. package/dist/transfer/capsule-import.js +3 -3
  278. package/dist/transfer/export-okf.js +16 -16
  279. package/dist/{tui-55UJMJX7.js → tui-2Z5WOV7L.js} +3 -3
  280. package/dist/tui-2Z5WOV7L.js.map +1 -0
  281. package/dist/verified-recall.js +15 -15
  282. package/dist/who-knows-cli.js +16 -16
  283. package/dist/who-knows.js +15 -15
  284. package/dist/write-envelope.js +4 -4
  285. package/package.json +2 -2
  286. package/src/access-http-offline-stream.ts +3 -0
  287. package/src/access-mcp-output-schemas.ts +1 -0
  288. package/src/access-service.ts +7 -18
  289. package/src/console/state.test.ts +56 -0
  290. package/src/console/state.ts +83 -0
  291. package/src/embedding-fallback.ts +325 -141
  292. package/src/embedding-generation-lock.ts +141 -0
  293. package/src/embedding-index-storage.test.ts +245 -0
  294. package/src/embedding-index-storage.ts +1068 -0
  295. package/src/index.ts +3 -3
  296. package/src/offline-sync-embedding-generation.test.ts +2173 -0
  297. package/src/offline-sync-embedding-generation.ts +885 -0
  298. package/src/offline-sync-exclude-globs.ts +112 -0
  299. package/src/offline-sync-file-io.ts +48 -0
  300. package/src/offline-sync-runtime-state.ts +48 -0
  301. package/src/offline-sync-snapshot-stream.ts +99 -0
  302. package/src/offline-sync-upload-staging.ts +291 -0
  303. package/src/offline-sync.test.ts +164 -1
  304. package/src/offline-sync.ts +458 -453
  305. package/src/orchestrator.ts +2 -1
  306. package/src/secure-store/secure-fs.ts +26 -6
  307. package/src/storage.ts +11 -6
  308. package/dist/chunk-3BTFBQLO.js.map +0 -1
  309. package/dist/chunk-AREGMX2J.js +0 -50
  310. package/dist/chunk-AREGMX2J.js.map +0 -1
  311. package/dist/chunk-FOH7AMMI.js.map +0 -1
  312. package/dist/chunk-FRCESVV6.js.map +0 -1
  313. package/dist/chunk-G6SVZWPB.js.map +0 -1
  314. package/dist/chunk-JN72EPOE.js.map +0 -1
  315. package/dist/chunk-RB776UH3.js.map +0 -1
  316. package/dist/chunk-WQZZDN5M.js.map +0 -1
  317. /package/dist/{auto-sync-6IPX2BAC.js.map → auto-sync-U5N3CB5Y.js.map} +0 -0
  318. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
  319. /package/dist/{capsule-merge-3MNURAQ3.js.map → capsule-merge-ZCXUPLNJ.js.map} +0 -0
  320. /package/dist/{chunk-JE334SIA.js.map → chunk-22DYJLFN.js.map} +0 -0
  321. /package/dist/{chunk-JGSQ6P4D.js.map → chunk-2DLWLNO4.js.map} +0 -0
  322. /package/dist/{chunk-LS3XH3UE.js.map → chunk-2NAD4YCI.js.map} +0 -0
  323. /package/dist/{chunk-SY5KDWGU.js.map → chunk-2WLMWP2T.js.map} +0 -0
  324. /package/dist/{chunk-P6WFK4ZJ.js.map → chunk-3O5BB45T.js.map} +0 -0
  325. /package/dist/{chunk-SOZUJCME.js.map → chunk-3SGTV7RD.js.map} +0 -0
  326. /package/dist/{chunk-YITN5WXI.js.map → chunk-4KN2JLQV.js.map} +0 -0
  327. /package/dist/{chunk-JKMHZF3Q.js.map → chunk-4MP7XR5J.js.map} +0 -0
  328. /package/dist/{chunk-7DE2NOON.js.map → chunk-4O4XCBOS.js.map} +0 -0
  329. /package/dist/{chunk-SLEAJ67D.js.map → chunk-4OQET73E.js.map} +0 -0
  330. /package/dist/{chunk-GDYU6VF4.js.map → chunk-5AYUHSAU.js.map} +0 -0
  331. /package/dist/{chunk-QWW2JDDX.js.map → chunk-5E6RYRYW.js.map} +0 -0
  332. /package/dist/{chunk-ICZQG4XE.js.map → chunk-5FAGCOZD.js.map} +0 -0
  333. /package/dist/{chunk-4IEOGIPB.js.map → chunk-5MNUBT7T.js.map} +0 -0
  334. /package/dist/{chunk-52II7PB6.js.map → chunk-645WOU5I.js.map} +0 -0
  335. /package/dist/{chunk-DUJYRVUD.js.map → chunk-6O2VUWV6.js.map} +0 -0
  336. /package/dist/{chunk-CS52MFDV.js.map → chunk-6UQFJULG.js.map} +0 -0
  337. /package/dist/{chunk-HESDNYRB.js.map → chunk-76M5OK7V.js.map} +0 -0
  338. /package/dist/{chunk-4CDYONWO.js.map → chunk-A4VSOUCB.js.map} +0 -0
  339. /package/dist/{chunk-HBBHL4YF.js.map → chunk-AE5U5NMS.js.map} +0 -0
  340. /package/dist/{chunk-CUCWX3MO.js.map → chunk-AEHVZY3X.js.map} +0 -0
  341. /package/dist/{chunk-DZ525SNQ.js.map → chunk-AEU4EQGV.js.map} +0 -0
  342. /package/dist/{chunk-VLLD4TOB.js.map → chunk-AWG3TIAY.js.map} +0 -0
  343. /package/dist/{chunk-72JNNJJJ.js.map → chunk-AXC4WSF3.js.map} +0 -0
  344. /package/dist/{chunk-N4S4NUX5.js.map → chunk-B3O3IRZN.js.map} +0 -0
  345. /package/dist/{chunk-FAN73COB.js.map → chunk-BGI4NUAO.js.map} +0 -0
  346. /package/dist/{chunk-XTYX6V52.js.map → chunk-BJVO4H5C.js.map} +0 -0
  347. /package/dist/{chunk-EHS5MJEF.js.map → chunk-BLVR4WYU.js.map} +0 -0
  348. /package/dist/{chunk-KFCMMDHH.js.map → chunk-BOZJD5PK.js.map} +0 -0
  349. /package/dist/{chunk-TM3RXL4G.js.map → chunk-BUQKSQXL.js.map} +0 -0
  350. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  351. /package/dist/{chunk-EVBI7S4J.js.map → chunk-C6U57KG4.js.map} +0 -0
  352. /package/dist/{chunk-VX6GCKBS.js.map → chunk-C7ZDKWPS.js.map} +0 -0
  353. /package/dist/{chunk-ZI7QA4MK.js.map → chunk-CRXRZA3G.js.map} +0 -0
  354. /package/dist/{chunk-VQNONC4E.js.map → chunk-CZJA2EQE.js.map} +0 -0
  355. /package/dist/{chunk-M536WNTQ.js.map → chunk-D5BSQR63.js.map} +0 -0
  356. /package/dist/{chunk-37S3IMYB.js.map → chunk-DGS34QSY.js.map} +0 -0
  357. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  358. /package/dist/{chunk-C3QXITIP.js.map → chunk-EQUD7FP4.js.map} +0 -0
  359. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  360. /package/dist/{chunk-EQ6YOIKO.js.map → chunk-FJLQXMTU.js.map} +0 -0
  361. /package/dist/{chunk-SRZW7FNU.js.map → chunk-FKD656D2.js.map} +0 -0
  362. /package/dist/{chunk-CLXTDOKV.js.map → chunk-FUNBJQYR.js.map} +0 -0
  363. /package/dist/{chunk-CLSYE5OZ.js.map → chunk-G2STTRYB.js.map} +0 -0
  364. /package/dist/{chunk-KP7SANCF.js.map → chunk-GWM6T5EQ.js.map} +0 -0
  365. /package/dist/{chunk-UA5DX5C3.js.map → chunk-GZDTVYK2.js.map} +0 -0
  366. /package/dist/{chunk-3GQC3NMB.js.map → chunk-H56VY4BY.js.map} +0 -0
  367. /package/dist/{chunk-L6UZEAID.js.map → chunk-H5BJWPUI.js.map} +0 -0
  368. /package/dist/{chunk-YSJTCOHR.js.map → chunk-HLB7GKGG.js.map} +0 -0
  369. /package/dist/{chunk-PUKTBULQ.js.map → chunk-HWLIMQE2.js.map} +0 -0
  370. /package/dist/{chunk-CWHV574S.js.map → chunk-IOSZHHJC.js.map} +0 -0
  371. /package/dist/{chunk-P2WZHLUF.js.map → chunk-JVPWRNHA.js.map} +0 -0
  372. /package/dist/{chunk-6ET2VII7.js.map → chunk-JYG2334B.js.map} +0 -0
  373. /package/dist/{chunk-WIKO4SCR.js.map → chunk-LHRQBMJN.js.map} +0 -0
  374. /package/dist/{chunk-K4J4E7WV.js.map → chunk-LO4UKPKX.js.map} +0 -0
  375. /package/dist/{chunk-LF5IGOOD.js.map → chunk-LU7E2MXE.js.map} +0 -0
  376. /package/dist/{chunk-RJLV4BKR.js.map → chunk-LV5H6FTN.js.map} +0 -0
  377. /package/dist/{chunk-3C3J243X.js.map → chunk-LW3HKRL7.js.map} +0 -0
  378. /package/dist/{chunk-4BZP2B4U.js.map → chunk-M6C7SOR6.js.map} +0 -0
  379. /package/dist/{chunk-SPSRGEDQ.js.map → chunk-MXCAJKMA.js.map} +0 -0
  380. /package/dist/{chunk-BMYZTZ5D.js.map → chunk-MY7B3HYC.js.map} +0 -0
  381. /package/dist/{chunk-NB3LRY4T.js.map → chunk-N4PYB235.js.map} +0 -0
  382. /package/dist/{chunk-S3YM4SZQ.js.map → chunk-N7QV3CMX.js.map} +0 -0
  383. /package/dist/{chunk-AQK54Q7O.js.map → chunk-NYKIUQL6.js.map} +0 -0
  384. /package/dist/{chunk-NE6PHPLG.js.map → chunk-OK52IY3W.js.map} +0 -0
  385. /package/dist/{chunk-6TT6HRE4.js.map → chunk-P2ODTYOF.js.map} +0 -0
  386. /package/dist/{chunk-W4AOR52H.js.map → chunk-PEBHPCX2.js.map} +0 -0
  387. /package/dist/{chunk-VYCOJKSX.js.map → chunk-PO7NXNGC.js.map} +0 -0
  388. /package/dist/{chunk-XAITGGX5.js.map → chunk-Q4AUIFUA.js.map} +0 -0
  389. /package/dist/{chunk-MQJL6CDZ.js.map → chunk-QAW55ZHJ.js.map} +0 -0
  390. /package/dist/{chunk-QVS22WTO.js.map → chunk-QI7K7BZ5.js.map} +0 -0
  391. /package/dist/{chunk-NE7I2RCT.js.map → chunk-R3X2T2LP.js.map} +0 -0
  392. /package/dist/{chunk-S7GCT7NU.js.map → chunk-RBITPCMK.js.map} +0 -0
  393. /package/dist/{chunk-BJRAELDH.js.map → chunk-SCXURCVT.js.map} +0 -0
  394. /package/dist/{chunk-BFO3IR56.js.map → chunk-SWBC3UER.js.map} +0 -0
  395. /package/dist/{chunk-YRLHEEQT.js.map → chunk-U6VC6RP3.js.map} +0 -0
  396. /package/dist/{chunk-MOQPEFLN.js.map → chunk-UP75KL3L.js.map} +0 -0
  397. /package/dist/{chunk-D5F7TKMH.js.map → chunk-V4JZCTMK.js.map} +0 -0
  398. /package/dist/{chunk-QEV26L2F.js.map → chunk-VALKBYBN.js.map} +0 -0
  399. /package/dist/{chunk-7LCGFUMB.js.map → chunk-VDX2J7OX.js.map} +0 -0
  400. /package/dist/{chunk-TDYAIXHK.js.map → chunk-VVX5PZFS.js.map} +0 -0
  401. /package/dist/{chunk-6NQ4LSMS.js.map → chunk-W3XXFPRI.js.map} +0 -0
  402. /package/dist/{chunk-4IIMMZZK.js.map → chunk-WKY7N3L7.js.map} +0 -0
  403. /package/dist/{chunk-VVDWZRZG.js.map → chunk-X5SHE2O3.js.map} +0 -0
  404. /package/dist/{chunk-NR2ASHBL.js.map → chunk-XOV7NRY5.js.map} +0 -0
  405. /package/dist/{chunk-6L3PBSAR.js.map → chunk-YJ7MW627.js.map} +0 -0
  406. /package/dist/{chunk-LQG5O5EP.js.map → chunk-Z3O53WZR.js.map} +0 -0
  407. /package/dist/{chunk-FB64XE3R.js.map → chunk-Z5LP6ILP.js.map} +0 -0
  408. /package/dist/{chunk-VKQI47DJ.js.map → chunk-Z6EZCN6Z.js.map} +0 -0
  409. /package/dist/{chunk-V2ES4WCZ.js.map → chunk-ZZZ4I455.js.map} +0 -0
  410. /package/dist/{graph-edge-decay-KQGS3L3B.js.map → embedding-generation-lock.js.map} +0 -0
  411. /package/dist/{resolution-DNX7FO7A.js.map → embedding-index-storage.js.map} +0 -0
  412. /package/dist/{state-ITXJKXRE.js.map → graph-edge-decay-MKIRZ762.js.map} +0 -0
  413. /package/dist/{tui-55UJMJX7.js.map → offline-sync-embedding-generation.js.map} +0 -0
  414. /package/dist/{trace-ZFVRH4GO.js.map → trace-LXXCMZJA.js.map} +0 -0
@@ -24,7 +24,37 @@ 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
+ EMBEDDING_SHARD_DIR_BASENAME,
31
+ applyEmbeddingGenerationRemoval,
32
+ applyEmbeddingGenerationTransaction,
33
+ assertEmbeddingGenerationStillIncluded,
34
+ computeOmittedEmbeddingGenerationPaths,
35
+ detectIncomingEmbeddingGenerations,
36
+ divergedEmbeddingGenerationDeferrals,
37
+ embeddingGenerationMembership,
38
+ embeddingMarkerDirOf,
39
+ embeddingShardDirOf,
40
+ isEmbeddingGenerationDirPath,
41
+ isPathInOmittedEmbeddingGeneration,
42
+ type EmbeddingGenerationTransactionIo,
43
+ type EmbeddingGenerationTransactionResult,
44
+ } from "./offline-sync-embedding-generation.js";
45
+ import { withEmbeddingGenerationLock, withEmbeddingGenerationLockIter } from "./embedding-generation-lock.js";
46
+ import {
47
+ isCanonicalRuntimeStatePath,
48
+ shouldDeleteAbsentIncomingOfflineRuntimeFile,
49
+ shouldPreferIncomingOfflineRuntimeFile,
50
+ } from "./offline-sync-runtime-state.js";
51
+ export { shouldPreferIncomingOfflineRuntimeFile } from "./offline-sync-runtime-state.js";
52
+ export { computeOmittedEmbeddingGenerationPaths } from "./offline-sync-embedding-generation.js";
53
+ export {
54
+ compileOfflineSyncExcludeGlobs,
55
+ globToRegExp,
56
+ parseOfflineSyncExcludes,
57
+ } from "./offline-sync-exclude-globs.js";
28
58
  import {
29
59
  isEncryptedOfflineSyncFile,
30
60
  readPlainOfflineSyncFileChunk,
@@ -33,8 +63,21 @@ import {
33
63
  type OfflineSyncExcludeFile,
34
64
  type OfflineSyncFileTarget,
35
65
  plainFileDigest,
66
+ SYNC_INTERNAL_DIR,
67
+ setSafeFileMtime,
68
+ assertNonNegativeFinite,
69
+ assertOfflineSyncMtimeMs,
70
+ OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS,
36
71
  } from "./offline-sync-file-io.js";
37
- import { CENSUS_MAX_MTIME_MS, isCensusMtimeMs, isSha256Hex } from "./census-validation.js";
72
+ import { CENSUS_MAX_MTIME_MS, isSha256Hex } from "./census-validation.js";
73
+ import {
74
+ cleanupOfflineUpload,
75
+ digestOfflineUploadStagingContent,
76
+ pruneOfflineUploadStaging,
77
+ writeOfflineUploadChunk,
78
+ writeSafeFileFromUpload,
79
+ hashText,
80
+ } from "./offline-sync-upload-staging.js";
38
81
  export type { OfflineSyncExcludeFile, OfflineSyncFileTarget } from "./offline-sync-file-io.js";
39
82
 
40
83
  export const OFFLINE_SYNC_SNAPSHOT_FORMAT = "remnic.offline-sync.snapshot.v1";
@@ -76,6 +119,14 @@ export interface OfflineSyncSnapshot {
76
119
  includeTranscripts: boolean;
77
120
  files: OfflineSyncFileRecord[];
78
121
  deletions?: OfflineSyncDeletionRevision[];
122
+ /**
123
+ * Embedding generation dirs whose on-disk members were ALL omitted from
124
+ * `files` because a push filter excluded at least one member (PR #3148
125
+ * round 4). Their absence here is NOT a delete instruction: a receiver
126
+ * keeps its local copy and its base entries until the generation is
127
+ * pushed unfiltered or genuinely deleted with deletion metadata.
128
+ */
129
+ omittedEmbeddingGenerationDirs?: string[];
79
130
  }
80
131
 
81
132
  export type OfflineSyncChange =
@@ -166,7 +217,21 @@ export interface OfflineSyncFileWriteChunksTarget extends OfflineSyncFileTarget
166
217
  chunks: AsyncIterable<Buffer>;
167
218
  }
168
219
 
169
- export interface OfflineSyncFileStagingWriteTarget extends OfflineSyncFileWriteTarget {}
220
+ export interface OfflineSyncFileStagingWriteTarget extends OfflineSyncFileWriteTarget {
221
+ /**
222
+ * ABSOLUTE final path whose canonical AAD the staged ciphertext binds
223
+ * (secure-store deployments): the offline-sync embedding generation
224
+ * transaction stages under a staging dir and publishes by rename, so the
225
+ * ciphertext must decrypt at the FINAL path. The AAD derives through the
226
+ * same filePathAad logic as ordinary reads (platform-consistent).
227
+ */
228
+ finalAadFilePath?: string;
229
+ }
230
+
231
+ export interface OfflineSyncFileStagingReadTarget extends OfflineSyncFileTarget {
232
+ /** See {@link OfflineSyncFileStagingWriteTarget.finalAadFilePath}. */
233
+ finalAadFilePath?: string;
234
+ }
170
235
 
171
236
  export interface OfflineSyncFileContentChunk extends Omit<OfflineSyncFileState, "sha256"> {
172
237
  sha256?: string;
@@ -205,9 +270,6 @@ interface OfflineSyncFileRecordOptions {
205
270
  signal?: AbortSignal;
206
271
  }
207
272
 
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
273
  const OFFLINE_SYNC_FAST_BASE_CTIME_TOLERANCE_MS = 1;
212
274
  const EXCLUDED_FILE_NAMES = new Set([
213
275
  ".sync-state.json",
@@ -228,52 +290,7 @@ const EXCLUDED_FILE_PREFIXES = [
228
290
  * matches zero or more whole segments, and a trailing `dir/star-star`
229
291
  * matches everything under `dir/` at any depth.
230
292
  */
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
293
 
274
- function hashText(value: string): string {
275
- return createHash("sha256").update(value).digest("hex");
276
- }
277
294
 
278
295
  function sha256Buffer(buffer: Buffer): { sha256: string; bytes: number } {
279
296
  return sha256Bytes(buffer);
@@ -307,20 +324,7 @@ function assertNonNegativeInteger(value: unknown, field: string): number {
307
324
  return value;
308
325
  }
309
326
 
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
327
 
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
328
 
325
329
  function assertBoolean(value: unknown, field: string): boolean {
326
330
  if (typeof value !== "boolean") {
@@ -407,6 +411,10 @@ function snapshotBuilderDeletions(options: {
407
411
  includeTranscripts: boolean;
408
412
  excludeNodeLocalState?: boolean;
409
413
  userExcludeRegexps?: readonly RegExp[];
414
+ /** Generation dirs omitted by the push enumeration (PR #3148): revisions
415
+ * for ANY member — including already-absent ones — are suppressed, so a
416
+ * filtered generation can never be announced as deleted. */
417
+ omittedGenerationDirs?: ReadonlySet<string>;
410
418
  }): OfflineSyncDeletionRevision[] | undefined {
411
419
  const deletions = normalizeDeletionRevisions(options.deletions, "deletions");
412
420
  if (deletions === undefined) return undefined;
@@ -418,6 +426,7 @@ function snapshotBuilderDeletions(options: {
418
426
  return deletions.filter((deletion) =>
419
427
  (scopedPaths === undefined || scopedPaths.has(deletion.path.toLowerCase())) &&
420
428
  !presentPaths.has(deletion.path.toLowerCase()) &&
429
+ !isDeletionInOmittedGeneration(deletion.path, options.omittedGenerationDirs) &&
421
430
  !(options.excludeNodeLocalState === false
422
431
  ? shouldExcludeRelPath(deletion.path, options.includeTranscripts)
423
432
  : shouldExcludePushRelPath(
@@ -427,11 +436,21 @@ function snapshotBuilderDeletions(options: {
427
436
  )));
428
437
  }
429
438
 
439
+ function isDeletionInOmittedGeneration(
440
+ relPath: string,
441
+ omittedGenerationDirs: ReadonlySet<string> | undefined,
442
+ ): boolean {
443
+ if (!omittedGenerationDirs || omittedGenerationDirs.size === 0) return false;
444
+ const membership = embeddingGenerationMembership(relPath);
445
+ return membership !== null && omittedGenerationDirs.has(membership.shardDir);
446
+ }
447
+
430
448
  export async function filterOfflineSyncDeletionRevisions(options: {
431
449
  root: string;
432
450
  deletions: readonly OfflineSyncDeletionRevision[];
433
451
  includeTranscripts?: boolean;
434
452
  userExcludeRegexps?: readonly RegExp[];
453
+ excludeFile?: OfflineSyncExcludeFile;
435
454
  }): Promise<OfflineSyncDeletionRevision[]> {
436
455
  const deletions = normalizeDeletionRevisions(options.deletions, "deletions");
437
456
  if (deletions === undefined) throw new Error("deletions must be an array");
@@ -442,9 +461,20 @@ export async function filterOfflineSyncDeletionRevisions(options: {
442
461
  "root",
443
462
  );
444
463
  const includeTranscripts = options.includeTranscripts !== false;
464
+ const omission = await computeOmittedEmbeddingGenerationPaths({
465
+ rootAbs: root.abs,
466
+ userExcludeRegexps: options.userExcludeRegexps,
467
+ excludeFile: options.excludeFile,
468
+ isExcludedRelPath: (relPath) =>
469
+ shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps),
470
+ });
471
+ const omittedDirs = new Set(omission.omittedDirs);
445
472
  const filtered: OfflineSyncDeletionRevision[] = [];
446
473
  for (const deletion of deletions) {
447
- if (shouldExcludePushRelPath(deletion.path, includeTranscripts, options.userExcludeRegexps)) {
474
+ if (
475
+ shouldExcludePushRelPath(deletion.path, includeTranscripts, options.userExcludeRegexps) ||
476
+ isDeletionInOmittedGeneration(deletion.path, omittedDirs)
477
+ ) {
448
478
  continue;
449
479
  }
450
480
  const filePath = path.join(root.abs, ...deletion.path.split("/"));
@@ -503,6 +533,26 @@ export function normalizeOfflineSyncSnapshot(
503
533
  if (excludedPath) {
504
534
  throw new Error(`offline sync snapshot contains excluded path: ${excludedPath}`);
505
535
  }
536
+ let omittedEmbeddingGenerationDirs: string[] | undefined;
537
+ if (obj.omittedEmbeddingGenerationDirs !== undefined) {
538
+ if (!Array.isArray(obj.omittedEmbeddingGenerationDirs)) {
539
+ throw new Error("offline sync snapshot omittedEmbeddingGenerationDirs must be an array");
540
+ }
541
+ const seenDirs = new Set<string>();
542
+ for (const [index, entry] of obj.omittedEmbeddingGenerationDirs.entries()) {
543
+ if (
544
+ typeof entry !== "string" ||
545
+ !isEmbeddingGenerationDirPath(entry) ||
546
+ seenDirs.has(entry)
547
+ ) {
548
+ throw new Error(
549
+ `omittedEmbeddingGenerationDirs[${index}] must be a unique canonical embeddings generation dir`,
550
+ );
551
+ }
552
+ seenDirs.add(entry);
553
+ }
554
+ omittedEmbeddingGenerationDirs = [...seenDirs].sort((left, right) => left.localeCompare(right));
555
+ }
506
556
  return {
507
557
  format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
508
558
  schemaVersion: 1,
@@ -511,6 +561,7 @@ export function normalizeOfflineSyncSnapshot(
511
561
  includeTranscripts,
512
562
  files,
513
563
  ...(deletions === undefined ? {} : { deletions }),
564
+ ...(omittedEmbeddingGenerationDirs === undefined ? {} : { omittedEmbeddingGenerationDirs }),
514
565
  };
515
566
  }
516
567
 
@@ -675,15 +726,7 @@ function shouldExcludePushRelPath(
675
726
 
676
727
  // Precompiled once at module load — this check sits on the hot
677
728
  // 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
729
 
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
730
 
688
731
  /**
689
732
  * Compile operator-supplied `offlineSyncExcludes` glob strings into a
@@ -692,46 +735,6 @@ function matchesOfflineSyncDefaultExclude(relPosix: string): boolean {
692
735
  * a thrown error as a fatal configuration mistake and refuse to start the
693
736
  * sync run rather than silently dropping the bad entry.
694
737
  */
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
738
 
736
739
  function shouldIgnoreIncomingRuntimePath(relPosix: string): boolean {
737
740
  if (isInternalRemnicStatePath(relPosix)) return true;
@@ -740,43 +743,6 @@ function shouldIgnoreIncomingRuntimePath(relPosix: string): boolean {
740
743
  return isCanonicalRuntimeStatePath(parts) && basename.includes(".tmp-");
741
744
  }
742
745
 
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
746
  function filterBaseFilesForMode(
781
747
  files: readonly OfflineSyncFileState[],
782
748
  includeTranscripts: boolean,
@@ -850,6 +816,13 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
850
816
  readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
851
817
  excludeFile?: OfflineSyncExcludeFile;
852
818
  userExcludeRegexps?: readonly RegExp[];
819
+ /**
820
+ * Precomputed whole-generation omission set (PR #3148 round 4): paths the
821
+ * walk must skip because their embedding generation lost ANY member to a
822
+ * push filter. When omitted and push excludes are active, the set is
823
+ * computed here so streaming census consumers get the same semantics.
824
+ */
825
+ skipEmbeddingGenerationPaths?: ReadonlySet<string>;
853
826
  /**
854
827
  * When false, enumeration uses only the legacy structural excludes —
855
828
  * the apply/pull-side view of local files. Push-side callers keep the
@@ -864,8 +837,19 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
864
837
  const rootAbs = path.resolve(options.root);
865
838
  const root = await prepareSafeArchiveRoot(rootAbs, "iterateOfflineSyncSnapshotFileRecords", "root");
866
839
  const includeTranscripts = options.includeTranscripts !== false;
867
-
868
- async function* walk(dirAbs: string): AsyncIterable<OfflineSyncFileRecord> {
840
+ const pushExcludes = options.excludeNodeLocalState !== false;
841
+ const skipGenerationPaths = options.skipEmbeddingGenerationPaths ??
842
+ (pushExcludes
843
+ ? (await computeOmittedEmbeddingGenerationPaths({
844
+ rootAbs: root.abs,
845
+ userExcludeRegexps: options.userExcludeRegexps,
846
+ excludeFile: options.excludeFile,
847
+ isExcludedRelPath: (relPath) =>
848
+ shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps),
849
+ })).omittedPaths
850
+ : undefined);
851
+
852
+ async function* walk(dirAbs: string, includeContent = options.includeContent === true): AsyncIterable<OfflineSyncFileRecord> {
869
853
  throwIfOfflineSyncAborted(options.signal);
870
854
  let entries = await readdir(dirAbs, { withFileTypes: true });
871
855
  entries = entries.sort((left, right) => left.name.localeCompare(right.name));
@@ -877,9 +861,17 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
877
861
  ? shouldExcludeRelPath(relPosix, includeTranscripts)
878
862
  : shouldExcludePushRelPath(relPosix, includeTranscripts, options.userExcludeRegexps)
879
863
  ) continue;
864
+ if (skipGenerationPaths?.has(relPosix)) continue;
880
865
  if (entry.isSymbolicLink()) continue;
881
866
  if (entry.isDirectory()) {
882
- yield* walk(abs);
867
+ if (isEmbeddingGenerationDirPath(relPosix)) {
868
+ yield* withEmbeddingGenerationLockIter(path.dirname(abs), (content) => walk(abs, content), includeContent,
869
+ () => assertEmbeddingGenerationStillIncluded(root.abs, relPosix, options.excludeFile,
870
+ (rel) => shouldExcludePushRelPath(rel, includeTranscripts, options.userExcludeRegexps),
871
+ []));
872
+ continue;
873
+ }
874
+ yield* walk(abs, includeContent);
883
875
  continue;
884
876
  }
885
877
  if (!entry.isFile()) continue;
@@ -892,7 +884,7 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
892
884
  root,
893
885
  relPath: relPosix,
894
886
  filePath: abs,
895
- includeContent: options.includeContent === true,
887
+ includeContent,
896
888
  readFile: options.readFile,
897
889
  readFileDigest: options.readFileDigest,
898
890
  signal: options.signal,
@@ -926,8 +918,23 @@ export async function buildOfflineSyncSnapshot(options: {
926
918
  }): Promise<OfflineSyncSnapshot> {
927
919
  throwIfOfflineSyncAborted(options.signal);
928
920
  const includeTranscripts = options.includeTranscripts !== false;
921
+ const pushExcludes = options.excludeNodeLocalState !== false;
922
+ const normalizedDeletions = normalizeDeletionRevisions(options.deletions, "deletions");
923
+ const omission = pushExcludes
924
+ ? await computeOmittedEmbeddingGenerationPaths({
925
+ rootAbs: path.resolve(options.root),
926
+ userExcludeRegexps: options.userExcludeRegexps,
927
+ excludeFile: options.excludeFile,
928
+ tombstonedPaths: (normalizedDeletions ?? []).map((deletion) => deletion.path),
929
+ isExcludedRelPath: (relPath) =>
930
+ shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps),
931
+ })
932
+ : undefined;
929
933
  const files: OfflineSyncFileRecord[] = [];
930
- for await (const file of iterateOfflineSyncSnapshotFileRecords(options)) files.push(file);
934
+ for await (const file of iterateOfflineSyncSnapshotFileRecords({
935
+ ...options,
936
+ skipEmbeddingGenerationPaths: omission?.omittedPaths,
937
+ })) files.push(file);
931
938
  throwIfOfflineSyncAborted(options.signal);
932
939
 
933
940
  const sortedFiles = files.sort(compareByPath);
@@ -937,6 +944,9 @@ export async function buildOfflineSyncSnapshot(options: {
937
944
  includeTranscripts,
938
945
  excludeNodeLocalState: options.excludeNodeLocalState,
939
946
  userExcludeRegexps: options.userExcludeRegexps,
947
+ omittedGenerationDirs: omission && omission.omittedDirs.length > 0
948
+ ? new Set(omission.omittedDirs)
949
+ : undefined,
940
950
  });
941
951
  return {
942
952
  format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
@@ -946,6 +956,9 @@ export async function buildOfflineSyncSnapshot(options: {
946
956
  includeTranscripts,
947
957
  files: sortedFiles,
948
958
  ...(deletions === undefined ? {} : { deletions }),
959
+ ...(omission && omission.omittedDirs.length > 0
960
+ ? { omittedEmbeddingGenerationDirs: omission.omittedDirs }
961
+ : {}),
949
962
  };
950
963
  }
951
964
 
@@ -984,6 +997,18 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
984
997
  const baseCapturedAtMs = rawBaseCapturedAtMs !== undefined && Number.isFinite(rawBaseCapturedAtMs)
985
998
  ? rawBaseCapturedAtMs
986
999
  : null;
1000
+ const pushExcludes = options.excludeNodeLocalState !== false;
1001
+ const normalizedDeletions = normalizeDeletionRevisions(options.deletions, "deletions");
1002
+ const omission = pushExcludes
1003
+ ? await computeOmittedEmbeddingGenerationPaths({
1004
+ rootAbs: root.abs,
1005
+ userExcludeRegexps: options.userExcludeRegexps,
1006
+ excludeFile: options.excludeFile,
1007
+ tombstonedPaths: (normalizedDeletions ?? []).map((deletion) => deletion.path),
1008
+ isExcludedRelPath: (relPath) =>
1009
+ shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps),
1010
+ })
1011
+ : undefined;
987
1012
  const files: OfflineSyncFileRecord[] = [];
988
1013
 
989
1014
  async function walk(dirAbs: string): Promise<void> {
@@ -999,8 +1024,18 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
999
1024
  ? shouldExcludeRelPath(relPosix, includeTranscripts)
1000
1025
  : shouldExcludePushRelPath(relPosix, includeTranscripts, options.userExcludeRegexps)
1001
1026
  ) continue;
1027
+ if (omission?.omittedPaths.has(relPosix)) continue;
1002
1028
  if (entry.isSymbolicLink()) continue;
1003
1029
  if (entry.isDirectory()) {
1030
+ if (isEmbeddingGenerationDirPath(relPosix)) {
1031
+ await withEmbeddingGenerationLock(path.dirname(abs), async () => {
1032
+ await assertEmbeddingGenerationStillIncluded(root.abs, relPosix, options.excludeFile,
1033
+ (rel) => shouldExcludePushRelPath(rel, includeTranscripts, options.userExcludeRegexps),
1034
+ (normalizedDeletions ?? []).map((deletion) => deletion.path));
1035
+ await walk(abs);
1036
+ });
1037
+ continue;
1038
+ }
1004
1039
  await walk(abs);
1005
1040
  continue;
1006
1041
  }
@@ -1043,6 +1078,9 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
1043
1078
  includeTranscripts,
1044
1079
  excludeNodeLocalState: options.excludeNodeLocalState,
1045
1080
  userExcludeRegexps: options.userExcludeRegexps,
1081
+ omittedGenerationDirs: omission && omission.omittedDirs.length > 0
1082
+ ? new Set(omission.omittedDirs)
1083
+ : undefined,
1046
1084
  });
1047
1085
  return {
1048
1086
  format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
@@ -1052,6 +1090,9 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
1052
1090
  includeTranscripts,
1053
1091
  files: sortedFiles,
1054
1092
  ...(deletions === undefined ? {} : { deletions }),
1093
+ ...(omission && omission.omittedDirs.length > 0
1094
+ ? { omittedEmbeddingGenerationDirs: omission.omittedDirs }
1095
+ : {}),
1055
1096
  };
1056
1097
  }
1057
1098
 
@@ -1491,10 +1532,21 @@ export async function applyOfflineSyncSnapshot(options: {
1491
1532
  currentFiles?: readonly OfflineSyncFileState[];
1492
1533
  deferredPaths?: readonly string[];
1493
1534
  allowMissingConflictContent?: boolean;
1535
+ /**
1536
+ * Explicit INCOMING content source for snapshot records without inline
1537
+ * `contentBase64` (oversized generation members the client fetched
1538
+ * chunk-wise into a private staging root). Consulted only for such
1539
+ * records; return null to keep the metadata-only behavior (the local
1540
+ * file must hash-match the record). Returned buffers are digest-verified
1541
+ * against the incoming record before anything is published.
1542
+ */
1543
+ readIncomingFile?: (target: { path: string; sha256: string; bytes: number }) => Promise<Buffer | null>;
1494
1544
  writeConflictCopies?: boolean;
1495
1545
  readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
1496
1546
  readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
1497
1547
  writeFile?: (target: OfflineSyncFileWriteTarget) => Promise<void>;
1548
+ writeStagingFile?: (target: OfflineSyncFileStagingWriteTarget) => Promise<void>;
1549
+ readStagingFile?: (target: OfflineSyncFileStagingReadTarget) => Promise<Buffer>;
1498
1550
  deleteFile?: (target: OfflineSyncFileDeleteTarget) => Promise<void>;
1499
1551
  recordDeletionRevision?: OfflineSyncRecordDeletionRevision;
1500
1552
  }): Promise<OfflineSyncApplySnapshotResult> {
@@ -1507,8 +1559,9 @@ export async function applyOfflineSyncSnapshot(options: {
1507
1559
  const deletionMtimeByPath = snapshot.deletions === undefined
1508
1560
  ? undefined
1509
1561
  : new Map(snapshot.deletions.map((deletion) => [deletion.path, deletion.mtimeMs] as const));
1510
- const incomingBuffers = verifyRecordContents(snapshot.files, "offline sync snapshot", {
1562
+ const incomingBuffers = await verifyRecordContents(snapshot.files, "offline sync snapshot", {
1511
1563
  requireContent: false,
1564
+ readIncomingFile: options.readIncomingFile,
1512
1565
  });
1513
1566
  const root = await ensureSyncRoot(options.root, "applyOfflineSyncSnapshot");
1514
1567
  const currentFiles = options.currentFiles
@@ -1523,12 +1576,19 @@ export async function applyOfflineSyncSnapshot(options: {
1523
1576
  excludeNodeLocalState: false,
1524
1577
  })).files;
1525
1578
  const currentMap = byPath(currentFiles);
1526
- const deferredPaths = new Set(options.deferredPaths ?? []);
1579
+ const deferredPaths = new Set([...(options.deferredPaths ?? []),
1580
+ ...divergedEmbeddingGenerationDeferrals({
1581
+ incomingFiles: snapshot.files, baseFiles: [...baseMap.values()], currentFiles,
1582
+ }),
1583
+ ]);
1584
+ const omittedGenerationDirs = new Set(snapshot.omittedEmbeddingGenerationDirs ?? []);
1527
1585
  if (deletionMtimeByPath && options.recordDeletionRevision) {
1528
1586
  for (const [relPath, mtimeMs] of deletionMtimeByPath) {
1529
1587
  if (
1530
1588
  currentMap.has(relPath) ||
1531
1589
  deferredPaths.has(relPath) ||
1590
+ (omittedGenerationDirs.size > 0 &&
1591
+ isPathInOmittedEmbeddingGeneration(relPath, omittedGenerationDirs)) ||
1532
1592
  matchesOfflineSyncDefaultExclude(relPath)
1533
1593
  ) {
1534
1594
  continue;
@@ -1554,6 +1614,183 @@ export async function applyOfflineSyncSnapshot(options: {
1554
1614
  return requiredBuffer(incomingBuffers, relPath);
1555
1615
  };
1556
1616
 
1617
+ // Embedding generations travel and replace as ONE unit (issue #3148).
1618
+ // Incoming shards form the generation; an incoming legacy marker is a
1619
+ // complete generation too (converted to the sharded layout, preserving the
1620
+ // one-way migration). A deferral anywhere in a generation defers the WHOLE
1621
+ // generation; a generation the push omitted by filter is announced by the
1622
+ // snapshot and its absence is never a delete instruction.
1623
+ const incomingGenerations = detectIncomingEmbeddingGenerations(incomingMap.keys());
1624
+ const generationDirs = new Set([
1625
+ ...incomingGenerations.shardDirs,
1626
+ ...incomingGenerations.legacyMarkerDirs,
1627
+ ]);
1628
+ const deferredGenerationDirs = new Set<string>();
1629
+ for (const deferredPath of deferredPaths) {
1630
+ const membership = embeddingGenerationMembership(deferredPath);
1631
+ if (membership) {
1632
+ deferredGenerationDirs.add(membership.shardDir);
1633
+ }
1634
+ }
1635
+ const transactionResults: EmbeddingGenerationTransactionResult[] = [];
1636
+ // Included and omitted cannot describe the same generation.
1637
+ for (const shardDir of generationDirs) {
1638
+ if (omittedGenerationDirs.has(shardDir)) {
1639
+ throw new EmbeddingIndexStorageError(
1640
+ `offline sync snapshot both includes and omits embedding generation ${shardDir}; refusing to apply`,
1641
+ );
1642
+ }
1643
+ }
1644
+ // Fail closed BEFORE any publication: custom storage IO (encrypted or
1645
+ // otherwise) without the staging pair would make the generation transaction
1646
+ // fall back to unencrypted raw writes — a plaintext downgrade of the staged
1647
+ // generation. Callers must wire both staging hooks (the CLI does).
1648
+ if (
1649
+ generationDirs.size > 0 &&
1650
+ (options.writeFile || options.readFile || options.readFileDigest || options.deleteFile) &&
1651
+ !(options.writeStagingFile && options.readStagingFile)
1652
+ ) {
1653
+ throw new EmbeddingIndexStorageError(
1654
+ "refusing to replace embedding generation: custom storage IO requires both writeStagingFile and readStagingFile hooks; unencrypted raw staging would plaintext-downgrade the index",
1655
+ );
1656
+ }
1657
+ for (const shardDir of generationDirs) {
1658
+ if (deferredGenerationDirs.has(shardDir)) continue;
1659
+ if (
1660
+ incomingGenerations.legacyMarkerDirs.has(shardDir) &&
1661
+ !incomingGenerations.shardDirs.has(shardDir)
1662
+ ) {
1663
+ // Cross-layout: an incoming legacy marker replaces a local sharded
1664
+ // generation as a whole (codex round 4). When shards are ALSO
1665
+ // incoming, the sharded set is the generation and the marker is
1666
+ // written as an inert artifact by the per-file loop below.
1667
+ const markerRel = [...incomingMap.keys()].find(
1668
+ (relPath) => embeddingMarkerDirOf(relPath) === shardDir.slice(0, -`/${EMBEDDING_SHARD_DIR_BASENAME}`.length),
1669
+ );
1670
+ if (!markerRel) continue;
1671
+ transactionResults.push(await applyEmbeddingGenerationTransaction({
1672
+ root,
1673
+ shardDirRel: shardDir,
1674
+ incomingShardPaths: [],
1675
+ incomingMarker: {
1676
+ path: markerRel,
1677
+ sha256: incomingMap.get(markerRel)!.sha256,
1678
+ buffer: incomingBuffers.get(markerRel) ?? null,
1679
+ },
1680
+ incomingMarkerPresent: false,
1681
+ incomingShardStates: new Map(),
1682
+ incomingBuffers,
1683
+ baseStates: baseMap,
1684
+ io: {
1685
+ readFile: options.readFile,
1686
+ writeStagingFile: options.writeStagingFile,
1687
+ readStagingFile: options.readStagingFile,
1688
+ deleteFile: options.deleteFile,
1689
+ },
1690
+ now: Date.now(),
1691
+ }));
1692
+ continue;
1693
+ }
1694
+ transactionResults.push(await applyEmbeddingGenerationTransaction({
1695
+ root,
1696
+ shardDirRel: shardDir,
1697
+ incomingShardPaths: [...incomingMap.keys()]
1698
+ .filter((relPath) => embeddingShardDirOf(relPath) === shardDir)
1699
+ .sort(),
1700
+ incomingMarker: null,
1701
+ incomingMarkerPresent: Boolean(
1702
+ incomingGenerations.legacyMarkerDirs.has(shardDir),
1703
+ ),
1704
+ incomingShardStates: new Map(
1705
+ [...incomingMap.values()]
1706
+ .filter((relPath) => embeddingShardDirOf(relPath.path) === shardDir)
1707
+ .map((state) => [state.path, state]),
1708
+ ),
1709
+ incomingBuffers,
1710
+ baseStates: baseMap,
1711
+ io: {
1712
+ readFile: options.readFile,
1713
+ writeStagingFile: options.writeStagingFile,
1714
+ readStagingFile: options.readStagingFile,
1715
+ deleteFile: options.deleteFile,
1716
+ },
1717
+ now: Date.now(),
1718
+ }));
1719
+ }
1720
+ // Whole-generation removal (codex round 6 P1): when the remote deleted a
1721
+ // previously shared sharded index, the deletion revisions cover EVERY
1722
+ // local member. Remove that generation as ONE atomic swap instead of
1723
+ // deleting shards one-by-one, so a crash or delete failure can never
1724
+ // expose a partial same-identity generation serving missing memories.
1725
+ const localGenerationDirs = new Set<string>();
1726
+ for (const relPath of currentMap.keys()) {
1727
+ const membership = embeddingGenerationMembership(relPath);
1728
+ if (membership?.kind === "shard") localGenerationDirs.add(membership.shardDir);
1729
+ }
1730
+ for (const shardDir of localGenerationDirs) {
1731
+ if (
1732
+ generationDirs.has(shardDir) ||
1733
+ deferredGenerationDirs.has(shardDir) ||
1734
+ omittedGenerationDirs.has(shardDir)
1735
+ ) {
1736
+ continue;
1737
+ }
1738
+ const localMembers = [...currentMap.keys()].filter(
1739
+ (relPath) => embeddingGenerationMembership(relPath)?.shardDir === shardDir,
1740
+ );
1741
+ if (localMembers.length === 0) continue;
1742
+ // Tombstone or shared-base absence authorizes whole-generation removal.
1743
+ const baseMembers = localMembers.filter((relPath) => baseMap.has(relPath));
1744
+ // Tombstone evidence is satisfied by the members the REMOTE knew: the
1745
+ // remote cannot tombstone local-only extras or an inert legacy marker,
1746
+ // and the removal sweeps those once the remote deletion is established.
1747
+ // Non-empty same-generation tombstones establish remote absence on
1748
+ // their own: they may name shards already absent locally (removed in an
1749
+ // earlier round), so the set is NOT required to intersect localMembers
1750
+ // (codex round 6, XiOb).
1751
+ const tombstonedMembers =
1752
+ deletionMtimeByPath === undefined
1753
+ ? []
1754
+ : [...deletionMtimeByPath.keys()].filter(
1755
+ (relPath) => embeddingGenerationMembership(relPath)?.shardDir === shardDir,
1756
+ );
1757
+ const tombstoneCovered = tombstonedMembers.length > 0;
1758
+ const baseEvidenced =
1759
+ baseMembers.length > 0 && baseMembers.every((relPath) => !incomingMap.has(relPath));
1760
+ if (!tombstoneCovered && !baseEvidenced) continue;
1761
+ transactionResults.push(await applyEmbeddingGenerationRemoval({
1762
+ root,
1763
+ shardDirRel: shardDir,
1764
+ io: { deleteFile: options.deleteFile, readFile: options.readFile },
1765
+ deletionMtimeByPath,
1766
+ baseStates: baseMap,
1767
+ now: Date.now(),
1768
+ }));
1769
+ }
1770
+
1771
+ const transactionHandled = new Set<string>();
1772
+ let generationUpserted = 0;
1773
+ let generationDeleted = 0;
1774
+ for (const result of transactionResults) {
1775
+ if (result.deferredLocalDivergence) {
1776
+ for (const relPath of result.handledPaths) deferredPaths.add(relPath);
1777
+ deferredGenerationDirs.add(result.shardDirRel);
1778
+ continue;
1779
+ }
1780
+ generationUpserted += result.upserted;
1781
+ generationDeleted += result.deleted;
1782
+ for (const relPath of result.handledPaths) transactionHandled.add(relPath);
1783
+ // Record converted shard paths directly; they may not be in any input map.
1784
+ for (const [relPath, state] of result.writtenStates) {
1785
+ nextBase.set(relPath, state);
1786
+ transactionHandled.add(relPath);
1787
+ }
1788
+ for (const relPath of result.removedPaths) {
1789
+ nextBase.delete(relPath);
1790
+ transactionHandled.add(relPath);
1791
+ }
1792
+ }
1793
+
1557
1794
  for (const relPath of unionPaths(baseMap, incomingMap, currentMap)) {
1558
1795
  const base = baseMap.get(relPath);
1559
1796
  const incoming = incomingMap.get(relPath);
@@ -1566,6 +1803,21 @@ export async function applyOfflineSyncSnapshot(options: {
1566
1803
  continue;
1567
1804
  }
1568
1805
 
1806
+ // The generation transaction already published or removed this path and
1807
+ // applied its nextBase bookkeeping above; nothing left to do.
1808
+ if (transactionHandled.has(relPath)) continue;
1809
+
1810
+ // A deferral anywhere in a generation defers the WHOLE generation: no
1811
+ // partial replacement in either direction, local files and base entries
1812
+ // stay exactly as they are.
1813
+ const membership = embeddingGenerationMembership(relPath);
1814
+ if (membership && deferredGenerationDirs.has(membership.shardDir)) {
1815
+ if (base) nextBase.set(relPath, base);
1816
+ else nextBase.delete(relPath);
1817
+ skipped += 1;
1818
+ continue;
1819
+ }
1820
+
1569
1821
  if (incoming) {
1570
1822
  if (currentEntry?.sha256 === incoming.sha256) {
1571
1823
  if (await setSafeFileMtime(root, relPath, incoming.mtimeMs)) {
@@ -1578,7 +1830,14 @@ export async function applyOfflineSyncSnapshot(options: {
1578
1830
  skipped += 1;
1579
1831
  continue;
1580
1832
  }
1581
- if (shouldPreferIncomingOfflineRuntimeFile(relPath) && currentEntry && base && incoming.sha256 === base.sha256) {
1833
+ // Remote unchanged from the shared base while the local file drifted:
1834
+ // keep the local copy (no content is needed to make that decision).
1835
+ // Members of a replaced generation never reach this branch — the
1836
+ // generation transaction or the deferral guard handled them above.
1837
+ if (
1838
+ shouldPreferIncomingOfflineRuntimeFile(relPath) &&
1839
+ currentEntry && base && incoming.sha256 === base.sha256
1840
+ ) {
1582
1841
  nextBase.set(relPath, base);
1583
1842
  skipped += 1;
1584
1843
  continue;
@@ -1659,6 +1918,18 @@ export async function applyOfflineSyncSnapshot(options: {
1659
1918
  skipped += 1;
1660
1919
  continue;
1661
1920
  }
1921
+ if (
1922
+ shouldPreferIncomingOfflineRuntimeFile(relPath) &&
1923
+ membership !== null && omittedGenerationDirs.has(membership.shardDir)
1924
+ ) {
1925
+ // The push omitted this whole generation because a filter excluded at
1926
+ // least one member (snapshot.omittedEmbeddingGenerationDirs): absence
1927
+ // is NOT a deletion — keep the local generation and its base entries
1928
+ // until it is pushed unfiltered or deleted with deletion metadata.
1929
+ pendingLocal += 1;
1930
+ skipped += 1;
1931
+ continue;
1932
+ }
1662
1933
  if (
1663
1934
  shouldPreferIncomingOfflineRuntimeFile(relPath) &&
1664
1935
  (base || shouldDeleteAbsentIncomingOfflineRuntimeFile(relPath))
@@ -1704,8 +1975,8 @@ export async function applyOfflineSyncSnapshot(options: {
1704
1975
  }
1705
1976
 
1706
1977
  return {
1707
- upserted,
1708
- deleted,
1978
+ upserted: upserted + generationUpserted,
1979
+ deleted: deleted + generationDeleted,
1709
1980
  skipped,
1710
1981
  pendingLocal,
1711
1982
  conflicts,
@@ -1740,7 +2011,7 @@ export async function applyOfflineSyncChangeset(options: {
1740
2011
  const records = changeset.changes
1741
2012
  .filter((change): change is Extract<OfflineSyncChange, { type: "upsert" }> => change.type === "upsert")
1742
2013
  .map((change) => change.file);
1743
- const incomingBuffers = verifyRecordContents(records, "offline sync changeset");
2014
+ const incomingBuffers = await verifyRecordContents(records, "offline sync changeset");
1744
2015
  const currentFiles = options.currentFiles
1745
2016
  ? filterBaseFilesForMode(normalizeFileStates(options.currentFiles), changeset.includeTranscripts).sort(compareByPath)
1746
2017
  : (await buildOfflineSyncSnapshotForPaths({
@@ -1859,14 +2130,37 @@ export async function applyOfflineSyncChangeset(options: {
1859
2130
  };
1860
2131
  }
1861
2132
 
1862
- function verifyRecordContents(
2133
+ async function verifyRecordContents(
1863
2134
  records: readonly OfflineSyncFileRecord[],
1864
2135
  context: string,
1865
- options: { requireContent?: boolean } = {},
1866
- ): Map<string, Buffer> {
2136
+ options: {
2137
+ requireContent?: boolean;
2138
+ readIncomingFile?: (target: { path: string; sha256: string; bytes: number }) => Promise<Buffer | null>;
2139
+ } = {},
2140
+ ): Promise<Map<string, Buffer>> {
1867
2141
  const buffers = new Map<string, Buffer>();
1868
2142
  for (const record of records) {
1869
2143
  if (typeof record.contentBase64 !== "string") {
2144
+ if (options.readIncomingFile) {
2145
+ // Explicit INCOMING content source for records whose bytes travel
2146
+ // out-of-band (chunked fetch into the client's private staging
2147
+ // root). Never the local current-state file: the buffer is
2148
+ // digest-verified against the incoming record before anything is
2149
+ // published. null keeps the metadata-only behavior below.
2150
+ const incoming = await options.readIncomingFile({
2151
+ path: record.path,
2152
+ sha256: record.sha256,
2153
+ bytes: record.bytes,
2154
+ });
2155
+ if (incoming) {
2156
+ const digest = sha256Buffer(incoming);
2157
+ if (digest.sha256 !== record.sha256 || digest.bytes !== record.bytes) {
2158
+ throw new Error(`${context}: incoming content checksum mismatch for ${record.path}`);
2159
+ }
2160
+ buffers.set(record.path, incoming);
2161
+ continue;
2162
+ }
2163
+ }
1870
2164
  if (options.requireContent === false) continue;
1871
2165
  throw new Error(`${context}: contentBase64 is required for ${record.path}`);
1872
2166
  }
@@ -1956,29 +2250,6 @@ async function writeSafeFile(
1956
2250
  }
1957
2251
  }
1958
2252
 
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
2253
 
1983
2254
  /**
1984
2255
  * Read the local file digest for a single relative path without applying
@@ -2232,272 +2503,6 @@ export async function applyOfflineSyncFileContentChunk(options: {
2232
2503
  }
2233
2504
  }
2234
2505
 
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
2506
 
2502
2507
  async function deleteSafeFile(
2503
2508
  root: SafeArchiveRoot,